diff --git a/README.md b/README.md index 6152bcd..6e312ae 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,9 @@ Then you can try building the examples: # Builds a basic main.cpp. nix build github:pdtpartners/nix-ninja#example-hello +# Builds a basic CMake project via a Ninja build graph. +nix build github:pdtpartners/nix-ninja#example-cmake + # Builds a basic main.cpp with dependency inference for its header. nix build github:pdtpartners/nix-ninja#example-header diff --git a/crates/nix-ninja-task/src/main.rs b/crates/nix-ninja-task/src/main.rs index 07dc945..edcd242 100644 --- a/crates/nix-ninja-task/src/main.rs +++ b/crates/nix-ninja-task/src/main.rs @@ -75,9 +75,15 @@ fn main() -> Result<()> { println!("nix-ninja-task: {desc}"); } + // CMake sometimes emits absolute build-dir prefixes like + // `cd /build//build && ...`, which are invalid after we recreate the + // build tree under /build/source/build. Strip that prefix and run in the + // recreated build dir we already chdir'd into. + let normalized_cmdline = normalize_cmdline(&cli.cmdline); + // Spawn cmdline process via sh like ninja upstream does. - println!("nix-ninja-task: Running: /bin/sh -c \"{}\"", cli.cmdline); - let exit_code = spawn_process(&cli.cmdline)?; + println!("nix-ninja-task: Running: /bin/sh -c \"{normalized_cmdline}\""); + let exit_code = spawn_process(&normalized_cmdline)?; if exit_code != 0 { println!("nix-ninja-task: Failed with exit code {exit_code}"); std::process::exit(exit_code); @@ -130,3 +136,31 @@ fn spawn_process(cmdline: &str) -> Result { let output = cmd.status()?; Ok(output.code().unwrap_or(1)) } + +fn normalize_cmdline(cmdline: &str) -> String { + let trimmed = cmdline.trim(); + let without_cd = match trimmed.split_once("&&") { + Some((prefix, rest)) if prefix.trim_start().starts_with("cd /build/") => rest.trim_start(), + _ => trimmed, + }; + + if let Some(alias) = extract_build_alias(without_cd) { + let old_prefix = format!("{alias}/"); + return without_cd.replace(&old_prefix, "/build/source/"); + } + + without_cd.to_string() +} + +fn extract_build_alias(cmdline: &str) -> Option { + for (idx, _) in cmdline.match_indices("/build/") { + let mut rest = &cmdline[idx + "/build/".len()..]; + let slash_idx = rest.find('/')?; + rest = &rest[..slash_idx]; + if rest.is_empty() || rest == "source" { + continue; + } + return Some(format!("/build/{rest}")); + } + None +} diff --git a/crates/nix-ninja/src/build.rs b/crates/nix-ninja/src/build.rs index 5933029..f89f732 100644 --- a/crates/nix-ninja/src/build.rs +++ b/crates/nix-ninja/src/build.rs @@ -6,6 +6,7 @@ use n2::graph::{Build, BuildId, FileId, Graph}; use n2::{canon, load, scanner}; use nix_ninja_task::derived_file::DerivedFile; use nix_tool::{NixTool, StoreConfig}; +use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; use std::path::PathBuf; @@ -21,7 +22,11 @@ pub fn build( build_filename: &str, targets: Vec, config: BuildConfig, -) -> Result { +) -> Result> { + if targets.is_empty() { + return Err(anyhow!("at least one target is required")); + } + let mut loader = load_file(build_filename)?; let nix = NixTool::new(StoreConfig { @@ -44,26 +49,31 @@ pub fn build( let mut scheduler = Scheduler::new(&mut loader.graph, &mut runner); - // TODO: Support multiple targets, probably treat it like a dynamically - // generated phony target. - let Some(name) = targets.first() else { - return Err(anyhow!("unimplemented")); - }; - let fid = scheduler - .lookup(name) - .ok_or_else(|| anyhow!("unknown path requested: {}", name))?; - let _ = scheduler.want_file(fid); + let mut target_fids: Vec<(String, FileId)> = Vec::new(); + for name in &targets { + let fid = scheduler + .lookup(name) + .ok_or_else(|| anyhow!("unknown path requested: {}", name))?; + scheduler.want_file(fid)?; + target_fids.push((name.clone(), fid)); + } scheduler.run()?; - // println!("Successfully generated all derivations"); - - let derived_file = runner.derived_files.get(&fid).ok_or(anyhow!( - "Missing derived file {:?} for target {}", - fid, - name - ))?; + let mut output_set: HashMap = HashMap::new(); + for (name, fid) in target_fids { + let derived_files = runner.derived_files.get(&fid).ok_or(anyhow!( + "Missing derived file {:?} for target {}", + fid, + name + ))?; + for derived_file in derived_files { + output_set.insert(derived_file.build_path.clone(), derived_file.clone()); + } + } - Ok(derived_file.clone()) + let mut outputs: Vec = output_set.into_values().collect(); + outputs.sort(); + Ok(outputs) } fn load_file(build_filename: &str) -> Result { @@ -304,7 +314,7 @@ impl<'a> Scheduler<'a> { continue; } - let bid = self.runner.wait(&mut self.graph.files)?; + let bid = self.runner.wait()?; // println!("Derivation for build {:?} has been written", &bid); self.ready_dependents(bid); } diff --git a/crates/nix-ninja/src/cli.rs b/crates/nix-ninja/src/cli.rs index c4d0e61..9fdcabc 100644 --- a/crates/nix-ninja/src/cli.rs +++ b/crates/nix-ninja/src/cli.rs @@ -94,12 +94,19 @@ pub fn run() -> Result { } match build(&cli, &build_dir) { - Ok(derived_file) => { + Ok(derived_files) => { if cli.is_output_derivation { + if derived_files.len() != 1 { + return Err(anyhow!( + "Expected exactly one derivation output, got {}", + derived_files.len() + )); + } + let derived_file = &derived_files[0]; let out = env::var("out").map_err(|_| anyhow!("Expected $out to be set"))?; fs::copy(derived_file.derived_path.store_path().path(), out)?; - } else { - local::symlink_derived_files(&nix_tool, &build_dir, &[derived_file])?; + } else if !derived_files.is_empty() { + local::symlink_derived_files(&nix_tool, &build_dir, &derived_files)?; } Ok(0) } @@ -110,7 +117,7 @@ pub fn run() -> Result { } } -fn build(cli: &Cli, build_dir: &Path) -> Result { +fn build(cli: &Cli, build_dir: &Path) -> Result> { let config = BuildConfig { build_dir: build_dir.to_path_buf(), store_dir: cli.store_dir.clone(), @@ -140,8 +147,14 @@ fn subtool( } "drv" => { let cli = Cli::parse(); - let derived_file = build(&cli, build_dir)?; - let output = nix_tool.derivation_show(&derived_file.derived_path.store_path())?; + let derived_files = build(&cli, build_dir)?; + if derived_files.len() != 1 { + return Err(anyhow!( + "Expected exactly one derivation output, got {}", + derived_files.len() + )); + } + let output = nix_tool.derivation_show(&derived_files[0].derived_path.store_path())?; let stdout = str::from_utf8(&output.stdout)?; println!("{stdout}"); } diff --git a/crates/nix-ninja/src/task.rs b/crates/nix-ninja/src/task.rs index dc1d240..dd79fb8 100644 --- a/crates/nix-ninja/src/task.rs +++ b/crates/nix-ninja/src/task.rs @@ -83,7 +83,7 @@ impl Deref for Task { pub struct BuildResult { pub bid: BuildId, pub derived_path: Option, - pub derived_files: Vec, + pub derived_files_by_fid: Vec<(FileId, Vec)>, pub err: Option, } @@ -97,7 +97,7 @@ pub struct RunnerConfig { /// Runner is an async runtime that spawns threads for each task. pub struct Runner { - pub derived_files: HashMap, + pub derived_files: HashMap>, build_dir_inputs: HashMap, tx: mpsc::Sender, @@ -198,7 +198,9 @@ impl Runner { let config = self.config.clone(); let nix_build_lock = self.nix_build_lock.clone(); std::thread::spawn(move || { - let (derived_path, err) = + let (derived_path, derived_files_by_fid, err) = if task.cmdline.is_none() { + (None, phony_output_mappings(task.outs(), &task.inputs), None) + } else { match build_task_derivation(tools.clone(), task.clone()) { Ok(drv) => match handle_derivation_result( tools.clone(), @@ -207,30 +209,44 @@ impl Runner { &config, nix_build_lock, ) { - Ok(final_derived_path) => (Some(final_derived_path), None), - Err(err) => (None, Some(err.context(format!("Failed to handle derivation result for task '{}' (derivation: {})\nDerivation JSON:\n{}", task.name, drv.name, drv.to_json_pretty().unwrap_or_else(|_| "Failed to serialize derivation".to_string()))))), + Ok(final_derived_path) => { + let mut outputs_by_fid: Vec<(FileId, Vec)> = Vec::new(); + for fid in task.outs() { + let file = &task.files[fid]; + let built_file = new_built_file( + final_derived_path.clone(), + file.name.clone().into(), + ); + outputs_by_fid.push((*fid, vec![built_file])); + } + (Some(final_derived_path), outputs_by_fid, None) + } + Err(err) => ( + None, + Vec::new(), + Some(err.context(format!( + "Failed to handle derivation result for task '{}' (derivation: {})\nDerivation JSON:\n{}", + task.name, + drv.name, + drv.to_json_pretty().unwrap_or_else(|_| "Failed to serialize derivation".to_string()) + ))), + ), }, - Err(err) => (None, Some(err.context(format!("Failed to build task derivation for task '{}'", task.name)))), - }; - - // Create DerivedFiles for all outputs if successful - let derived_files = if let Some(ref final_derived_path) = derived_path { - let mut drv_outputs: Vec = Vec::new(); - for fid in task.outs() { - let file = &task.files[fid]; - let built_file = - new_built_file(final_derived_path.clone(), file.name.clone().into()); - drv_outputs.push(built_file); + Err(err) => ( + None, + Vec::new(), + Some(err.context(format!( + "Failed to build task derivation for task '{}'", + task.name + ))), + ), } - drv_outputs - } else { - Vec::new() }; let result = BuildResult { bid, derived_path, - derived_files, + derived_files_by_fid, err, }; let _ = tx.send(result); @@ -239,7 +255,7 @@ impl Runner { Ok(()) } - pub fn wait(&mut self, files: &mut graph::GraphFiles) -> Result { + pub fn wait(&mut self) -> Result { let result = self.rx.recv().unwrap(); if let Some(err) = result.err { eprintln!("Error: {err}"); @@ -264,8 +280,10 @@ impl Runner { )); } - for derived_file in result.derived_files { - self.add_derived_file(files, derived_file.clone()); + for (fid, mut derived_files) in result.derived_files_by_fid { + derived_files.sort(); + derived_files.dedup(); + self.derived_files.insert(fid, derived_files); } Ok(result.bid) @@ -282,7 +300,11 @@ impl Runner { None => files.id_from_canonical(path_str), }; - self.derived_files.entry(fid).or_insert(derived_file); + let entry = self.derived_files.entry(fid).or_default(); + if !entry.contains(&derived_file) { + entry.push(derived_file); + entry.sort(); + } fid } @@ -301,33 +323,36 @@ impl Runner { // they must all be linked into the derivation's source directory. let mut input_set: HashMap = HashMap::new(); for fid in build.ordering_ins() { - // TODO: what about phony inputs? - let input = match self.derived_files.get(fid) { - Some(df) => df.to_owned(), - None => { - let file = &files.by_id[*fid]; - if file.name.starts_with(&store_dir) { - // TODO: Perhaps need to add this as inputSrc? But - // will also have to change DerivedFile to have source - // Option, because we don't want it to be - // added to $NIX_NINJA_INPUTS. - // DerivedFile { - // path: SingleDerivedPath::Opaque(StorePath::new(file.name)), - // source: &file.name, - // } - continue; - } - - let input = new_opaque_file( - &self.tools.nix_tool, - &self.config.build_dir, - file.name.clone().into(), - )?; - self.add_derived_file(files, input.clone().to_owned()); - input.to_owned() + if let Some(derived_files) = self.derived_files.get(fid) { + for input in derived_files { + input_set.insert(input.build_path.clone(), input.clone()); } - }; - input_set.insert(input.build_path.clone(), input.clone()); + continue; + } + + let file = &files.by_id[*fid]; + if file.name == "." { + continue; + } + if file.name.starts_with(&store_dir) { + // TODO: Perhaps need to add this as inputSrc? But + // will also have to change DerivedFile to have source + // Option, because we don't want it to be + // added to $NIX_NINJA_INPUTS. + // DerivedFile { + // path: SingleDerivedPath::Opaque(StorePath::new(file.name)), + // source: &file.name, + // } + continue; + } + + let input = new_opaque_file( + &self.tools.nix_tool, + &self.config.build_dir, + file.name.clone().into(), + )?; + self.add_derived_file(files, input.clone()); + input_set.insert(input.build_path.clone(), input); } let Some(primary_fid) = build.outs().iter().next() else { @@ -354,30 +379,31 @@ impl Runner { let Some(fid) = files.lookup(&arg) else { continue; }; - let input = match self.derived_files.get(&fid) { - Some(derived_file) => derived_file, - None => match self.build_dir_inputs.get(&fid) { - Some(derived_file) => derived_file, - None => { - continue; - } - }, + if let Some(derived_files) = self.derived_files.get(&fid) { + for input in derived_files { + input_set.insert(input.build_path.clone(), input.clone()); + } + continue; + } + + let Some(input) = self.build_dir_inputs.get(&fid) else { + continue; }; input_set.insert(input.build_path.clone(), input.clone()); } - } - // TODO: Can we avoid this? Technically the build rule isn't complete. - // - // Currently need this because there are rules that depend on - // configuration phase generated files in Cpp Nix for example - // `src/libutil/config-util.hh` which has a command like: - // `-Isrc/libutil -include config-util.hh`. - // - // One way is to parse all the includes, then add it to our search - // path above. - for input in self.build_dir_inputs.values() { - input_set.insert(input.build_path.clone(), input.clone()); + // TODO: Can we avoid this? Technically the build rule isn't complete. + // + // Currently need this because there are rules that depend on + // configuration phase generated files in Cpp Nix for example + // `src/libutil/config-util.hh` which has a command like: + // `-Isrc/libutil -include config-util.hh`. + // + // One way is to parse all the includes, then add it to our search + // path above. + for input in self.build_dir_inputs.values() { + input_set.insert(input.build_path.clone(), input.clone()); + } } let mut inputs: Vec = input_set.into_values().collect(); @@ -408,20 +434,25 @@ impl Runner { } } +fn phony_output_mappings(outputs: &[T], inputs: &[U]) -> Vec<(T, Vec)> { + outputs + .iter() + .map(|output| (*output, inputs.to_vec())) + .collect() +} + fn build_task_derivation(tools: Tools, task: Task) -> Result { - let cmdline = match &task.cmdline { - Some(c) => c, - None => { - return Err(anyhow!("Phony tasks not yet supported")); - } - }; + let cmdline = task + .cmdline + .clone() + .ok_or_else(|| anyhow!("Missing command line for task '{}'", task.name))?; let mut drv = Derivation::new( &task.name, &task.system, &format!("{}/bin/nix-ninja-task", tools.nix_ninja_task), ); - drv.add_arg(cmdline); + drv.add_arg(&cmdline); if let Some(desc) = &task.desc { drv.add_arg(&format!("--description={desc}")); @@ -432,7 +463,7 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { let final_value = if key == "NIX_CFLAGS_COMPILE" { // Also add a deterministic random seed based on the task's // cmdline for reproducible builds. - let deterministic_seed = generate_frandom_seed(cmdline); + let deterministic_seed = generate_frandom_seed(&cmdline); format!("{value} -frandom-seed={deterministic_seed}") } else { value.clone() @@ -464,7 +495,6 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { // Handle when rule's dep = gcc, which means we need to find all the // implicit header dependencies normally handled by gcc's depfiles. - let mut discovered_inputs: Vec = Vec::new(); if let Some(deps) = &task.deps { if deps == "gcc" { // Only opaque inputs are processed by gcc @@ -481,7 +511,7 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { &tools.nix_tool, &task.store_dir, &task.build_dir, - cmdline, + &cmdline, files, None, )?; @@ -502,7 +532,6 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { input_set.insert(encoded); drv.add_derived_path(&derived_file.derived_path); - discovered_inputs.push(derived_file); } } } @@ -515,9 +544,20 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { // Add all ninja build outputs. let mut outputs: Vec = Vec::new(); + let mut seen_outputs: HashSet = HashSet::new(); for output_path in &task.outputs { + let relative_output = + relative_from(output_path, &task.build_dir).unwrap_or_else(|| output_path.clone()); + let mut output_name = relative_output.to_string_lossy().into_owned(); + canon::canonicalize_path(&mut output_name); + let canonical_output = PathBuf::from(output_name); + + if !seen_outputs.insert(canonical_output.clone()) { + continue; + } + // Declare a content addressed output. - let normalized_name = normalize_output(&output_path.to_string_lossy()); + let normalized_name = normalize_output(&canonical_output.to_string_lossy()); drv.add_ca_output(&normalized_name, HashAlgorithm::Sha256, OutputHashMode::Nar); // Create a placeholder and encode output for nix-ninja-task. @@ -525,8 +565,8 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { let encoded = format!( "{}:{}:{}", &placeholder.render().display(), - &output_path.display(), - &output_path.display() + &canonical_output.display(), + &canonical_output.display() ); outputs.push(encoded); } @@ -545,11 +585,10 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { .next() .ok_or_else(|| anyhow!("No command found in cmdline"))?; - // TODO: If you don't find it it's ok, e.g. ./generated_binary - let cmdline_path = which_store_path(cmdline_binary)?; - - drv.add_input_src(&cmdline_path); - path.push(format!("{cmdline_path}/bin")); + if let Some(cmdline_path) = cmdline_binary_store_path(cmdline_binary)? { + drv.add_input_src(&cmdline_path); + path.push(format!("{cmdline_path}/bin")); + } drv.set_env("PATH", &path.join(":")); } @@ -720,6 +759,23 @@ pub fn which_store_path(binary_name: &str) -> Result { StorePath::new(store_path) } +fn cmdline_binary_store_path(binary_name: &str) -> Result> { + if let Ok(path) = which_store_path(binary_name) { + return Ok(Some(path)); + } + + if !binary_name.starts_with("/nix/store/") { + return Ok(None); + } + + let store_path = Path::new(binary_name) + .parent() // Get bin/ directory + .and_then(|p| p.parent()) // Get the store path ($out) + .ok_or_else(|| anyhow!("Cannot determine store path from binary: {binary_name}"))?; + + Ok(Some(StorePath::new(store_path)?)) +} + fn extract_store_paths(store_regex: &Regex, s: &str) -> Result> { let mut store_paths = Vec::new(); for cap in store_regex.find_iter(s) { @@ -827,3 +883,28 @@ fn generate_frandom_seed(cmdline: &str) -> String { let result = hasher.finalize(); format!("{result:x}")[..16].to_string() } + +#[cfg(test)] +mod tests { + use super::phony_output_mappings; + + #[test] + fn phony_outputs_clone_inputs_per_output() { + let outputs = vec![1usize, 2usize]; + let inputs = vec!["a".to_string(), "b".to_string()]; + + let mappings = phony_output_mappings(&outputs, &inputs); + assert_eq!(mappings.len(), 2); + assert_eq!(mappings[0], (1usize, inputs.clone())); + assert_eq!(mappings[1], (2usize, inputs)); + } + + #[test] + fn phony_outputs_allow_empty_inputs() { + let outputs = vec![7usize]; + let inputs: Vec = Vec::new(); + + let mappings = phony_output_mappings(&outputs, &inputs); + assert_eq!(mappings, vec![(7usize, Vec::new())]); + } +} diff --git a/modules/flake/examples/cmake/CMakeLists.txt b/modules/flake/examples/cmake/CMakeLists.txt new file mode 100644 index 0000000..d41a2f4 --- /dev/null +++ b/modules/flake/examples/cmake/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.20) + +project(example-cmake LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(hello main.cpp) diff --git a/modules/flake/examples/cmake/main.cpp b/modules/flake/examples/cmake/main.cpp new file mode 100644 index 0000000..95c5753 --- /dev/null +++ b/modules/flake/examples/cmake/main.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + std::cout << "Hello CMake example!" << std::endl; + return 0; +} diff --git a/modules/flake/overlays.nix b/modules/flake/overlays.nix index 9860ee0..dbbec2e 100644 --- a/modules/flake/overlays.nix +++ b/modules/flake/overlays.nix @@ -46,6 +46,10 @@ inherit (self) nix-ninja nix-ninja-task; }; + mkCMakePackage = self.callPackage ./pkgs/mkCMakePackage { + inherit (self) nix-ninja nix-ninja-task; + }; + # meson --internal symbolextractor depends on readelf. # meson = super.meson.overrideAttrs(o: { # buildInputs = (o.buildInputs or []) ++ [ @@ -113,6 +117,12 @@ nativeBuildInputs = [ self.nlohmann_json self.pkg-config ]; }; + example-cmake = self.mkCMakePackage { + name = "example-cmake"; + src = ./examples/cmake; + target = "hello"; + }; + example-nix = self.callPackage ./examples/nix { src = inputs.nix; }; }; diff --git a/modules/flake/packages.nix b/modules/flake/packages.nix index f3079e5..046873c 100644 --- a/modules/flake/packages.nix +++ b/modules/flake/packages.nix @@ -23,6 +23,7 @@ example-multi-source = pkgs.example-multi-source.target; example-shared-lib = pkgs.example-shared-lib.target; example-dynamic-deps = pkgs.example-dynamic-deps.target; + example-cmake = pkgs.example-cmake.target; example-nix = pkgs.example-nix.target; }; @@ -31,6 +32,7 @@ packages = with pkgs; [ agg + cmake gnumake just meson diff --git a/modules/flake/pkgs/mkCMakePackage/default.nix b/modules/flake/pkgs/mkCMakePackage/default.nix new file mode 100644 index 0000000..113392d --- /dev/null +++ b/modules/flake/pkgs/mkCMakePackage/default.nix @@ -0,0 +1,75 @@ +{ cmake +, coreutils +, nix +, nix-ninja +, nix-ninja-task +, patchelf +, stdenv +}: + +{ name ? "${args'.pname}-${args'.version}" +, src +, target +, cmakeFlags ? [ ] +, nativeBuildInputs ? [ ] +, ... +}@args': + +let + normalizedTarget = builtins.replaceStrings [ "/" ] [ "-" ] target; + + ninjaDrv = stdenv.mkDerivation (args' // { + name = "${name}.drv"; + + nativeBuildInputs = [ + cmake + coreutils + nix + nix-ninja + nix-ninja-task + patchelf + ] ++ nativeBuildInputs; + + requiredSystemFeatures = [ "recursive-nix" ]; + + cmakeFlags = [ + "-G" + "Ninja" + "-DCMAKE_MAKE_PROGRAM=${nix-ninja}/bin/nix-ninja" + "-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY" + "-DCMAKE_C_COMPILER_FORCED=ON" + "-DCMAKE_CXX_COMPILER_FORCED=ON" + "-DCMAKE_C_COMPILER_WORKS=ON" + "-DCMAKE_CXX_COMPILER_WORKS=ON" + ] ++ cmakeFlags; + + preConfigure = '' + export NIX_NINJA_DRV="true" + export NINJA="${nix-ninja}/bin/nix-ninja" + export NIX_CONFIG="extra-experimental-features = nix-command ca-derivations dynamic-derivations" + ''; + + buildPhase = '' + runHook preBuild + nix-ninja ${target} + runHook postBuild + ''; + + dontUseCmakeInstall = true; + dontUseCmakeCheck = true; + + # stdenv adds a -rpath with a self reference but self references are not + # allowed by text output. + NIX_NO_SELF_RPATH = true; + + __contentAddressed = true; + outputHashMode = "text"; + outputHashAlgo = "sha256"; + + passthru = { + target = builtins.outputOf ninjaDrv.outPath normalizedTarget; + }; + }); + +in +ninjaDrv diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index c344537..c780590 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -5,5 +5,6 @@ nixosTests.nix-build-multi-source = import ./tests/nix-build-multi-source.nix; nixosTests.nix-build-shared-lib = import ./tests/nix-build-shared-lib.nix; nixosTests.nix-build-dynamic-deps = import ./tests/nix-build-dynamic-deps.nix; + nixosTests.nix-build-cmake = import ./tests/nix-build-cmake.nix; }; } diff --git a/modules/nixos/tests/nix-build-cmake.nix b/modules/nixos/tests/nix-build-cmake.nix new file mode 100644 index 0000000..59ad017 --- /dev/null +++ b/modules/nixos/tests/nix-build-cmake.nix @@ -0,0 +1,8 @@ +{ self, pkgs, lib, ... }@args: + +import ./nix-build.nix { + flakeOutput = "example-cmake"; + inputsFrom = [ pkgs.example-cmake ]; + cmdline = "hello"; + expectedStdout = "Hello CMake example!"; +} args