Skip to content

Commit

Permalink
Support sys-crates build script dependencies.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Alexei Filippov committed Jun 15, 2020
1 parent 77ad6cc commit d5b7c0f
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 23 deletions.
41 changes: 34 additions & 7 deletions cargo/cargo_build_script.bzl
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
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")

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 <crate_name>_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,
Expand Down Expand Up @@ -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,
)
Expand All @@ -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,
),
]
Expand All @@ -66,20 +87,22 @@ _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,
allow_files = True,
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
Expand Down Expand Up @@ -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,
)
10 changes: 7 additions & 3 deletions cargo/cargo_build_script_runner/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand All @@ -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);
}
}
Expand Down
64 changes: 51 additions & 13 deletions cargo/cargo_build_script_runner/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum BuildScriptOutput {
Flags(String),
/// cargo:rustc-env
Env(String),
/// cargo:VAR=VALUE
DepEnv(String),
}

impl BuildScriptOutput {
Expand All @@ -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::<Vec<_>>();
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)))
},
}
}

Expand Down Expand Up @@ -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<BuildScriptOutput>, 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::<Vec<_>>()
.join(" ")
}

/// Convert a vector of [BuildScriptOutput] into a flagfile.
pub fn to_flags(v: &Vec<BuildScriptOutput>) -> String {
v.iter()
Expand Down Expand Up @@ -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()));
Expand All @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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.""",
},
)

Expand All @@ -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.""",
},
)

Expand Down Expand Up @@ -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,
)
Expand Down

0 comments on commit d5b7c0f

Please sign in to comment.