Skip to content
Draft
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
5 changes: 5 additions & 0 deletions cargo/defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ load(
_cargo_bootstrap_repository = "cargo_bootstrap_repository",
_cargo_env = "cargo_env",
)
load(
"//cargo/private:cargo_build_info.bzl",
_cargo_build_info = "cargo_build_info",
)
load(
"//cargo/private:cargo_build_script_wrapper.bzl",
_cargo_build_script = "cargo_build_script",
Expand All @@ -23,6 +27,7 @@ load(
load("//cargo/private:cargo_toml_env_vars.bzl", _cargo_toml_env_vars = "cargo_toml_env_vars")

cargo_bootstrap_repository = _cargo_bootstrap_repository
cargo_build_info = _cargo_build_info
cargo_env = _cargo_env

cargo_build_script = _cargo_build_script
Expand Down
12 changes: 11 additions & 1 deletion cargo/private/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
load("//rust:defs.bzl", "rust_binary")

rust_binary(
name = "cargo_build_info_runner",
srcs = ["cargo_build_info_runner.rs"],
edition = "2021",
visibility = ["//visibility:public"],
)

rust_binary(
name = "copy_file",
srcs = ["copy_file.rs"],
Expand Down Expand Up @@ -39,6 +46,9 @@ copy_file(

bzl_library(
name = "bzl_lib",
srcs = glob(["**/*.bzl"]),
srcs = glob(["*.bzl"]),
deps = [
"@rules_cc//cc/common",
],
visibility = ["//:__subpackages__"],
)
152 changes: 152 additions & 0 deletions cargo/private/cargo_build_info.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Generic rule for packaging files as a `BuildInfo` provider with optional `CcInfo` propagation.

Serves as a build-script replacement for `-sys` crates that need files placed in `OUT_DIR`
and/or linking against a C/C++ library.
"""

load("@rules_cc//cc:defs.bzl", "CcInfo")
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("//rust:rust_common.bzl", "BuildInfo")

# buildifier: disable=bzl-visibility
load("//rust/private:utils.bzl", "get_lib_name_default")

def _get_user_link_flags(cc_info):
linker_flags = []
for linker_input in cc_info.linking_context.linker_inputs.to_list():
linker_flags.extend(linker_input.user_link_flags)
return linker_flags

def _cargo_build_info_impl(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")
link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags")
link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths")

compile_data = []
cc_link_flags = []
cc_link_search_paths = []

if ctx.attr.cc_lib:
cc_info = ctx.attr.cc_lib[CcInfo]
for linker_input in cc_info.linking_context.linker_inputs.to_list():
for lib in linker_input.libraries:
if lib.static_library:
cc_link_flags.append("-lstatic={}".format(get_lib_name_default(lib.static_library)))
cc_link_search_paths.append(lib.static_library.dirname)
compile_data.append(lib.static_library)
elif lib.pic_static_library:
cc_link_flags.append("-lstatic={}".format(get_lib_name_default(lib.pic_static_library)))
cc_link_search_paths.append(lib.pic_static_library.dirname)
compile_data.append(lib.pic_static_library)

dep_env_lines = []
if ctx.attr.dep_env:
if ctx.attr.links:
prefix = "DEP_{}_".format(ctx.attr.links.replace("-", "_").upper())
else:
prefix = ""
dep_env_lines = ["{}{}={}".format(prefix, k, v) for k, v in ctx.attr.dep_env.items()]

# Collect files and their destinations, validating single-file labels.
input_files = []
file_args = []
for label, dests_json in ctx.attr.out_dir_files.items():
src_files = label.files.to_list()
if len(src_files) != 1:
fail("Expected exactly one file for {}, got {}".format(label, len(src_files)))
src_file = src_files[0]
input_files.append(src_file)
for dest in json.decode(dests_json):
file_args.append("{}={}".format(dest, src_file.path))

args = ctx.actions.args()
args.add(out_dir.path, format = "--out_dir=%s")
args.add(env_out, format = "--env_out=%s")
args.add(flags_out, format = "--flags_out=%s")
args.add(link_flags, format = "--link_flags=%s")
args.add(link_search_paths, format = "--link_search_paths=%s")
args.add(dep_env_out, format = "--dep_env_out=%s")

args.add_all(file_args, format_each = "--file=%s")
args.add_all(ctx.attr.rustc_flags, format_each = "--rustc_flag=%s")
args.add_all(
[
"{}={}".format(k, v)
for k, v in ctx.attr.rustc_env.items()
],
format_each = "--rustc_env=%s",
)
args.add_all(dep_env_lines, format_each = "--dep_env=%s")
args.add_all(cc_link_flags, format_each = "--link_flag=%s")
args.add_all(
depset(cc_link_search_paths).to_list(),
format_each = "--link_search_path=%s",
)

ctx.actions.run(
mnemonic = "CargoBuildInfo",
executable = ctx.executable._runner,
arguments = [args],
inputs = input_files + compile_data,
outputs = [out_dir, env_out, dep_env_out, flags_out, link_flags, link_search_paths],
)

providers = [
DefaultInfo(files = depset([out_dir])),
BuildInfo(
out_dir = out_dir,
rustc_env = env_out,
dep_env = dep_env_out,
flags = flags_out,
linker_flags = link_flags,
link_search_paths = link_search_paths,
compile_data = depset(compile_data),
),
]

if ctx.attr.cc_lib:
providers.append(CcInfo(
linking_context = cc_common.create_linking_context(
linker_inputs = depset([cc_common.create_linker_input(
owner = ctx.label,
user_link_flags = _get_user_link_flags(ctx.attr.cc_lib[CcInfo]),
)]),
),
))

return providers

cargo_build_info = rule(
doc = "Packages files into an `OUT_DIR` and returns a `BuildInfo` provider, serving as a drop-in replacement for `cargo_build_script`.",
implementation = _cargo_build_info_impl,
attrs = {
"cc_lib": attr.label(
doc = "Optional `cc_library` whose `CcInfo` linking context is propagated. Static libraries are added to link flags and search paths.",
providers = [CcInfo],
),
"dep_env": attr.string_dict(
doc = "Environment variables exported to dependent build scripts. Keys are auto-prefixed with `DEP_{LINKS}_` when `links` is set.",
),
"links": attr.string(
doc = "The Cargo `links` field value. Used to prefix `dep_env` keys with `DEP_{LINKS}_`.",
),
"out_dir_files": attr.label_keyed_string_dict(
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.",
allow_files = True,
),
"rustc_env": attr.string_dict(
doc = "Extra environment variables to set for rustc.",
),
"rustc_flags": attr.string_list(
doc = "Extra flags to pass to rustc.",
),
"_runner": attr.label(
executable = True,
cfg = "exec",
default = Label("//cargo/private:cargo_build_info_runner"),
),
},
)
144 changes: 144 additions & 0 deletions cargo/private/cargo_build_info_runner.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! Process wrapper for `CargoBuildInfo` actions.
//!
//! Copies source files into an OUT_DIR and writes metadata files
//! (env, dep_env, flags, link_flags, link_search_paths) consumed by `BuildInfo`.

use std::fs;
use std::path::Path;

struct Args {
/// Directory path for the `OUT_DIR` output tree.
out_dir: String,
/// Files to copy into `out_dir` as `(dest_relative_path, src_path)` pairs.
files: Vec<(String, String)>,
/// Output path for the `BuildInfo.rustc_env` file (one `K=V` per line).
env_out: String,
/// Output path for the `BuildInfo.flags` file (one rustc flag per line).
flags_out: String,
/// Output path for the `BuildInfo.linker_flags` file (one linker flag per line).
link_flags_out: String,
/// Output path for the `BuildInfo.link_search_paths` file (one `-Lnative=` path per line).
link_search_paths_out: String,
/// Output path for the `BuildInfo.dep_env` file (one `K=V` per line, already prefixed).
dep_env_out: String,
/// Extra flags to pass to rustc, written to `flags_out`.
rustc_flags: Vec<String>,
/// Extra environment variables for rustc as `K=V`, written to `env_out`.
rustc_envs: Vec<String>,
/// Dependency environment variables as `K=V`, written to `dep_env_out`.
dep_envs: Vec<String>,
/// Linker flags derived from `CcInfo` (e.g. `-lstatic=crypto`), written to `link_flags_out`.
link_flags: Vec<String>,
/// Library search paths derived from `CcInfo`, formatted as `-Lnative=` and written to `link_search_paths_out`.
link_search_paths: Vec<String>,
}

impl Args {
/// Parse command line arguments.
fn parse() -> Self {
let mut out_dir: Option<String> = None;
let mut files = Vec::new();
let mut env_out: Option<String> = None;
let mut flags_out: Option<String> = None;
let mut link_flags_out: Option<String> = None;
let mut link_search_paths_out: Option<String> = None;
let mut dep_env_out: Option<String> = None;
let mut rustc_flags = Vec::new();
let mut rustc_envs = Vec::new();
let mut dep_envs = Vec::new();
let mut link_flags = Vec::new();
let mut link_search_paths = Vec::new();

for mut arg in std::env::args().skip(1) {
if arg.starts_with("--out_dir=") {
out_dir = Some(arg.split_off("--out_dir=".len()));
} else if arg.starts_with("--file=") {
let val = arg.split_off("--file=".len());
let (dest, src) = val
.split_once('=')
.unwrap_or_else(|| panic!("--file value must be dest=src, got: {val}"));
files.push((dest.to_owned(), src.to_owned()));
} else if arg.starts_with("--env_out=") {
env_out = Some(arg.split_off("--env_out=".len()));
} else if arg.starts_with("--flags_out=") {
flags_out = Some(arg.split_off("--flags_out=".len()));
} else if arg.starts_with("--link_flags=") {
link_flags_out = Some(arg.split_off("--link_flags=".len()));
} else if arg.starts_with("--link_search_paths=") {
link_search_paths_out = Some(arg.split_off("--link_search_paths=".len()));
} else if arg.starts_with("--dep_env_out=") {
dep_env_out = Some(arg.split_off("--dep_env_out=".len()));
} else if arg.starts_with("--rustc_flag=") {
rustc_flags.push(arg.split_off("--rustc_flag=".len()));
} else if arg.starts_with("--rustc_env=") {
rustc_envs.push(arg.split_off("--rustc_env=".len()));
} else if arg.starts_with("--dep_env=") {
dep_envs.push(arg.split_off("--dep_env=".len()));
} else if arg.starts_with("--link_flag=") {
link_flags.push(arg.split_off("--link_flag=".len()));
} else if arg.starts_with("--link_search_path=") {
link_search_paths.push(arg.split_off("--link_search_path=".len()));
} else {
panic!("cargo_build_info_runner: unknown argument: {arg}");
}
}

Args {
out_dir: out_dir.expect("--out_dir is required"),
files,
env_out: env_out.expect("--env_out is required"),
flags_out: flags_out.expect("--flags_out is required"),
link_flags_out: link_flags_out.expect("--link_flags is required"),
link_search_paths_out: link_search_paths_out.expect("--link_search_paths is required"),
dep_env_out: dep_env_out.expect("--dep_env_out is required"),
rustc_flags,
rustc_envs,
dep_envs,
link_flags,
link_search_paths,
}
}
}

fn write_lines(path: &str, lines: &[String]) {
let content = if lines.is_empty() {
String::new()
} else {
lines.join("\n")
};
fs::write(path, content).unwrap_or_else(|e| panic!("Failed to write {path}: {e}"));
}

fn main() {
let args = Args::parse();

fs::create_dir_all(&args.out_dir)
.unwrap_or_else(|e| panic!("Failed to create out_dir {:?}: {e}", args.out_dir));

if args.files.is_empty() {
fs::write(Path::new(&args.out_dir).join(".empty"), "")
.unwrap_or_else(|e| panic!("Failed to write .empty sentinel: {e}"));
}

for (dest_name, src_path) in &args.files {
let dest = Path::new(&args.out_dir).join(dest_name);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)
.unwrap_or_else(|e| panic!("Failed to create parent dir {:?}: {e}", parent));
}
fs::copy(src_path, &dest)
.unwrap_or_else(|e| panic!("Failed to copy {:?} -> {:?}: {e}", src_path, dest));
}

write_lines(&args.flags_out, &args.rustc_flags);
write_lines(&args.env_out, &args.rustc_envs);
write_lines(&args.dep_env_out, &args.dep_envs);
write_lines(&args.link_flags_out, &args.link_flags);

let search_paths: Vec<String> = args
.link_search_paths
.iter()
.map(|p| format!("-Lnative=${{pwd}}/{p}"))
.collect();
write_lines(&args.link_search_paths_out, &search_paths);
}
Loading
Loading