Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
hydraJobs = { inherit (legacyPackages) nix-template; };
checks = { inherit (legacyPackages) nix-template; }; # items to be ran as part of `nix flake check`
devShells.default = with legacyPackages; mkShell {
nativeBuildInputs = [ rustc cargo clippy pkg-config ];
nativeBuildInputs = [ rustc cargo clippy pkg-config rustfmt ];
buildInputs = [ openssl ];
};
}) // {
Expand Down
141 changes: 100 additions & 41 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,20 @@ pub fn validate_and_serialize_matches(
let include_meta: bool = !matches.is_present("no-meta");

let nixpkgs_layout = matches.is_present("by-name");
assert(!(nixpkgs_layout && matches.value_of("pname") == Some("CHANGE") && matches.value_of("from-url") == None),
"'-p,--pname' or '-u,--from-url' is required when using the --by-name flag");
assert(
!(nixpkgs_layout
&& matches.value_of("pname") == Some("CHANGE")
&& matches.value_of("from-url") == None),
"'-p,--pname' or '-u,--from-url' is required when using the --by-name flag",
);

if matches.is_present("by-name") {
match arg_to_type::<Template>(matches.value_of("TEMPLATE")) {
Template::module | Template::test | Template::mkshell => {
assert(false, "--by-name cannot be used with the 'module', 'test', or 'mkshell' templates");
Template::Module | Template::Test | Template::Mkshell => {
assert(
false,
"--by-name cannot be used with the 'module', 'test', or 'mkshell' templates",
);
}
_ => {}
}
Expand Down Expand Up @@ -290,6 +297,10 @@ pub fn validate_and_serialize_matches(
domain: "CHANGE".to_owned(),
build_inputs: Vec::new(),
native_build_inputs: Vec::new(),
use_cargo_lock_file: false,
cargo_lock_git_deps: Vec::new(),
go_module_path: String::new(),
python_format: "setuptools".to_owned(),
};

if let Some(url) = matches.value_of("from-url") {
Expand All @@ -299,7 +310,7 @@ pub fn validate_and_serialize_matches(

// Auto-detect template when "auto" is selected (either explicitly or as
// default). Uses remote source (--from-url) or local directory (CWD).
if info.template == Template::auto && !matches.is_present("no-detect") {
if info.template == Template::Auto && !matches.is_present("no-detect") {
let candidates = if matches.is_present("from-url") {
// Remote detection: materialise source from URL
crate::detect::detect_template_candidates(&info)
Expand All @@ -312,7 +323,7 @@ pub fn validate_and_serialize_matches(
match candidates.len() {
0 => {
eprintln!("nix-template: no build system detected; defaulting to stdenv");
info.template = Template::stdenv;
info.template = Template::Stdenv(crate::types::StdenvVariant::Default);
}
1 => {
eprintln!(
Expand Down Expand Up @@ -342,56 +353,86 @@ pub fn validate_and_serialize_matches(
}
}
}
} else if info.template == Template::auto {
} else if info.template == Template::Auto {
// --no-detect was specified
info.template = Template::stdenv;
info.template = Template::Stdenv(crate::types::StdenvVariant::Default);
}

// Python format auto-detection: works in both local and remote modes.
// For remote mode, we materialise the source to inspect pyproject.toml.
// For local mode without --init-* (which handles this in build.rs),
// we inspect the current working directory.
if info.template.is_python() {
let format_str = if matches.is_present("from-url") {
// Materialise remote source and detect format
if let Some(source_path) = crate::source::materialise_source(&info) {
crate::detect::detect_python_format(&source_path)
} else {
"setuptools".to_owned()
}
} else {
let cwd = std::env::current_dir().unwrap_or_default();
crate::detect::detect_python_format(&cwd)
};
// Update the format in the Python config
if let Some(config) = info.template.python_config_mut() {
config.format = crate::types::PythonFormat::from_str(&format_str);
}
info.python_format = format_str;
}

// Dependency hash prefetching is on by default when --from-url is provided.
// Users can disable via --skip-vendor-hashes.
let should_prefetch_hashes = matches.is_present("from-url")
&& !matches.is_present("skip-vendor-hashes");
let should_prefetch_hashes =
matches.is_present("from-url") && !matches.is_present("skip-vendor-hashes");
if should_prefetch_hashes {
if let Some(hash) = prefetch_dependency_hash(&info) {
match info.template {
Template::rust => info.cargo_hash = hash,
Template::go => info.vendor_hash = hash,
Template::npm => info.npm_deps_hash = hash,
Template::pnpm => info.pnpm_deps_hash = hash,
match &info.template {
Template::Rust(_) => info.cargo_hash = hash,
Template::Go(_) => info.vendor_hash = hash,
Template::Node(config) => match config.variant {
crate::types::NodeVariant::Npm => info.npm_deps_hash = hash,
crate::types::NodeVariant::Pnpm => info.pnpm_deps_hash = hash,
},
_ => {}
}
}
}

// Inference is on by default for the rust, go, ruby, stdenv, and stdenvNoCC
// templates whenever we have a real source to inspect. Users can disable via `--skip-infer-deps`.
let infer_enabled = matches.is_present("from-url")
&& !matches.is_present("skip-infer-deps");
let infer_enabled = matches.is_present("from-url") && !matches.is_present("skip-infer-deps");
if infer_enabled {
match info.template {
Template::rust => {
match &info.template {
Template::Rust(_) => {
if let Some((build, native)) = infer_rust_dependencies(&info) {
info.build_inputs = build;
info.native_build_inputs = native;
}
}
Template::go => {
Template::Go(_) => {
if let Some((build, native)) = infer_go_dependencies(&info) {
info.build_inputs = build;
info.native_build_inputs = native;
}
}
Template::ruby => {
Template::Ruby => {
ruby::infer_dependencies(&mut info);
}
Template::stdenv | Template::stdenvNoCC => {
Template::Stdenv(_) => {
buildsystem::infer_buildsystem_dependencies(&mut info);
}
Template::dotnet => {
Template::Dotnet => {
if let Some(project_file) = infer_dotnet_project_file(&info) {
info.project_file = project_file;
}
}
Template::Python(_) => {
let deps = crate::deps::python::infer_python_dependencies(&info);
if !deps.is_empty() {
info.propagated_build_inputs = deps;
}
}
_ => {}
}
}
Expand All @@ -414,13 +455,14 @@ pub fn validate_and_serialize_matches(
// The path may be rewritten downstream when one of the --init-* flags
// triggers the structured nix/ layout. Skip the existence check in
// that case; main.rs re-checks each artefact before writing.
let init_will_rewrite_path = matches.is_present("init-flake")
|| matches.is_present("init-npins");
let init_will_rewrite_path =
matches.is_present("init-flake") || matches.is_present("init-npins");
assert(
matches.is_present("stdout")
|| init_will_rewrite_path
|| !path_to_write.exists(),
&format!("Cannot write to file '{}', already exists", path_to_write.display()),
matches.is_present("stdout") || init_will_rewrite_path || !path_to_write.exists(),
&format!(
"Cannot write to file '{}', already exists",
path_to_write.display()
),
);

info
Expand Down Expand Up @@ -463,19 +505,24 @@ pub fn build_expression_info_from_interactive(
domain: "CHANGE".to_owned(),
build_inputs: Vec::new(),
native_build_inputs: Vec::new(),
use_cargo_lock_file: false,
cargo_lock_git_deps: Vec::new(),
go_module_path: String::new(),
python_format: "setuptools".to_owned(),
};

// If URL was provided, fetch metadata
if let Some(url) = data.url {
read_meta_from_url(&url, &mut info, data.include_prereleases);
}

// Vendor hash prefetching is enabled by default (opt-out via skip flag)
if !skip_vendor_hashes {
// Vendor hash prefetching is enabled by default (opt-out via skip flag).
// Skip for Rust when using cargoLock.lockFile (no hash needed).
if !skip_vendor_hashes && !info.use_cargo_lock_file {
if let Some(hash) = prefetch_dependency_hash(&info) {
match info.template {
Template::rust => info.cargo_hash = hash,
Template::go => info.vendor_hash = hash,
match &info.template {
Template::Rust(_) => info.cargo_hash = hash,
Template::Go(_) => info.vendor_hash = hash,
_ => {}
}
}
Expand All @@ -486,30 +533,36 @@ pub fn build_expression_info_from_interactive(
info.build_inputs = build;
info.native_build_inputs = native;
} else if infer_deps {
match info.template {
Template::rust => {
match &info.template {
Template::Rust(_) => {
if let Some((build, native)) = infer_rust_dependencies(&info) {
info.build_inputs = build;
info.native_build_inputs = native;
}
}
Template::go => {
Template::Go(_) => {
if let Some((build, native)) = infer_go_dependencies(&info) {
info.build_inputs = build;
info.native_build_inputs = native;
}
}
Template::ruby => {
Template::Ruby => {
ruby::infer_dependencies(&mut info);
}
Template::stdenv | Template::stdenvNoCC => {
Template::Stdenv(_) => {
buildsystem::infer_buildsystem_dependencies(&mut info);
}
Template::dotnet => {
Template::Dotnet => {
if let Some(project_file) = infer_dotnet_project_file(&info) {
info.project_file = project_file;
}
}
Template::Python(_) => {
let deps = crate::deps::python::infer_python_dependencies(&info);
if !deps.is_empty() {
info.propagated_build_inputs = deps;
}
}
_ => {}
}
}
Expand Down Expand Up @@ -561,7 +614,13 @@ mod tests {

#[test]
fn test_url() {
let m = build_cli().get_matches_from(vec!["nix-template", "python_package", "-u", "https://pypi.org/project/requests/", "--by-name"]);
let m = build_cli().get_matches_from(vec![
"nix-template",
"python_package",
"-u",
"https://pypi.org/project/requests/",
"--by-name",
]);
assert_eq!(m.is_present("stdout"), false);
assert_eq!(m.is_present("by-name"), true);
assert_eq!(m.occurrences_of("from-url"), 1);
Expand Down
Loading
Loading