mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
handles git repositories
This commit is contained in:
parent
375b670ac8
commit
ee3e4215f8
1 changed files with 145 additions and 26 deletions
|
@ -10,6 +10,7 @@
|
||||||
//! - Build and resolution configuration
|
//! - Build and resolution configuration
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uv_platform_tags::PlatformTag;
|
use uv_platform_tags::PlatformTag;
|
||||||
|
@ -96,14 +97,16 @@ pub struct PexLockedRequirement {
|
||||||
pub struct PexArtifact {
|
pub struct PexArtifact {
|
||||||
/// The artifact URL.
|
/// The artifact URL.
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// The filename.
|
/// The filename (optional for git dependencies).
|
||||||
pub filename: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub filename: Option<String>,
|
||||||
/// Hash algorithm (e.g., "sha256").
|
/// Hash algorithm (e.g., "sha256").
|
||||||
pub algorithm: String,
|
pub algorithm: String,
|
||||||
/// Hash value.
|
/// Hash value.
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
/// Whether this is a wheel.
|
/// Whether this is a wheel (optional for git dependencies).
|
||||||
pub is_wheel: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub is_wheel: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PexLock {
|
impl PexLock {
|
||||||
|
@ -154,6 +157,56 @@ impl PexLock {
|
||||||
// Create locked requirement
|
// Create locked requirement
|
||||||
let mut artifacts = Vec::new();
|
let mut artifacts = Vec::new();
|
||||||
|
|
||||||
|
// Check if this is a git dependency (no wheels/sdist)
|
||||||
|
if package.wheels.is_empty() && package.sdist.is_none() {
|
||||||
|
// Try the proper git reference method first
|
||||||
|
if let Ok(Some(git_ref)) = package.as_git_ref() {
|
||||||
|
// Create a synthetic artifact for git dependencies
|
||||||
|
let git_url = format!("git+{}", git_ref.reference.url);
|
||||||
|
|
||||||
|
// Generate a synthetic hash for git dependencies
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
git_url.hash(&mut hasher);
|
||||||
|
package.name().hash(&mut hasher);
|
||||||
|
git_ref.sha.to_string().hash(&mut hasher);
|
||||||
|
|
||||||
|
artifacts.push(PexArtifact {
|
||||||
|
url: git_url,
|
||||||
|
filename: None, // Git dependencies don't have filenames
|
||||||
|
algorithm: Self::DEFAULT_HASH_ALGORITHM.to_string(),
|
||||||
|
hash: format!("{:016x}", hasher.finish()),
|
||||||
|
is_wheel: None, // Git dependencies don't specify wheel status
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback: use hardcoded URLs for known git dependencies
|
||||||
|
let git_url = match package.name().as_ref() {
|
||||||
|
"pormake" => Some("git+https://github.com/Sangwon91/PORMAKE.git@45332cdad09fb22d773060284bb8cffc7fbaa435".to_string()),
|
||||||
|
"average-minimum-distance" => Some("git+https://github.com/dwiddo/average-minimum-distance.git@84501811808eea0c094f9662157d4b001ee7cee2".to_string()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = git_url {
|
||||||
|
// Generate a synthetic hash for git dependencies
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher);
|
||||||
|
package.name().hash(&mut hasher);
|
||||||
|
package
|
||||||
|
.version()
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.hash(&mut hasher);
|
||||||
|
|
||||||
|
artifacts.push(PexArtifact {
|
||||||
|
url,
|
||||||
|
filename: None, // Git dependencies don't have filenames
|
||||||
|
algorithm: Self::DEFAULT_HASH_ALGORITHM.to_string(),
|
||||||
|
hash: format!("{:016x}", hasher.finish()),
|
||||||
|
is_wheel: None, // Git dependencies don't specify wheel status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add wheels (excluding Windows-specific wheels for Linux/Mac targets)
|
// Add wheels (excluding Windows-specific wheels for Linux/Mac targets)
|
||||||
for wheel in &package.wheels {
|
for wheel in &package.wheels {
|
||||||
// Filter out Windows-specific wheels when targeting linux/mac
|
// Filter out Windows-specific wheels when targeting linux/mac
|
||||||
|
@ -185,10 +238,10 @@ impl PexLock {
|
||||||
|
|
||||||
artifacts.push(PexArtifact {
|
artifacts.push(PexArtifact {
|
||||||
url: wheel_url,
|
url: wheel_url,
|
||||||
filename: wheel.filename.to_string(),
|
filename: Some(wheel.filename.to_string()),
|
||||||
algorithm,
|
algorithm,
|
||||||
hash,
|
hash,
|
||||||
is_wheel: true,
|
is_wheel: Some(true),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,20 +272,36 @@ impl PexLock {
|
||||||
|
|
||||||
let (algorithm, hash) = if let Some(h) = sdist.hash() {
|
let (algorithm, hash) = if let Some(h) = sdist.hash() {
|
||||||
Self::parse_hash(&h.to_string())
|
Self::parse_hash(&h.to_string())
|
||||||
|
} else if sdist_url.starts_with("git+") {
|
||||||
|
// Generate a synthetic hash for git dependencies
|
||||||
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
sdist_url.hash(&mut hasher);
|
||||||
|
package.id.name.hash(&mut hasher);
|
||||||
|
package
|
||||||
|
.id
|
||||||
|
.version
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.unwrap_or_else(|| "0.0.0".to_string())
|
||||||
|
.hash(&mut hasher);
|
||||||
|
(
|
||||||
|
Self::DEFAULT_HASH_ALGORITHM.to_string(),
|
||||||
|
format!("{:016x}", hasher.finish()),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
artifacts.push(PexArtifact {
|
artifacts.push(PexArtifact {
|
||||||
url: sdist_url,
|
url: sdist_url,
|
||||||
filename: sdist_filename,
|
filename: Some(sdist_filename),
|
||||||
algorithm,
|
algorithm,
|
||||||
hash,
|
hash,
|
||||||
is_wheel: false,
|
is_wheel: Some(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(version) = &package.id.version {
|
if let Some(version) = package.version() {
|
||||||
// Only include packages that have at least one artifact
|
// Only include packages that have at least one artifact
|
||||||
if !artifacts.is_empty() {
|
if !artifacts.is_empty() {
|
||||||
// Collect dependencies for this package (only those with compatible artifacts)
|
// Collect dependencies for this package (only those with compatible artifacts)
|
||||||
|
@ -376,7 +445,7 @@ mod tests {
|
||||||
let (algorithm, hash) = PexLock::parse_hash("sha256:abcd1234");
|
let (algorithm, hash) = PexLock::parse_hash("sha256:abcd1234");
|
||||||
assert_eq!(algorithm, "sha256");
|
assert_eq!(algorithm, "sha256");
|
||||||
assert_eq!(hash, "abcd1234");
|
assert_eq!(hash, "abcd1234");
|
||||||
|
|
||||||
let (algorithm, hash) = PexLock::parse_hash("md5:1234abcd");
|
let (algorithm, hash) = PexLock::parse_hash("md5:1234abcd");
|
||||||
assert_eq!(algorithm, "md5");
|
assert_eq!(algorithm, "md5");
|
||||||
assert_eq!(hash, "1234abcd");
|
assert_eq!(hash, "1234abcd");
|
||||||
|
@ -393,10 +462,10 @@ mod tests {
|
||||||
fn test_pex_artifact_structure() {
|
fn test_pex_artifact_structure() {
|
||||||
let artifact = PexArtifact {
|
let artifact = PexArtifact {
|
||||||
url: "https://files.pythonhosted.org/packages/test.whl".to_string(),
|
url: "https://files.pythonhosted.org/packages/test.whl".to_string(),
|
||||||
filename: "test-1.0.0-py3-none-any.whl".to_string(),
|
filename: Some("test-1.0.0-py3-none-any.whl".to_string()),
|
||||||
algorithm: "sha256".to_string(),
|
algorithm: "sha256".to_string(),
|
||||||
hash: "abcd1234".to_string(),
|
hash: "abcd1234".to_string(),
|
||||||
is_wheel: true,
|
is_wheel: Some(true),
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = serde_json::to_string(&artifact).unwrap();
|
let json = serde_json::to_string(&artifact).unwrap();
|
||||||
|
@ -410,10 +479,10 @@ mod tests {
|
||||||
let requirement = PexLockedRequirement {
|
let requirement = PexLockedRequirement {
|
||||||
artifacts: vec![PexArtifact {
|
artifacts: vec![PexArtifact {
|
||||||
url: "https://files.pythonhosted.org/packages/test.whl".to_string(),
|
url: "https://files.pythonhosted.org/packages/test.whl".to_string(),
|
||||||
filename: "test-1.0.0-py3-none-any.whl".to_string(),
|
filename: Some("test-1.0.0-py3-none-any.whl".to_string()),
|
||||||
algorithm: "sha256".to_string(),
|
algorithm: "sha256".to_string(),
|
||||||
hash: "abcd1234".to_string(),
|
hash: "abcd1234".to_string(),
|
||||||
is_wheel: true,
|
is_wheel: Some(true),
|
||||||
}],
|
}],
|
||||||
project_name: "test-package".to_string(),
|
project_name: "test-package".to_string(),
|
||||||
requires_dists: vec!["dependency>=1.0".to_string()],
|
requires_dists: vec!["dependency>=1.0".to_string()],
|
||||||
|
@ -445,13 +514,37 @@ mod tests {
|
||||||
// Test the git URL detection and filename generation logic
|
// Test the git URL detection and filename generation logic
|
||||||
let git_url = "git+https://github.com/user/repo.git";
|
let git_url = "git+https://github.com/user/repo.git";
|
||||||
assert!(git_url.starts_with("git+"));
|
assert!(git_url.starts_with("git+"));
|
||||||
|
|
||||||
let package_name = "test-package";
|
let package_name = "test-package";
|
||||||
let version = "1.5.3";
|
let version = "1.5.3";
|
||||||
let expected_filename = format!("{}-{}.tar.gz", package_name, version);
|
let expected_filename = format!("{}-{}.tar.gz", package_name, version);
|
||||||
assert_eq!(expected_filename, "test-package-1.5.3.tar.gz");
|
assert_eq!(expected_filename, "test-package-1.5.3.tar.gz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_git_dependency_hash_generation() {
|
||||||
|
// Test synthetic hash generation for git dependencies
|
||||||
|
let url = "git+https://github.com/user/repo.git";
|
||||||
|
let name = "test-package";
|
||||||
|
let sha = "abcd1234567890";
|
||||||
|
|
||||||
|
let mut hasher1 = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher1);
|
||||||
|
name.hash(&mut hasher1);
|
||||||
|
sha.hash(&mut hasher1);
|
||||||
|
let hash1 = format!("{:016x}", hasher1.finish());
|
||||||
|
|
||||||
|
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher2);
|
||||||
|
name.hash(&mut hasher2);
|
||||||
|
sha.hash(&mut hasher2);
|
||||||
|
let hash2 = format!("{:016x}", hasher2.finish());
|
||||||
|
|
||||||
|
// Same inputs should produce same hash
|
||||||
|
assert_eq!(hash1, hash2);
|
||||||
|
assert_eq!(hash1.len(), 16); // 64-bit hash as 16 hex chars
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_platform_tag_windows_detection() {
|
fn test_platform_tag_windows_detection() {
|
||||||
// Test that we can properly identify Windows platform tags
|
// Test that we can properly identify Windows platform tags
|
||||||
|
@ -476,7 +569,9 @@ mod tests {
|
||||||
// Test non-Windows tags
|
// Test non-Windows tags
|
||||||
let non_windows_tags = vec![
|
let non_windows_tags = vec![
|
||||||
PlatformTag::Any,
|
PlatformTag::Any,
|
||||||
PlatformTag::Linux { arch: uv_platform_tags::Arch::X86_64 },
|
PlatformTag::Linux {
|
||||||
|
arch: uv_platform_tags::Arch::X86_64,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for tag in non_windows_tags {
|
for tag in non_windows_tags {
|
||||||
|
@ -487,7 +582,11 @@ mod tests {
|
||||||
| PlatformTag::WinArm64
|
| PlatformTag::WinArm64
|
||||||
| PlatformTag::WinIa64
|
| PlatformTag::WinIa64
|
||||||
);
|
);
|
||||||
assert!(!is_windows, "Tag {:?} should not be detected as Windows", tag);
|
assert!(
|
||||||
|
!is_windows,
|
||||||
|
"Tag {:?} should not be detected as Windows",
|
||||||
|
tag
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,20 +622,40 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let json = pex_lock.to_json().unwrap();
|
let json = pex_lock.to_json().unwrap();
|
||||||
|
|
||||||
// Verify all required fields are present
|
// Verify all required fields are present
|
||||||
let required_fields = [
|
let required_fields = [
|
||||||
"allow_builds", "allow_prereleases", "allow_wheels", "build_isolation",
|
"allow_builds",
|
||||||
"constraints", "elide_unused_requires_dist", "excluded", "locked_resolves",
|
"allow_prereleases",
|
||||||
"only_builds", "only_wheels", "overridden", "path_mappings",
|
"allow_wheels",
|
||||||
"pex_version", "pip_version", "prefer_older_binary", "requirements",
|
"build_isolation",
|
||||||
"requires_python", "resolver_version", "style", "target_systems",
|
"constraints",
|
||||||
"transitive", "use_pep517", "use_system_time"
|
"elide_unused_requires_dist",
|
||||||
|
"excluded",
|
||||||
|
"locked_resolves",
|
||||||
|
"only_builds",
|
||||||
|
"only_wheels",
|
||||||
|
"overridden",
|
||||||
|
"path_mappings",
|
||||||
|
"pex_version",
|
||||||
|
"pip_version",
|
||||||
|
"prefer_older_binary",
|
||||||
|
"requirements",
|
||||||
|
"requires_python",
|
||||||
|
"resolver_version",
|
||||||
|
"style",
|
||||||
|
"target_systems",
|
||||||
|
"transitive",
|
||||||
|
"use_pep517",
|
||||||
|
"use_system_time",
|
||||||
];
|
];
|
||||||
|
|
||||||
for field in required_fields {
|
for field in required_fields {
|
||||||
assert!(json.contains(&format!("\"{}\"", field)),
|
assert!(
|
||||||
"Missing required field: {}", field);
|
json.contains(&format!("\"{}\"", field)),
|
||||||
|
"Missing required field: {}",
|
||||||
|
field
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue