cargo_gn/
lib.rs

1mod deps;
2
3use std::env;
4use std::path::PathBuf;
5use std::process::Command;
6use std::process::Stdio;
7
8#[derive(Clone, Debug)]
9struct Dirs {
10  pub out: PathBuf,
11  pub root: PathBuf,
12}
13
14fn get_dirs(manifest_dir: Option<&str>) -> Dirs {
15  // The OUT_DIR is going to be a crate-specific directory like
16  // "target/debug/build/cargo_gn_example-eee5160084460b2c"
17  // But we want to share the GN build amongst all crates
18  // and return the path "target/debug". So to find it, we walk up three
19  // directories.
20  // TODO(ry) This is quite brittle - if Cargo changes the directory structure
21  // this could break.
22  let out = env::var("OUT_DIR").map(PathBuf::from).unwrap();
23  let out = out
24    .parent()
25    .unwrap()
26    .parent()
27    .unwrap()
28    .parent()
29    .unwrap()
30    .to_owned();
31
32  let root = match manifest_dir {
33    Some(s) => env::current_dir().unwrap().join(s),
34    None => env::var("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap(),
35  };
36
37  let mut dirs = Dirs { out, root };
38  maybe_symlink_root_dir(&mut dirs);
39  dirs
40}
41
42#[cfg(not(target_os = "windows"))]
43fn maybe_symlink_root_dir(_: &mut Dirs) {}
44
45#[cfg(target_os = "windows")]
46fn maybe_symlink_root_dir(dirs: &mut Dirs) {
47  // GN produces invalid paths if the source (a.k.a. root) directory is on a
48  // different drive than the output. If this is the case we'll create a
49  // symlink called "gn_root' in the out directory, next to 'gn_out', so it
50  // appears as if they're both on the same drive.
51  use std::fs::remove_dir;
52  use std::os::windows::fs::symlink_dir;
53  use std::path::{Component, Path};
54
55  let get_prefix = |p: &Path| {
56    p.components()
57      .find_map(|c| match c {
58        Component::Prefix(p) => Some(p),
59        _ => None,
60      })
61      .map(|p| p.as_os_str().to_owned())
62  };
63
64  let Dirs { out, root } = dirs;
65  if get_prefix(out) != get_prefix(root) {
66    let symlink = &*out.join("gn_root");
67    let target = &*root.canonicalize().unwrap();
68
69    println!("Creating symlink {:?} to {:?}", &symlink, &root);
70
71    loop {
72      match symlink.canonicalize() {
73        Ok(existing) if existing == target => break,
74        Ok(_) => remove_dir(symlink).expect("remove_dir failed"),
75        Err(_) => {
76          break symlink_dir(target, symlink).expect("symlink_dir failed")
77        }
78      }
79    }
80
81    dirs.root = symlink.to_path_buf();
82  }
83}
84
85pub fn is_debug() -> bool {
86  // Cargo sets PROFILE to either "debug" or "release", which conveniently
87  // matches the build modes we support.
88  let m = env::var("PROFILE").unwrap();
89  if m == "release" {
90    false
91  } else if m == "debug" {
92    true
93  } else {
94    panic!("unhandled PROFILE value {}", m)
95  }
96}
97
98fn gn() -> String {
99  env::var("GN").unwrap_or_else(|_| "gn".to_owned())
100}
101
102pub type NinjaEnv = Vec<(String, String)>;
103
104fn ninja(gn_out_dir: &PathBuf, maybe_env: Option<NinjaEnv>) -> Command {
105  let cmd_string = env::var("NINJA").unwrap_or_else(|_| "ninja".to_owned());
106  let mut cmd = Command::new(cmd_string);
107  cmd.arg("-C");
108  cmd.arg(&gn_out_dir);
109  if let Some(env) = maybe_env {
110    for item in env {
111      cmd.env(item.0, item.1);
112    }
113  }
114  cmd
115}
116
117pub type GnArgs = Vec<String>;
118
119pub fn maybe_gen(manifest_dir: &str, gn_args: GnArgs) -> PathBuf {
120  let dirs = get_dirs(Some(manifest_dir));
121  let gn_out_dir = dirs.out.join("gn_out");
122
123  if !gn_out_dir.exists() || !gn_out_dir.join("build.ninja").exists() {
124    let args = gn_args.join(" ");
125
126    let path = env::current_dir().unwrap();
127    println!("The current directory is {}", path.display());
128    println!(
129      "gn gen --root={} {}",
130      dirs.root.display(),
131      gn_out_dir.display()
132    );
133    let mut cmd = Command::new(gn());
134    cmd.arg(format!("--root={}", dirs.root.display()));
135    cmd.arg("gen");
136    cmd.arg(&gn_out_dir);
137    cmd.arg("--args=".to_owned() + &args);
138    cmd.stdout(Stdio::inherit());
139    cmd.stderr(Stdio::inherit());
140    cmd.envs(env::vars());
141    run(&mut cmd, "gn gen");
142  }
143  gn_out_dir
144}
145
146pub fn build(target: &str, maybe_env: Option<NinjaEnv>) {
147  let gn_out_dir = get_dirs(None).out.join("gn_out");
148
149  // This helps Rust source files locate the snapshot, source map etc.
150  println!("cargo:rustc-env=GN_OUT_DIR={}", gn_out_dir.display());
151
152  let mut cmd = ninja(&gn_out_dir, maybe_env.clone());
153  cmd.arg(target);
154  run(&mut cmd, "ninja");
155
156  rerun_if_changed(&gn_out_dir, maybe_env, target);
157
158  // TODO This is not sufficent. We need to use "gn desc" to query the target
159  // and figure out what else we need to add to the link.
160  println!(
161    "cargo:rustc-link-search=native={}/obj/",
162    gn_out_dir.display()
163  );
164}
165
166/// build.rs does not get re-run unless we tell cargo about what files we
167/// depend on. This outputs a bunch of rerun-if-changed lines to stdout.
168fn rerun_if_changed(
169  out_dir: &PathBuf,
170  maybe_env: Option<NinjaEnv>,
171  target: &str,
172) {
173  let deps = deps::ninja_get_deps(out_dir, maybe_env, target);
174  for d in deps {
175    let p = out_dir.join(d);
176    assert!(p.exists());
177    println!("cargo:rerun-if-changed={}", p.display());
178  }
179}
180
181fn run(cmd: &mut Command, program: &str) {
182  use std::io::ErrorKind;
183  println!("running: {:?}", cmd);
184  let status = match cmd.status() {
185    Ok(status) => status,
186    Err(ref e) if e.kind() == ErrorKind::NotFound => {
187      fail(&format!(
188        "failed to execute command: {}\nis `{}` not installed?",
189        e, program
190      ));
191    }
192    Err(e) => fail(&format!("failed to execute command: {}", e)),
193  };
194  if !status.success() {
195    fail(&format!(
196      "command did not execute successfully, got: {}",
197      status
198    ));
199  }
200}
201
202fn fail(s: &str) -> ! {
203  panic!("\n{}\n\nbuild script failed, must exit now", s)
204}