2019-11-01 13:50:12 -04:00
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
2020-12-27 11:37:14 -05:00
use fslock ::LockFile ;
2024-03-15 19:30:37 -04:00
use miniz_oxide ::inflate ::stream ::inflate ;
use miniz_oxide ::inflate ::stream ::InflateState ;
use miniz_oxide ::MZFlush ;
use miniz_oxide ::MZResult ;
use miniz_oxide ::MZStatus ;
use miniz_oxide ::StreamResult ;
2021-02-08 14:57:55 -05:00
use std ::collections ::HashSet ;
2019-10-31 12:03:44 -04:00
use std ::env ;
2020-10-07 02:39:02 -04:00
use std ::fs ;
2023-06-05 13:28:56 -04:00
use std ::io ;
2024-03-15 19:30:37 -04:00
use std ::io ::Read ;
use std ::io ::Seek ;
use std ::io ::Write ;
2019-10-31 12:03:44 -04:00
use std ::path ::Path ;
2019-11-29 21:58:29 -05:00
use std ::path ::PathBuf ;
2019-12-13 20:17:45 -05:00
use std ::process ::exit ;
2019-10-31 12:03:44 -04:00
use std ::process ::Command ;
2021-02-08 14:57:55 -05:00
use std ::process ::Stdio ;
2019-10-31 12:03:44 -04:00
use which ::which ;
2019-10-15 21:31:05 -04:00
fn main ( ) {
2022-05-23 20:31:58 -04:00
println! ( " cargo:rerun-if-changed=.gn " ) ;
println! ( " cargo:rerun-if-changed=BUILD.gn " ) ;
2021-03-30 08:33:39 -04:00
println! ( " cargo:rerun-if-changed=src/binding.cc " ) ;
2021-10-25 19:40:09 -04:00
// These are all the environment variables that we check. This is
// probably more than what is needed, but missing an important
// variable can lead to broken links when switching rusty_v8
// versions.
let envs = vec! [
" CCACHE " ,
" CLANG_BASE_PATH " ,
2023-07-01 13:21:12 -04:00
" CXXSTDLIB " ,
2021-10-25 19:40:09 -04:00
" DENO_TRYBUILD " ,
" DOCS_RS " ,
" GN " ,
" GN_ARGS " ,
" HOST " ,
" NINJA " ,
" OUT_DIR " ,
" RUSTY_V8_ARCHIVE " ,
" RUSTY_V8_MIRROR " ,
" SCCACHE " ,
" V8_FORCE_DEBUG " ,
" V8_FROM_SOURCE " ,
2023-03-21 12:11:44 -04:00
" PYTHON " ,
2023-04-25 16:22:01 -04:00
" DISABLE_CLANG " ,
" EXTRA_GN_ARGS " ,
" NO_PRINT_GN_ARGS " ,
2024-04-12 17:26:06 -04:00
" CARGO_ENCODED_RUSTFLAGS " ,
2021-10-25 19:40:09 -04:00
] ;
for env in envs {
println! ( " cargo:rerun-if-env-changed= {} " , env ) ;
}
2019-12-22 10:22:44 -05:00
// Detect if trybuild tests are being compiled.
let is_trybuild = env ::var_os ( " DENO_TRYBUILD " ) . is_some ( ) ;
2020-01-05 13:52:23 -05:00
// Don't build V8 if "cargo doc" is being run. This is to support docs.rs.
2020-10-06 09:34:33 -04:00
let is_cargo_doc = env ::var_os ( " DOCS_RS " ) . is_some ( ) ;
2019-12-22 10:22:44 -05:00
2020-01-05 13:52:23 -05:00
// Don't build V8 if the rust language server (RLS) is running.
let is_rls = env ::var_os ( " CARGO " )
. map ( PathBuf ::from )
. as_ref ( )
. and_then ( | p | p . file_stem ( ) )
. and_then ( | f | f . to_str ( ) )
. map ( | s | s . starts_with ( " rls " ) )
. unwrap_or ( false ) ;
2022-05-02 03:14:24 -04:00
// Early exit
if is_cargo_doc | | is_rls {
return ;
2019-12-22 10:22:44 -05:00
}
2020-03-17 17:59:37 -04:00
2022-05-02 03:14:24 -04:00
print_link_flags ( ) ;
// Don't attempt rebuild but link
if is_trybuild {
return ;
2019-12-22 10:22:44 -05:00
}
2022-05-02 03:14:24 -04:00
2024-04-12 17:26:06 -04:00
let is_asan = if let Some ( rustflags ) = env ::var_os ( " CARGO_ENCODED_RUSTFLAGS " )
{
let rustflags = rustflags . to_string_lossy ( ) ;
rustflags . find ( " -Z sanitizer=address " ) . is_some ( )
| | rustflags . find ( " -Zsanitizer=address " ) . is_some ( )
} else {
false
} ;
2022-05-02 03:14:24 -04:00
// Build from source
if env ::var_os ( " V8_FROM_SOURCE " ) . is_some ( ) {
2024-04-15 14:01:09 -04:00
if is_asan & & std ::env ::var_os ( " OPT_LEVEL " ) . unwrap_or_default ( ) = = " 0 " {
panic! ( " v8 crate cannot be compiled with OPT_LEVEL=0 and ASAN. \n Try `[profile.dev.package.v8] opt-level = 1`. \n Aborting before miscompilations cause issues. " ) ;
}
2024-04-12 17:26:06 -04:00
return build_v8 ( is_asan ) ;
2022-05-02 03:14:24 -04:00
}
// utilize a lockfile to prevent linking of
// only partially downloaded static library.
let root = env ::current_dir ( ) . unwrap ( ) ;
let out_dir = env ::var_os ( " OUT_DIR " ) . unwrap ( ) ;
let lockfilepath = root
. join ( out_dir )
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( )
. join ( " lib_download.fslock " ) ;
println! ( " download lockfile: {:?} " , & lockfilepath ) ;
let mut lockfile = LockFile ::open ( & lockfilepath )
. expect ( " Couldn't open lib download lockfile. " ) ;
lockfile . lock ( ) . expect ( " Couldn't get lock " ) ;
download_static_lib_binaries ( ) ;
lockfile . unlock ( ) . expect ( " Couldn't unlock lockfile " ) ;
2019-12-22 10:22:44 -05:00
}
2024-04-12 17:26:06 -04:00
fn build_v8 ( is_asan : bool ) {
2019-11-29 21:58:29 -05:00
env ::set_var ( " DEPOT_TOOLS_WIN_TOOLCHAIN " , " 0 " ) ;
2019-11-02 14:51:37 -04:00
2019-11-29 21:58:29 -05:00
// cargo publish doesn't like pyc files.
env ::set_var ( " PYTHONDONTWRITEBYTECODE " , " 1 " ) ;
// git submodule update --init --recursive
let libcxx_src = PathBuf ::from ( " buildtools/third_party/libc++/trunk/src " ) ;
if ! libcxx_src . is_dir ( ) {
eprintln! (
" missing source code. Run 'git submodule update --init --recursive' "
) ;
2019-12-13 20:17:45 -05:00
exit ( 1 ) ;
2019-11-29 21:58:29 -05:00
}
if need_gn_ninja_download ( ) {
2020-03-14 00:26:29 -04:00
download_ninja_gn_binaries ( ) ;
2019-10-31 12:03:44 -04:00
}
2024-05-12 13:00:14 -04:00
// `#[cfg(...)]` attributes don't work as expected from build.rs -- they refer to the configuration
// of the host system which the build.rs script will be running on. In short, `cfg!(target_<os/arch>)`
// is actually the host os/arch instead of target os/arch while cross compiling. Instead, Environment variables
// are the officially approach to get the target os/arch in build.rs.
let target_os = env ::var ( " CARGO_CFG_TARGET_OS " ) . unwrap ( ) ;
let target_arch = env ::var ( " CARGO_CFG_TARGET_ARCH " ) . unwrap ( ) ;
2019-11-01 17:28:09 -04:00
// On windows, rustc cannot link with a V8 debug build.
2024-05-12 13:00:14 -04:00
let mut gn_args = if is_debug ( ) & & target_os ! = " windows " {
2022-01-04 15:32:48 -05:00
// Note: When building for Android aarch64-qemu, use release instead of debug.
2019-10-31 12:03:44 -04:00
vec! [ " is_debug=true " . to_string ( ) ]
} else {
vec! [ " is_debug=false " . to_string ( ) ]
} ;
2024-04-12 17:26:06 -04:00
if is_asan {
gn_args . push ( " is_asan=true " . to_string ( ) ) ;
}
2024-05-12 13:00:14 -04:00
if env ::var ( " CARGO_FEATURE_USE_CUSTOM_LIBCXX " ) . is_err ( ) {
2022-03-16 11:20:09 -04:00
gn_args . push ( " use_custom_libcxx=false " . to_string ( ) ) ;
}
2021-03-06 20:17:33 -05:00
// Fix GN's host_cpu detection when using x86_64 bins on Apple Silicon
if cfg! ( target_os = " macos " ) & & cfg! ( target_arch = " aarch64 " ) {
gn_args . push ( " host_cpu= \" arm64 \" " . to_string ( ) )
}
2023-04-25 16:22:01 -04:00
if env ::var_os ( " DISABLE_CLANG " ) . is_some ( ) {
gn_args . push ( " is_clang=false " . into ( ) ) ;
// -gline-tables-only is Clang-only
gn_args . push ( " line_tables_only=false " . into ( ) ) ;
} else if let Some ( clang_base_path ) = find_compatible_system_clang ( ) {
2024-04-12 17:26:06 -04:00
println! ( " clang_base_path (system): {} " , clang_base_path . display ( ) ) ;
2019-12-26 09:43:39 -05:00
gn_args . push ( format! ( " clang_base_path= {:?} " , clang_base_path ) ) ;
gn_args . push ( " treat_warnings_as_errors=false " . to_string ( ) ) ;
} else {
2024-04-12 17:26:06 -04:00
println! ( " using Chromium's clang " ) ;
2019-12-26 09:43:39 -05:00
let clang_base_path = clang_download ( ) ;
gn_args . push ( format! ( " clang_base_path= {:?} " , clang_base_path ) ) ;
2022-01-04 15:32:48 -05:00
2024-05-12 13:00:14 -04:00
if target_os = = " android " & & target_arch = = " aarch64 " {
2022-01-04 15:32:48 -05:00
gn_args . push ( " treat_warnings_as_errors=false " . to_string ( ) ) ;
}
2019-12-26 09:43:39 -05:00
}
2019-11-29 21:58:29 -05:00
2019-10-31 12:03:44 -04:00
if let Some ( p ) = env ::var_os ( " SCCACHE " ) {
2021-07-29 22:40:16 -04:00
cc_wrapper ( & mut gn_args , Path ::new ( & p ) ) ;
2019-10-31 12:03:44 -04:00
} else if let Ok ( p ) = which ( " sccache " ) {
cc_wrapper ( & mut gn_args , & p ) ;
2020-09-03 13:22:07 -04:00
} else if let Some ( p ) = env ::var_os ( " CCACHE " ) {
2021-07-29 22:40:16 -04:00
cc_wrapper ( & mut gn_args , Path ::new ( & p ) ) ;
2020-09-03 13:22:07 -04:00
} else if let Ok ( p ) = which ( " ccache " ) {
cc_wrapper ( & mut gn_args , & p ) ;
2019-10-31 12:03:44 -04:00
} else {
2020-09-03 13:22:07 -04:00
println! ( " cargo:warning=Not using sccache or ccache " ) ;
2019-10-31 12:03:44 -04:00
}
2019-10-18 15:36:58 -04:00
2019-12-26 09:43:39 -05:00
if let Ok ( args ) = env ::var ( " GN_ARGS " ) {
for arg in args . split_whitespace ( ) {
gn_args . push ( arg . to_string ( ) ) ;
}
}
2024-05-12 13:00:14 -04:00
// cross-compilation setup
if target_arch = = " aarch64 " {
gn_args . push ( r # "target_cpu="arm64""# . to_string ( ) ) ;
gn_args . push ( " use_sysroot=true " . to_string ( ) ) ;
maybe_install_sysroot ( " arm64 " ) ;
maybe_install_sysroot ( " amd64 " ) ;
}
if target_arch = = " arm " {
gn_args . push ( r # "target_cpu="arm""# . to_string ( ) ) ;
gn_args . push ( r # "v8_target_cpu="arm""# . to_string ( ) ) ;
gn_args . push ( " use_sysroot=true " . to_string ( ) ) ;
maybe_install_sysroot ( " i386 " ) ;
maybe_install_sysroot ( " arm " ) ;
}
2019-12-26 09:43:39 -05:00
2020-10-31 08:32:33 -04:00
let target_triple = env ::var ( " TARGET " ) . unwrap ( ) ;
// check if the target triple describes a non-native environment
2024-05-12 13:00:14 -04:00
if target_triple ! = env ::var ( " HOST " ) . unwrap ( ) & & target_os = = " android " {
let arch = if target_arch = = " x86_64 " {
" x64 "
} else if target_arch = = " aarch64 " {
" arm64 "
} else {
" unknown "
2020-10-31 08:32:33 -04:00
} ;
2024-05-12 13:00:14 -04:00
if target_arch = = " x86_64 " {
maybe_install_sysroot ( " amd64 " ) ;
}
gn_args . push ( format! ( r # "v8_target_cpu="{}""# , arch ) . to_string ( ) ) ;
gn_args . push ( format! ( r # "target_cpu="{}""# , arch ) . to_string ( ) ) ;
gn_args . push ( r # "target_os="android""# . to_string ( ) ) ;
gn_args . push ( " treat_warnings_as_errors=false " . to_string ( ) ) ;
gn_args . push ( " use_sysroot=true " . to_string ( ) ) ;
2022-01-04 15:32:48 -05:00
2024-05-12 13:00:14 -04:00
// NDK 23 and above removes libgcc entirely.
// https://github.com/rust-lang/rust/pull/85806
if ! Path ::new ( " ./third_party/android_ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++ " ) . exists ( ) {
2024-04-22 17:16:26 -04:00
assert! ( Command ::new ( " curl " )
. arg ( " -L " )
. arg ( " -o " ) . arg ( " ./third_party/android-ndk-r26c-linux.zip " )
. arg ( " https://dl.google.com/android/repository/android-ndk-r26c-linux.zip " )
. status ( )
. unwrap ( )
. success ( ) ) ;
assert! ( Command ::new ( " unzip " )
. arg ( " -d " ) . arg ( " ./third_party/ " )
. arg ( " -o " )
. arg ( " -q " )
. arg ( " ./third_party/android-ndk-r26c-linux.zip " )
. status ( )
. unwrap ( )
. success ( ) ) ;
fs ::rename ( " ./third_party/android-ndk-r26c " , " ./third_party/android_ndk " ) . unwrap ( ) ;
fs ::remove_file ( " ./third_party/android-ndk-r26c-linux.zip " ) . unwrap ( ) ;
}
2024-05-12 13:00:14 -04:00
static CHROMIUM_URI : & str = " https://chromium.googlesource.com " ;
maybe_clone_repo (
" ./third_party/android_platform " ,
& format! (
" {}/chromium/src/third_party/android_platform.git " ,
CHROMIUM_URI
) ,
) ;
maybe_clone_repo (
" ./third_party/catapult " ,
& format! ( " {} /catapult.git " , CHROMIUM_URI ) ,
) ;
2020-10-31 08:32:33 -04:00
}
2020-02-12 21:43:38 -05:00
2021-08-12 00:00:20 -04:00
if target_triple . starts_with ( " i686- " ) {
gn_args . push ( r # "target_cpu="x86""# . to_string ( ) ) ;
}
2019-11-19 17:48:50 -05:00
let gn_root = env ::var ( " CARGO_MANIFEST_DIR " ) . unwrap ( ) ;
2019-11-01 00:10:00 -04:00
2021-02-08 14:57:55 -05:00
let gn_out = maybe_gen ( & gn_root , gn_args ) ;
2019-10-31 12:03:44 -04:00
assert! ( gn_out . exists ( ) ) ;
assert! ( gn_out . join ( " args.gn " ) . exists ( ) ) ;
2023-04-25 16:22:01 -04:00
if env ::var_os ( " NO_PRINT_GN_ARGS " ) . is_none ( ) {
print_gn_args ( & gn_out ) ;
}
2021-02-08 14:57:55 -05:00
build ( " rusty_v8 " , None ) ;
2019-10-15 21:31:05 -04:00
}
2019-10-31 12:03:44 -04:00
2022-07-08 10:55:58 -04:00
fn print_gn_args ( gn_out_dir : & Path ) {
assert! ( Command ::new ( gn ( ) )
2023-03-21 12:11:44 -04:00
. arg ( format! ( " --script-executable= {} " , python ( ) ) )
2022-07-08 10:55:58 -04:00
. arg ( " args " )
2022-11-25 08:32:52 -05:00
. arg ( gn_out_dir )
2022-07-08 10:55:58 -04:00
. arg ( " --list " )
. status ( )
. unwrap ( )
. success ( ) ) ;
}
2022-01-04 15:32:48 -05:00
fn maybe_clone_repo ( dest : & str , repo : & str ) {
if ! Path ::new ( & dest ) . exists ( ) {
assert! ( Command ::new ( " git " )
. arg ( " clone " )
. arg ( " --depth=1 " )
. arg ( repo )
. arg ( dest )
. status ( )
. unwrap ( )
. success ( ) ) ;
}
}
2020-02-12 21:43:38 -05:00
fn maybe_install_sysroot ( arch : & str ) {
let sysroot_path = format! ( " build/linux/debian_sid_ {} -sysroot " , arch ) ;
if ! PathBuf ::from ( sysroot_path ) . is_dir ( ) {
2023-03-21 12:11:44 -04:00
assert! ( Command ::new ( python ( ) )
2020-02-12 21:43:38 -05:00
. arg ( " ./build/linux/sysroot_scripts/install-sysroot.py " )
. arg ( format! ( " --arch= {} " , arch ) )
. status ( )
2022-07-26 13:21:23 -04:00
. unwrap ( )
. success ( ) ) ;
2020-02-12 21:43:38 -05:00
}
}
2024-05-12 13:00:14 -04:00
fn host_platform ( ) -> String {
2023-07-01 13:21:12 -04:00
let os = if cfg! ( target_os = " linux " ) {
" linux "
} else if cfg! ( target_os = " macos " ) {
" mac "
} else if cfg! ( target_os = " windows " ) {
" windows "
} else {
" unknown "
} ;
let arch = if cfg! ( target_arch = " x86_64 " ) {
" amd64 "
} else if cfg! ( target_arch = " aarch64 " ) {
" arm64 "
2024-05-12 13:00:14 -04:00
} else if cfg! ( target_arch = " arm " ) {
" arm "
2023-07-01 13:21:12 -04:00
} else {
" unknown "
} ;
2022-12-18 00:11:46 -05:00
format! ( " {os} - {arch} " )
2019-10-31 12:03:44 -04:00
}
2020-03-14 00:26:29 -04:00
fn download_ninja_gn_binaries ( ) {
2020-11-30 10:35:56 -05:00
let target_dir = build_dir ( ) ;
2020-03-14 00:26:29 -04:00
let bin_dir = target_dir
2022-12-18 00:11:46 -05:00
. join ( " ninja_gn_binaries-20221218 " )
2024-05-12 13:00:14 -04:00
. join ( host_platform ( ) ) ;
2020-03-14 00:26:29 -04:00
let gn = bin_dir . join ( " gn " ) ;
let ninja = bin_dir . join ( " ninja " ) ;
2019-11-29 21:58:29 -05:00
#[ cfg(windows) ]
let gn = gn . with_extension ( " exe " ) ;
#[ cfg(windows) ]
let ninja = ninja . with_extension ( " exe " ) ;
2019-10-31 12:03:44 -04:00
2019-12-05 17:03:33 -05:00
if ! gn . exists ( ) | | ! ninja . exists ( ) {
2023-03-21 12:11:44 -04:00
assert! ( Command ::new ( python ( ) )
2020-03-14 00:26:29 -04:00
. arg ( " ./tools/ninja_gn_binaries.py " )
2019-12-05 17:03:33 -05:00
. arg ( " --dir " )
2020-03-14 00:26:29 -04:00
. arg ( & target_dir )
2019-12-05 17:03:33 -05:00
. status ( )
2022-07-26 13:21:23 -04:00
. unwrap ( )
. success ( ) ) ;
2019-12-05 17:03:33 -05:00
}
assert! ( gn . exists ( ) ) ;
assert! ( ninja . exists ( ) ) ;
2019-11-29 21:58:29 -05:00
env ::set_var ( " GN " , gn ) ;
env ::set_var ( " NINJA " , ninja ) ;
}
2020-11-30 10:35:56 -05:00
fn static_lib_url ( ) -> String {
2021-10-25 16:35:19 -04:00
if let Ok ( custom_archive ) = env ::var ( " RUSTY_V8_ARCHIVE " ) {
return custom_archive ;
}
2020-08-28 06:03:34 -04:00
let default_base = " https://github.com/denoland/rusty_v8/releases/download " ;
let base =
env ::var ( " RUSTY_V8_MIRROR " ) . unwrap_or_else ( | _ | default_base . into ( ) ) ;
2020-03-17 17:59:37 -04:00
let version = env ::var ( " CARGO_PKG_VERSION " ) . unwrap ( ) ;
let target = env ::var ( " TARGET " ) . unwrap ( ) ;
2024-05-12 13:00:14 -04:00
let target_os = env ::var ( " CARGO_CFG_TARGET_OS " ) . unwrap ( ) ;
2021-09-22 06:02:55 -04:00
// Note: we always use the release build on windows.
2024-05-12 13:00:14 -04:00
if target_os = = " windows " {
2024-03-15 19:30:37 -04:00
return format! ( " {} /v {} /rusty_v8_release_ {} .lib.gz " , base , version , target ) ;
2020-11-30 10:35:56 -05:00
}
2021-09-22 06:02:55 -04:00
// Use v8 in release mode unless $V8_FORCE_DEBUG=true
let profile = match env_bool ( " V8_FORCE_DEBUG " ) {
true = > " debug " ,
_ = > " release " ,
} ;
2024-03-15 19:30:37 -04:00
format! (
" {}/v{}/librusty_v8_{}_{}.a.gz " ,
base , version , profile , target
)
2021-09-22 06:02:55 -04:00
}
fn env_bool ( key : & str ) -> bool {
matches! (
env ::var ( key ) . unwrap_or_default ( ) . as_str ( ) ,
" true " | " 1 " | " yes "
)
2020-11-30 10:35:56 -05:00
}
fn static_lib_name ( ) -> & 'static str {
2024-05-12 13:00:14 -04:00
let target_os = env ::var ( " CARGO_CFG_TARGET_OS " ) . unwrap ( ) ;
if target_os = = " windows " {
" rusty_v8.lib "
} else {
" librusty_v8.a "
2020-03-17 17:59:37 -04:00
}
}
2020-11-30 10:35:56 -05:00
fn static_lib_path ( ) -> PathBuf {
static_lib_dir ( ) . join ( static_lib_name ( ) )
}
2022-05-02 03:14:24 -04:00
fn static_checksum_path ( ) -> PathBuf {
let mut t = static_lib_path ( ) ;
t . set_extension ( " sum " ) ;
t
}
2020-11-30 10:35:56 -05:00
fn static_lib_dir ( ) -> PathBuf {
build_dir ( ) . join ( " gn_out " ) . join ( " obj " )
}
fn build_dir ( ) -> PathBuf {
let root = env ::current_dir ( ) . unwrap ( ) ;
// target/debug//build/rusty_v8-d9e5a424d4f96994/out/
let out_dir = env ::var_os ( " OUT_DIR " ) . expect (
" The 'OUT_DIR' environment is not set (it should be something like \
' target / debug / rusty_v8 - { hash } ' ) . " ,
) ;
let out_dir_abs = root . join ( out_dir ) ;
// This would be target/debug or target/release
out_dir_abs
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( )
. to_path_buf ( )
}
2024-03-15 18:16:44 -04:00
fn replace_non_alphanumeric ( url : & str ) -> String {
url
. chars ( )
. map ( | c | if c . is_ascii_alphanumeric ( ) { c } else { '_' } )
. collect ( )
}
2020-05-31 14:36:56 -04:00
fn download_file ( url : String , filename : PathBuf ) {
2020-10-07 02:39:02 -04:00
if ! url . starts_with ( " http: " ) & & ! url . starts_with ( " https: " ) {
2023-06-05 13:28:56 -04:00
copy_archive ( & url , & filename ) ;
2020-10-07 02:39:02 -04:00
return ;
}
2024-03-15 18:16:44 -04:00
// If there is a `.cargo/.rusty_v8/<escaped URL>` file, use that instead
// of downloading.
if let Ok ( mut path ) = home ::cargo_home ( ) {
path = path . join ( " .rusty_v8 " ) . join ( replace_non_alphanumeric ( & url ) ) ;
println! ( " Looking for download in ' {path:?} ' " ) ;
if path . exists ( ) {
copy_archive ( & path . to_string_lossy ( ) , & filename ) ;
return ;
}
}
2022-05-02 03:14:24 -04:00
// tmp file to download to so we don't clobber the existing one
let tmpfile = {
let mut t = filename . clone ( ) ;
t . set_extension ( " tmp " ) ;
t
} ;
if tmpfile . exists ( ) {
println! ( " Deleting old tmpfile {} " , tmpfile . display ( ) ) ;
std ::fs ::remove_file ( & tmpfile ) . unwrap ( ) ;
}
2020-05-31 14:36:56 -04:00
// Try downloading with python first. Python is a V8 build dependency,
// so this saves us from adding a Rust HTTP client dependency.
2024-03-15 19:30:37 -04:00
println! ( " Downloading (using Python) {} " , url ) ;
2023-03-21 12:11:44 -04:00
let status = Command ::new ( python ( ) )
2020-05-31 14:36:56 -04:00
. arg ( " ./tools/download_file.py " )
. arg ( " --url " )
. arg ( & url )
. arg ( " --filename " )
2022-05-02 03:14:24 -04:00
. arg ( & tmpfile )
2020-05-31 14:36:56 -04:00
. status ( ) ;
// Python is only a required dependency for `V8_FROM_SOURCE` builds.
// If python is not available, try falling back to curl.
let status = match status {
Ok ( status ) if status . success ( ) = > status ,
_ = > {
println! ( " Python downloader failed, trying with curl. " ) ;
Command ::new ( " curl " )
. arg ( " -L " )
2022-08-26 10:16:20 -04:00
. arg ( " -f " )
2020-05-31 14:36:56 -04:00
. arg ( " -s " )
. arg ( " -o " )
2022-05-02 03:14:24 -04:00
. arg ( & tmpfile )
2020-05-31 14:36:56 -04:00
. arg ( & url )
. status ( )
. unwrap ( )
}
} ;
2022-05-02 03:14:24 -04:00
// Assert DL was successful
2020-05-31 14:36:56 -04:00
assert! ( status . success ( ) ) ;
2022-05-02 03:14:24 -04:00
assert! ( tmpfile . exists ( ) ) ;
// Write checksum (i.e url) & move file
std ::fs ::write ( static_checksum_path ( ) , url ) . unwrap ( ) ;
2024-03-15 19:30:37 -04:00
copy_archive ( & tmpfile . to_string_lossy ( ) , & filename ) ;
std ::fs ::remove_file ( & tmpfile ) . unwrap ( ) ;
2020-05-31 14:36:56 -04:00
assert! ( filename . exists ( ) ) ;
2022-05-02 03:14:24 -04:00
assert! ( static_checksum_path ( ) . exists ( ) ) ;
assert! ( ! tmpfile . exists ( ) ) ;
2020-05-31 14:36:56 -04:00
}
2020-03-17 17:59:37 -04:00
fn download_static_lib_binaries ( ) {
2020-11-30 10:35:56 -05:00
let url = static_lib_url ( ) ;
2020-03-17 17:59:37 -04:00
println! ( " static lib URL: {} " , url ) ;
2020-11-30 10:35:56 -05:00
let dir = static_lib_dir ( ) ;
std ::fs ::create_dir_all ( & dir ) . unwrap ( ) ;
println! ( " cargo:rustc-link-search= {} " , dir . display ( ) ) ;
2020-03-17 17:59:37 -04:00
2022-05-02 03:14:24 -04:00
// Checksum (i.e: url) to avoid redownloads
match std ::fs ::read_to_string ( static_checksum_path ( ) ) {
Ok ( c ) if c = = static_lib_url ( ) = > return ,
_ = > { }
} ;
download_file ( url , static_lib_path ( ) ) ;
2020-03-17 17:59:37 -04:00
}
2024-03-15 19:30:37 -04:00
fn decompress_to_writer < R , W > ( input : & mut R , output : & mut W ) -> io ::Result < ( ) >
where
R : Read ,
W : Write ,
{
let mut inflate_state = InflateState ::default ( ) ;
let mut input_buffer = [ 0 ; 16 * 1024 ] ;
let mut output_buffer = [ 0 ; 16 * 1024 ] ;
let mut input_offset = 0 ;
// Skip the gzip header
gzip_header ::read_gz_header ( input ) . unwrap ( ) ;
loop {
let bytes_read = input . read ( & mut input_buffer [ input_offset .. ] ) ? ;
let bytes_avail = input_offset + bytes_read ;
let StreamResult {
bytes_consumed ,
bytes_written ,
status ,
} = inflate (
& mut inflate_state ,
& input_buffer [ .. bytes_avail ] ,
& mut output_buffer ,
MZFlush ::None ,
) ;
if status ! = MZResult ::Ok ( MZStatus ::Ok )
& & status ! = MZResult ::Ok ( MZStatus ::StreamEnd )
{
return Err ( io ::Error ::new (
io ::ErrorKind ::Other ,
format! ( " Decompression error {status:?} " ) ,
) ) ;
}
output . write_all ( & output_buffer [ .. bytes_written ] ) ? ;
// Move remaining bytes to the beginning of the buffer
input_buffer . copy_within ( bytes_consumed .. bytes_avail , 0 ) ;
input_offset = bytes_avail - bytes_consumed ;
if status = = MZResult ::Ok ( MZStatus ::StreamEnd ) {
break ; // End of decompression
}
}
Ok ( ( ) )
}
2023-06-05 13:28:56 -04:00
/// Copy the V8 archive at `url` to `filename`.
///
/// This function doesn't use `std::fs::copy` because that would
/// preveserve the file attributes such as ownership and mode flags.
/// Instead, it copies the file contents to a new file.
/// This is necessary because the V8 archive could live inside a read-only
/// filesystem, and subsequent builds would fail to overwrite it.
fn copy_archive ( url : & str , filename : & Path ) {
2024-03-15 19:30:37 -04:00
println! ( " Copying {url} to {filename:?} " ) ;
2023-06-05 13:28:56 -04:00
let mut src = fs ::File ::open ( url ) . unwrap ( ) ;
let mut dst = fs ::File ::create ( filename ) . unwrap ( ) ;
2024-03-15 19:30:37 -04:00
// Allow both GZIP and non-GZIP downloads
let mut header = [ 0 ; 2 ] ;
src . read_exact ( & mut header ) . unwrap ( ) ;
src . seek ( io ::SeekFrom ::Start ( 0 ) ) . unwrap ( ) ;
if header = = [ 0x1f , 0x8b ] {
println! ( " Detected GZIP archive " ) ;
decompress_to_writer ( & mut src , & mut dst ) . unwrap ( ) ;
} else {
println! ( " Not a GZIP archive " ) ;
io ::copy ( & mut src , & mut dst ) . unwrap ( ) ;
}
2023-06-05 13:28:56 -04:00
}
2019-12-22 10:22:44 -05:00
fn print_link_flags ( ) {
println! ( " cargo:rustc-link-lib=static=rusty_v8 " ) ;
2024-05-12 13:00:14 -04:00
let should_dyn_link_libcxx = env ::var ( " CARGO_FEATURE_USE_CUSTOM_LIBCXX " )
. is_err ( )
2022-03-16 11:20:09 -04:00
| | env ::var ( " GN_ARGS " ) . map_or ( false , | gn_args | {
gn_args
. split_whitespace ( )
. any ( | ba | ba = = " use_custom_libcxx=false " )
} ) ;
if should_dyn_link_libcxx {
// Based on https://github.com/alexcrichton/cc-rs/blob/fba7feded71ee4f63cfe885673ead6d7b4f2f454/src/lib.rs#L2462
2023-07-01 13:21:12 -04:00
if let Ok ( stdlib ) = env ::var ( " CXXSTDLIB " ) {
if ! stdlib . is_empty ( ) {
println! ( " cargo:rustc-link-lib=dylib= {} " , stdlib ) ;
}
} else {
let target = env ::var ( " TARGET " ) . unwrap ( ) ;
if target . contains ( " msvc " ) {
// nothing to link to
} else if target . contains ( " apple " )
| | target . contains ( " freebsd " )
| | target . contains ( " openbsd " )
{
println! ( " cargo:rustc-link-lib=dylib=c++ " ) ;
} else if target . contains ( " android " ) {
println! ( " cargo:rustc-link-lib=dylib=c++_shared " ) ;
} else {
println! ( " cargo:rustc-link-lib=dylib=stdc++ " ) ;
}
2022-03-16 11:20:09 -04:00
}
}
2024-05-12 13:00:14 -04:00
let target_os = env ::var ( " CARGO_CFG_TARGET_OS " ) . unwrap ( ) ;
let target_env = env ::var ( " CARGO_CFG_TARGET_ENV " ) . unwrap ( ) ;
2022-03-16 11:20:09 -04:00
2024-05-12 13:00:14 -04:00
if target_os = = " windows " {
2019-12-22 10:22:44 -05:00
println! ( " cargo:rustc-link-lib=dylib=winmm " ) ;
println! ( " cargo:rustc-link-lib=dylib=dbghelp " ) ;
}
2022-05-18 04:53:34 -04:00
2024-05-12 13:00:14 -04:00
if target_env = = " msvc " {
2022-05-18 04:53:34 -04:00
// On Windows, including libcpmt[d]/msvcprt[d] explicitly links the C++
// standard library, which libc++ needs for exception_ptr internals.
2024-05-12 13:00:14 -04:00
if env ::var ( " CARGO_FEATURE_CRT_STATIC " ) . is_ok ( ) {
2022-05-18 04:53:34 -04:00
println! ( " cargo:rustc-link-lib=libcpmt " ) ;
} else {
println! ( " cargo:rustc-link-lib=dylib=msvcprt " ) ;
}
}
2019-12-22 10:22:44 -05:00
}
2021-08-21 11:21:08 -04:00
// Chromium depot_tools contains helpers
// which delegate to the "relevant" `buildtools`
// directory when invoked, so they don't count.
fn not_in_depot_tools ( p : PathBuf ) -> bool {
! p . as_path ( ) . to_str ( ) . unwrap ( ) . contains ( " depot_tools " )
}
2019-11-29 21:58:29 -05:00
fn need_gn_ninja_download ( ) -> bool {
2021-08-21 11:21:08 -04:00
let has_ninja = which ( " ninja " ) . map_or ( false , not_in_depot_tools )
| | env ::var_os ( " NINJA " ) . is_some ( ) ;
let has_gn = which ( " gn " ) . map_or ( false , not_in_depot_tools )
| | env ::var_os ( " GN " ) . is_some ( ) ;
2020-12-25 04:20:53 -05:00
2021-03-27 15:16:35 -04:00
! has_ninja | | ! has_gn
2019-11-29 21:58:29 -05:00
}
2019-12-26 09:43:39 -05:00
// Chromiums gn arg clang_base_path is currently compatible with:
// * Apples clang and clang from homebrew's llvm@x packages
// * the official binaries from releases.llvm.org
// * unversioned (Linux) packages of clang (if recent enough)
// but unfortunately it doesn't work with version-suffixed packages commonly
// found in Linux packet managers
fn is_compatible_clang_version ( clang_path : & Path ) -> bool {
if let Ok ( o ) = Command ::new ( clang_path ) . arg ( " --version " ) . output ( ) {
2019-12-27 10:54:26 -05:00
let _output = String ::from_utf8 ( o . stdout ) . unwrap ( ) ;
// TODO check version output to make sure it's supported.
const _MIN_APPLE_CLANG_VER : f32 = 11.0 ;
const _MIN_LLVM_CLANG_VER : f32 = 8.0 ;
return true ;
2019-12-26 09:43:39 -05:00
}
false
}
fn find_compatible_system_clang ( ) -> Option < PathBuf > {
if let Ok ( p ) = env ::var ( " CLANG_BASE_PATH " ) {
let base_path = Path ::new ( & p ) ;
let clang_path = base_path . join ( " bin " ) . join ( " clang " ) ;
if is_compatible_clang_version ( & clang_path ) {
return Some ( base_path . to_path_buf ( ) ) ;
}
}
2019-11-29 21:58:29 -05:00
2019-12-26 09:43:39 -05:00
None
}
// Download chromium's clang into OUT_DIR because Cargo will not allow us to
// modify the source directory.
fn clang_download ( ) -> PathBuf {
2020-11-30 10:35:56 -05:00
let clang_base_path = build_dir ( ) . join ( " clang " ) ;
2024-04-12 17:26:06 -04:00
println! ( " clang_base_path (downloaded) {} " , clang_base_path . display ( ) ) ;
2023-03-21 12:11:44 -04:00
assert! ( Command ::new ( python ( ) )
2019-11-29 21:58:29 -05:00
. arg ( " ./tools/clang/scripts/update.py " )
2019-12-04 10:44:54 -05:00
. arg ( " --output-dir " )
2019-11-29 21:58:29 -05:00
. arg ( & clang_base_path )
2019-11-01 13:50:12 -04:00
. status ( )
2022-07-26 13:21:23 -04:00
. unwrap ( )
. success ( ) ) ;
2019-11-29 21:58:29 -05:00
assert! ( clang_base_path . exists ( ) ) ;
clang_base_path
2019-10-31 12:03:44 -04:00
}
fn cc_wrapper ( gn_args : & mut Vec < String > , sccache_path : & Path ) {
gn_args . push ( format! ( " cc_wrapper= {:?} " , sccache_path ) ) ;
}
2021-02-08 14:57:55 -05:00
struct Dirs {
pub out : PathBuf ,
pub root : PathBuf ,
}
fn get_dirs ( manifest_dir : Option < & str > ) -> Dirs {
// The OUT_DIR is going to be a crate-specific directory like
// "target/debug/build/cargo_gn_example-eee5160084460b2c"
// But we want to share the GN build amongst all crates
// and return the path "target/debug". So to find it, we walk up three
// directories.
// TODO(ry) This is quite brittle - if Cargo changes the directory structure
// this could break.
let out = env ::var ( " OUT_DIR " ) . map ( PathBuf ::from ) . unwrap ( ) ;
let out = out
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( )
. parent ( )
. unwrap ( )
. to_owned ( ) ;
let root = match manifest_dir {
Some ( s ) = > env ::current_dir ( ) . unwrap ( ) . join ( s ) ,
None = > env ::var ( " CARGO_MANIFEST_DIR " ) . map ( PathBuf ::from ) . unwrap ( ) ,
} ;
let mut dirs = Dirs { out , root } ;
maybe_symlink_root_dir ( & mut dirs ) ;
dirs
}
#[ cfg(not(target_os = " windows " )) ]
fn maybe_symlink_root_dir ( _ : & mut Dirs ) { }
#[ cfg(target_os = " windows " ) ]
fn maybe_symlink_root_dir ( dirs : & mut Dirs ) {
// GN produces invalid paths if the source (a.k.a. root) directory is on a
// different drive than the output. If this is the case we'll create a
// symlink called "gn_root' in the out directory, next to 'gn_out', so it
// appears as if they're both on the same drive.
use std ::fs ::remove_dir ;
use std ::os ::windows ::fs ::symlink_dir ;
let get_prefix = | p : & Path | {
p . components ( )
. find_map ( | c | match c {
std ::path ::Component ::Prefix ( p ) = > Some ( p ) ,
_ = > None ,
} )
. map ( | p | p . as_os_str ( ) . to_owned ( ) )
} ;
let Dirs { out , root } = dirs ;
if get_prefix ( out ) ! = get_prefix ( root ) {
let symlink = & * out . join ( " gn_root " ) ;
let target = & * root . canonicalize ( ) . unwrap ( ) ;
println! ( " Creating symlink {:?} to {:?} " , & symlink , & root ) ;
loop {
match symlink . canonicalize ( ) {
Ok ( existing ) if existing = = target = > break ,
Ok ( _ ) = > remove_dir ( symlink ) . expect ( " remove_dir failed " ) ,
Err ( _ ) = > {
break symlink_dir ( target , symlink ) . expect ( " symlink_dir failed " )
}
}
}
dirs . root = symlink . to_path_buf ( ) ;
}
}
pub fn is_debug ( ) -> bool {
// Cargo sets PROFILE to either "debug" or "release", which conveniently
// matches the build modes we support.
let m = env ::var ( " PROFILE " ) . unwrap ( ) ;
if m = = " release " {
false
} else if m = = " debug " {
true
} else {
panic! ( " unhandled PROFILE value {} " , m )
}
}
fn gn ( ) -> String {
env ::var ( " GN " ) . unwrap_or_else ( | _ | " gn " . to_owned ( ) )
}
2023-03-21 12:11:44 -04:00
/*
* Get the system ' s python binary - specified via the PYTHON environment
* variable or defaulting to ` python3 ` .
* /
fn python ( ) -> String {
env ::var ( " PYTHON " ) . unwrap_or_else ( | _ | " python3 " . to_owned ( ) )
}
2021-02-08 14:57:55 -05:00
type NinjaEnv = Vec < ( String , String ) > ;
2021-03-27 15:16:35 -04:00
fn ninja ( gn_out_dir : & Path , maybe_env : Option < NinjaEnv > ) -> Command {
2021-02-08 14:57:55 -05:00
let cmd_string = env ::var ( " NINJA " ) . unwrap_or_else ( | _ | " ninja " . to_owned ( ) ) ;
let mut cmd = Command ::new ( cmd_string ) ;
cmd . arg ( " -C " ) ;
2022-11-25 08:32:52 -05:00
cmd . arg ( gn_out_dir ) ;
2023-07-01 13:21:12 -04:00
if let Ok ( jobs ) = env ::var ( " NUM_JOBS " ) {
cmd . arg ( " -j " ) ;
cmd . arg ( jobs ) ;
}
2021-02-08 14:57:55 -05:00
if let Some ( env ) = maybe_env {
for item in env {
cmd . env ( item . 0 , item . 1 ) ;
}
}
cmd
}
pub type GnArgs = Vec < String > ;
pub fn maybe_gen ( manifest_dir : & str , gn_args : GnArgs ) -> PathBuf {
let dirs = get_dirs ( Some ( manifest_dir ) ) ;
let gn_out_dir = dirs . out . join ( " gn_out " ) ;
if ! gn_out_dir . exists ( ) | | ! gn_out_dir . join ( " build.ninja " ) . exists ( ) {
2023-04-25 16:22:01 -04:00
let args = if let Ok ( extra_args ) = env ::var ( " EXTRA_GN_ARGS " ) {
format! ( " {} {} " , gn_args . join ( " " ) , extra_args )
} else {
gn_args . join ( " " )
} ;
2021-02-08 14:57:55 -05:00
let path = env ::current_dir ( ) . unwrap ( ) ;
println! ( " The current directory is {} " , path . display ( ) ) ;
println! (
" gn gen --root={} {} " ,
dirs . root . display ( ) ,
gn_out_dir . display ( )
) ;
2022-07-26 13:21:23 -04:00
assert! ( Command ::new ( gn ( ) )
. arg ( format! ( " --root= {} " , dirs . root . display ( ) ) )
2023-03-21 12:11:44 -04:00
. arg ( format! ( " --script-executable= {} " , python ( ) ) )
2022-07-26 13:21:23 -04:00
. arg ( " gen " )
. arg ( & gn_out_dir )
. arg ( " --args= " . to_owned ( ) + & args )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. envs ( env ::vars ( ) )
. status ( )
2024-04-12 17:26:06 -04:00
. expect ( " Could not run `gn` " )
2022-07-26 13:21:23 -04:00
. success ( ) ) ;
2021-02-08 14:57:55 -05:00
}
gn_out_dir
}
pub fn build ( target : & str , maybe_env : Option < NinjaEnv > ) {
let gn_out_dir = get_dirs ( None ) . out . join ( " gn_out " ) ;
2022-05-07 13:47:17 -04:00
rerun_if_changed ( & gn_out_dir , maybe_env . clone ( ) , target ) ;
2021-02-08 14:57:55 -05:00
// This helps Rust source files locate the snapshot, source map etc.
println! ( " cargo:rustc-env=GN_OUT_DIR= {} " , gn_out_dir . display ( ) ) ;
2022-07-26 13:21:23 -04:00
assert! ( ninja ( & gn_out_dir , maybe_env )
. arg ( target )
. status ( )
. unwrap ( )
. success ( ) ) ;
2021-12-11 05:16:43 -05:00
2021-02-08 14:57:55 -05:00
// TODO This is not sufficent. We need to use "gn desc" to query the target
// and figure out what else we need to add to the link.
println! (
" cargo:rustc-link-search=native={}/obj/ " ,
gn_out_dir . display ( )
) ;
}
/// build.rs does not get re-run unless we tell cargo about what files we
/// depend on. This outputs a bunch of rerun-if-changed lines to stdout.
2021-03-27 15:16:35 -04:00
fn rerun_if_changed ( out_dir : & Path , maybe_env : Option < NinjaEnv > , target : & str ) {
2021-02-08 14:57:55 -05:00
let deps = ninja_get_deps ( out_dir , maybe_env , target ) ;
for d in deps {
let p = out_dir . join ( d ) ;
2022-07-25 13:22:32 -04:00
assert! ( p . exists ( ) , " Path doesn't exist: {:?} " , p ) ;
2021-02-08 14:57:55 -05:00
println! ( " cargo:rerun-if-changed= {} " , p . display ( ) ) ;
}
}
fn ninja_get_deps (
2021-03-27 15:16:35 -04:00
out_dir : & Path ,
2021-02-08 14:57:55 -05:00
maybe_env : Option < NinjaEnv > ,
target : & str ,
) -> HashSet < String > {
let mut cmd = ninja ( out_dir , maybe_env . clone ( ) ) ;
cmd . arg ( " -t " ) ;
cmd . arg ( " graph " ) ;
cmd . arg ( target ) ;
let output = cmd . output ( ) . expect ( " ninja -t graph failed " ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let graph_files = parse_ninja_graph ( & stdout ) ;
let mut cmd = ninja ( out_dir , maybe_env ) ;
cmd . arg ( target ) ;
cmd . arg ( " -t " ) ;
cmd . arg ( " deps " ) ;
let output = cmd . output ( ) . expect ( " ninja -t deps failed " ) ;
let stdout = String ::from_utf8 ( output . stdout ) . unwrap ( ) ;
let deps_files = parse_ninja_deps ( & stdout ) ;
2022-05-07 13:47:17 -04:00
graph_files . union ( & deps_files ) . map ( String ::from ) . collect ( )
2021-02-08 14:57:55 -05:00
}
pub fn parse_ninja_deps ( s : & str ) -> HashSet < String > {
let mut out = HashSet ::new ( ) ;
for line in s . lines ( ) {
if line . starts_with ( " " ) {
let filename = line . trim ( ) . to_string ( ) ;
out . insert ( filename ) ;
}
}
out
}
/// A parser for the output of "ninja -t graph". It returns all of the input
/// files.
pub fn parse_ninja_graph ( s : & str ) -> HashSet < String > {
let mut out = HashSet ::new ( ) ;
// This is extremely hacky and likely to break.
for line in s . lines ( ) {
if line . starts_with ( '\"' )
& & line . contains ( " label= " )
& & ! line . contains ( " shape= " )
& & ! line . contains ( " -> " )
{
let filename = line . split ( '\"' ) . nth ( 3 ) . unwrap ( ) ;
if ! filename . starts_with ( " .. " ) {
continue ;
}
out . insert ( filename . to_string ( ) ) ;
}
}
out
}
#[ cfg(test) ]
mod test {
use super ::* ;
2022-05-23 15:58:47 -04:00
2021-02-08 14:57:55 -05:00
const MOCK_GRAPH : & str = r #"
digraph ninja {
rankdir = " LR "
node [ fontsize = 10 , shape = box , height = 0.25 ]
edge [ fontsize = 10 ]
" 0x7fc3c040c210 " [ label = " default " ]
" 0x7fc3c040a7f0 " -> " 0x7fc3c040c210 " [ label = " phony " ]
" 0x7fc3c040a7f0 " [ label = " obj/default.stamp " ]
" 0x7fc3c040a790 " [ label = " stamp " , shape = ellipse ]
" 0x7fc3c040a790 " -> " 0x7fc3c040a7f0 "
" 0x7fc3c040a6c0 " -> " 0x7fc3c040a790 " [ arrowhead = none ]
" 0x7fc3c040a8a0 " -> " 0x7fc3c040a790 " [ arrowhead = none ]
" 0x7fc3c040a920 " -> " 0x7fc3c040a790 " [ arrowhead = none ]
" 0x7fc3c040a6c0 " [ label = " obj/count_bytes.stamp " ]
" 0x7fc3c040a4d0 " -> " 0x7fc3c040a6c0 " [ label = " stamp " ]
" 0x7fc3c040a4d0 " [ label = " gen/output.txt " ]
" 0x7fc3c040a400 " [ label = " ___count_bytes___build_toolchain_mac_clang_x64__rule " , shape = ellipse ]
" 0x7fc3c040a400 " -> " 0x7fc3c040a4d0 "
" 0x7fc3c040a580 " -> " 0x7fc3c040a400 " [ arrowhead = none ]
" 0x7fc3c040a620 " -> " 0x7fc3c040a400 " [ arrowhead = none ]
" 0x7fc3c040a580 " [ label = " ../../../example/src/count_bytes.py " ]
" 0x7fc3c040a620 " [ label = " ../../../example/src/input.txt " ]
" 0x7fc3c040a8a0 " [ label = " foo " ]
" 0x7fc3c040b5e0 " [ label = " link " , shape = ellipse ]
" 0x7fc3c040b5e0 " -> " 0x7fc3c040a8a0 "
" 0x7fc3c040b5e0 " -> " 0x7fc3c040b6d0 "
" 0x7fc3c040b5e0 " -> " 0x7fc3c040b780 "
" 0x7fc3c040b5e0 " -> " 0x7fc3c040b820 "
" 0x7fc3c040b020 " -> " 0x7fc3c040b5e0 " [ arrowhead = none ]
" 0x7fc3c040a920 " -> " 0x7fc3c040b5e0 " [ arrowhead = none ]
" 0x7fc3c040b020 " [ label = " obj/foo/foo.o " ]
" 0x7fc3c040b0d0 " -> " 0x7fc3c040b020 " [ label = " cxx " ]
" 0x7fc3c040b0d0 " [ label = " ../../../example/src/foo.cc " ]
" 0x7fc3c040a920 " [ label = " obj/libhello.a " ]
" 0x7fc3c040be00 " -> " 0x7fc3c040a920 " [ label = " alink " ]
" 0x7fc3c040be00 " [ label = " obj/hello/hello.o " ]
" 0x7fc3c040beb0 " -> " 0x7fc3c040be00 " [ label = " cxx " ]
" 0x7fc3c040beb0 " [ label = " ../../../example/src/hello.cc " ]
}
" #;
#[ test ]
fn test_parse_ninja_graph ( ) {
let files = parse_ninja_graph ( MOCK_GRAPH ) ;
assert! ( files . contains ( " ../../../example/src/input.txt " ) ) ;
assert! ( files . contains ( " ../../../example/src/count_bytes.py " ) ) ;
assert! ( ! files . contains ( " obj/hello/hello.o " ) ) ;
}
}