diff --git a/Cargo.toml b/Cargo.toml index ad780409..e873559c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,11 @@ exclude = [ "!v8/tools/testrunner/utils/dump_build_config.py", ] +[features] +# Enable this feature to download and prebuilt V8 binaries from +# https://github.com/denoland/rusty_v8/releases +binary = [] + [dependencies] lazy_static = "1.4.0" libc = "0.2.67" diff --git a/README.md b/README.md index 2cda9c13..b33f5290 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ V8 Version: 8.2.308, 2020-03-12 Due to the complexity and size of V8's build, this is nontrivial. For example the crate size must be kept under 10 MiB in order to publish. -## Build +## Build V8 from Source Use `cargo build -vv` to build the crate. @@ -56,6 +56,20 @@ environmental variable. Env vars used in build.rs: `SCCACHE`, `GN`, `NINJA`, `CLANG_BASE_PATH`, `GN_ARGS` +## Binary Build + +V8 is very large and take a long time to compile. Many users may prefer to use +a prebuilt version of V8. We publish static libs for every version of rusty v8 +on [Github](https://github.com/denoland/rusty_v8/releases). + +To use these prebuilt binaries use the `binary` feature: + +``` +cargo build --features="binary" +``` + +This will cause rusty v8 to download the binaries during the build process. + ## FAQ **Building V8 takes over 30 minutes, this is too slow for me to use this crate. diff --git a/build.rs b/build.rs index 12e3876d..91016d0b 100644 --- a/build.rs +++ b/build.rs @@ -23,9 +23,12 @@ fn main() { .map(|s| s.starts_with("rls")) .unwrap_or(false); - if !(is_trybuild || is_cargo_doc | is_rls) { + if cfg!(feature = "binary") { + download_static_lib_binaries(); + } else if !(is_trybuild || is_cargo_doc | is_rls) { build_v8() } + if !(is_cargo_doc || is_rls) { print_link_flags() } @@ -166,6 +169,70 @@ fn download_ninja_gn_binaries() { env::set_var("NINJA", ninja); } +fn static_lib_url() -> (String, String) { + let base = "https://github.com/denoland/rusty_v8/releases/download"; + let version = env::var("CARGO_PKG_VERSION").unwrap(); + let target = env::var("TARGET").unwrap(); + if cfg!(target_os = "windows") { + // Note: we always use the release build on windows. + let url = format!("{}/v{}/rusty_v8_release_{}.lib", base, version, target); + let static_lib_name = "rusty_v8.lib".to_string(); + (url, static_lib_name) + } else { + let profile = env::var("PROFILE").unwrap(); + assert!(profile == "release" || profile == "debug"); + let url = + format!("{}/v{}/librusty_v8_{}_{}.a", base, version, profile, target); + let static_lib_name = "librusty_v8.a".to_string(); + (url, static_lib_name) + } +} + +fn download_static_lib_binaries() { + let (url, static_lib_name) = static_lib_url(); + println!("static lib URL: {}", url); + + let root = env::current_dir().unwrap(); + + // target/debug//build/rusty_v8-d9e5a424d4f96994/out/ + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_dir_abs = root.join(out_dir); + + // This would be target/debug or target/release + let target_dir = out_dir_abs + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap(); + let obj_dir = target_dir.join("gn_out").join("obj"); + std::fs::create_dir_all(&obj_dir).unwrap(); + + println!("cargo:rustc-link-search={}", obj_dir.display()); + + let filename = obj_dir.join(static_lib_name); + + if filename.exists() { + println!("static lib already exists {}", filename.display()); + println!("To re-download this file, it must be manually deleted."); + } else { + // Using python to do the HTTP download because it's already a dependency + // and so we don't have to add a Rust HTTP client dependency. + println!("Downloading {}", url); + let status = Command::new("python") + .arg("./tools/download_file.py") + .arg("--url") + .arg(url) + .arg("--filename") + .arg(&filename) + .status() + .unwrap(); + assert!(status.success()); + assert!(filename.exists()); + } +} + fn print_link_flags() { println!("cargo:rustc-link-lib=static=rusty_v8"); diff --git a/tools/download_file.py b/tools/download_file.py new file mode 100755 index 00000000..90222c68 --- /dev/null +++ b/tools/download_file.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +from __future__ import print_function +import argparse +import os +import sys + +try: + from urllib2 import HTTPError, URLError, urlopen +except ImportError: # For Py3 compatibility + from urllib.error import HTTPError, URLError + from urllib.request import urlopen + +def DownloadUrl(url, output_file): + """Download url into output_file.""" + CHUNK_SIZE = 4096 + num_retries = 3 + retry_wait_s = 5 # Doubled at each retry. + + while True: + try: + sys.stdout.write('Downloading %s...' % url) + sys.stdout.flush() + response = urlopen(url) + bytes_done = 0 + while True: + chunk = response.read(CHUNK_SIZE) + if not chunk: + break + output_file.write(chunk) + bytes_done += len(chunk) + if bytes_done == 0: + raise URLError("empty response") + print(' Done.') + return + except URLError as e: + sys.stdout.write('\n') + print(e) + if num_retries == 0 or isinstance(e, HTTPError) and e.code == 404: + raise e + num_retries -= 1 + print('Retrying in %d s ...' % retry_wait_s) + sys.stdout.flush() + time.sleep(retry_wait_s) + retry_wait_s *= 2 + + +def main(): + parser = argparse.ArgumentParser(description='Download a file') + parser.add_argument('--filename', help='where to put the file') + parser.add_argument('--url', help='what url to download') + args = parser.parse_args() + with open(args.filename, "wb") as f: + DownloadUrl(args.url, f) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/ninja_gn_binaries.py b/tools/ninja_gn_binaries.py index b22f6fa3..6d2acf3c 100755 --- a/tools/ninja_gn_binaries.py +++ b/tools/ninja_gn_binaries.py @@ -25,7 +25,6 @@ DIR = None def DownloadUrl(url, output_file): """Download url into output_file.""" CHUNK_SIZE = 4096 - TOTAL_DOTS = 10 num_retries = 3 retry_wait_s = 5 # Doubled at each retry.