-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Migrate all Rust packages to importCargoLock
#217084
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/usr/bin/env nix-shell | ||
#!nix-shell -I nixpkgs=. -i bash -p "import ./maintainers/scripts/convert-to-import-cargo-lock" nix-prefetch-git | ||
|
||
convert-to-import-cargo-lock "$@" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
[package] | ||
name = "convert-to-import-cargo-lock" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
anyhow = { version = "1.0.69" } | ||
basic-toml = "0.1.1" | ||
serde = { version = "1.0.152", features = ["derive"] } | ||
serde_json = "1.0.93" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
with import ../../../. { }; | ||
|
||
rustPlatform.buildRustPackage { | ||
name = "convert-to-import-cargo-lock"; | ||
|
||
src = lib.cleanSourceWith { | ||
src = ./.; | ||
filter = name: type: | ||
let | ||
name' = builtins.baseNameOf name; | ||
in | ||
name' != "default.nix" && name' != "target"; | ||
}; | ||
|
||
cargoLock.lockFile = ./Cargo.lock; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
with import ../../../. { }; | ||
|
||
mkShell { | ||
packages = [ rustc cargo clippy rustfmt ] ++ lib.optional stdenv.isDarwin libiconv; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
#![warn(clippy::pedantic)] | ||
#![allow(clippy::too_many_lines)] | ||
|
||
use anyhow::anyhow; | ||
use serde::Deserialize; | ||
use std::{collections::HashMap, env, fs, path::PathBuf, process::Command}; | ||
|
||
#[derive(Deserialize)] | ||
struct CargoLock<'a> { | ||
#[serde(rename = "package", borrow)] | ||
packages: Vec<Package<'a>>, | ||
metadata: Option<HashMap<&'a str, &'a str>>, | ||
} | ||
|
||
#[derive(Deserialize)] | ||
struct Package<'a> { | ||
name: &'a str, | ||
version: &'a str, | ||
source: Option<&'a str>, | ||
checksum: Option<&'a str>, | ||
} | ||
|
||
#[derive(Deserialize)] | ||
struct PrefetchOutput { | ||
sha256: String, | ||
} | ||
|
||
fn main() -> anyhow::Result<()> { | ||
let mut hashes = HashMap::new(); | ||
|
||
let attr_count = env::args().len() - 1; | ||
|
||
for (i, attr) in env::args().skip(1).enumerate() { | ||
println!("converting {attr} ({}/{attr_count})", i + 1); | ||
|
||
convert(&attr, &mut hashes)?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
fn convert(attr: &str, hashes: &mut HashMap<String, String>) -> anyhow::Result<()> { | ||
let package_path = nix_eval(format!("{attr}.meta.position"))? | ||
.and_then(|p| p.split_once(':').map(|(f, _)| PathBuf::from(f))); | ||
|
||
if package_path.is_none() { | ||
eprintln!("can't automatically convert {attr}: doesn't exist"); | ||
return Ok(()); | ||
} | ||
|
||
let package_path = package_path.unwrap(); | ||
|
||
if package_path.with_file_name("Cargo.lock").exists() { | ||
eprintln!("skipping {attr}: already has a vendored Cargo.lock"); | ||
return Ok(()); | ||
} | ||
|
||
let mut src = PathBuf::from( | ||
String::from_utf8( | ||
Command::new("nix-build") | ||
.arg("-A") | ||
.arg(format!("{attr}.src")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this doesn't respect $ rg 'cargoPatches =' | wc -l
50 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, will make it skip those and rerun. I naively assumed/forgot that my check of "is there a vendored Cargo.lock already" would(n't) be sufficient. |
||
.output()? | ||
.stdout, | ||
)? | ||
.trim(), | ||
); | ||
|
||
if !src.exists() { | ||
eprintln!("can't automatically convert {attr}: src doesn't exist (bad attr?)"); | ||
return Ok(()); | ||
} else if !src.metadata()?.is_dir() { | ||
eprintln!("can't automatically convert {attr}: src isn't a directory"); | ||
return Ok(()); | ||
} | ||
|
||
if let Some(mut source_root) = nix_eval(format!("{attr}.sourceRoot"))?.map(PathBuf::from) { | ||
source_root = source_root.components().skip(1).collect(); | ||
src.push(source_root); | ||
} | ||
|
||
let cargo_lock_path = src.join("Cargo.lock"); | ||
|
||
if !cargo_lock_path.exists() { | ||
eprintln!("can't automatically convert {attr}: src doesn't contain Cargo.lock"); | ||
return Ok(()); | ||
} | ||
|
||
let cargo_lock_content = fs::read_to_string(cargo_lock_path)?; | ||
|
||
let cargo_lock: CargoLock = basic_toml::from_str(&cargo_lock_content)?; | ||
|
||
let mut git_dependencies = Vec::new(); | ||
|
||
for package in cargo_lock.packages.iter().filter(|p| { | ||
p.source.is_some() | ||
&& p.checksum | ||
.or_else(|| { | ||
cargo_lock | ||
.metadata | ||
.as_ref()? | ||
.get( | ||
format!("checksum {} {} ({})", p.name, p.version, p.source.unwrap()) | ||
.as_str(), | ||
) | ||
.copied() | ||
}) | ||
.is_none() | ||
}) { | ||
let (typ, original_url) = package | ||
.source | ||
.unwrap() | ||
.split_once('+') | ||
.expect("dependency should have well-formed source url"); | ||
|
||
if let Some(hash) = hashes.get(original_url) { | ||
git_dependencies.push(( | ||
format!("{}-{}", package.name, package.version), | ||
hash.clone(), | ||
)); | ||
|
||
continue; | ||
} | ||
Comment on lines
+116
to
+123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to deduplicate at the local level as well, as |
||
|
||
assert_eq!( | ||
typ, "git", | ||
"packages without checksums should be git dependencies" | ||
); | ||
|
||
let (mut url, rev) = original_url | ||
.split_once('#') | ||
.expect("git dependency should have commit"); | ||
|
||
// TODO: improve | ||
if let Some((u, _)) = url.split_once('?') { | ||
url = u; | ||
} | ||
|
||
let prefetch_output: PrefetchOutput = serde_json::from_slice( | ||
&Command::new("nix-prefetch-git") | ||
.args(["--url", url, "--rev", rev, "--quiet"]) | ||
.output()? | ||
.stdout, | ||
)?; | ||
|
||
let output_hash = String::from_utf8( | ||
Command::new("nix") | ||
.args([ | ||
"--extra-experimental-features", | ||
"nix-command", | ||
"hash", | ||
"to-sri", | ||
"--type", | ||
"sha256", | ||
&prefetch_output.sha256, | ||
]) | ||
.output()? | ||
.stdout, | ||
)?; | ||
|
||
let hash = output_hash.trim().to_string(); | ||
|
||
git_dependencies.push(( | ||
format!("{}-{}", package.name, package.version), | ||
output_hash.trim().to_string().clone(), | ||
)); | ||
|
||
hashes.insert(original_url.to_string(), hash); | ||
} | ||
|
||
fs::write( | ||
package_path.with_file_name("Cargo.lock"), | ||
cargo_lock_content, | ||
)?; | ||
|
||
let mut package_lines: Vec<_> = fs::read_to_string(&package_path)? | ||
.lines() | ||
.map(String::from) | ||
.collect(); | ||
|
||
let (cargo_deps_line_index, cargo_deps_line) = package_lines | ||
.iter_mut() | ||
.enumerate() | ||
.find(|(_, l)| { | ||
l.trim_start().starts_with("cargoHash") || l.trim_start().starts_with("cargoSha256") | ||
}) | ||
.expect("package should contain cargoHash/cargoSha256"); | ||
|
||
let spaces = " ".repeat(cargo_deps_line.len() - cargo_deps_line.trim_start().len()); | ||
|
||
if git_dependencies.is_empty() { | ||
*cargo_deps_line = format!("{spaces}cargoLock.lockFile = ./Cargo.lock;"); | ||
} else { | ||
*cargo_deps_line = format!("{spaces}cargoLock = {{"); | ||
|
||
let mut index_iter = cargo_deps_line_index + 1..; | ||
|
||
package_lines.insert( | ||
index_iter.next().unwrap(), | ||
format!("{spaces} lockFile = ./Cargo.lock;"), | ||
); | ||
|
||
package_lines.insert( | ||
index_iter.next().unwrap(), | ||
format!("{spaces} outputHashes = {{"), | ||
); | ||
|
||
for ((dep, hash), index) in git_dependencies.drain(..).zip(&mut index_iter) { | ||
package_lines.insert(index, format!("{spaces} {dep:?} = {hash:?};")); | ||
} | ||
|
||
package_lines.insert(index_iter.next().unwrap(), format!("{spaces} }};")); | ||
package_lines.insert(index_iter.next().unwrap(), format!("{spaces}}};")); | ||
} | ||
|
||
if package_lines.last().map(String::as_str) != Some("") { | ||
package_lines.push(String::new()); | ||
} | ||
|
||
fs::write(package_path, package_lines.join("\n"))?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn nix_eval(attr: impl AsRef<str>) -> anyhow::Result<Option<String>> { | ||
let output = String::from_utf8( | ||
Command::new("nix-instantiate") | ||
.args(["--eval", "-A", attr.as_ref()]) | ||
.output()? | ||
.stdout, | ||
)?; | ||
|
||
let trimmed = output.trim(); | ||
|
||
if trimmed.is_empty() || trimmed == "null" { | ||
Ok(None) | ||
} else { | ||
Ok(Some( | ||
trimmed | ||
.strip_prefix('"') | ||
.and_then(|p| p.strip_suffix('"')) | ||
.ok_or_else(|| anyhow!("couldn't parse nix-instantiate output: {output:?}"))? | ||
.to_string(), | ||
)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could make sense to add a link to this PR here or in the Rust source, IMO