# A Rust toolchain whose compiler is a dependency rather than a name. # # The prelude's system_rust_toolchain hands RustToolchainInfo # `RunInfo(args = ["rustc"])` — a bare string. Nothing about the compiler is an # input to any action, so nothing about it reaches the action's cache digest, # and there is nothing for a remote worker to materialise. Both of this repo's # problems come from that one line: a shared cache cannot tell two compilers # apart, and remote execution has no compiler to run. # # Here the dist tarball is a dep. cmd_args carries the archive's directory into # every rustc invocation, so buck knows the action depends on it. load("@prelude//rust:rust_toolchain.bzl", "PanicRuntime", "RustToolchainInfo") def _hermetic_rust_toolchain_impl(ctx): dist = ctx.attrs.dist[DefaultInfo].default_outputs[0] # The dist tarball keeps its components in sibling directories, so the # compiler's default sysroot (rustc/) holds no libstd — it lives under # rust-std-/. The same stitching scripts/rustc does by hand, said # to buck instead. The compiler's own codegen libraries are found by rpath, # relative to the binary, so only the sysroot has to be named. # rustc goes through a wrapper because the sysroot cannot travel any other # way: rustc_flags takes plain strings, so an artifact path cannot be put # there, and putting --sysroot on the compiler unconditionally breaks # buck's own rustc_cfg action, which passes an empty one. The wrapper, the # tarball and the sysroot are all inputs, which is the point. wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0] # Cross-compiling needs a sysroot holding two triples, which no single # archive carries, so the Android configuration passes one stitched # together in toolchains//dist. Everything else takes the component the # dist tarball already has for the host it runs on. if ctx.attrs.sysroot != None: sysroot = ctx.attrs.sysroot[DefaultInfo].default_outputs[0] else: sysroot = cmd_args(dist, format = "{}/rust-std-" + ctx.attrs.rustc_target_triple) compiler = cmd_args(wrapper, dist, sysroot) rustdoc = cmd_args(dist, format = "{}/rustc/bin/rustdoc") return [ DefaultInfo(), RustToolchainInfo( allow_lints = ctx.attrs.allow_lints, clippy_driver = RunInfo(args = cmd_args(dist, format = "{}/clippy/bin/clippy-driver")), clippy_toml = None, compiler = RunInfo(args = compiler), default_edition = ctx.attrs.default_edition, panic_runtime = PanicRuntime("unwind"), deny_lints = ctx.attrs.deny_lints, doctests = ctx.attrs.doctests, nightly_features = ctx.attrs.nightly_features, report_unused_deps = ctx.attrs.report_unused_deps, rustc_binary_flags = ctx.attrs.rustc_binary_flags, rustc_flags = ctx.attrs.rustc_flags, rustc_target_triple = ctx.attrs.rustc_target_triple, rustc_test_flags = ctx.attrs.rustc_test_flags, rustdoc = RunInfo(args = rustdoc), rustdoc_flags = ctx.attrs.rustdoc_flags, warn_lints = ctx.attrs.warn_lints, ), ] hermetic_rust_toolchain = rule( impl = _hermetic_rust_toolchain_impl, attrs = { "allow_lints": attrs.list(attrs.string(), default = []), "default_edition": attrs.option(attrs.string(), default = None), "deny_lints": attrs.list(attrs.string(), default = []), "dist": attrs.dep(providers = [DefaultInfo]), "wrapper": attrs.dep(providers = [DefaultInfo]), "doctests": attrs.bool(default = False), "nightly_features": attrs.bool(default = True), "report_unused_deps": attrs.bool(default = False), "rustc_binary_flags": attrs.list(attrs.arg(), default = []), "rustc_flags": attrs.list(attrs.arg(), default = []), "rustc_target_triple": attrs.string(), "sysroot": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None), "rustc_test_flags": attrs.list(attrs.arg(), default = []), "rustdoc_flags": attrs.list(attrs.arg(), default = []), "warn_lints": attrs.list(attrs.string(), default = []), }, is_toolchain_rule = True, ) # A C toolchain whose tools are dependencies rather than names. # # The prelude's system_cxx_toolchain cannot be made hermetic: it takes its # tools as plain strings, so nothing it names becomes an input, and it # hardcodes nm/objcopy/objdump/ranlib/strip as bare PATH lookups regardless of # what is passed. This is the same construction with a fetched archive behind # every tool that this graph actually runs. # # Which archive is the caller's business: the wrapper is asked for a mode — cc, # c++, ar, ranlib or objcopy — and what it runs for that mode is between it and # the dist beside it. zcc.sh answers with zig, ndk.sh with the NDK's clang. load("@prelude//cxx:cxx_toolchain_types.bzl", "BinaryUtilitiesInfo", "CCompilerInfo", "CxxCompilerInfo", "CxxInternalTools", "CxxPlatformInfo", "CxxToolchainInfo", "DepTrackingMode", "LinkerInfo", "LinkerType", "PicBehavior", "RuntimeDependencyHandling", "ShlibInterfacesMode") load("@prelude//cxx:headers.bzl", "HeaderMode") load("@prelude//linking:link_info.bzl", "LinkOrdering", "LinkStyle") load("@prelude//linking:lto.bzl", "LtoMode") def _hermetic_cxx_toolchain_impl(ctx: AnalysisContext): dist = ctx.attrs.dist[DefaultInfo].default_outputs[0] wrapper = ctx.attrs.wrapper[DefaultInfo].default_outputs[0] # A wrapper takes the directory it was given, then whatever else it needs # to find its tools, then the mode. zcc.sh needs nothing else — zig is in # the directory. ndk.sh is handed a manifest, because what it runs is not # an input at all: see the note there. def zig(mode): if ctx.attrs.manifest != None: manifest = ctx.attrs.manifest[DefaultInfo].default_outputs[0] return RunInfo(args = cmd_args(wrapper, dist, manifest, mode)) return RunInfo(args = cmd_args(wrapper, dist, mode)) cc = zig("cc") return [ DefaultInfo(), CxxToolchainInfo( internal_tools = ctx.attrs.internal_tools[CxxInternalTools], linker_info = LinkerInfo( linker = cc, linker_flags = ["-fuse-ld=lld"] + ctx.attrs.link_flags, post_linker_flags = [], archiver = zig("ar"), archiver_type = "gnu", archiver_supports_argfiles = True, generate_linker_maps = False, lto_mode = LtoMode("none"), type = LinkerType("gnu"), link_binaries_locally = True, link_libraries_locally = True, archive_objects_locally = True, use_archiver_flags = True, static_dep_runtime_ld_flags = [], static_pic_dep_runtime_ld_flags = [], shared_dep_runtime_ld_flags = [], independent_shlib_interface_linker_flags = [], shlib_interfaces = ShlibInterfacesMode("disabled"), link_style = LinkStyle(ctx.attrs.link_style), link_weight = 1, binary_extension = "", object_file_extension = "o", shared_library_name_default_prefix = "lib", shared_library_name_format = "{}.so", shared_library_versioned_name_format = "{}.so.{}", static_library_extension = "a", force_full_hybrid_if_capable = False, is_pdb_generated = False, link_ordering = ctx.attrs.link_ordering, ), bolt_enabled = False, # zig carries ar, ranlib and objcopy and nothing else. nm, objdump # and strip stay bare names: this graph never reaches them — shared # library interfaces are disabled and nothing is stripped — and a # build that did would need an llvm dist fetched alongside zig # rather than a PATH lookup that happens to work. binary_utilities_info = BinaryUtilitiesInfo( nm = RunInfo(args = ["nm"]), objcopy = zig("objcopy"), objdump = RunInfo(args = ["objdump"]), ranlib = zig("ranlib"), strip = RunInfo(args = ["strip"]), dwp = None, bolt_msdk = None, ), cxx_compiler_info = CxxCompilerInfo( compiler = zig("c++"), preprocessor_flags = [], compiler_flags = ctx.attrs.cxx_flags, compiler_type = "clang", ), c_compiler_info = CCompilerInfo( compiler = cc, preprocessor_flags = [], compiler_flags = ctx.attrs.c_flags, compiler_type = "clang", ), as_compiler_info = CCompilerInfo( compiler = cc, compiler_type = "clang", ), asm_compiler_info = CCompilerInfo( compiler = cc, compiler_type = "clang", ), header_mode = HeaderMode("symlink_tree_only"), cpp_dep_tracking_mode = DepTrackingMode("show_headers"), pic_behavior = PicBehavior("supported"), llvm_link = None, use_dep_files = True, runtime_dependency_handling = RuntimeDependencyHandling("no_symlink"), ), # Names the target architecture, not the host's: this is what the # prelude stamps into per-platform output paths, so an Android build # and a desktop one cannot land on the same one. CxxPlatformInfo(name = ctx.attrs.platform_name), ] hermetic_cxx_toolchain = rule( impl = _hermetic_cxx_toolchain_impl, attrs = { "c_flags": attrs.list(attrs.arg(), default = []), "cxx_flags": attrs.list(attrs.arg(), default = []), "dist": attrs.dep(providers = [DefaultInfo]), "internal_tools": attrs.default_only(attrs.dep(providers = [CxxInternalTools], default = "prelude//cxx/tools:internal_tools")), "link_flags": attrs.list(attrs.arg(), default = []), "link_ordering": attrs.option(attrs.enum(LinkOrdering.values()), default = None), "link_style": attrs.string(default = "shared"), "manifest": attrs.option(attrs.dep(providers = [DefaultInfo]), default = None), "platform_name": attrs.string(default = "x86_64"), "wrapper": attrs.dep(providers = [DefaultInfo]), }, is_toolchain_rule = True, )