|
| 1 | +# Copyright (c) Meta Platforms, Inc. and affiliates. |
| 2 | +# |
| 3 | +# This source code is dual-licensed under either the MIT license found in the |
| 4 | +# LICENSE-MIT file in the root directory of this source tree or the Apache |
| 5 | +# License, Version 2.0 found in the LICENSE-APACHE file in the root directory |
| 6 | +# of this source tree. You may select, at your option, one of the |
| 7 | +# above-listed licenses. |
| 8 | + |
| 9 | +# Cargo build script runner compatible with Reindeer-generated targets. |
| 10 | +# |
| 11 | +# Use this reindeer.toml configuration to refer to this rule: |
| 12 | +# |
| 13 | +# [buck] |
| 14 | +# buckfile_imports = """ |
| 15 | +# load("@prelude//rust:cargo_buildscript.bzl", "buildscript_run") |
| 16 | +# """ |
| 17 | +# |
| 18 | +# # optional (this matches the default rule name): |
| 19 | +# buildscript_genrule = "buildscript_run" |
| 20 | +# |
| 21 | + |
| 22 | +load("@prelude//decls:common.bzl", "buck") |
| 23 | +load("@prelude//decls:toolchains_common.bzl", "toolchains_common") |
| 24 | +load("@prelude//os_lookup:defs.bzl", "Os", "OsLookup") |
| 25 | +load("@prelude//rust:rust_toolchain.bzl", "RustToolchainInfo") |
| 26 | +load("@prelude//rust:targets.bzl", "targets") |
| 27 | +load( |
| 28 | + "@prelude//rust/tools:buildscript_platform.bzl", |
| 29 | + "buildscript_platform_constraints", |
| 30 | + "transition_alias", |
| 31 | +) |
| 32 | +load("@prelude//utils:cmd_script.bzl", "cmd_script") |
| 33 | +load("@prelude//utils:selects.bzl", "selects") |
| 34 | +load("@prelude//rust:build.bzl", "dependency_args") |
| 35 | +load("@prelude//rust:build_params.bzl", "MetadataKind") |
| 36 | +load( |
| 37 | + "@prelude//rust:cargo_package.bzl", |
| 38 | + "apply_platform_attrs", |
| 39 | + "get_reindeer_platform_names", |
| 40 | + "get_reindeer_platforms", |
| 41 | +) |
| 42 | +load("@prelude//rust:context.bzl", "DepCollectionContext") |
| 43 | +load( |
| 44 | + "@prelude//rust:link_info.bzl", |
| 45 | + "DEFAULT_STATIC_LINK_STRATEGY", |
| 46 | + "RustProcMacroPlugin", |
| 47 | + "gather_explicit_sysroot_deps", |
| 48 | + "resolve_rust_deps_inner", |
| 49 | +) |
| 50 | +load("@prelude//rust:rust_toolchain.bzl", "PanicRuntime") |
| 51 | + |
| 52 | +def _make_rustc_shim(ctx: AnalysisContext, cwd: Artifact) -> cmd_args: |
| 53 | + # Build scripts expect to receive a `rustc` which "just works." However, |
| 54 | + # our rustc sometimes has no sysroot available, so we need to make a shim |
| 55 | + # which supplies the sysroot deps if necessary |
| 56 | + toolchain_info = ctx.attrs._rust_toolchain[RustToolchainInfo] |
| 57 | + explicit_sysroot_deps = toolchain_info.explicit_sysroot_deps |
| 58 | + if explicit_sysroot_deps: |
| 59 | + dep_ctx = DepCollectionContext( |
| 60 | + advanced_unstable_linking = False, |
| 61 | + include_doc_deps = False, |
| 62 | + is_proc_macro = False, |
| 63 | + explicit_sysroot_deps = explicit_sysroot_deps, |
| 64 | + panic_runtime = PanicRuntime("unwind"), # not actually used |
| 65 | + ) |
| 66 | + deps = gather_explicit_sysroot_deps(dep_ctx) |
| 67 | + deps = resolve_rust_deps_inner(ctx, deps) |
| 68 | + dep_args, _ = dependency_args( |
| 69 | + ctx = ctx, |
| 70 | + compile_ctx = None, |
| 71 | + toolchain_info = toolchain_info, |
| 72 | + deps = deps, |
| 73 | + subdir = "any", |
| 74 | + dep_link_strategy = DEFAULT_STATIC_LINK_STRATEGY, |
| 75 | + dep_metadata_kind = MetadataKind("full"), |
| 76 | + is_rustdoc_test = False, |
| 77 | + ) |
| 78 | + |
| 79 | + null_path = "nul" if ctx.attrs._exec_os_type[OsLookup].os == Os("windows") else "/dev/null" |
| 80 | + dep_args = cmd_args("--sysroot=" + null_path, dep_args, relative_to = cwd) |
| 81 | + dep_file, _ = ctx.actions.write("rustc_dep_file", dep_args, allow_args = True) |
| 82 | + sysroot_args = cmd_args("@", dep_file, delimiter = "", hidden = dep_args) |
| 83 | + else: |
| 84 | + sysroot_args = cmd_args() |
| 85 | + |
| 86 | + shim = cmd_script( |
| 87 | + ctx = ctx, |
| 88 | + name = "__rustc_shim", |
| 89 | + cmd = cmd_args(toolchain_info.compiler, sysroot_args, relative_to = cwd), |
| 90 | + language = ctx.attrs._exec_os_type[OsLookup].script, |
| 91 | + ) |
| 92 | + |
| 93 | + return cmd_args(shim, relative_to = cwd) |
| 94 | + |
| 95 | +def _cargo_buildscript_impl(ctx: AnalysisContext) -> list[Provider]: |
| 96 | + toolchain_info = ctx.attrs._rust_toolchain[RustToolchainInfo] |
| 97 | + |
| 98 | + cwd = ctx.actions.declare_output("cwd", dir = True) |
| 99 | + out_dir = ctx.actions.declare_output("OUT_DIR", dir = True) |
| 100 | + rustc_flags = ctx.actions.declare_output("rustc_flags") |
| 101 | + # *BUCKAL-ONLY* metadata environment variables for *dependent* buildscript runners to consume |
| 102 | + metadata = ctx.actions.declare_output("METADATA") |
| 103 | + |
| 104 | + if ctx.attrs.manifest_dir != None: |
| 105 | + manifest_dir = ctx.attrs.manifest_dir[DefaultInfo].default_outputs[0] |
| 106 | + else: |
| 107 | + manifest_dir = ctx.actions.symlinked_dir("manifest_dir", ctx.attrs.filegroup_for_manifest_dir) |
| 108 | + |
| 109 | + cmd = [ |
| 110 | + ctx.attrs.runner[RunInfo], |
| 111 | + cmd_args("--buildscript=", ctx.attrs.buildscript[RunInfo], delimiter = ""), |
| 112 | + cmd_args("--rustc-cfg=", ctx.attrs.rustc_cfg[DefaultInfo].default_outputs[0], delimiter = ""), |
| 113 | + cmd_args("--manifest-dir=", manifest_dir, delimiter = ""), |
| 114 | + cmd_args("--create-cwd=", cwd.as_output(), delimiter = ""), |
| 115 | + cmd_args("--outfile=", rustc_flags.as_output(), delimiter = ""), |
| 116 | + cmd_args("--outenv=", metadata.as_output(), delimiter = ""), |
| 117 | + ] |
| 118 | + |
| 119 | + # *BUCKAL-ONLY* read environment variable source files and set them for buildscript runner |
| 120 | + for env_src in ctx.attrs.env_srcs: |
| 121 | + cmd.append(cmd_args("--extra-env=", env_src[DefaultInfo].default_outputs[0], delimiter = "")) |
| 122 | + |
| 123 | + # See https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts |
| 124 | + |
| 125 | + env = {} |
| 126 | + env["CARGO"] = "/bin/false" |
| 127 | + env["CARGO_PKG_NAME"] = ctx.attrs.package_name |
| 128 | + env["CARGO_PKG_VERSION"] = ctx.attrs.version |
| 129 | + env["OUT_DIR"] = out_dir.as_output() |
| 130 | + env["RUSTC"] = _make_rustc_shim(ctx, cwd) |
| 131 | + env["RUSTC_LINKER"] = "/bin/false" |
| 132 | + env["RUST_BACKTRACE"] = "1" |
| 133 | + |
| 134 | + if toolchain_info.rustc_target_triple: |
| 135 | + env["TARGET"] = toolchain_info.rustc_target_triple |
| 136 | + else: |
| 137 | + cmd.append(cmd_args("--rustc-host-tuple=", ctx.attrs.rustc_host_tuple[DefaultInfo].default_outputs[0], delimiter = "")) |
| 138 | + |
| 139 | + # \037 == \x1f == the magic delimiter specified in the environment variable |
| 140 | + # reference above. |
| 141 | + env["CARGO_ENCODED_RUSTFLAGS"] = cmd_args(toolchain_info.rustc_flags, delimiter = "\037") |
| 142 | + |
| 143 | + host_triple = targets.exec_triple(ctx) |
| 144 | + if host_triple: |
| 145 | + env["HOST"] = host_triple |
| 146 | + |
| 147 | + for feature in ctx.attrs.features: |
| 148 | + upper_feature = feature.upper().replace("-", "_") |
| 149 | + env["CARGO_FEATURE_{}".format(upper_feature)] = "1" |
| 150 | + |
| 151 | + # Environment variables specified in the target's attributes get priority |
| 152 | + # over all the above. |
| 153 | + for k, v in ctx.attrs.env.items(): |
| 154 | + env[k] = cmd_args(v, relative_to = cwd) |
| 155 | + |
| 156 | + ctx.actions.run( |
| 157 | + cmd, |
| 158 | + env = env, |
| 159 | + category = "buildscript", |
| 160 | + ) |
| 161 | + |
| 162 | + return [DefaultInfo( |
| 163 | + default_output = None, |
| 164 | + sub_targets = { |
| 165 | + "out_dir": [DefaultInfo(default_output = out_dir)], |
| 166 | + "rustc_flags": [DefaultInfo(default_output = rustc_flags)], |
| 167 | + "metadata": [DefaultInfo(default_output = metadata)], |
| 168 | + }, |
| 169 | + )] |
| 170 | + |
| 171 | +_cargo_buildscript_rule = rule( |
| 172 | + impl = _cargo_buildscript_impl, |
| 173 | + attrs = { |
| 174 | + "buildscript": attrs.exec_dep(providers = [RunInfo]), |
| 175 | + "env": attrs.dict(key = attrs.string(), value = attrs.arg(), default = {}), |
| 176 | + # *BUCKAL-ONLY* list of environment variable source files to set for the buildscript |
| 177 | + "env_srcs": attrs.list(attrs.dep(), default = []), |
| 178 | + "features": attrs.list(attrs.string(), default = []), |
| 179 | + "filegroup_for_manifest_dir": attrs.option(attrs.dict(key = attrs.string(), value = attrs.source()), default = None), |
| 180 | + "manifest_dir": attrs.option(attrs.dep(), default = None), |
| 181 | + "package_name": attrs.string(), |
| 182 | + "runner": attrs.default_only(attrs.exec_dep(providers = [RunInfo], default = "buckal//tool:buildscript_run")), |
| 183 | + # *IMPORTANT* rustc_cfg must be a `dep` and not an `exec_dep` because |
| 184 | + # we want the `rustc --cfg` for the target platform, not the exec platform. |
| 185 | + "rustc_cfg": attrs.dep(default = "prelude//rust/tools:rustc_cfg"), |
| 186 | + "rustc_host_tuple": attrs.dep(default = "prelude//rust/tools:rustc_host_tuple"), |
| 187 | + "version": attrs.string(), |
| 188 | + "_exec_os_type": buck.exec_os_type_arg(), |
| 189 | + "_rust_toolchain": toolchains_common.rust(), |
| 190 | + }, |
| 191 | + # Always empty, but needed to prevent errors |
| 192 | + uses_plugins = [RustProcMacroPlugin], |
| 193 | +) |
| 194 | + |
| 195 | +def buildscript_run( |
| 196 | + name, |
| 197 | + buildscript_rule, |
| 198 | + package_name, |
| 199 | + version, |
| 200 | + platform = {}, |
| 201 | + # path to crate's directory in source tree, e.g. "vendor/serde-1.0.100" |
| 202 | + local_manifest_dir = None, |
| 203 | + # target or subtarget containing crate, e.g. ":serde.git[serde]" |
| 204 | + manifest_dir = None, |
| 205 | + buildscript_compatible_with = None, |
| 206 | + **kwargs): |
| 207 | + kwargs = apply_platform_attrs(platform, kwargs) |
| 208 | + |
| 209 | + if manifest_dir == None and local_manifest_dir == None: |
| 210 | + existing_filegroup_name = "{}-{}.crate".format(package_name, version) |
| 211 | + if rule_exists(existing_filegroup_name): |
| 212 | + manifest_dir = ":{}".format(existing_filegroup_name) |
| 213 | + else: |
| 214 | + local_manifest_dir = "vendor/{}-{}".format(package_name, version) |
| 215 | + |
| 216 | + filegroup_for_manifest_dir = None |
| 217 | + if local_manifest_dir != None: |
| 218 | + prefix_with_trailing_slash = "{}/".format(local_manifest_dir) |
| 219 | + filegroup_for_manifest_dir = { |
| 220 | + path.removeprefix(prefix_with_trailing_slash): path |
| 221 | + for path in glob(["{}/**".format(local_manifest_dir)]) |
| 222 | + } |
| 223 | + |
| 224 | + def platform_buildscript_build_name(plat): |
| 225 | + if name.endswith("-build-script-run"): |
| 226 | + # This is the expected case for Reindeer-generated targets, which |
| 227 | + # come in pairs build-script-run and build-script-build. |
| 228 | + return "{}-build-script-build-{}".format( |
| 229 | + name.removesuffix("-build-script-run"), |
| 230 | + plat, |
| 231 | + ) |
| 232 | + else: |
| 233 | + return "{}-{}".format(name, plat) |
| 234 | + |
| 235 | + if not rule_exists("buildscript_for_platform="): |
| 236 | + buildscript_platform_constraints( |
| 237 | + name = "buildscript_for_platform=", |
| 238 | + reindeer_platforms = get_reindeer_platform_names(), |
| 239 | + ) |
| 240 | + |
| 241 | + for plat in get_reindeer_platform_names(): |
| 242 | + transition_alias( |
| 243 | + name = platform_buildscript_build_name(plat), |
| 244 | + actual = buildscript_rule, |
| 245 | + incoming_transition = ":buildscript_for_platform=[{}]".format(plat), |
| 246 | + target_compatible_with = buildscript_compatible_with, |
| 247 | + visibility = [], |
| 248 | + ) |
| 249 | + |
| 250 | + buildscript_rule = selects.apply( |
| 251 | + get_reindeer_platforms(), |
| 252 | + lambda plat: buildscript_rule if plat == None else ":{}".format(platform_buildscript_build_name(plat)), |
| 253 | + ) |
| 254 | + |
| 255 | + _cargo_buildscript_rule( |
| 256 | + name = name, |
| 257 | + buildscript = buildscript_rule, |
| 258 | + package_name = package_name, |
| 259 | + version = version, |
| 260 | + filegroup_for_manifest_dir = filegroup_for_manifest_dir, |
| 261 | + manifest_dir = manifest_dir, |
| 262 | + **kwargs |
| 263 | + ) |
0 commit comments