Skip to content
Open
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
38 changes: 36 additions & 2 deletions crates/nix-ninja-task/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,15 @@ fn main() -> Result<()> {
println!("nix-ninja-task: {desc}");
}

// CMake sometimes emits absolute build-dir prefixes like
// `cd /build/<src>/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);
Expand Down Expand Up @@ -130,3 +136,31 @@ fn spawn_process(cmdline: &str) -> Result<i32> {
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<String> {
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
}
48 changes: 29 additions & 19 deletions crates/nix-ninja/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +22,11 @@ pub fn build(
build_filename: &str,
targets: Vec<String>,
config: BuildConfig,
) -> Result<DerivedFile> {
) -> Result<Vec<DerivedFile>> {
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 {
Expand All @@ -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<PathBuf, DerivedFile> = 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<DerivedFile> = output_set.into_values().collect();
outputs.sort();
Ok(outputs)
}

fn load_file(build_filename: &str) -> Result<load::Loader> {
Expand Down Expand Up @@ -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);
}
Expand Down
25 changes: 19 additions & 6 deletions crates/nix-ninja/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,19 @@ pub fn run() -> Result<i32> {
}

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)
}
Expand All @@ -110,7 +117,7 @@ pub fn run() -> Result<i32> {
}
}

fn build(cli: &Cli, build_dir: &Path) -> Result<DerivedFile> {
fn build(cli: &Cli, build_dir: &Path) -> Result<Vec<DerivedFile>> {
let config = BuildConfig {
build_dir: build_dir.to_path_buf(),
store_dir: cli.store_dir.clone(),
Expand Down Expand Up @@ -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}");
}
Expand Down
Loading