xref: /aosp_15_r20/external/bazelbuild-rules_rust/cargo/private/cargo_bootstrap.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan"""The `cargo_bootstrap` rule is used for bootstrapping cargo binaries in a repository rule."""
2*d4726bddSHONG Yifan
3*d4726bddSHONG Yifanload("//cargo/private:cargo_utils.bzl", "get_rust_tools")
4*d4726bddSHONG Yifanload("//rust:defs.bzl", "rust_common")
5*d4726bddSHONG Yifanload("//rust/platform:triple.bzl", "get_host_triple")
6*d4726bddSHONG Yifan
7*d4726bddSHONG Yifan_CARGO_BUILD_MODES = [
8*d4726bddSHONG Yifan    "release",
9*d4726bddSHONG Yifan    "debug",
10*d4726bddSHONG Yifan]
11*d4726bddSHONG Yifan
12*d4726bddSHONG Yifan_FAIL_MESSAGE = """\
13*d4726bddSHONG YifanProcess exited with code '{code}'
14*d4726bddSHONG Yifan# ARGV ########################################################################
15*d4726bddSHONG Yifan{argv}
16*d4726bddSHONG Yifan
17*d4726bddSHONG Yifan# STDOUT ######################################################################
18*d4726bddSHONG Yifan{stdout}
19*d4726bddSHONG Yifan
20*d4726bddSHONG Yifan# STDERR ######################################################################
21*d4726bddSHONG Yifan{stderr}
22*d4726bddSHONG Yifan"""
23*d4726bddSHONG Yifan
24*d4726bddSHONG Yifandef cargo_bootstrap(
25*d4726bddSHONG Yifan        repository_ctx,
26*d4726bddSHONG Yifan        cargo_bin,
27*d4726bddSHONG Yifan        rustc_bin,
28*d4726bddSHONG Yifan        binary,
29*d4726bddSHONG Yifan        cargo_manifest,
30*d4726bddSHONG Yifan        environment = {},
31*d4726bddSHONG Yifan        quiet = False,
32*d4726bddSHONG Yifan        build_mode = "release",
33*d4726bddSHONG Yifan        target_dir = None,
34*d4726bddSHONG Yifan        timeout = 600):
35*d4726bddSHONG Yifan    """A function for bootstrapping a cargo binary within a repository rule
36*d4726bddSHONG Yifan
37*d4726bddSHONG Yifan    Args:
38*d4726bddSHONG Yifan        repository_ctx (repository_ctx): The rule's context object.
39*d4726bddSHONG Yifan        cargo_bin (path): The path to a Cargo binary.
40*d4726bddSHONG Yifan        rustc_bin (path): The path to a Rustc binary.
41*d4726bddSHONG Yifan        binary (str): The binary to build (the `--bin` parameter for Cargo).
42*d4726bddSHONG Yifan        cargo_manifest (path): The path to a Cargo manifest (Cargo.toml file).
43*d4726bddSHONG Yifan        environment (dict): Environment variables to use during execution.
44*d4726bddSHONG Yifan        quiet (bool, optional): Whether or not to print output from the Cargo command.
45*d4726bddSHONG Yifan        build_mode (str, optional): The build mode to use
46*d4726bddSHONG Yifan        target_dir (path, optional): The directory in which to produce build outputs
47*d4726bddSHONG Yifan            (Cargo's --target-dir argument).
48*d4726bddSHONG Yifan        timeout (int, optional): Maximum duration of the Cargo build command in seconds,
49*d4726bddSHONG Yifan
50*d4726bddSHONG Yifan    Returns:
51*d4726bddSHONG Yifan        path: The path of the built binary within the target directory
52*d4726bddSHONG Yifan    """
53*d4726bddSHONG Yifan
54*d4726bddSHONG Yifan    if not target_dir:
55*d4726bddSHONG Yifan        target_dir = repository_ctx.path(".")
56*d4726bddSHONG Yifan
57*d4726bddSHONG Yifan    args = [
58*d4726bddSHONG Yifan        cargo_bin,
59*d4726bddSHONG Yifan        "build",
60*d4726bddSHONG Yifan        "--bin",
61*d4726bddSHONG Yifan        binary,
62*d4726bddSHONG Yifan        "--locked",
63*d4726bddSHONG Yifan        "--target-dir",
64*d4726bddSHONG Yifan        target_dir,
65*d4726bddSHONG Yifan        "--manifest-path",
66*d4726bddSHONG Yifan        cargo_manifest,
67*d4726bddSHONG Yifan    ]
68*d4726bddSHONG Yifan
69*d4726bddSHONG Yifan    if build_mode not in _CARGO_BUILD_MODES:
70*d4726bddSHONG Yifan        fail("'{}' is not a supported build mode. Use one of {}".format(build_mode, _CARGO_BUILD_MODES))
71*d4726bddSHONG Yifan
72*d4726bddSHONG Yifan    if build_mode == "release":
73*d4726bddSHONG Yifan        args.append("--release")
74*d4726bddSHONG Yifan
75*d4726bddSHONG Yifan    env = dict({
76*d4726bddSHONG Yifan        "RUSTC": str(rustc_bin),
77*d4726bddSHONG Yifan    }.items() + environment.items())
78*d4726bddSHONG Yifan
79*d4726bddSHONG Yifan    repository_ctx.report_progress("Cargo Bootstrapping {}".format(binary))
80*d4726bddSHONG Yifan    result = repository_ctx.execute(
81*d4726bddSHONG Yifan        args,
82*d4726bddSHONG Yifan        environment = env,
83*d4726bddSHONG Yifan        quiet = quiet,
84*d4726bddSHONG Yifan        timeout = timeout,
85*d4726bddSHONG Yifan    )
86*d4726bddSHONG Yifan
87*d4726bddSHONG Yifan    if result.return_code != 0:
88*d4726bddSHONG Yifan        fail(_FAIL_MESSAGE.format(
89*d4726bddSHONG Yifan            code = result.return_code,
90*d4726bddSHONG Yifan            argv = args,
91*d4726bddSHONG Yifan            stdout = result.stdout,
92*d4726bddSHONG Yifan            stderr = result.stderr,
93*d4726bddSHONG Yifan        ))
94*d4726bddSHONG Yifan
95*d4726bddSHONG Yifan    extension = ""
96*d4726bddSHONG Yifan    if "win" in repository_ctx.os.name:
97*d4726bddSHONG Yifan        extension = ".exe"
98*d4726bddSHONG Yifan
99*d4726bddSHONG Yifan    binary_path = "{}/{}{}".format(
100*d4726bddSHONG Yifan        build_mode,
101*d4726bddSHONG Yifan        binary,
102*d4726bddSHONG Yifan        extension,
103*d4726bddSHONG Yifan    )
104*d4726bddSHONG Yifan
105*d4726bddSHONG Yifan    if not repository_ctx.path(binary_path).exists:
106*d4726bddSHONG Yifan        fail("Failed to produce binary at {}".format(binary_path))
107*d4726bddSHONG Yifan
108*d4726bddSHONG Yifan    return binary_path
109*d4726bddSHONG Yifan
110*d4726bddSHONG Yifan_BUILD_FILE_CONTENT = """\
111*d4726bddSHONG Yifanload("@rules_rust//rust:defs.bzl", "rust_binary")
112*d4726bddSHONG Yifan
113*d4726bddSHONG Yifanpackage(default_visibility = ["//visibility:public"])
114*d4726bddSHONG Yifan
115*d4726bddSHONG Yifanexports_files([
116*d4726bddSHONG Yifan    "{binary_name}",
117*d4726bddSHONG Yifan    "{binary}"
118*d4726bddSHONG Yifan])
119*d4726bddSHONG Yifan
120*d4726bddSHONG Yifanalias(
121*d4726bddSHONG Yifan    name = "binary",
122*d4726bddSHONG Yifan    actual = "{binary}",
123*d4726bddSHONG Yifan)
124*d4726bddSHONG Yifan
125*d4726bddSHONG Yifanrust_binary(
126*d4726bddSHONG Yifan    name = "install",
127*d4726bddSHONG Yifan    rustc_env = {{
128*d4726bddSHONG Yifan        "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath {binary})"
129*d4726bddSHONG Yifan    }},
130*d4726bddSHONG Yifan    data = [
131*d4726bddSHONG Yifan        "{binary}",
132*d4726bddSHONG Yifan    ],
133*d4726bddSHONG Yifan    srcs = [
134*d4726bddSHONG Yifan        "@rules_rust//cargo/bootstrap:bootstrap_installer.rs"
135*d4726bddSHONG Yifan    ],
136*d4726bddSHONG Yifan)
137*d4726bddSHONG Yifan"""
138*d4726bddSHONG Yifan
139*d4726bddSHONG Yifandef _collect_environ(repository_ctx, host_triple):
140*d4726bddSHONG Yifan    """Gather environment varialbes to use from the current rule context
141*d4726bddSHONG Yifan
142*d4726bddSHONG Yifan    Args:
143*d4726bddSHONG Yifan        repository_ctx (repository_ctx): The rule's context object.
144*d4726bddSHONG Yifan        host_triple (str): A string of the current host triple
145*d4726bddSHONG Yifan
146*d4726bddSHONG Yifan    Returns:
147*d4726bddSHONG Yifan        dict: A map of environment variables
148*d4726bddSHONG Yifan    """
149*d4726bddSHONG Yifan    env_vars = dict(json.decode(repository_ctx.attr.env.get(host_triple, "{}")))
150*d4726bddSHONG Yifan
151*d4726bddSHONG Yifan    # Gather the path for each label and ensure it exists
152*d4726bddSHONG Yifan    env_labels = dict(json.decode(repository_ctx.attr.env_label.get(host_triple, "{}")))
153*d4726bddSHONG Yifan    env_labels = {key: repository_ctx.path(Label(value)) for (key, value) in env_labels.items()}
154*d4726bddSHONG Yifan    for key in env_labels:
155*d4726bddSHONG Yifan        if not env_labels[key].exists:
156*d4726bddSHONG Yifan            fail("File for key '{}' does not exist: {}", key, env_labels[key])
157*d4726bddSHONG Yifan    env_labels = {key: str(value) for (key, value) in env_labels.items()}
158*d4726bddSHONG Yifan
159*d4726bddSHONG Yifan    return dict(env_vars.items() + env_labels.items())
160*d4726bddSHONG Yifan
161*d4726bddSHONG Yifandef _detect_changes(repository_ctx):
162*d4726bddSHONG Yifan    """Inspect files that are considered inputs to the build for changes
163*d4726bddSHONG Yifan
164*d4726bddSHONG Yifan    Args:
165*d4726bddSHONG Yifan        repository_ctx (repository_ctx): The rule's context object.
166*d4726bddSHONG Yifan    """
167*d4726bddSHONG Yifan    # Simply generating a `path` object consideres the file as 'tracked' or
168*d4726bddSHONG Yifan    # 'consumed' which means changes to it will trigger rebuilds
169*d4726bddSHONG Yifan
170*d4726bddSHONG Yifan    for src in repository_ctx.attr.srcs:
171*d4726bddSHONG Yifan        repository_ctx.path(src)
172*d4726bddSHONG Yifan
173*d4726bddSHONG Yifan    repository_ctx.path(repository_ctx.attr.cargo_lockfile)
174*d4726bddSHONG Yifan    repository_ctx.path(repository_ctx.attr.cargo_toml)
175*d4726bddSHONG Yifan
176*d4726bddSHONG Yifandef _cargo_bootstrap_repository_impl(repository_ctx):
177*d4726bddSHONG Yifan    # Pretend to Bazel that this rule's input files have been used, so that it will re-run the rule if they change.
178*d4726bddSHONG Yifan    _detect_changes(repository_ctx)
179*d4726bddSHONG Yifan
180*d4726bddSHONG Yifan    # Expects something like `1.56.0`, or `nightly/2021-09-08`.
181*d4726bddSHONG Yifan    version, _, iso_date = repository_ctx.attr.version.partition("/")
182*d4726bddSHONG Yifan    if iso_date:
183*d4726bddSHONG Yifan        channel = version
184*d4726bddSHONG Yifan        version = iso_date
185*d4726bddSHONG Yifan    else:
186*d4726bddSHONG Yifan        channel = "stable"
187*d4726bddSHONG Yifan
188*d4726bddSHONG Yifan    host_triple = get_host_triple(repository_ctx)
189*d4726bddSHONG Yifan    cargo_template = repository_ctx.attr.rust_toolchain_cargo_template
190*d4726bddSHONG Yifan    rustc_template = repository_ctx.attr.rust_toolchain_rustc_template
191*d4726bddSHONG Yifan
192*d4726bddSHONG Yifan    tools = get_rust_tools(
193*d4726bddSHONG Yifan        cargo_template = cargo_template,
194*d4726bddSHONG Yifan        rustc_template = rustc_template,
195*d4726bddSHONG Yifan        host_triple = host_triple,
196*d4726bddSHONG Yifan        channel = channel,
197*d4726bddSHONG Yifan        version = version,
198*d4726bddSHONG Yifan    )
199*d4726bddSHONG Yifan
200*d4726bddSHONG Yifan    binary_name = repository_ctx.attr.binary or repository_ctx.name
201*d4726bddSHONG Yifan
202*d4726bddSHONG Yifan    # In addition to platform specific environment variables, a common set (indicated by `*`) will always
203*d4726bddSHONG Yifan    # be gathered.
204*d4726bddSHONG Yifan    environment = dict(_collect_environ(repository_ctx, "*").items() + _collect_environ(repository_ctx, host_triple.str).items())
205*d4726bddSHONG Yifan
206*d4726bddSHONG Yifan    built_binary = cargo_bootstrap(
207*d4726bddSHONG Yifan        repository_ctx = repository_ctx,
208*d4726bddSHONG Yifan        cargo_bin = repository_ctx.path(tools.cargo),
209*d4726bddSHONG Yifan        rustc_bin = repository_ctx.path(tools.rustc),
210*d4726bddSHONG Yifan        binary = binary_name,
211*d4726bddSHONG Yifan        cargo_manifest = repository_ctx.path(repository_ctx.attr.cargo_toml),
212*d4726bddSHONG Yifan        build_mode = repository_ctx.attr.build_mode,
213*d4726bddSHONG Yifan        environment = environment,
214*d4726bddSHONG Yifan        timeout = repository_ctx.attr.timeout,
215*d4726bddSHONG Yifan    )
216*d4726bddSHONG Yifan
217*d4726bddSHONG Yifan    # Create a symlink so that the binary can be accesed via it's target name
218*d4726bddSHONG Yifan    repository_ctx.symlink(built_binary, binary_name)
219*d4726bddSHONG Yifan
220*d4726bddSHONG Yifan    repository_ctx.file("BUILD.bazel", _BUILD_FILE_CONTENT.format(
221*d4726bddSHONG Yifan        binary_name = binary_name,
222*d4726bddSHONG Yifan        binary = built_binary,
223*d4726bddSHONG Yifan    ))
224*d4726bddSHONG Yifan
225*d4726bddSHONG Yifancargo_bootstrap_repository = repository_rule(
226*d4726bddSHONG Yifan    doc = "A rule for bootstrapping a Rust binary using [Cargo](https://doc.rust-lang.org/cargo/)",
227*d4726bddSHONG Yifan    implementation = _cargo_bootstrap_repository_impl,
228*d4726bddSHONG Yifan    attrs = {
229*d4726bddSHONG Yifan        "binary": attr.string(
230*d4726bddSHONG Yifan            doc = "The binary to build (the `--bin` parameter for Cargo). If left empty, the repository name will be used.",
231*d4726bddSHONG Yifan        ),
232*d4726bddSHONG Yifan        "build_mode": attr.string(
233*d4726bddSHONG Yifan            doc = "The build mode the binary should be built with",
234*d4726bddSHONG Yifan            values = [
235*d4726bddSHONG Yifan                "debug",
236*d4726bddSHONG Yifan                "release",
237*d4726bddSHONG Yifan            ],
238*d4726bddSHONG Yifan            default = "release",
239*d4726bddSHONG Yifan        ),
240*d4726bddSHONG Yifan        "cargo_lockfile": attr.label(
241*d4726bddSHONG Yifan            doc = "The lockfile of the crate_universe resolver",
242*d4726bddSHONG Yifan            allow_single_file = ["Cargo.lock"],
243*d4726bddSHONG Yifan            mandatory = True,
244*d4726bddSHONG Yifan        ),
245*d4726bddSHONG Yifan        "cargo_toml": attr.label(
246*d4726bddSHONG Yifan            doc = "The path of the crate_universe resolver manifest (`Cargo.toml` file)",
247*d4726bddSHONG Yifan            allow_single_file = ["Cargo.toml"],
248*d4726bddSHONG Yifan            mandatory = True,
249*d4726bddSHONG Yifan        ),
250*d4726bddSHONG Yifan        "env": attr.string_dict(
251*d4726bddSHONG Yifan            doc = (
252*d4726bddSHONG Yifan                "A mapping of platform triple to a set of environment variables. See " +
253*d4726bddSHONG Yifan                "[cargo_env](#cargo_env) for usage details. Additionally, the platform triple `*` applies to all platforms."
254*d4726bddSHONG Yifan            ),
255*d4726bddSHONG Yifan        ),
256*d4726bddSHONG Yifan        "env_label": attr.string_dict(
257*d4726bddSHONG Yifan            doc = (
258*d4726bddSHONG Yifan                "A mapping of platform triple to a set of environment variables. This " +
259*d4726bddSHONG Yifan                "attribute differs from `env` in that all variables passed here must be " +
260*d4726bddSHONG Yifan                "fully qualified labels of files. See [cargo_env](#cargo_env) for usage details. " +
261*d4726bddSHONG Yifan                "Additionally, the platform triple `*` applies to all platforms."
262*d4726bddSHONG Yifan            ),
263*d4726bddSHONG Yifan        ),
264*d4726bddSHONG Yifan        "rust_toolchain_cargo_template": attr.string(
265*d4726bddSHONG Yifan            doc = (
266*d4726bddSHONG Yifan                "The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " +
267*d4726bddSHONG Yifan                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
268*d4726bddSHONG Yifan                "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " +
269*d4726bddSHONG Yifan                "replaced in the string if present."
270*d4726bddSHONG Yifan            ),
271*d4726bddSHONG Yifan            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}",
272*d4726bddSHONG Yifan        ),
273*d4726bddSHONG Yifan        "rust_toolchain_rustc_template": attr.string(
274*d4726bddSHONG Yifan            doc = (
275*d4726bddSHONG Yifan                "The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " +
276*d4726bddSHONG Yifan                "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
277*d4726bddSHONG Yifan                "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " +
278*d4726bddSHONG Yifan                "replaced in the string if present."
279*d4726bddSHONG Yifan            ),
280*d4726bddSHONG Yifan            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}",
281*d4726bddSHONG Yifan        ),
282*d4726bddSHONG Yifan        "srcs": attr.label_list(
283*d4726bddSHONG Yifan            doc = "Souce files of the crate to build. Passing source files here can be used to trigger rebuilds when changes are made",
284*d4726bddSHONG Yifan            allow_files = True,
285*d4726bddSHONG Yifan        ),
286*d4726bddSHONG Yifan        "timeout": attr.int(
287*d4726bddSHONG Yifan            doc = "Maximum duration of the Cargo build command in seconds",
288*d4726bddSHONG Yifan            default = 600,
289*d4726bddSHONG Yifan        ),
290*d4726bddSHONG Yifan        "version": attr.string(
291*d4726bddSHONG Yifan            doc = "The version of Rust the currently registered toolchain is using. Eg. `1.56.0`, or `nightly/2021-09-08`",
292*d4726bddSHONG Yifan            default = rust_common.default_version,
293*d4726bddSHONG Yifan        ),
294*d4726bddSHONG Yifan    },
295*d4726bddSHONG Yifan)
296*d4726bddSHONG Yifan
297*d4726bddSHONG Yifandef cargo_env(env):
298*d4726bddSHONG Yifan    """A helper for generating platform specific environment variables
299*d4726bddSHONG Yifan
300*d4726bddSHONG Yifan    ```python
301*d4726bddSHONG Yifan    load("@rules_rust//rust:defs.bzl", "rust_common")
302*d4726bddSHONG Yifan    load("@rules_rust//cargo:defs.bzl", "cargo_bootstrap_repository", "cargo_env")
303*d4726bddSHONG Yifan
304*d4726bddSHONG Yifan    cargo_bootstrap_repository(
305*d4726bddSHONG Yifan        name = "bootstrapped_bin",
306*d4726bddSHONG Yifan        cargo_lockfile = "//:Cargo.lock",
307*d4726bddSHONG Yifan        cargo_toml = "//:Cargo.toml",
308*d4726bddSHONG Yifan        srcs = ["//:resolver_srcs"],
309*d4726bddSHONG Yifan        version = rust_common.default_version,
310*d4726bddSHONG Yifan        binary = "my-crate-binary",
311*d4726bddSHONG Yifan        env = {
312*d4726bddSHONG Yifan            "x86_64-unknown-linux-gnu": cargo_env({
313*d4726bddSHONG Yifan                "FOO": "BAR",
314*d4726bddSHONG Yifan            }),
315*d4726bddSHONG Yifan        },
316*d4726bddSHONG Yifan        env_label = {
317*d4726bddSHONG Yifan            "aarch64-unknown-linux-musl": cargo_env({
318*d4726bddSHONG Yifan                "DOC": "//:README.md",
319*d4726bddSHONG Yifan            }),
320*d4726bddSHONG Yifan        }
321*d4726bddSHONG Yifan    )
322*d4726bddSHONG Yifan    ```
323*d4726bddSHONG Yifan
324*d4726bddSHONG Yifan    Args:
325*d4726bddSHONG Yifan        env (dict): A map of environment variables
326*d4726bddSHONG Yifan
327*d4726bddSHONG Yifan    Returns:
328*d4726bddSHONG Yifan        str: A json encoded string of the environment variables
329*d4726bddSHONG Yifan    """
330*d4726bddSHONG Yifan    return json.encode(dict(env))
331