Skip to content

Commit d70c4e8

Browse files
committed
Added cargo_build_info rule
1 parent a9565ff commit d70c4e8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+9329
-6843
lines changed

cargo/defs.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ load(
88
_cargo_bootstrap_repository = "cargo_bootstrap_repository",
99
_cargo_env = "cargo_env",
1010
)
11+
load(
12+
"//cargo/private:cargo_build_info.bzl",
13+
_cargo_build_info = "cargo_build_info",
14+
)
1115
load(
1216
"//cargo/private:cargo_build_script_wrapper.bzl",
1317
_cargo_build_script = "cargo_build_script",
@@ -23,6 +27,7 @@ load(
2327
load("//cargo/private:cargo_toml_env_vars.bzl", _cargo_toml_env_vars = "cargo_toml_env_vars")
2428

2529
cargo_bootstrap_repository = _cargo_bootstrap_repository
30+
cargo_build_info = _cargo_build_info
2631
cargo_env = _cargo_env
2732

2833
cargo_build_script = _cargo_build_script

cargo/private/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
22
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
33
load("//rust:defs.bzl", "rust_binary")
44

5+
rust_binary(
6+
name = "cargo_build_info_runner",
7+
srcs = ["cargo_build_info_runner.rs"],
8+
edition = "2021",
9+
visibility = ["//visibility:public"],
10+
)
11+
512
rust_binary(
613
name = "copy_file",
714
srcs = ["copy_file.rs"],

cargo/private/cargo_build_info.bzl

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""Generic rule for packaging files as a `BuildInfo` provider with optional `CcInfo` propagation.
2+
3+
Serves as a build-script replacement for `-sys` crates that need files placed in `OUT_DIR`
4+
and/or linking against a C/C++ library.
5+
"""
6+
7+
load("@rules_cc//cc:defs.bzl", "CcInfo")
8+
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
9+
load("//rust:rust_common.bzl", "BuildInfo")
10+
11+
# buildifier: disable=bzl-visibility
12+
load("//rust/private:utils.bzl", "get_lib_name_default")
13+
14+
def _get_user_link_flags(cc_info):
15+
linker_flags = []
16+
for linker_input in cc_info.linking_context.linker_inputs.to_list():
17+
linker_flags.extend(linker_input.user_link_flags)
18+
return linker_flags
19+
20+
def _cargo_build_info_impl(ctx):
21+
out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir")
22+
env_out = ctx.actions.declare_file(ctx.label.name + ".env")
23+
dep_env_out = ctx.actions.declare_file(ctx.label.name + ".depenv")
24+
flags_out = ctx.actions.declare_file(ctx.label.name + ".flags")
25+
link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags")
26+
link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths")
27+
28+
compile_data = []
29+
cc_link_flags = []
30+
cc_link_search_paths = []
31+
32+
if ctx.attr.cc_lib:
33+
cc_info = ctx.attr.cc_lib[CcInfo]
34+
for linker_input in cc_info.linking_context.linker_inputs.to_list():
35+
for lib in linker_input.libraries:
36+
if lib.static_library:
37+
cc_link_flags.append("-lstatic={}".format(get_lib_name_default(lib.static_library)))
38+
cc_link_search_paths.append(lib.static_library.dirname)
39+
compile_data.append(lib.static_library)
40+
elif lib.pic_static_library:
41+
cc_link_flags.append("-lstatic={}".format(get_lib_name_default(lib.pic_static_library)))
42+
cc_link_search_paths.append(lib.pic_static_library.dirname)
43+
compile_data.append(lib.pic_static_library)
44+
45+
dep_env_lines = []
46+
if ctx.attr.dep_env:
47+
if ctx.attr.links:
48+
prefix = "DEP_{}_".format(ctx.attr.links.replace("-", "_").upper())
49+
else:
50+
prefix = ""
51+
dep_env_lines = ["{}{}={}".format(prefix, k, v) for k, v in ctx.attr.dep_env.items()]
52+
53+
# Collect files and their destinations, validating single-file labels.
54+
input_files = []
55+
file_args = []
56+
for label, dests_json in ctx.attr.out_dir_files.items():
57+
src_files = label.files.to_list()
58+
if len(src_files) != 1:
59+
fail("Expected exactly one file for {}, got {}".format(label, len(src_files)))
60+
src_file = src_files[0]
61+
input_files.append(src_file)
62+
for dest in json.decode(dests_json):
63+
file_args.append("{}={}".format(dest, src_file.path))
64+
65+
args = ctx.actions.args()
66+
args.add(out_dir.path, format = "--out_dir=%s")
67+
args.add(env_out, format = "--env_out=%s")
68+
args.add(flags_out, format = "--flags_out=%s")
69+
args.add(link_flags, format = "--link_flags=%s")
70+
args.add(link_search_paths, format = "--link_search_paths=%s")
71+
args.add(dep_env_out, format = "--dep_env_out=%s")
72+
73+
args.add_all(file_args, format_each = "--file=%s")
74+
args.add_all(ctx.attr.rustc_flags, format_each = "--rustc_flag=%s")
75+
args.add_all(
76+
[
77+
"{}={}".format(k, v)
78+
for k, v in ctx.attr.rustc_env.items()
79+
],
80+
format_each = "--rustc_env=%s",
81+
)
82+
args.add_all(dep_env_lines, format_each = "--dep_env=%s")
83+
args.add_all(cc_link_flags, format_each = "--link_flag=%s")
84+
args.add_all(
85+
depset(cc_link_search_paths).to_list(),
86+
format_each = "--link_search_path=%s",
87+
)
88+
89+
ctx.actions.run(
90+
mnemonic = "CargoBuildInfo",
91+
executable = ctx.executable._runner,
92+
arguments = [args],
93+
inputs = input_files + compile_data,
94+
outputs = [out_dir, env_out, dep_env_out, flags_out, link_flags, link_search_paths],
95+
)
96+
97+
providers = [
98+
DefaultInfo(files = depset([out_dir])),
99+
BuildInfo(
100+
out_dir = out_dir,
101+
rustc_env = env_out,
102+
dep_env = dep_env_out,
103+
flags = flags_out,
104+
linker_flags = link_flags,
105+
link_search_paths = link_search_paths,
106+
compile_data = depset(compile_data),
107+
),
108+
]
109+
110+
if ctx.attr.cc_lib:
111+
providers.append(CcInfo(
112+
linking_context = cc_common.create_linking_context(
113+
linker_inputs = depset([cc_common.create_linker_input(
114+
owner = ctx.label,
115+
user_link_flags = _get_user_link_flags(ctx.attr.cc_lib[CcInfo]),
116+
)]),
117+
),
118+
))
119+
120+
return providers
121+
122+
cargo_build_info = rule(
123+
doc = "Packages files into an `OUT_DIR` and returns a `BuildInfo` provider, serving as a drop-in replacement for `cargo_build_script`.",
124+
implementation = _cargo_build_info_impl,
125+
attrs = {
126+
"cc_lib": attr.label(
127+
doc = "Optional `cc_library` whose `CcInfo` linking context is propagated. Static libraries are added to link flags and search paths.",
128+
providers = [CcInfo],
129+
),
130+
"dep_env": attr.string_dict(
131+
doc = "Environment variables exported to dependent build scripts. Keys are auto-prefixed with `DEP_{LINKS}_` when `links` is set.",
132+
),
133+
"links": attr.string(
134+
doc = "The Cargo `links` field value. Used to prefix `dep_env` keys with `DEP_{LINKS}_`.",
135+
),
136+
"out_dir_files": attr.label_keyed_string_dict(
137+
doc = "Map of source file labels to JSON-encoded lists of destination paths within `OUT_DIR`. Use the `cargo_build_info` macro for an ergonomic `{dest: label}` interface.",
138+
allow_files = True,
139+
),
140+
"rustc_env": attr.string_dict(
141+
doc = "Extra environment variables to set for rustc.",
142+
),
143+
"rustc_flags": attr.string_list(
144+
doc = "Extra flags to pass to rustc.",
145+
),
146+
"_runner": attr.label(
147+
executable = True,
148+
cfg = "exec",
149+
default = Label("//cargo/private:cargo_build_info_runner"),
150+
),
151+
},
152+
)
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//! Process wrapper for `CargoBuildInfo` actions.
2+
//!
3+
//! Copies source files into an OUT_DIR and writes metadata files
4+
//! (env, dep_env, flags, link_flags, link_search_paths) consumed by `BuildInfo`.
5+
6+
use std::fs;
7+
use std::path::Path;
8+
9+
struct Args {
10+
/// Directory path for the `OUT_DIR` output tree.
11+
out_dir: String,
12+
/// Files to copy into `out_dir` as `(dest_relative_path, src_path)` pairs.
13+
files: Vec<(String, String)>,
14+
/// Output path for the `BuildInfo.rustc_env` file (one `K=V` per line).
15+
env_out: String,
16+
/// Output path for the `BuildInfo.flags` file (one rustc flag per line).
17+
flags_out: String,
18+
/// Output path for the `BuildInfo.linker_flags` file (one linker flag per line).
19+
link_flags_out: String,
20+
/// Output path for the `BuildInfo.link_search_paths` file (one `-Lnative=` path per line).
21+
link_search_paths_out: String,
22+
/// Output path for the `BuildInfo.dep_env` file (one `K=V` per line, already prefixed).
23+
dep_env_out: String,
24+
/// Extra flags to pass to rustc, written to `flags_out`.
25+
rustc_flags: Vec<String>,
26+
/// Extra environment variables for rustc as `K=V`, written to `env_out`.
27+
rustc_envs: Vec<String>,
28+
/// Dependency environment variables as `K=V`, written to `dep_env_out`.
29+
dep_envs: Vec<String>,
30+
/// Linker flags derived from `CcInfo` (e.g. `-lstatic=crypto`), written to `link_flags_out`.
31+
link_flags: Vec<String>,
32+
/// Library search paths derived from `CcInfo`, formatted as `-Lnative=` and written to `link_search_paths_out`.
33+
link_search_paths: Vec<String>,
34+
}
35+
36+
impl Args {
37+
/// Parse command line arguments.
38+
fn parse() -> Self {
39+
let mut out_dir: Option<String> = None;
40+
let mut files = Vec::new();
41+
let mut env_out: Option<String> = None;
42+
let mut flags_out: Option<String> = None;
43+
let mut link_flags_out: Option<String> = None;
44+
let mut link_search_paths_out: Option<String> = None;
45+
let mut dep_env_out: Option<String> = None;
46+
let mut rustc_flags = Vec::new();
47+
let mut rustc_envs = Vec::new();
48+
let mut dep_envs = Vec::new();
49+
let mut link_flags = Vec::new();
50+
let mut link_search_paths = Vec::new();
51+
52+
for mut arg in std::env::args().skip(1) {
53+
if arg.starts_with("--out_dir=") {
54+
out_dir = Some(arg.split_off("--out_dir=".len()));
55+
} else if arg.starts_with("--file=") {
56+
let val = arg.split_off("--file=".len());
57+
let (dest, src) = val
58+
.split_once('=')
59+
.unwrap_or_else(|| panic!("--file value must be dest=src, got: {val}"));
60+
files.push((dest.to_owned(), src.to_owned()));
61+
} else if arg.starts_with("--env_out=") {
62+
env_out = Some(arg.split_off("--env_out=".len()));
63+
} else if arg.starts_with("--flags_out=") {
64+
flags_out = Some(arg.split_off("--flags_out=".len()));
65+
} else if arg.starts_with("--link_flags=") {
66+
link_flags_out = Some(arg.split_off("--link_flags=".len()));
67+
} else if arg.starts_with("--link_search_paths=") {
68+
link_search_paths_out = Some(arg.split_off("--link_search_paths=".len()));
69+
} else if arg.starts_with("--dep_env_out=") {
70+
dep_env_out = Some(arg.split_off("--dep_env_out=".len()));
71+
} else if arg.starts_with("--rustc_flag=") {
72+
rustc_flags.push(arg.split_off("--rustc_flag=".len()));
73+
} else if arg.starts_with("--rustc_env=") {
74+
rustc_envs.push(arg.split_off("--rustc_env=".len()));
75+
} else if arg.starts_with("--dep_env=") {
76+
dep_envs.push(arg.split_off("--dep_env=".len()));
77+
} else if arg.starts_with("--link_flag=") {
78+
link_flags.push(arg.split_off("--link_flag=".len()));
79+
} else if arg.starts_with("--link_search_path=") {
80+
link_search_paths.push(arg.split_off("--link_search_path=".len()));
81+
} else {
82+
panic!("cargo_build_info_runner: unknown argument: {arg}");
83+
}
84+
}
85+
86+
Args {
87+
out_dir: out_dir.expect("--out_dir is required"),
88+
files,
89+
env_out: env_out.expect("--env_out is required"),
90+
flags_out: flags_out.expect("--flags_out is required"),
91+
link_flags_out: link_flags_out.expect("--link_flags is required"),
92+
link_search_paths_out: link_search_paths_out.expect("--link_search_paths is required"),
93+
dep_env_out: dep_env_out.expect("--dep_env_out is required"),
94+
rustc_flags,
95+
rustc_envs,
96+
dep_envs,
97+
link_flags,
98+
link_search_paths,
99+
}
100+
}
101+
}
102+
103+
fn write_lines(path: &str, lines: &[String]) {
104+
let content = if lines.is_empty() {
105+
String::new()
106+
} else {
107+
lines.join("\n")
108+
};
109+
fs::write(path, content).unwrap_or_else(|e| panic!("Failed to write {path}: {e}"));
110+
}
111+
112+
fn main() {
113+
let args = Args::parse();
114+
115+
fs::create_dir_all(&args.out_dir)
116+
.unwrap_or_else(|e| panic!("Failed to create out_dir {:?}: {e}", args.out_dir));
117+
118+
if args.files.is_empty() {
119+
fs::write(Path::new(&args.out_dir).join(".empty"), "")
120+
.unwrap_or_else(|e| panic!("Failed to write .empty sentinel: {e}"));
121+
}
122+
123+
for (dest_name, src_path) in &args.files {
124+
let dest = Path::new(&args.out_dir).join(dest_name);
125+
if let Some(parent) = dest.parent() {
126+
fs::create_dir_all(parent)
127+
.unwrap_or_else(|e| panic!("Failed to create parent dir {:?}: {e}", parent));
128+
}
129+
fs::copy(src_path, &dest)
130+
.unwrap_or_else(|e| panic!("Failed to copy {:?} -> {:?}: {e}", src_path, dest));
131+
}
132+
133+
write_lines(&args.flags_out, &args.rustc_flags);
134+
write_lines(&args.env_out, &args.rustc_envs);
135+
write_lines(&args.dep_env_out, &args.dep_envs);
136+
write_lines(&args.link_flags_out, &args.link_flags);
137+
138+
let search_paths: Vec<String> = args
139+
.link_search_paths
140+
.iter()
141+
.map(|p| format!("-Lnative=${{pwd}}/{p}"))
142+
.collect();
143+
write_lines(&args.link_search_paths_out, &search_paths);
144+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""cargo_build_info wrapper that only exists until the min supported version
2+
of Bazel includes https://github.com/bazelbuild/bazel/issues/7989
3+
"""
4+
5+
load(":cargo_build_info.bzl", _cargo_build_info = "cargo_build_info")
6+
7+
def cargo_build_info(
8+
name,
9+
out_dir_files = {},
10+
cc_lib = None,
11+
rustc_flags = None,
12+
rustc_env = None,
13+
dep_env = None,
14+
links = "",
15+
**kwargs):
16+
"""Packages files into an `OUT_DIR` and returns a `BuildInfo` provider.
17+
18+
This is a generic build-script replacement for `-sys` crates. It places
19+
arbitrary files into an `OUT_DIR` directory, optionally propagates `CcInfo`
20+
from a `cc_library`, and returns `BuildInfo` so the crate's `lib.rs` can
21+
use `include!(concat!(env!("OUT_DIR"), "/..."))` unchanged.
22+
23+
Use with `override_target_build_script` in crate annotations:
24+
25+
```python
26+
crate.annotation(
27+
crate = "my-native-sys",
28+
override_target_build_script = "//path:my_sys_bs",
29+
)
30+
```
31+
32+
Args:
33+
name: Unique name for this target.
34+
out_dir_files: Dict mapping destination filenames (within `OUT_DIR`) to
35+
source file labels. A single label can appear as multiple destinations.
36+
Example: `{"bindings.rs": ":my_bindgen", "config.h": ":my_header"}`
37+
cc_lib: Optional `cc_library` label for `CcInfo` propagation (link flags,
38+
search paths). The library's static archives are added as link dependencies.
39+
rustc_flags: Extra flags to pass to rustc.
40+
rustc_env: Extra environment variables for rustc.
41+
dep_env: Environment variables exported to dependent build scripts.
42+
Auto-prefixed with `DEP_{LINKS}_` when `links` is set.
43+
links: Cargo `links` field value, used to prefix `dep_env` keys.
44+
**kwargs: Common attributes forwarded to the underlying rule
45+
(`visibility`, `tags`, `target_compatible_with`, `exec_compatible_with`).
46+
"""
47+
inverted = {}
48+
for dest, label in out_dir_files.items():
49+
if label not in inverted:
50+
inverted[label] = []
51+
inverted[label].append(dest)
52+
53+
_cargo_build_info(
54+
name = name,
55+
out_dir_files = {label: json.encode(dests) for label, dests in inverted.items()},
56+
cc_lib = cc_lib,
57+
rustc_flags = rustc_flags or [],
58+
rustc_env = rustc_env or {},
59+
dep_env = dep_env or {},
60+
links = links,
61+
**kwargs
62+
)

cargo/tests/cargo_build_info/BUILD.bazel

Whitespace-only changes.

0 commit comments

Comments
 (0)