Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sys-crates build script dependencies. #341

Merged
merged 2 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 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 if not provided.
if not crate_name:
crate_name = ctx.label.name
if crate_name.endswith("_build_script"):
crate_name = crate_name.replace("_build_script", "")
crate_name = crate_name.replace("_", "-")

env = {
"CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
"CARGO_MANIFEST_DIR": manifest_dir,
Expand Down Expand Up @@ -36,11 +46,26 @@ 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],
# dep_env_file contains additional environment variables coming from
# direct dependency sys-crates' build scripts. These need to be made
# available to the current crate build script.
# See https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
# for details.
cmd = ""
a1ph marked this conversation as resolved.
Show resolved Hide resolved
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 +74,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 +92,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 +156,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