├── .gitignore ├── example ├── src │ ├── input.txt │ ├── foo.cc │ ├── hello.cc │ ├── hello.h │ ├── build.rs │ ├── count_bytes.py │ └── main.rs ├── Cargo.toml ├── .gn └── BUILD.gn ├── rustfmt.toml ├── .gitmodules ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── test.sh ├── README.md ├── .travis.yml └── src ├── deps.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /example/src/input.txt: -------------------------------------------------------------------------------- 1 | hello 2 | 3 | world 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | tab_spaces = 2 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "build"] 2 | path = example/build 3 | url = https://github.com/timniederhausen/gn-build 4 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. 2 | max_width = 80 3 | tab_spaces = 2 4 | -------------------------------------------------------------------------------- /example/src/foo.cc: -------------------------------------------------------------------------------- 1 | #include "hello.h" 2 | 3 | int main() { 4 | if (returns42() != 42) { 5 | return 1; 6 | } 7 | hello(); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo_gn_example" 3 | version = "0.0.1" 4 | authors = ["Ryan Dahl "] 5 | edition = "2018" 6 | 7 | build = "src/build.rs" 8 | 9 | [build-dependencies] 10 | cargo_gn = { path = "../" } 11 | -------------------------------------------------------------------------------- /example/.gn: -------------------------------------------------------------------------------- 1 | # Uses https://github.com/timniederhausen/gn-build 2 | # TODO(ry) It would be cool if cargo_gn could provide a default .gn file somehow 3 | # so that users didn't need to provide this boilerplate. 4 | buildconfig = "//build/config/BUILDCONFIG.gn" 5 | -------------------------------------------------------------------------------- /example/src/hello.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "hello.h" 3 | 4 | void hello() { 5 | #ifdef IS_RELEASE 6 | printf("hello release\n"); 7 | #else 8 | printf("hello debug\n"); 9 | #endif 10 | } 11 | 12 | int32_t returns42() { 13 | return 42; 14 | } 15 | -------------------------------------------------------------------------------- /example/src/hello.h: -------------------------------------------------------------------------------- 1 | #ifndef HELLO_H 2 | #define HELLO_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | void hello(); 11 | int32_t returns42(); 12 | 13 | 14 | #ifdef __cplusplus 15 | } // extern "C" 16 | #endif 17 | 18 | #endif // HELLO_H 19 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "cargo_gn" 5 | version = "0.0.15" 6 | 7 | [[package]] 8 | name = "cargo_gn_example" 9 | version = "0.0.1" 10 | dependencies = [ 11 | "cargo_gn 0.0.15", 12 | ] 13 | 14 | -------------------------------------------------------------------------------- /example/src/build.rs: -------------------------------------------------------------------------------- 1 | use cargo_gn; 2 | 3 | fn main() { 4 | let gn_args = if cargo_gn::is_debug() { 5 | vec!["is_debug=true".to_string()] 6 | } else { 7 | vec!["is_debug=false".to_string()] 8 | }; 9 | let gn_out = cargo_gn::maybe_gen(".", gn_args); 10 | assert!(gn_out.exists()); 11 | assert!(gn_out.join("args.gn").exists()); 12 | cargo_gn::build("default", None); 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo_gn" 3 | version = "0.0.15" 4 | authors = ["Ryan Dahl "] 5 | edition = "2018" 6 | repository = "https://github.com/denoland/cargo_gn" 7 | readme = "README.md" 8 | description = "Cargo integration with the GN build system" 9 | license = "MIT" 10 | 11 | [workspace] 12 | members = [ 13 | "example", 14 | ".", 15 | ] 16 | 17 | [badges] 18 | travis-ci = { repository = "denoland/cargo_gn" } 19 | -------------------------------------------------------------------------------- /example/src/count_bytes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | parser = argparse.ArgumentParser() 6 | parser.add_argument("--output") 7 | parser.add_argument('--input') 8 | args = parser.parse_args() 9 | 10 | out_file = open(args.output, "w+") 11 | in_file = open(args.input, "r") 12 | 13 | in_data = in_file.read() 14 | in_bytes = len(in_data) 15 | 16 | print "input bytes", in_bytes 17 | out_file.write("bytes: %d\n" % in_bytes) 18 | out_file.close() 19 | -------------------------------------------------------------------------------- /example/BUILD.gn: -------------------------------------------------------------------------------- 1 | group("default") { 2 | deps = [ 3 | ":count_bytes", 4 | ":foo", 5 | ":hello", 6 | ] 7 | } 8 | 9 | action("count_bytes") { 10 | script = "src/count_bytes.py" 11 | sources = [ 12 | "src/input.txt", 13 | ] 14 | outputs = [ 15 | "$target_gen_dir/output.txt", 16 | ] 17 | args = [ "--output" ] + rebase_path(outputs, root_build_dir) + [ "--input" ] + 18 | rebase_path(sources, root_build_dir) 19 | } 20 | 21 | executable("foo") { 22 | sources = [ 23 | "src/foo.cc", 24 | ] 25 | deps = [ 26 | ":hello", 27 | ] 28 | } 29 | 30 | static_library("hello") { 31 | sources = [ 32 | "src/hello.cc", 33 | "src/hello.h", 34 | ] 35 | if (is_debug) { 36 | defines = [ "IS_DEBUG=1" ] 37 | } else { 38 | defines = [ "IS_RELEASE=1" ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | #[link(name = "hello")] 4 | extern "C" { 5 | fn hello(); 6 | fn returns42() -> i32; 7 | } 8 | 9 | fn main() { 10 | assert_eq!(unsafe { returns42() }, 42); 11 | unsafe { hello() }; 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::*; 17 | use std::path::PathBuf; 18 | use std::process::Command; 19 | 20 | #[test] 21 | fn check_returns42() { 22 | assert_eq!(unsafe { returns42() }, 42); 23 | } 24 | 25 | #[test] 26 | fn run_foo_executable() { 27 | let foo = PathBuf::from(env!("OUT_DIR")).join("../../../gn_out/foo"); 28 | assert!(foo.exists()); 29 | let output = Command::new(foo).output().expect("foo failed"); 30 | if cfg!(debug_assertions) { 31 | assert_eq!(output.stdout, b"hello debug\n"); 32 | } else { 33 | assert_eq!(output.stdout, b"hello release\n"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Be sure to set $NINJA and $GN env vars correctly before running this script. 3 | CARGO_TEST="cargo test -vv --all" 4 | 5 | # First we run cargo test, to make sure everything is built. 6 | $CARGO_TEST || exit 39 7 | 8 | # If we run cargo test again, we should not rebuild. 9 | $CARGO_TEST | grep count_bytes && exit 40 10 | $CARGO_TEST | grep CXX && exit 41 11 | 12 | # Rebuild if we touch an explicitly listed source file 13 | touch example/src/hello.cc 14 | $CARGO_TEST | grep CXX || exit 42 15 | 16 | # Rebuild If we touch the input to an action 17 | touch example/src/input.txt 18 | $CARGO_TEST | grep count_bytes || exit 43 19 | 20 | # Rebuild if we touch a header file not directly listed in the BUILD.gn 21 | touch example/src/hello.h 22 | $CARGO_TEST | grep CXX || exit 44 23 | 24 | # TODO(ry) Rebuild if we touch a BUILD.gn file. 25 | # touch example/BUILD.gn 26 | # $CARGO_TEST | grep "gen ." || exit 45 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cargo GN integration 2 | 3 | [![Travis Status](https://travis-ci.com/denoland/cargo_gn.svg?branch=master)](https://travis-ci.com/denoland/cargo_gn) 4 | 5 | https://crates.io/crates/cargo_gn 6 | 7 | This package allows Rust users to quickly hook into the GN build system. 8 | It provides built-in gn and ninja tools that hook semi-automatically into 9 | Cargo's `build.rs`. 10 | 11 | Put the following in your `Cargo.toml` 12 | 13 | ```toml 14 | [build-dependencies] 15 | cargo_gn = "0.0.13" 16 | ``` 17 | 18 | Now you should be able to add a `.gn` file in the root of your project and 19 | start using `BUILD.gn`. See the example directory for a complete example: 20 | https://github.com/denoland/cargo_gn/tree/master/example 21 | 22 | Use `cargo build -vv` in order to see ninja output. 23 | 24 | Read more about gn here: https://gn.googlesource.com/gn 25 | 26 | The GN/Ninja executables are assumed to be "gn" and "ninja" unless $GN and 27 | $NINJA environmental variables are set. 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. 2 | sudo: false 3 | dist: xenial 4 | language: c++ 5 | git: 6 | depth: 1 7 | env: 8 | global: 9 | - RUST_VERSION=1.36.0 10 | - RUSTUP_HOME=$HOME/.rustup/ 11 | - RUST_BACKTRACE=full 12 | - CARGO_TARGET_DIR=$HOME/target 13 | - PATH=$HOME/.cargo/bin:$HOME/ninja:$HOME/gn/out:$PATH 14 | cache: 15 | directories: 16 | - "$RUSTUP_HOME" 17 | - $HOME/ninja 18 | - $HOME/gn 19 | 20 | install: 21 | - |- 22 | # Install Rust. 23 | if [ ! $(rustc --version | grep $RUST_VERSION) ]; then 24 | curl -sSf https://sh.rustup.rs | sh -s -- -y \ 25 | --default-toolchain $RUST_VERSION 26 | rustup default $RUST_VERSION 27 | rustup component add clippy 28 | fi 29 | rustc --version 30 | cargo --version 31 | - |- 32 | # Remove unnnecessary cargo and rustup directories. 33 | # This keeps the Travis CI cache small and fast. 34 | rm -rf "$RUSTUP_HOME"downloads 35 | rm -rf "$RUSTUP_HOME"tmp 36 | rm -rf "$RUSTUP_HOME"toolchains/*/etc 37 | rm -rf "$RUSTUP_HOME"toolchains/*/share 38 | - export NINJA=$HOME/ninja/ninja && export GN=$HOME/gn/out/gn 39 | - |- 40 | # Build Ninja 41 | if [ ! -d ninja ]; then 42 | git clone https://github.com/ninja-build/ninja.git --depth=1 $HOME/ninja 43 | cd $HOME/ninja 44 | python ./bootstrap.py 45 | fi 46 | - |- 47 | # Build GN 48 | if [ ! -d gn ]; then 49 | git clone https://gn.googlesource.com/gn $HOME/gn 50 | cd $HOME/gn 51 | python build/gen.py 52 | $NINJA -C out gn 53 | fi 54 | 55 | before_script: 56 | # Start sccache, then throw away the S3 access key. 57 | - |- 58 | sccache --start-server 59 | unset AWS_SECRET_ACCESS_KEY 60 | - set -e 61 | 62 | script: 63 | - cd $TRAVIS_BUILD_DIR 64 | - ./test.sh 65 | -------------------------------------------------------------------------------- /src/deps.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::path::PathBuf; 3 | 4 | pub fn ninja_get_deps( 5 | out_dir: &PathBuf, 6 | maybe_env: Option, 7 | target: &str, 8 | ) -> HashSet { 9 | let mut cmd = crate::ninja(out_dir, maybe_env.clone()); 10 | cmd.arg("-t"); 11 | cmd.arg("graph"); 12 | cmd.arg(target); 13 | let output = cmd.output().expect("ninja -t graph failed"); 14 | let stdout = String::from_utf8(output.stdout).unwrap(); 15 | let graph_files = parse_ninja_graph(&stdout); 16 | 17 | let mut cmd = crate::ninja(out_dir, maybe_env); 18 | cmd.arg(target); 19 | cmd.arg("-t"); 20 | cmd.arg("deps"); 21 | let output = cmd.output().expect("ninja -t deps failed"); 22 | let stdout = String::from_utf8(output.stdout).unwrap(); 23 | let deps_files = parse_ninja_deps(&stdout); 24 | 25 | // TODO(ry) There's probably a simpler way to union two HashSet 26 | // objects. 27 | let mut out = HashSet::::new(); 28 | for x in graph_files.union(&deps_files) { 29 | out.insert(x.to_string()); 30 | } 31 | out 32 | } 33 | 34 | pub fn parse_ninja_deps(s: &str) -> HashSet { 35 | let mut out = HashSet::new(); 36 | for line in s.lines() { 37 | if line.starts_with(" ") { 38 | let filename = line.trim().to_string(); 39 | out.insert(filename); 40 | } 41 | } 42 | out 43 | } 44 | 45 | /// A parser for the output of "ninja -t graph". It returns all of the input 46 | /// files. 47 | pub fn parse_ninja_graph(s: &str) -> HashSet { 48 | let mut out = HashSet::new(); 49 | // This is extremely hacky and likely to break. 50 | for line in s.lines() { 51 | //println!("line {}", line); 52 | if line.starts_with('\"') 53 | && line.contains("label=") 54 | && !line.contains("shape=") 55 | && !line.contains(" -> ") 56 | { 57 | let filename = line.split('\"').nth(3).unwrap(); 58 | if !filename.starts_with("..") { 59 | continue; 60 | } 61 | out.insert(filename.to_string()); 62 | println!("filename {}", filename); 63 | } 64 | } 65 | out 66 | } 67 | 68 | #[cfg(test)] 69 | mod test { 70 | use super::*; 71 | const MOCK_GRAPH: &str = r#" 72 | digraph ninja { 73 | rankdir="LR" 74 | node [fontsize=10, shape=box, height=0.25] 75 | edge [fontsize=10] 76 | "0x7fc3c040c210" [label="default"] 77 | "0x7fc3c040a7f0" -> "0x7fc3c040c210" [label=" phony"] 78 | "0x7fc3c040a7f0" [label="obj/default.stamp"] 79 | "0x7fc3c040a790" [label="stamp", shape=ellipse] 80 | "0x7fc3c040a790" -> "0x7fc3c040a7f0" 81 | "0x7fc3c040a6c0" -> "0x7fc3c040a790" [arrowhead=none] 82 | "0x7fc3c040a8a0" -> "0x7fc3c040a790" [arrowhead=none] 83 | "0x7fc3c040a920" -> "0x7fc3c040a790" [arrowhead=none] 84 | "0x7fc3c040a6c0" [label="obj/count_bytes.stamp"] 85 | "0x7fc3c040a4d0" -> "0x7fc3c040a6c0" [label=" stamp"] 86 | "0x7fc3c040a4d0" [label="gen/output.txt"] 87 | "0x7fc3c040a400" [label="___count_bytes___build_toolchain_mac_clang_x64__rule", shape=ellipse] 88 | "0x7fc3c040a400" -> "0x7fc3c040a4d0" 89 | "0x7fc3c040a580" -> "0x7fc3c040a400" [arrowhead=none] 90 | "0x7fc3c040a620" -> "0x7fc3c040a400" [arrowhead=none] 91 | "0x7fc3c040a580" [label="../../../example/src/count_bytes.py"] 92 | "0x7fc3c040a620" [label="../../../example/src/input.txt"] 93 | "0x7fc3c040a8a0" [label="foo"] 94 | "0x7fc3c040b5e0" [label="link", shape=ellipse] 95 | "0x7fc3c040b5e0" -> "0x7fc3c040a8a0" 96 | "0x7fc3c040b5e0" -> "0x7fc3c040b6d0" 97 | "0x7fc3c040b5e0" -> "0x7fc3c040b780" 98 | "0x7fc3c040b5e0" -> "0x7fc3c040b820" 99 | "0x7fc3c040b020" -> "0x7fc3c040b5e0" [arrowhead=none] 100 | "0x7fc3c040a920" -> "0x7fc3c040b5e0" [arrowhead=none] 101 | "0x7fc3c040b020" [label="obj/foo/foo.o"] 102 | "0x7fc3c040b0d0" -> "0x7fc3c040b020" [label=" cxx"] 103 | "0x7fc3c040b0d0" [label="../../../example/src/foo.cc"] 104 | "0x7fc3c040a920" [label="obj/libhello.a"] 105 | "0x7fc3c040be00" -> "0x7fc3c040a920" [label=" alink"] 106 | "0x7fc3c040be00" [label="obj/hello/hello.o"] 107 | "0x7fc3c040beb0" -> "0x7fc3c040be00" [label=" cxx"] 108 | "0x7fc3c040beb0" [label="../../../example/src/hello.cc"] 109 | } 110 | "#; 111 | 112 | #[test] 113 | fn test_parse_ninja_graph() { 114 | let files = parse_ninja_graph(MOCK_GRAPH); 115 | assert!(files.contains("../../../example/src/input.txt")); 116 | assert!(files.contains("../../../example/src/count_bytes.py")); 117 | assert!(!files.contains("obj/hello/hello.o")); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod deps; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | use std::process::Command; 6 | use std::process::Stdio; 7 | 8 | #[derive(Clone, Debug)] 9 | struct Dirs { 10 | pub out: PathBuf, 11 | pub root: PathBuf, 12 | } 13 | 14 | fn 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"))] 43 | fn maybe_symlink_root_dir(_: &mut Dirs) {} 44 | 45 | #[cfg(target_os = "windows")] 46 | fn 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 | 85 | pub 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 | 98 | fn gn() -> String { 99 | env::var("GN").unwrap_or_else(|_| "gn".to_owned()) 100 | } 101 | 102 | pub type NinjaEnv = Vec<(String, String)>; 103 | 104 | fn ninja(gn_out_dir: &PathBuf, maybe_env: Option) -> 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 | 117 | pub type GnArgs = Vec; 118 | 119 | pub 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 | 146 | pub fn build(target: &str, maybe_env: Option) { 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. 168 | fn rerun_if_changed( 169 | out_dir: &PathBuf, 170 | maybe_env: Option, 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 | 181 | fn 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 | 202 | fn fail(s: &str) -> ! { 203 | panic!("\n{}\n\nbuild script failed, must exit now", s) 204 | } 205 | --------------------------------------------------------------------------------