/ toolchains / rust.bzl
rust.bzl
1 # toolchains/rust.bzl 2 # 3 # Hermetic Rust toolchain and rules using Nix store paths. 4 # 5 # Paths are read from .buckconfig.local [rust] section, generated by `nix develop`. 6 # No PATH lookup. Just absolute Nix store paths. 7 # 8 # Rules: 9 # rust_toolchain - toolchain definition 10 # rust_binary - executable 11 # rust_library - rlib 12 13 # NOTE: Use upstream @prelude types for toolchain provider compatibility. 14 # Our custom rust_binary/rust_library rules below don't use the toolchain 15 # provider - they read config directly. But if anyone uses prelude rust rules, 16 # they'll need the upstream provider type. 17 load("@prelude//rust:rust_toolchain.bzl", "PanicRuntime", "RustToolchainInfo") 18 19 # Import RustCrateInfo so we can handle deps from crates_io() 20 load("@toolchains//rust_crate.bzl", "RustCrateInfo") 21 22 # Provider for rust library outputs 23 RustLibraryInfo = provider(fields = [ 24 "rlib", # The .rlib artifact 25 "crate_name", # Crate name for --extern 26 "transitive_deps", # List of all transitive rlib artifacts (for -L paths) 27 ]) 28 29 def _rust_toolchain_impl(ctx: AnalysisContext) -> list[Provider]: 30 """ 31 Rust toolchain with paths from .buckconfig.local. 32 33 Reads [rust] section for absolute Nix store paths: 34 rustc - Rust compiler 35 rustdoc - Documentation generator 36 clippy - Linter 37 cargo - Package manager (for build scripts) 38 """ 39 40 # Read tool paths from config 41 rustc = read_root_config("rust", "rustc", "rustc") 42 rustdoc = read_root_config("rust", "rustdoc", "rustdoc") 43 clippy_driver = read_root_config("rust", "clippy_driver", "clippy-driver") 44 45 # Target triple 46 target_triple = read_root_config("rust", "target_triple", "x86_64-unknown-linux-gnu") 47 48 return [ 49 DefaultInfo(), 50 RustToolchainInfo( 51 compiler = RunInfo(args = [rustc]), 52 rustdoc = RunInfo(args = [rustdoc]), 53 clippy_driver = RunInfo(args = [clippy_driver]), 54 rustc_target_triple = target_triple, 55 default_edition = ctx.attrs.default_edition, 56 panic_runtime = PanicRuntime(ctx.attrs.panic_runtime), 57 rustc_flags = ctx.attrs.rustc_flags, 58 rustc_binary_flags = ctx.attrs.rustc_binary_flags, 59 rustc_test_flags = ctx.attrs.rustc_test_flags, 60 rustdoc_flags = ctx.attrs.rustdoc_flags, 61 allow_lints = ctx.attrs.allow_lints, 62 deny_lints = ctx.attrs.deny_lints, 63 warn_lints = ctx.attrs.warn_lints, 64 report_unused_deps = ctx.attrs.report_unused_deps, 65 doctests = ctx.attrs.doctests, 66 ), 67 ] 68 69 rust_toolchain = rule( 70 impl = _rust_toolchain_impl, 71 attrs = { 72 "default_edition": attrs.string(default = "2021"), 73 "panic_runtime": attrs.string(default = "unwind"), 74 "rustc_flags": attrs.list(attrs.string(), default = []), 75 "rustc_binary_flags": attrs.list(attrs.string(), default = []), 76 "rustc_test_flags": attrs.list(attrs.string(), default = []), 77 "rustdoc_flags": attrs.list(attrs.string(), default = []), 78 "allow_lints": attrs.list(attrs.string(), default = []), 79 "deny_lints": attrs.list(attrs.string(), default = []), 80 "warn_lints": attrs.list(attrs.string(), default = []), 81 "report_unused_deps": attrs.bool(default = False), 82 "doctests": attrs.bool(default = False), 83 }, 84 is_toolchain_rule = True, 85 ) 86 87 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 88 # rust_binary 89 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 90 91 def _rust_binary_impl(ctx: AnalysisContext) -> list[Provider]: 92 """ 93 Build a Rust binary. 94 95 Simple: rustc srcs -o output 96 """ 97 rustc = read_root_config("rust", "rustc", "rustc") 98 99 out = ctx.actions.declare_output(ctx.attrs.name) 100 101 cmd = cmd_args([rustc]) 102 103 # Edition 104 cmd.add("--edition", ctx.attrs.edition) 105 106 # Optimization 107 cmd.add("-O") 108 109 # Output 110 cmd.add("-o", out.as_output()) 111 112 # Collect deps (rlibs) - handle both RustLibraryInfo and RustCrateInfo 113 for dep in ctx.attrs.deps: 114 if RustLibraryInfo in dep: 115 lib_info = dep[RustLibraryInfo] 116 cmd.add(cmd_args("--extern", cmd_args(lib_info.crate_name, "=", lib_info.rlib, delimiter = ""))) 117 # Add search path for RustLibraryInfo deps too 118 cmd.add(cmd_args(lib_info.rlib, format = "-Ldependency={}", parent = 1)) 119 # Add search paths for all transitive deps 120 for trans_rlib in lib_info.transitive_deps: 121 cmd.add(cmd_args(trans_rlib, format = "-Ldependency={}", parent = 1)) 122 elif RustCrateInfo in dep: 123 crate_info = dep[RustCrateInfo] 124 cmd.add(cmd_args("--extern", cmd_args(crate_info.crate_name, "=", crate_info.rlib, delimiter = ""))) 125 # Add search path for this dep 126 cmd.add(cmd_args(crate_info.rlib, format = "-Ldependency={}", parent = 1)) 127 # Add search paths for all transitive deps 128 for trans_rlib in crate_info.transitive_deps: 129 cmd.add(cmd_args(trans_rlib, format = "-Ldependency={}", parent = 1)) 130 131 # Binary root is the first source file 132 # Other sources are included as hidden deps so they're tracked for rebuilds 133 if ctx.attrs.srcs: 134 cmd.add(ctx.attrs.srcs[0]) 135 if len(ctx.attrs.srcs) > 1: 136 cmd.add(cmd_args(hidden = ctx.attrs.srcs[1:])) 137 138 ctx.actions.run(cmd, category = "rustc") 139 140 return [ 141 DefaultInfo(default_output = out), 142 RunInfo(args = cmd_args(out)), 143 ] 144 145 rust_binary = rule( 146 impl = _rust_binary_impl, 147 attrs = { 148 "srcs": attrs.list(attrs.source()), 149 "deps": attrs.list(attrs.dep(), default = []), 150 "edition": attrs.string(default = "2021"), 151 }, 152 ) 153 154 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 155 # rust_library 156 # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 157 158 def _rust_library_impl(ctx: AnalysisContext) -> list[Provider]: 159 """ 160 Build a Rust library (rlib or proc-macro). 161 162 The first source file is treated as the crate root (lib.rs). 163 Other sources are tracked as dependencies but not passed directly to rustc. 164 """ 165 rustc = read_root_config("rust", "rustc", "rustc") 166 167 crate_name = ctx.attrs.crate_name or ctx.attrs.name 168 169 # Determine crate type and output extension 170 if ctx.attrs.proc_macro: 171 out = ctx.actions.declare_output("lib{}.so".format(crate_name)) 172 crate_type = "proc-macro" 173 else: 174 out = ctx.actions.declare_output("lib{}.rlib".format(crate_name)) 175 crate_type = "rlib" 176 177 cmd = cmd_args([rustc]) 178 179 # Build crate 180 cmd.add("--crate-type", crate_type) 181 cmd.add("--crate-name", crate_name) 182 183 # Edition 184 cmd.add("--edition", ctx.attrs.edition) 185 186 # Optimization 187 cmd.add("-O") 188 189 # Output 190 cmd.add("-o", out.as_output()) 191 192 # Proc-macro crates need access to the proc_macro crate from sysroot 193 if ctx.attrs.proc_macro: 194 cmd.add("--extern", "proc_macro") 195 196 # Features 197 for feature in ctx.attrs.features: 198 cmd.add("--cfg", 'feature="{}"'.format(feature)) 199 200 # Collect transitive deps for propagation 201 transitive_deps = [] 202 203 # Collect deps - handle both RustLibraryInfo and RustCrateInfo 204 for dep in ctx.attrs.deps: 205 if RustLibraryInfo in dep: 206 lib_info = dep[RustLibraryInfo] 207 cmd.add(cmd_args("--extern", cmd_args(lib_info.crate_name, "=", lib_info.rlib, delimiter = ""))) 208 # Add search path for RustLibraryInfo deps too 209 cmd.add(cmd_args(lib_info.rlib, format = "-Ldependency={}", parent = 1)) 210 # Collect this dep's rlib for transitive propagation 211 transitive_deps.append(lib_info.rlib) 212 # Also add all of its transitive deps 213 for trans_rlib in lib_info.transitive_deps: 214 cmd.add(cmd_args(trans_rlib, format = "-Ldependency={}", parent = 1)) 215 transitive_deps.append(trans_rlib) 216 elif RustCrateInfo in dep: 217 crate_info = dep[RustCrateInfo] 218 cmd.add(cmd_args("--extern", cmd_args(crate_info.crate_name, "=", crate_info.rlib, delimiter = ""))) 219 # Add search path for this dep 220 cmd.add(cmd_args(crate_info.rlib, format = "-Ldependency={}", parent = 1)) 221 # Collect this dep's rlib for transitive propagation 222 transitive_deps.append(crate_info.rlib) 223 # Add search paths for all transitive deps 224 for trans_rlib in crate_info.transitive_deps: 225 cmd.add(cmd_args(trans_rlib, format = "-Ldependency={}", parent = 1)) 226 transitive_deps.append(trans_rlib) 227 228 # Crate root is the first source file 229 # Other sources are included as hidden deps so they're tracked for rebuilds 230 if ctx.attrs.srcs: 231 cmd.add(ctx.attrs.srcs[0]) 232 if len(ctx.attrs.srcs) > 1: 233 cmd.add(cmd_args(hidden = ctx.attrs.srcs[1:])) 234 235 ctx.actions.run(cmd, category = "rustc") 236 237 return [ 238 DefaultInfo(default_output = out), 239 RustLibraryInfo(rlib = out, crate_name = crate_name, transitive_deps = transitive_deps), 240 ] 241 242 rust_library = rule( 243 impl = _rust_library_impl, 244 attrs = { 245 "srcs": attrs.list(attrs.source()), 246 "deps": attrs.list(attrs.dep(), default = []), 247 "edition": attrs.string(default = "2021"), 248 "crate_name": attrs.option(attrs.string(), default = None), 249 "proc_macro": attrs.bool(default = False), 250 "features": attrs.list(attrs.string(), default = []), 251 }, 252 )