From 2b2b55fcc25ddaba99b8fdaac151771ea1bcf658 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 17 Jan 2017 13:22:05 -0800 Subject: [PATCH 1/6] Add dep-info generation Make cargo output a ".d" file containing dependency info (in a format that make and ninja can consume) for each artifact it produces. This will help in integrating into other build systems. --- src/cargo/ops/cargo_rustc/mod.rs | 5 + src/cargo/ops/cargo_rustc/output_depinfo.rs | 127 ++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/cargo/ops/cargo_rustc/output_depinfo.rs diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 370b7e1dca4..3fb4033ed21 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -18,6 +18,8 @@ use util::Freshness; use self::job::{Job, Work}; use self::job_queue::JobQueue; +use self::output_depinfo::output_depinfo; + pub use self::compilation::Compilation; pub use self::context::{Context, Unit}; pub use self::custom_build::{BuildOutput, BuildMap, BuildScripts}; @@ -30,6 +32,7 @@ mod job; mod job_queue; mod layout; mod links; +mod output_depinfo; #[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)] pub enum Kind { Host, Target } @@ -184,6 +187,8 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, .or_insert(HashSet::new()) .extend(feats.iter().map(|feat| format!("feature=\"{}\"", feat))); } + + output_depinfo(&mut cx, unit)?; } for (&(ref pkg, _), output) in cx.build_state.outputs.lock().unwrap().iter() { diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs new file mode 100644 index 00000000000..a571d516ccb --- /dev/null +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -0,0 +1,127 @@ +use std::borrow::ToOwned; +use std::collections::HashSet; +use std::io::{Read, Write}; +use std::fs::File; +use std::path::{Path, PathBuf}; + +use core::{TargetKind}; +use ops::{Context, Unit}; +use util::{CargoResult, internal, human}; + +#[derive(Debug)] +struct DepLine { + target: String, + deps: Vec, +} + +struct DepFile { + dir: String, + deps: Vec, +} + +fn render_filename>(path: P, basedir: Option<&str>) -> CargoResult { + let path = path.as_ref(); + let relpath = match basedir { + None => path, + Some(base) => match path.strip_prefix(base) { + Ok(relpath) => relpath, + _ => path, + } + }; + relpath.to_str().ok_or(internal("path not utf-8")).map(ToOwned::to_owned) +} + +fn read_dep_file>(path: P) -> CargoResult { + let mut file = File::open(&path).map_err(|_| + human("error opening ".to_string() + path.as_ref().to_str().unwrap_or("(bad unicode")))?; + let mut contents = String::new(); + let _ = file.read_to_string(&mut contents)?; + let mut spl = contents.split('\0'); + let dir = spl.next().ok_or(internal("dependency file empty"))?; + let dep_txt = spl.next().ok_or(internal("dependency file missing null byte"))?; + let mut result = Vec::new(); + for line in dep_txt.lines() { + let mut line_spl = line.split(": "); + if let Some(target) = line_spl.next() { + if let Some(deps) = line_spl.next() { + let deps = deps.split_whitespace().map(ToOwned::to_owned).collect(); + result.push(DepLine { + target: target.to_string(), + deps: deps, + }); + } + } + } + Ok(DepFile { + dir: dir.to_string(), + deps: result, + }) +} + +fn add_deps(depfile: &DepFile, deps: &mut HashSet) { + let dep_dir = PathBuf::from(&depfile.dir); + for depline in &depfile.deps { + for dep in &depline.deps { + deps.insert(dep_dir.join(dep)); + } + } +} + +// TODO: probably better to use Context::target_filenames for this +fn target_filename(context: &mut Context, unit: &Unit) -> CargoResult { + let (dir, base) = context.link_stem(&unit).ok_or(internal("can't get link stem"))?; + if unit.target.is_lib() { + Ok(dir.join(["lib", &base, ".rlib"].concat())) + } else { + Ok(dir.join(base)) + } +} + +fn add_deps_for_unit(deps: &mut HashSet, context: &mut Context, unit: &Unit) + -> CargoResult<()> +{ + // TODO: this is duplicated against filename in fingerprint.rs + let kind = match *unit.target.kind() { + TargetKind::Lib(..) => "lib", + TargetKind::Bin => "bin", + TargetKind::Test => "integration-test", + TargetKind::Example => "example", + TargetKind::Bench => "bench", + TargetKind::CustomBuild => "build-script", + }; + let flavor = if unit.profile.test { + "test-" + } else if unit.profile.doc { + "doc-" + } else { + "" + }; + let dep_filename = ["dep-", flavor, kind, "-", &context.file_stem(&unit)].concat(); + let path = context.fingerprint_dir(&unit).join(&dep_filename); + let depfile = read_dep_file(&path)?; + add_deps(&depfile, deps); + Ok(()) +} + +pub fn output_depinfo(context: &mut Context, unit: &Unit) -> CargoResult<()> { + let mut deps = HashSet::new(); + add_deps_for_unit(&mut deps, context, unit)?; + for dep_unit in &context.dep_targets(unit)? { + let source_id = dep_unit.pkg.package_id().source_id(); + if source_id.is_path() { + add_deps_for_unit(&mut deps, context, dep_unit)?; + } + } + let filename = target_filename(context, unit)?; + let mut output_path = filename.clone().into_os_string(); + output_path.push(".d"); + let basedir = None; // TODO + let target_fn = render_filename(filename, basedir)?; + let mut outfile = File::create(output_path)?; + write!(outfile, "{}:", target_fn)?; + for dep in &deps { + write!(outfile, " {}", render_filename(dep, basedir)?)?; + } + writeln!(outfile, "")?; + Ok(()) +} From 1ed7f69d9f8927352127a0ed0740164020516ea3 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Tue, 24 Jan 2017 17:49:39 -0800 Subject: [PATCH 2/6] Track TargetKind::Example split --- src/cargo/ops/cargo_rustc/output_depinfo.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index a571d516ccb..7c8ac327609 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -85,7 +85,8 @@ fn add_deps_for_unit(deps: &mut HashSet, context: &mut Context, unit: & TargetKind::Lib(..) => "lib", TargetKind::Bin => "bin", TargetKind::Test => "integration-test", - TargetKind::Example => "example", + TargetKind::ExampleBin | + TargetKind::ExampleLib(..) => "example", TargetKind::Bench => "bench", TargetKind::CustomBuild => "build-script", }; From f9fdc76407785932a1b3e909f608a2b325471a93 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Jan 2017 09:09:07 -0800 Subject: [PATCH 3/6] More improvements Use Context::target_filenames rather than trying to redo that. Traverse path dependencies recursively, so we get transitive deps. Use existing fingerprint parsing. --- src/cargo/ops/cargo_rustc/fingerprint.rs | 12 +- src/cargo/ops/cargo_rustc/output_depinfo.rs | 131 +++++--------------- 2 files changed, 44 insertions(+), 99 deletions(-) diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 02ea5a47f62..d892a651721 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -563,7 +563,8 @@ fn log_compare(unit: &Unit, compare: &CargoResult<()>) { } } -fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { +// Parse the dep-info into a list of paths +pub fn parse_dep_info(dep_info: &Path) -> CargoResult>> { macro_rules! fs_try { ($e:expr) => (match $e { Ok(e) => e, Err(..) => return Ok(None) }) } @@ -600,8 +601,15 @@ fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { } paths.push(cwd.join(&file)); } + Ok(Some(paths)) +} - Ok(mtime_if_fresh(&dep_info, paths.iter())) +fn dep_info_mtime_if_fresh(dep_info: &Path) -> CargoResult> { + if let Some(paths) = parse_dep_info(dep_info)? { + Ok(mtime_if_fresh(&dep_info, paths.iter())) + } else { + Ok(None) + } } fn pkg_fingerprint(cx: &Context, pkg: &Package) -> CargoResult { diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index 7c8ac327609..add28e023e3 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -1,23 +1,11 @@ -use std::borrow::ToOwned; use std::collections::HashSet; -use std::io::{Read, Write}; +use std::io::Write; use std::fs::File; use std::path::{Path, PathBuf}; -use core::{TargetKind}; use ops::{Context, Unit}; -use util::{CargoResult, internal, human}; - -#[derive(Debug)] -struct DepLine { - target: String, - deps: Vec, -} - -struct DepFile { - dir: String, - deps: Vec, -} +use util::{CargoResult, internal}; +use ops::cargo_rustc::fingerprint; fn render_filename>(path: P, basedir: Option<&str>) -> CargoResult { let path = path.as_ref(); @@ -28,101 +16,50 @@ fn render_filename>(path: P, basedir: Option<&str>) -> CargoResul _ => path, } }; - relpath.to_str().ok_or(internal("path not utf-8")).map(ToOwned::to_owned) + relpath.to_str().ok_or(internal("path not utf-8")).map(|f| f.replace(" ", "\\ ")) } -fn read_dep_file>(path: P) -> CargoResult { - let mut file = File::open(&path).map_err(|_| - human("error opening ".to_string() + path.as_ref().to_str().unwrap_or("(bad unicode")))?; - let mut contents = String::new(); - let _ = file.read_to_string(&mut contents)?; - let mut spl = contents.split('\0'); - let dir = spl.next().ok_or(internal("dependency file empty"))?; - let dep_txt = spl.next().ok_or(internal("dependency file missing null byte"))?; - let mut result = Vec::new(); - for line in dep_txt.lines() { - let mut line_spl = line.split(": "); - if let Some(target) = line_spl.next() { - if let Some(deps) = line_spl.next() { - let deps = deps.split_whitespace().map(ToOwned::to_owned).collect(); - result.push(DepLine { - target: target.to_string(), - deps: deps, - }); - } - } +fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context<'a, 'b>, + unit: &Unit<'a>, visited: &mut HashSet>) -> CargoResult<()> +{ + if visited.contains(unit) { + return Ok(()); } - Ok(DepFile { - dir: dir.to_string(), - deps: result, - }) -} + visited.insert(unit.clone()); -fn add_deps(depfile: &DepFile, deps: &mut HashSet) { - let dep_dir = PathBuf::from(&depfile.dir); - for depline in &depfile.deps { - for dep in &depline.deps { - deps.insert(dep_dir.join(dep)); + let dep_info_loc = fingerprint::dep_info_loc(context, unit); + if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? { + for path in paths { + deps.insert(path); } } -} - -// TODO: probably better to use Context::target_filenames for this -fn target_filename(context: &mut Context, unit: &Unit) -> CargoResult { - let (dir, base) = context.link_stem(&unit).ok_or(internal("can't get link stem"))?; - if unit.target.is_lib() { - Ok(dir.join(["lib", &base, ".rlib"].concat())) - } else { - Ok(dir.join(base)) - } -} -fn add_deps_for_unit(deps: &mut HashSet, context: &mut Context, unit: &Unit) - -> CargoResult<()> -{ - // TODO: this is duplicated against filename in fingerprint.rs - let kind = match *unit.target.kind() { - TargetKind::Lib(..) => "lib", - TargetKind::Bin => "bin", - TargetKind::Test => "integration-test", - TargetKind::ExampleBin | - TargetKind::ExampleLib(..) => "example", - TargetKind::Bench => "bench", - TargetKind::CustomBuild => "build-script", - }; - let flavor = if unit.profile.test { - "test-" - } else if unit.profile.doc { - "doc-" - } else { - "" - }; - let dep_filename = ["dep-", flavor, kind, "-", &context.file_stem(&unit)].concat(); - let path = context.fingerprint_dir(&unit).join(&dep_filename); - let depfile = read_dep_file(&path)?; - add_deps(&depfile, deps); - Ok(()) -} - -pub fn output_depinfo(context: &mut Context, unit: &Unit) -> CargoResult<()> { - let mut deps = HashSet::new(); - add_deps_for_unit(&mut deps, context, unit)?; + // recursively traverse all transitive dependencies for dep_unit in &context.dep_targets(unit)? { let source_id = dep_unit.pkg.package_id().source_id(); if source_id.is_path() { - add_deps_for_unit(&mut deps, context, dep_unit)?; + add_deps_for_unit(deps, context, dep_unit, visited)?; } } - let filename = target_filename(context, unit)?; - let mut output_path = filename.clone().into_os_string(); - output_path.push(".d"); + Ok(()) +} + +pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) -> CargoResult<()> { + let mut deps = HashSet::new(); + let mut visited = HashSet::new(); + add_deps_for_unit(&mut deps, context, unit, &mut visited)?; let basedir = None; // TODO - let target_fn = render_filename(filename, basedir)?; - let mut outfile = File::create(output_path)?; - write!(outfile, "{}:", target_fn)?; - for dep in &deps { - write!(outfile, " {}", render_filename(dep, basedir)?)?; + for (_filename, link_dst, _linkable) in context.target_filenames(unit)? { + if let Some(link_dst) = link_dst { + let output_path = link_dst.with_extension("d"); + let target_fn = render_filename(link_dst, basedir)?; + let mut outfile = File::create(output_path)?; + write!(outfile, "{}:", target_fn)?; + for dep in &deps { + write!(outfile, " {}", render_filename(dep, basedir)?)?; + } + writeln!(outfile, "")?; + } } - writeln!(outfile, "")?; Ok(()) } From abf8e35611d9681dfa8cae3d296b2ac28209be35 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Jan 2017 13:12:14 -0800 Subject: [PATCH 4/6] Buffer file writing for dep-info Use BufWriter when generating .d files with dep-info, to avoid excessive numbers of write syscalls. --- src/cargo/ops/cargo_rustc/output_depinfo.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index add28e023e3..5ed98eb1159 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -1,5 +1,5 @@ use std::collections::HashSet; -use std::io::Write; +use std::io::{Write, BufWriter}; use std::fs::File; use std::path::{Path, PathBuf}; @@ -53,7 +53,7 @@ pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) -> if let Some(link_dst) = link_dst { let output_path = link_dst.with_extension("d"); let target_fn = render_filename(link_dst, basedir)?; - let mut outfile = File::create(output_path)?; + let mut outfile = BufWriter::new(File::create(output_path)?); write!(outfile, "{}:", target_fn)?; for dep in &deps { write!(outfile, " {}", render_filename(dep, basedir)?)?; From d79249b629a1fb532a16c2c9d60717afaf7a204a Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Jan 2017 13:43:13 -0800 Subject: [PATCH 5/6] Add build script rerun-if-changed dependencies Also pick up the rerun-if-changed dependencies from build scripts. --- src/cargo/ops/cargo_rustc/output_depinfo.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index 5ed98eb1159..188956187a8 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -27,6 +27,7 @@ fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context< } visited.insert(unit.clone()); + // Add dependencies from rustc dep-info output (stored in fingerprint directory) let dep_info_loc = fingerprint::dep_info_loc(context, unit); if let Some(paths) = fingerprint::parse_dep_info(&dep_info_loc)? { for path in paths { @@ -34,7 +35,15 @@ fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context< } } - // recursively traverse all transitive dependencies + // Add rerun-if-changed dependencies + let key = (unit.pkg.package_id().clone(), unit.kind); + if let Some(output) = context.build_state.outputs.lock().unwrap().get(&key) { + for path in &output.rerun_if_changed { + deps.insert(path.into()); + } + } + + // Recursively traverse all transitive dependencies for dep_unit in &context.dep_targets(unit)? { let source_id = dep_unit.pkg.package_id().source_id(); if source_id.is_path() { From 5cb69957e31ad5c33aa9cbdc745cbd84874c6736 Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 26 Jan 2017 16:01:36 -0800 Subject: [PATCH 6/6] Add simple tests, cleanup Tests for existence for dep-info output in simple compilation cases. Deletes the dep-info file if it fails (for example, if it can't find one of the dep-info inputs). --- src/cargo/ops/cargo_rustc/output_depinfo.rs | 39 +++++++--- tests/dep-info.rs | 79 +++++++++++++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 tests/dep-info.rs diff --git a/src/cargo/ops/cargo_rustc/output_depinfo.rs b/src/cargo/ops/cargo_rustc/output_depinfo.rs index 188956187a8..c559e4741ee 100644 --- a/src/cargo/ops/cargo_rustc/output_depinfo.rs +++ b/src/cargo/ops/cargo_rustc/output_depinfo.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use std::io::{Write, BufWriter}; -use std::fs::File; +use std::io::{Write, BufWriter, ErrorKind}; +use std::fs::{self, File}; use std::path::{Path, PathBuf}; use ops::{Context, Unit}; @@ -22,10 +22,9 @@ fn render_filename>(path: P, basedir: Option<&str>) -> CargoResul fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context<'a, 'b>, unit: &Unit<'a>, visited: &mut HashSet>) -> CargoResult<()> { - if visited.contains(unit) { + if !visited.insert(unit.clone()) { return Ok(()); } - visited.insert(unit.clone()); // Add dependencies from rustc dep-info output (stored in fingerprint directory) let dep_info_loc = fingerprint::dep_info_loc(context, unit); @@ -33,6 +32,10 @@ fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context< for path in paths { deps.insert(path); } + } else { + debug!("can't find dep_info for {:?} {:?}", + unit.pkg.package_id(), unit.profile); + return Err(internal("dep_info missing")); } // Add rerun-if-changed dependencies @@ -56,18 +59,32 @@ fn add_deps_for_unit<'a, 'b>(deps: &mut HashSet, context: &mut Context< pub fn output_depinfo<'a, 'b>(context: &mut Context<'a, 'b>, unit: &Unit<'a>) -> CargoResult<()> { let mut deps = HashSet::new(); let mut visited = HashSet::new(); - add_deps_for_unit(&mut deps, context, unit, &mut visited)?; + let success = add_deps_for_unit(&mut deps, context, unit, &mut visited).is_ok(); let basedir = None; // TODO for (_filename, link_dst, _linkable) in context.target_filenames(unit)? { if let Some(link_dst) = link_dst { let output_path = link_dst.with_extension("d"); - let target_fn = render_filename(link_dst, basedir)?; - let mut outfile = BufWriter::new(File::create(output_path)?); - write!(outfile, "{}:", target_fn)?; - for dep in &deps { - write!(outfile, " {}", render_filename(dep, basedir)?)?; + if success { + let mut outfile = BufWriter::new(File::create(output_path)?); + let target_fn = render_filename(link_dst, basedir)?; + write!(outfile, "{}:", target_fn)?; + for dep in &deps { + write!(outfile, " {}", render_filename(dep, basedir)?)?; + } + writeln!(outfile, "")?; + } else { + // dep-info generation failed, so delete output file. This will usually + // cause the build system to always rerun the build rule, which is correct + // if inefficient. + match fs::remove_file(output_path) { + Err(err) => { + if err.kind() != ErrorKind::NotFound { + return Err(err.into()); + } + } + _ => () + } } - writeln!(outfile, "")?; } } Ok(()) diff --git a/tests/dep-info.rs b/tests/dep-info.rs new file mode 100644 index 00000000000..2f0513e7b7e --- /dev/null +++ b/tests/dep-info.rs @@ -0,0 +1,79 @@ +extern crate cargotest; +extern crate hamcrest; + +use cargotest::support::{basic_bin_manifest, main_file, execs, project}; +use hamcrest::{assert_that, existing_file}; + +#[test] +fn build_dep_info() { + let p = project("foo") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/foo.rs", &main_file(r#""i am foo""#, &[])); + + assert_that(p.cargo_process("build"), execs().with_status(0)); + + let depinfo_bin_path = &p.bin("foo").with_extension("d"); + + assert_that(depinfo_bin_path, existing_file()); +} + +#[test] +fn build_dep_info_lib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["lib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "lib").with_extension("d"), existing_file()); +} + + +#[test] +fn build_dep_info_rlib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["rlib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "rlib").with_extension("d"), existing_file()); +} + +#[test] +fn build_dep_info_dylib() { + let p = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [[example]] + name = "ex" + crate-type = ["dylib"] + "#) + .file("src/lib.rs", "") + .file("examples/ex.rs", ""); + + assert_that(p.cargo_process("build").arg("--example=ex"), execs().with_status(0)); + assert_that(&p.example_lib("ex", "dylib").with_extension("d"), existing_file()); +}