From d5b7c0f38299579daedb16a7991ec4cea8dec46b Mon Sep 17 00:00:00 2001 From: Alexei Filippov Date: Mon, 15 Jun 2020 11:03:29 -0700 Subject: [PATCH] Support sys-crates build script dependencies. Sys-crates may produce "cargo:var=value" output that must be translated into DEP_CRATE_NAME_VAR=VALUE environment variable that is then must be made visible to all direct dependent crates build files. This in particular is used by openssl and openssl-sys crates. --- cargo/cargo_build_script.bzl | 41 ++++++++++++++--- cargo/cargo_build_script_runner/bin.rs | 10 ++-- cargo/cargo_build_script_runner/lib.rs | 64 ++++++++++++++++++++------ rust/private/rustc.bzl | 3 ++ 4 files changed, 95 insertions(+), 23 deletions(-) diff --git a/cargo/cargo_build_script.bzl b/cargo/cargo_build_script.bzl index 022970d402..420822edcc 100644 --- a/cargo/cargo_build_script.bzl +++ b/cargo/cargo_build_script.bzl @@ -1,4 +1,4 @@ -load("@io_bazel_rules_rust//rust:private/rustc.bzl", "BuildInfo", "get_compilation_mode_opts") +load("@io_bazel_rules_rust//rust:private/rustc.bzl", "BuildInfo", "DepInfo", "get_compilation_mode_opts") load("@io_bazel_rules_rust//rust:private/utils.bzl", "find_toolchain") load("@io_bazel_rules_rust//rust:rust.bzl", "rust_binary") @@ -6,9 +6,19 @@ def _cargo_build_script_run(ctx, script): toolchain = find_toolchain(ctx) out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir") env_out = ctx.actions.declare_file(ctx.label.name + ".env") + dep_env_out = ctx.actions.declare_file(ctx.label.name + ".depenv") flags_out = ctx.actions.declare_file(ctx.label.name + ".flags") manifest_dir = "%s.runfiles/%s" % (script.path, ctx.label.workspace_name or ctx.workspace_name) compilation_mode_opt_level = get_compilation_mode_opts(ctx, toolchain).opt_level + + crate_name = ctx.attr.crate_name + # Derive crate name from the rule label which is _build_script + # TODO: Remove once cargo_raze starts providing crate_name attribute. + if not crate_name: + if not ctx.label.name.endswith("_build_script"): + fail("Error: Unexpected name of the build script target: %s" % ctx.label.name) + crate_name = ctx.label.name.replace("_build_script", "").replace("_", "-") + env = { "CARGO_CFG_TARGET_ARCH": toolchain.target_arch, "CARGO_MANIFEST_DIR": manifest_dir, @@ -36,11 +46,21 @@ def _cargo_build_script_run(ctx, script): ], ) - ctx.actions.run( - executable = ctx.executable._cargo_build_script_runner, - arguments = [script.path, env_out.path, flags_out.path], - outputs = [out_dir, env_out, flags_out], + cmd = "" + dep_env_files = [] + for dep in ctx.attr.deps: + if DepInfo in dep and dep[DepInfo].dep_env: + dep_env_file = dep[DepInfo].dep_env + cmd += "export $(cat %s); " % dep_env_file.path + dep_env_files.append(dep_env_file) + cmd += "$@" + + ctx.actions.run_shell( + command = cmd, + arguments = [ctx.executable._cargo_build_script_runner.path, script.path, crate_name, env_out.path, flags_out.path, dep_env_out.path], + outputs = [out_dir, env_out, flags_out, dep_env_out], tools = tools, + inputs = dep_env_files, mnemonic = "CargoBuildScriptRun", env = env, ) @@ -49,6 +69,7 @@ def _cargo_build_script_run(ctx, script): BuildInfo( out_dir = out_dir, rustc_env = env_out, + dep_env = dep_env_out, flags = flags_out, ), ] @@ -66,6 +87,7 @@ _build_script_run = rule( cfg = "host", doc = "The binary script to run, generally a rust_binary target. ", ), + "crate_name": attr.string(), "crate_features": attr.string_list(doc = "The list of rust features that the build script should consider activated."), "_cargo_build_script_runner": attr.label( executable = True, @@ -73,13 +95,14 @@ _build_script_run = rule( default = Label("//cargo/cargo_build_script_runner:cargo_build_script_runner"), cfg = "host", ), + "deps": attr.label_list(), }, toolchains = [ "@io_bazel_rules_rust//rust:toolchain", ], ) -def cargo_build_script(name, crate_features=[], **kwargs): +def cargo_build_script(name, crate_name="", crate_features=[], deps=[], **kwargs): """ Compile and execute a rust build script to generate build attributes @@ -128,12 +151,16 @@ def cargo_build_script(name, crate_features=[], **kwargs): The `hello_lib` target will be build with the flags and the environment variables declared by the build script in addition to the file generated by it. """ - rust_binary(name = name + "_script_", + rust_binary( + name = name + "_script_", crate_features = crate_features, + deps = deps, **kwargs, ) _build_script_run( name = name, script = ":%s_script_" % name, + crate_name = crate_name, crate_features = crate_features, + deps = deps, ) diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs index 7946e9361a..8160be610c 100644 --- a/cargo/cargo_build_script_runner/bin.rs +++ b/cargo/cargo_build_script_runner/bin.rs @@ -33,8 +33,8 @@ fn main() { let manifest_dir = canonicalize(&manifest_dir_env).expect(&format!("Failed to canonicalize '{}'", manifest_dir_env)); let out_dir = canonicalize(&out_dir_env).expect(&format!("Failed to canonicalize '{}'", out_dir_env)); let rustc = canonicalize(&rustc_env).expect(&format!("Failed to canonicalize '{}'", rustc_env)); - match (args.next(), args.next(), args.next()) { - (Some(progname), Some(envfile), Some(flagfile)) => { + match (args.next(), args.next(), args.next(), args.next(), args.next()) { + (Some(progname), Some(crate_name), Some(envfile), Some(flagfile), Some(depenvfile)) => { let output = BuildScriptOutput::from_command( Command::new( canonicalize(&progname).expect(&format!("Failed to canonicalize '{}'", progname))) @@ -47,13 +47,17 @@ fn main() { File::create(&envfile).expect(&format!("Unable to create file {}", envfile)); f.write_all(BuildScriptOutput::to_env(&output).as_bytes()) .expect(&format!("Unable to write file {}", envfile)); + let mut f = + File::create(&depenvfile).expect(&format!("Unable to create file {}", depenvfile)); + f.write_all(BuildScriptOutput::to_dep_env(&output, &crate_name).as_bytes()) + .expect(&format!("Unable to write file {}", depenvfile)); let mut f = File::create(&flagfile).expect(&format!("Unable to create file {}", flagfile)); f.write_all(BuildScriptOutput::to_flags(&output).as_bytes()) .expect(&format!("Unable to write file {}", flagfile)); } _ => { - eprintln!("Usage: $0 progname envfile flagfile [arg1...argn]"); + eprintln!("Usage: $0 progname crate_name envfile flagfile depenvfile [arg1...argn]"); exit(1); } } diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs index 387626fd24..ebac504cdb 100644 --- a/cargo/cargo_build_script_runner/lib.rs +++ b/cargo/cargo_build_script_runner/lib.rs @@ -30,6 +30,8 @@ pub enum BuildScriptOutput { Flags(String), /// cargo:rustc-env Env(String), + /// cargo:VAR=VALUE + DepEnv(String), } impl BuildScriptOutput { @@ -45,26 +47,33 @@ impl BuildScriptOutput { return None; } let param = split[1].trim().to_owned(); - match split[0] { - "cargo:rustc-link-lib" => Some(BuildScriptOutput::LinkLib(param)), - "cargo:rustc-link-search" => Some(BuildScriptOutput::LinkSearch(param)), - "cargo:rustc-cfg" => Some(BuildScriptOutput::Cfg(param)), - "cargo:rustc-flags" => Some(BuildScriptOutput::Flags(param)), - "cargo:rustc-env" => Some(BuildScriptOutput::Env(param)), - "cargo:rerun-if-changed" | "cargo:rerun-if-env-changed" => + let key_split = split[0].splitn(2, ':').collect::>(); + if key_split.len() <= 1 || key_split[0] != "cargo" { + // Not a cargo directive. + return None + } + match key_split[1] { + "rustc-link-lib" => Some(BuildScriptOutput::LinkLib(param)), + "rustc-link-search" => Some(BuildScriptOutput::LinkSearch(param)), + "rustc-cfg" => Some(BuildScriptOutput::Cfg(param)), + "rustc-flags" => Some(BuildScriptOutput::Flags(param)), + "rustc-env" => Some(BuildScriptOutput::Env(param)), + "rerun-if-changed" | "rerun-if-env-changed" => // Ignored because Bazel will re-run if those change all the time. None, - "cargo:warning" => { + "warning" => { eprintln!("Build Script Warning: {}", split[1]); None }, - _ => { - // Not yet supported: - // cargo:KEY=VALUE — Metadata, used by links scripts. + "rustc-cdylib-link-arg" => { // cargo:rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates. eprintln!("Warning: build script returned unsupported directive `{}`", split[0]); None }, + _ => { + // cargo:KEY=VALUE — Metadata, used by links scripts. + Some(BuildScriptOutput::DepEnv(format!("{}={}", key_split[1].to_uppercase(), param))) + }, } } @@ -109,6 +118,28 @@ impl BuildScriptOutput { .join(" ") } + /// Convert a vector of [BuildScriptOutput] into a list of dependencies environment variables. + pub fn to_dep_env(v: &Vec, crate_name: &str) -> String { + // TODO: make use of `strip_suffix`. + const SYS_CRATE_SUFFIX: &str = "-sys"; + let name = if crate_name.ends_with(SYS_CRATE_SUFFIX) { + crate_name.split_at(crate_name.rfind(SYS_CRATE_SUFFIX).unwrap()).0 + } else { + crate_name + }; + let prefix = format!("DEP_{}_", name.replace("-", "_").to_uppercase()); + v.iter() + .filter_map(|x| { + if let BuildScriptOutput::DepEnv(env) = x { + Some(format!("{}{}", prefix, env.to_owned())) + } else { + None + } + }) + .collect::>() + .join(" ") + } + /// Convert a vector of [BuildScriptOutput] into a flagfile. pub fn to_flags(v: &Vec) -> String { v.iter() @@ -138,14 +169,15 @@ cargo:rustc-env=FOO=BAR cargo:rustc-link-search=bleh cargo:rustc-env=BAR=FOO cargo:rustc-flags=-Lblah -cargo:invalid=ignored cargo:rerun-if-changed=ignored cargo:rustc-cfg=feature=awesome +cargo:version=123 +cargo:version_number=1010107f ", ); let reader = BufReader::new(buff); let result = BuildScriptOutput::from_reader(reader); - assert_eq!(result.len(), 6); + assert_eq!(result.len(), 8); assert_eq!(result[0], BuildScriptOutput::LinkLib("sdfsdf".to_owned())); assert_eq!(result[1], BuildScriptOutput::Env("FOO=BAR".to_owned())); assert_eq!(result[2], BuildScriptOutput::LinkSearch("bleh".to_owned())); @@ -155,7 +187,13 @@ cargo:rustc-cfg=feature=awesome result[5], BuildScriptOutput::Cfg("feature=awesome".to_owned()) ); + assert_eq!(result[6], BuildScriptOutput::DepEnv("VERSION=123".to_owned())); + assert_eq!(result[7], BuildScriptOutput::DepEnv("VERSION_NUMBER=1010107f".to_owned())); + assert_eq!( + BuildScriptOutput::to_dep_env(&result, "my-crate-sys"), + "DEP_MY_CRATE_VERSION=123 DEP_MY_CRATE_VERSION_NUMBER=1010107f".to_owned() + ); assert_eq!( BuildScriptOutput::to_env(&result), "FOO=BAR BAR=FOO".to_owned() diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 50e9b4c112..48c8079dd6 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -45,6 +45,7 @@ BuildInfo = provider( "flags": """File: file containing additional flags to pass to rustc""", "out_dir": """File: directory containing the result of a build script""", "rustc_env": """File: file containing additional environment variables to set for rustc.""", + "dep_env": """File: extra build script environment varibles to be set to direct dependencies.""", }, ) @@ -62,6 +63,7 @@ DepInfo = provider( "transitive_dylibs": "depset[File]", "transitive_staticlibs": "depset[File]", "transitive_libs": "List[File]: All transitive dependencies, not filtered by type.", + "dep_env": """File: File with environment variables direct dependencies build scripts rely upon.""", }, ) @@ -185,6 +187,7 @@ def collect_deps(label, deps, proc_macro_deps, aliases, toolchain): transitive_dylibs = transitive_dylibs, transitive_staticlibs = transitive_staticlibs, transitive_libs = transitive_libs.to_list(), + dep_env = build_info.dep_env if build_info else None, ), build_info, )