| 1 | const Build = @This(); |
| 2 | |
| 3 | const builtin = @import("builtin"); |
| 4 | |
| 5 | const std = @import("std.zig"); |
| 6 | const Io = std.Io; |
| 7 | const fs = std.fs; |
| 8 | const mem = std.mem; |
| 9 | const panic = std.debug.panic; |
| 10 | const assert = std.debug.assert; |
| 11 | const log = std.log; |
| 12 | const Allocator = std.mem.Allocator; |
| 13 | const Target = std.Target; |
| 14 | const process = std.process; |
| 15 | const File = std.Io.File; |
| 16 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 17 | const ArrayList = std.ArrayList; |
| 18 | const fatal = std.process.fatal; |
| 19 | |
| 20 | pub const Cache = @import("Build/Cache.zig"); |
| 21 | pub const Step = @import("Build/Step.zig"); |
| 22 | pub const Module = @import("Build/Module.zig"); |
| 23 | pub const abi = @import("Build/abi.zig"); |
| 24 | /// The serialized output of configure phase ingested by make phase. |
| 25 | pub const Configuration = @import("Build/Configuration.zig"); |
| 26 | /// Logic that transforms `Build` into `Configuration`. |
| 27 | pub const Serialize = @import("Build/Serialize.zig"); |
| 28 | |
| 29 | /// Shared state among all Build instances. |
| 30 | graph: *Graph, |
| 31 | install_tls: Step.TopLevel, |
| 32 | uninstall_tls: Step.TopLevel, |
| 33 | allocator: Allocator, |
| 34 | default_step: *Step, |
| 35 | top_level_steps: std.array_hash_map.String(*Step.TopLevel), |
| 36 | /// Path to the directory containing build.zig. |
| 37 | root: Cache.Path, |
| 38 | debug_log_scopes: []const []const u8 = &.{}, |
| 39 | /// Number of stack frames captured when a `StackTrace` is recorded for debug purposes, |
| 40 | /// in particular at `Step` creation. |
| 41 | /// Set to 0 to disable stack collection. |
| 42 | debug_stack_frames_count: u8 = 8, |
| 43 | |
| 44 | user_input_options: PackageOptions.Map, |
| 45 | available_options_map: std.array_hash_map.String(AvailableOption) = .empty, |
| 46 | invalid_user_input: bool, |
| 47 | |
| 48 | dep_prefix: []const u8 = "", |
| 49 | |
| 50 | modules: std.array_hash_map.String(*Module), |
| 51 | |
| 52 | named_writefiles: std.array_hash_map.String(*Step.WriteFile), |
| 53 | named_lazy_paths: std.array_hash_map.String(LazyPath), |
| 54 | /// The hash of this instance's package. `""` means that this is the root package. |
| 55 | pkg_hash: []const u8, |
| 56 | /// A mapping from dependency names to package hashes. |
| 57 | available_deps: AvailableDeps, |
| 58 | |
| 59 | pub const ConfigureDependency = struct { |
| 60 | lazy_path: LazyPath, |
| 61 | mode: std.Build.Configuration.PathDep.Mode, |
| 62 | }; |
| 63 | |
| 64 | pub const ReleaseMode = enum { |
| 65 | off, |
| 66 | any, |
| 67 | fast, |
| 68 | safe, |
| 69 | small, |
| 70 | }; |
| 71 | |
| 72 | /// Shared state among all Build instances. |
| 73 | /// Settings that are here rather than in Build are not configurable per-package. |
| 74 | pub const Graph = struct { |
| 75 | io: Io, |
| 76 | /// Process lifetime. |
| 77 | arena: Allocator, |
| 78 | system_integration_options: std.array_hash_map.String(SystemLibraryMode) = .empty, |
| 79 | system_package_mode: bool = false, |
| 80 | zig_exe: []const u8, |
| 81 | environ_map: process.Environ.Map, |
| 82 | needed_lazy_dependencies: std.array_hash_map.String(void) = .empty, |
| 83 | /// Information about the native target. Computed before build() is invoked. |
| 84 | host: ResolvedTarget, |
| 85 | dependency_cache: PackageInstanceMap = .empty, |
| 86 | allow_so_scripts: ?bool = null, |
| 87 | time_report: bool = false, |
| 88 | verbose: bool = false, |
| 89 | /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also |
| 90 | /// respects the '--color' flag. |
| 91 | stderr_mode: ?Io.Terminal.Mode = null, |
| 92 | release_mode: ReleaseMode = .off, |
| 93 | |
| 94 | /// Indexes correspond to `Configuration.GeneratedFileIndex`. |
| 95 | generated_files: std.ArrayList(*Step), |
| 96 | wip_configuration: Configuration.Wip, |
| 97 | |
| 98 | cache_poison: CachePoison = .pure, |
| 99 | /// Observing this data causes cache poisoning. See `CachePoison`. |
| 100 | search_prefixes: std.ArrayList([]const u8) = .empty, |
| 101 | |
| 102 | /// Populated by calling one of: |
| 103 | /// * `dependOnFileContents` |
| 104 | /// * `dependOnFileMetadata` |
| 105 | /// * `dependOnDirectory` |
| 106 | configure_dependencies: ArrayList(ConfigureDependency) = .empty, |
| 107 | |
| 108 | /// If the cache is poisoned means that the **configure logic** had side |
| 109 | /// effects, or otherwise did something that could not be tracked by the |
| 110 | /// cache system. |
| 111 | /// |
| 112 | /// This is not to be confused with whether individual steps may have side |
| 113 | /// effects when being evaluated; it has to do with the logic inside build.zig |
| 114 | /// itself. For example, a `Run` step that prints "hello world" has side |
| 115 | /// effects *at make time* and therefore does not warrant setting this flag, |
| 116 | /// while checking for the existence of `scdoc` *at configure time* in order to |
| 117 | /// choose the default value for a configuration option does. |
| 118 | /// |
| 119 | /// Keeping the cache pure will make `zig build` faster, bypassing the |
| 120 | /// configurer process when identical configuration would be generated. |
| 121 | /// |
| 122 | /// When the cache is poisoned, the maker process will delete the build |
| 123 | /// configuration file upon ingesting it since it cannot be reused. |
| 124 | pub const CachePoison = enum { |
| 125 | pure, |
| 126 | poisoned, |
| 127 | /// Indicates the user would like to see a stack trace if the cache |
| 128 | /// would become poisoned. |
| 129 | disallowed, |
| 130 | /// Indicates the user would like to ignore the cache being poisoned |
| 131 | /// and cache anyway, opting into cache hits on stale configuration. |
| 132 | ignored, |
| 133 | }; |
| 134 | |
| 135 | pub fn addGeneratedFile(graph: *Graph, owner: *Step) Configuration.GeneratedFileIndex { |
| 136 | graph.generated_files.append(graph.arena, owner) catch @panic("OOM"); |
| 137 | return @fromBackingInt(@intCast(graph.generated_files.items.len - 1)); |
| 138 | } |
| 139 | |
| 140 | pub fn dupeString(graph: *const Graph, bytes: []const u8) []const u8 { |
| 141 | return graph.arena.dupe(u8, bytes) catch @panic("OOM"); |
| 142 | } |
| 143 | |
| 144 | pub fn dupePath(graph: *const Graph, bytes: []const u8) []const u8 { |
| 145 | return dupePathInner(graph.arena, bytes); |
| 146 | } |
| 147 | |
| 148 | fn dupePathInner(arena: Allocator, bytes: []const u8) []const u8 { |
| 149 | if (builtin.os.tag != .windows) return arena.dupe(u8, bytes) catch @panic("OOM"); |
| 150 | const the_copy = arena.dupe(u8, bytes) catch @panic("OOM"); |
| 151 | mem.replaceScalar(u8, the_copy, '/', '\\'); |
| 152 | return the_copy; |
| 153 | } |
| 154 | |
| 155 | pub fn dupeStrings(graph: *const Graph, strings: []const []const u8) []const []const u8 { |
| 156 | const array = graph.alloc([]const u8, strings.len); |
| 157 | for (array, strings) |*dest, source| dest.* = dupeString(graph, source); |
| 158 | return array; |
| 159 | } |
| 160 | |
| 161 | /// An absolute path or a path relative to the current working directory of |
| 162 | /// the build runner process. |
| 163 | /// |
| 164 | /// Use of this function indicates a dependency on the host system. |
| 165 | pub fn cwdRelativePath(graph: *Graph, sub_path: []const u8) LazyPath { |
| 166 | return @This().path(graph, .cwd, sub_path); |
| 167 | } |
| 168 | |
| 169 | /// A path whose components and contents are known at some point during |
| 170 | /// `Step` resolution, relative to the provided base directory. |
| 171 | pub fn path(graph: *Graph, base: Configuration.LazyPath.Relative.Base, sub_path: []const u8) LazyPath { |
| 172 | assert(base != .build_root); |
| 173 | return .{ .relative = .{ |
| 174 | .base = base, |
| 175 | .sub_path = @This().dupePath(graph, sub_path), |
| 176 | } }; |
| 177 | } |
| 178 | |
| 179 | /// Allocates using the global process arena, failing the build on |
| 180 | /// allocation failure. |
| 181 | pub fn alloc(graph: *const Graph, comptime T: type, n: usize) []T { |
| 182 | return graph.arena.allocAdvancedWithRetAddr(T, null, n, @returnAddress()) catch @panic("OOM"); |
| 183 | } |
| 184 | |
| 185 | /// Allocates using the global process arena, failing the build on |
| 186 | /// allocation failure. |
| 187 | pub fn create(graph: *const Graph, comptime T: type) *T { |
| 188 | return @ptrCast(graph.arena.allocBytesAligned(.of(T), @sizeOf(T), @returnAddress()) catch @panic("OOM")); |
| 189 | } |
| 190 | |
| 191 | pub fn addBytesList(graph: *Graph, bytes_list: []const []const u8) []const Configuration.Bytes { |
| 192 | const result = graph.alloc(Configuration.Bytes, bytes_list.len); |
| 193 | for (result, bytes_list) |*d, s| d.* = addBytes(graph, s); |
| 194 | return result; |
| 195 | } |
| 196 | |
| 197 | pub fn addBytes(graph: *Graph, bytes: []const u8) Configuration.Bytes { |
| 198 | const wc = &graph.wip_configuration; |
| 199 | return wc.addBytes(bytes) catch @panic("OOM"); |
| 200 | } |
| 201 | |
| 202 | pub fn addString(graph: *Graph, bytes: []const u8) Configuration.String { |
| 203 | const wc = &graph.wip_configuration; |
| 204 | return wc.addString(bytes) catch @panic("OOM"); |
| 205 | } |
| 206 | |
| 207 | /// Indicates that the **configure logic** had side effects, or otherwise |
| 208 | /// did something that could not be tracked by the cache system. |
| 209 | /// |
| 210 | /// See `CachePoison` documentation for more details. |
| 211 | /// |
| 212 | /// As an alternative to calling this function, consider these APIs instead: |
| 213 | /// * `dependOnFileContents` |
| 214 | pub fn poisonCache(graph: *Graph) void { |
| 215 | switch (graph.cache_poison) { |
| 216 | .pure => graph.cache_poison = .poisoned, |
| 217 | .poisoned => return, |
| 218 | .disallowed => @panic("cache poisoned"), |
| 219 | .ignored => log.warn("ignoring cache poisoning", .{}), |
| 220 | } |
| 221 | } |
| 222 | }; |
| 223 | |
| 224 | const AvailableDeps = []const struct { []const u8, []const u8 }; |
| 225 | |
| 226 | pub const SystemLibraryMode = enum { |
| 227 | /// User asked for the library to be disabled. |
| 228 | /// The build runner has not confirmed whether the setting is recognized yet. |
| 229 | user_disabled, |
| 230 | /// User asked for the library to be enabled. |
| 231 | /// The build runner has not confirmed whether the setting is recognized yet. |
| 232 | user_enabled, |
| 233 | /// The build runner has confirmed that this setting is recognized. |
| 234 | /// System integration with this library has been resolved to off. |
| 235 | declared_disabled, |
| 236 | /// The build runner has confirmed that this setting is recognized. |
| 237 | /// System integration with this library has been resolved to on. |
| 238 | declared_enabled, |
| 239 | }; |
| 240 | |
| 241 | const PackageInstanceMap = std.array_hash_map.Custom(PackageInstanceKey, *Dependency, struct { |
| 242 | pub fn hash(_: @This(), k: PackageInstanceKey) u32 { |
| 243 | var hasher = std.hash.Wyhash.init(0); |
| 244 | hasher.update(k.pkg_hash); |
| 245 | for (k.options.keys(), k.options.values()) |option_key, option_value| { |
| 246 | hasher.update(option_key); |
| 247 | option_value.hash(&hasher); |
| 248 | } |
| 249 | return @truncate(hasher.final()); |
| 250 | } |
| 251 | |
| 252 | pub fn eql(_: @This(), a: PackageInstanceKey, b: PackageInstanceKey, _: usize) bool { |
| 253 | if (!mem.eql(u8, a.pkg_hash, b.pkg_hash)) return false; |
| 254 | if (a.options.count() != b.options.count()) return false; |
| 255 | for ( |
| 256 | a.options.keys(), |
| 257 | b.options.keys(), |
| 258 | a.options.values(), |
| 259 | b.options.values(), |
| 260 | ) |a_key, b_key, a_val, b_val| { |
| 261 | if (!mem.eql(u8, a_key, b_key)) return false; |
| 262 | if (!a_val.eql(b_val)) return false; |
| 263 | } |
| 264 | return true; |
| 265 | } |
| 266 | }, true); |
| 267 | |
| 268 | const PackageInstanceKey = struct { |
| 269 | pkg_hash: []const u8, |
| 270 | options: *const PackageOptions.Map, |
| 271 | }; |
| 272 | |
| 273 | /// Build system implementation details. |
| 274 | pub const PackageOptions = struct { |
| 275 | pub const Map = std.array_hash_map.String(UserProvided); |
| 276 | |
| 277 | pub const UserProvided = union(enum) { |
| 278 | flag: void, |
| 279 | scalar: []const u8, |
| 280 | list: std.ArrayList([]const u8), |
| 281 | map: std.array_hash_map.String(*const UserProvided), |
| 282 | lazy_path: LazyPath, |
| 283 | lazy_path_list: std.ArrayList(LazyPath), |
| 284 | |
| 285 | fn eql(a: UserProvided, b: UserProvided) bool { |
| 286 | if (std.meta.activeTag(a) != b) return false; |
| 287 | return switch (a) { |
| 288 | .flag => true, |
| 289 | .scalar => |a_scalar| return mem.eql(u8, a_scalar, b.scalar), |
| 290 | .list => |a_list| { |
| 291 | if (a_list.items.len != b.list.items.len) return false; |
| 292 | for (a_list.items, b.list.items) |a_elem, b_elem| { |
| 293 | if (!mem.eql(u8, a_elem, b_elem)) |
| 294 | return false; |
| 295 | } |
| 296 | return true; |
| 297 | }, |
| 298 | .map => |a_map| { |
| 299 | if (a_map.count() != b.map.count()) return false; |
| 300 | for (a_map.keys(), a_map.values(), b.map.keys(), b.map.values()) |a_key, a_val, b_key, b_val| { |
| 301 | if (!mem.eql(u8, a_key, b_key)) return false; |
| 302 | if (!a_val.eql(b_val.*)) return false; |
| 303 | } |
| 304 | return true; |
| 305 | }, |
| 306 | .lazy_path => |a_lazy_path| return a_lazy_path.eql(b.lazy_path), |
| 307 | .lazy_path_list => |a_lazy_path_list| { |
| 308 | if (a_lazy_path_list.items.len != b.lazy_path_list.items.len) return false; |
| 309 | for (a_lazy_path_list.items, b.lazy_path_list.items) |a_lp, b_lp| { |
| 310 | if (!a_lp.eql(b_lp)) return false; |
| 311 | } |
| 312 | return true; |
| 313 | }, |
| 314 | }; |
| 315 | } |
| 316 | |
| 317 | fn hash(a: UserProvided, hasher: *std.hash.Wyhash) void { |
| 318 | hasher.update(&mem.toBytes(std.meta.activeTag(a))); |
| 319 | switch (a) { |
| 320 | .flag => {}, |
| 321 | .scalar => |scalar| hasher.update(scalar), |
| 322 | .list => |*list| for (list.items) |elem| hasher.update(elem), |
| 323 | .map => |*map| for (map.keys(), map.values()) |key, val| { |
| 324 | hasher.update(key); |
| 325 | val.hash(hasher); |
| 326 | }, |
| 327 | .lazy_path => |lp| lp.hash(hasher), |
| 328 | .lazy_path_list => |*list| for (list.items) |lp| lp.hash(hasher), |
| 329 | } |
| 330 | } |
| 331 | }; |
| 332 | |
| 333 | fn fromArgs(arena: Allocator, map: *PackageOptions.Map, args: anytype) void { |
| 334 | const args_info = @typeInfo(@TypeOf(args)).@"struct"; |
| 335 | inline for (args_info.field_names, args_info.field_types) |field_name, field_type| { |
| 336 | if (field_type == @TypeOf(null)) continue; |
| 337 | addPackageOptionFromArg(arena, map, field_name, field_type, @field(args, field_name)); |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | pub fn sort(map: *Map) void { |
| 342 | map.sortUnstable(@as(struct { |
| 343 | keys: []const []const u8, |
| 344 | pub fn lessThan(this: @This(), a_index: usize, b_index: usize) bool { |
| 345 | return mem.lessThan(u8, this.keys[a_index], this.keys[b_index]); |
| 346 | } |
| 347 | }, .{ .keys = map.keys() })); |
| 348 | } |
| 349 | }; |
| 350 | |
| 351 | const AvailableOption = struct { |
| 352 | type_id: Configuration.AvailableOption.Type, |
| 353 | description: []const u8, |
| 354 | /// If the `type_id` is `enum` or `enum_list` this provides the list of enum options |
| 355 | enum_options: ?[]const []const u8, |
| 356 | }; |
| 357 | |
| 358 | /// Build system implementation detail. |
| 359 | pub fn create( |
| 360 | graph: *Graph, |
| 361 | root: Cache.Path, |
| 362 | available_deps: AvailableDeps, |
| 363 | ) error{OutOfMemory}!*Build { |
| 364 | const arena = graph.arena; |
| 365 | |
| 366 | const b = try arena.create(Build); |
| 367 | b.* = .{ |
| 368 | .graph = graph, |
| 369 | .root = root, |
| 370 | .invalid_user_input = false, |
| 371 | .allocator = arena, |
| 372 | .user_input_options = .empty, |
| 373 | .top_level_steps = .{}, |
| 374 | .default_step = undefined, |
| 375 | .install_tls = .{ |
| 376 | .step = .init(.{ |
| 377 | .tag = .top_level, |
| 378 | .name = "install", |
| 379 | .owner = b, |
| 380 | }), |
| 381 | .description = "Copy build artifacts to prefix path", |
| 382 | }, |
| 383 | .uninstall_tls = .{ |
| 384 | .step = .init(.{ |
| 385 | .tag = .top_level, |
| 386 | .name = "uninstall", |
| 387 | .owner = b, |
| 388 | }), |
| 389 | .description = "Remove build artifacts from prefix path", |
| 390 | }, |
| 391 | .modules = .empty, |
| 392 | .named_writefiles = .empty, |
| 393 | .named_lazy_paths = .empty, |
| 394 | .pkg_hash = "", |
| 395 | .available_deps = available_deps, |
| 396 | }; |
| 397 | try b.top_level_steps.put(arena, b.install_tls.step.name, &b.install_tls); |
| 398 | try b.top_level_steps.put(arena, b.uninstall_tls.step.name, &b.uninstall_tls); |
| 399 | b.default_step = &b.install_tls.step; |
| 400 | return b; |
| 401 | } |
| 402 | |
| 403 | fn createChild( |
| 404 | parent: *Build, |
| 405 | dep_name: []const u8, |
| 406 | root: Cache.Path, |
| 407 | pkg_hash: []const u8, |
| 408 | pkg_deps: AvailableDeps, |
| 409 | user_input_options: PackageOptions.Map, |
| 410 | ) error{OutOfMemory}!*Build { |
| 411 | const arena = parent.graph.arena; |
| 412 | const child = try arena.create(Build); |
| 413 | child.* = .{ |
| 414 | .graph = parent.graph, |
| 415 | .root = root, |
| 416 | .allocator = arena, |
| 417 | .install_tls = .{ |
| 418 | .step = .init(.{ |
| 419 | .tag = .top_level, |
| 420 | .name = "install", |
| 421 | .owner = child, |
| 422 | }), |
| 423 | .description = "Copy build artifacts to prefix path", |
| 424 | }, |
| 425 | .uninstall_tls = .{ |
| 426 | .step = .init(.{ |
| 427 | .tag = .top_level, |
| 428 | .name = "uninstall", |
| 429 | .owner = child, |
| 430 | }), |
| 431 | .description = "Remove build artifacts from prefix path", |
| 432 | }, |
| 433 | .user_input_options = user_input_options, |
| 434 | .invalid_user_input = false, |
| 435 | .default_step = undefined, |
| 436 | .top_level_steps = .{}, |
| 437 | .debug_log_scopes = parent.debug_log_scopes, |
| 438 | .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }), |
| 439 | .modules = .empty, |
| 440 | .named_writefiles = .empty, |
| 441 | .named_lazy_paths = .empty, |
| 442 | .pkg_hash = pkg_hash, |
| 443 | .available_deps = pkg_deps, |
| 444 | }; |
| 445 | try child.top_level_steps.put(arena, child.install_tls.step.name, &child.install_tls); |
| 446 | try child.top_level_steps.put(arena, child.uninstall_tls.step.name, &child.uninstall_tls); |
| 447 | child.default_step = &child.install_tls.step; |
| 448 | return child; |
| 449 | } |
| 450 | |
| 451 | fn addPackageOptionFromArg( |
| 452 | arena: Allocator, |
| 453 | map: *PackageOptions.Map, |
| 454 | field_name: [:0]const u8, |
| 455 | comptime T: type, |
| 456 | /// If null, the value won't be added, but `T` will still be type-checked. |
| 457 | maybe_value: ?T, |
| 458 | ) void { |
| 459 | map.ensureUnusedCapacity(arena, 2) catch @panic("OOM"); |
| 460 | switch (T) { |
| 461 | Target.Query => return if (maybe_value) |v| { |
| 462 | map.putAssumeCapacity(field_name, .{ .scalar = v.zigTriple(arena) catch @panic("OOM") }); |
| 463 | map.putAssumeCapacity("cpu", .{ .scalar = v.serializeCpuAlloc(arena) catch @panic("OOM") }); |
| 464 | }, |
| 465 | ResolvedTarget => return if (maybe_value) |v| { |
| 466 | map.putAssumeCapacity(field_name, .{ .scalar = v.query.zigTriple(arena) catch @panic("OOM") }); |
| 467 | map.putAssumeCapacity("cpu", .{ .scalar = v.query.serializeCpuAlloc(arena) catch @panic("OOM") }); |
| 468 | }, |
| 469 | std.zig.BuildId => return if (maybe_value) |v| { |
| 470 | map.putAssumeCapacity(field_name, .{ |
| 471 | .scalar = std.fmt.allocPrint(arena, "{f}", .{v}) catch @panic("OOM"), |
| 472 | }); |
| 473 | }, |
| 474 | LazyPath => return if (maybe_value) |v| { |
| 475 | map.putAssumeCapacity(field_name, .{ .lazy_path = v.dupeInner(arena) }); |
| 476 | }, |
| 477 | []const LazyPath => return if (maybe_value) |v| { |
| 478 | var list: std.ArrayList(LazyPath) = .empty; |
| 479 | const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); |
| 480 | for (v, elems) |lp, *elem| elem.* = lp.dupeInner(arena); |
| 481 | map.putAssumeCapacity(field_name, .{ .lazy_path_list = list }); |
| 482 | }, |
| 483 | []const u8 => return if (maybe_value) |v| { |
| 484 | map.putAssumeCapacity(field_name, .{ .scalar = arena.dupe(u8, v) catch @panic("OOM") }); |
| 485 | }, |
| 486 | []const []const u8 => return if (maybe_value) |v| { |
| 487 | var list: std.ArrayList([]const u8) = .empty; |
| 488 | const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); |
| 489 | for (v, elems) |s, *elem| elem.* = arena.dupe(u8, s) catch @panic("OOM"); |
| 490 | map.putAssumeCapacity(field_name, .{ .list = list }); |
| 491 | }, |
| 492 | else => switch (@typeInfo(T)) { |
| 493 | .bool => return if (maybe_value) |v| { |
| 494 | map.putAssumeCapacity(field_name, .{ .scalar = if (v) "true" else "false" }); |
| 495 | }, |
| 496 | .@"enum", .enum_literal => return if (maybe_value) |v| { |
| 497 | map.putAssumeCapacity(field_name, .{ .scalar = @tagName(v) }); |
| 498 | }, |
| 499 | .comptime_int, .int => return if (maybe_value) |v| { |
| 500 | map.putAssumeCapacity(field_name, .{ |
| 501 | .scalar = std.fmt.allocPrint(arena, "{d}", .{v}) catch @panic("OOM"), |
| 502 | }); |
| 503 | }, |
| 504 | .comptime_float, .float => return if (maybe_value) |v| { |
| 505 | map.putAssumeCapacity(field_name, .{ |
| 506 | .scalar = std.fmt.allocPrint(arena, "{x}", .{v}) catch @panic("OOM"), |
| 507 | }); |
| 508 | }, |
| 509 | .pointer => |ptr_info| switch (ptr_info.size) { |
| 510 | .one => switch (@typeInfo(ptr_info.child)) { |
| 511 | .array => |array_info| return addPackageOptionFromArg( |
| 512 | arena, |
| 513 | map, |
| 514 | field_name, |
| 515 | @Pointer(.slice, .{ .@"const" = true }, array_info.child, null), |
| 516 | maybe_value orelse null, |
| 517 | ), |
| 518 | else => {}, |
| 519 | }, |
| 520 | .slice => switch (@typeInfo(ptr_info.child)) { |
| 521 | .@"enum" => return if (maybe_value) |v| { |
| 522 | var list: std.ArrayList([]const u8) = .empty; |
| 523 | const elems = list.addManyAsSlice(arena, v.len) catch @panic("OOM"); |
| 524 | for (elems, v) |*elem, tag| elem.* = @tagName(tag); |
| 525 | map.putAssumeCapacity(field_name, .{ .list = list }); |
| 526 | }, |
| 527 | else => return addPackageOptionFromArg( |
| 528 | arena, |
| 529 | map, |
| 530 | field_name, |
| 531 | @Pointer(ptr_info.size, .{ .@"const" = true }, ptr_info.child, null), |
| 532 | maybe_value orelse null, |
| 533 | ), |
| 534 | }, |
| 535 | else => {}, |
| 536 | }, |
| 537 | .null => unreachable, |
| 538 | .optional => |info| switch (@typeInfo(info.child)) { |
| 539 | .optional => {}, |
| 540 | else => return addPackageOptionFromArg(arena, map, field_name, info.child, maybe_value orelse null), |
| 541 | }, |
| 542 | else => {}, |
| 543 | }, |
| 544 | } |
| 545 | @compileError("option '" ++ field_name ++ "' has unsupported type: " ++ @typeName(T)); |
| 546 | } |
| 547 | |
| 548 | /// Create a set of key-value pairs that can be converted into a Zig source |
| 549 | /// file and then inserted into a Zig compilation's module table for importing. |
| 550 | /// |
| 551 | /// This provides a way to expose build.zig values to Zig source code with |
| 552 | /// `@import`. Related: `Module.addOptions`. |
| 553 | pub fn addOptions(b: *Build) *Step.Options { |
| 554 | return Step.Options.create(b); |
| 555 | } |
| 556 | |
| 557 | pub const ExecutableOptions = struct { |
| 558 | name: []const u8, |
| 559 | root_module: *Module, |
| 560 | version: ?std.SemanticVersion = null, |
| 561 | linkage: ?std.builtin.LinkMode = null, |
| 562 | max_rss: u64 = 0, |
| 563 | use_llvm: ?bool = null, |
| 564 | use_lld: ?bool = null, |
| 565 | zig_lib_dir: ?LazyPath = null, |
| 566 | /// Deprecated. This functionality will be moved to an external package: |
| 567 | /// https://codeberg.org/ziglang/rc |
| 568 | /// |
| 569 | /// Embed a `.manifest` file in the compilation if the object format supports it. |
| 570 | /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference |
| 571 | /// Manifest files must have the extension `.manifest`. |
| 572 | /// Can be set regardless of target. The `.manifest` file will be ignored |
| 573 | /// if the target object format does not support embedded manifests. |
| 574 | win32_manifest: ?LazyPath = null, |
| 575 | }; |
| 576 | |
| 577 | pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile { |
| 578 | return .create(b, .{ |
| 579 | .name = options.name, |
| 580 | .root_module = options.root_module, |
| 581 | .version = options.version, |
| 582 | .kind = .exe, |
| 583 | .linkage = options.linkage, |
| 584 | .max_rss = options.max_rss, |
| 585 | .use_llvm = options.use_llvm, |
| 586 | .use_lld = options.use_lld, |
| 587 | .zig_lib_dir = options.zig_lib_dir, |
| 588 | .win32_manifest = options.win32_manifest, |
| 589 | }); |
| 590 | } |
| 591 | |
| 592 | pub const ObjectOptions = struct { |
| 593 | name: []const u8, |
| 594 | root_module: *Module, |
| 595 | max_rss: u64 = 0, |
| 596 | use_llvm: ?bool = null, |
| 597 | use_lld: ?bool = null, |
| 598 | zig_lib_dir: ?LazyPath = null, |
| 599 | }; |
| 600 | |
| 601 | pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile { |
| 602 | return .create(b, .{ |
| 603 | .name = options.name, |
| 604 | .root_module = options.root_module, |
| 605 | .kind = .obj, |
| 606 | .max_rss = options.max_rss, |
| 607 | .use_llvm = options.use_llvm, |
| 608 | .use_lld = options.use_lld, |
| 609 | .zig_lib_dir = options.zig_lib_dir, |
| 610 | }); |
| 611 | } |
| 612 | |
| 613 | pub const LibraryOptions = struct { |
| 614 | linkage: std.builtin.LinkMode = .static, |
| 615 | name: []const u8, |
| 616 | root_module: *Module, |
| 617 | version: ?std.SemanticVersion = null, |
| 618 | max_rss: u64 = 0, |
| 619 | use_llvm: ?bool = null, |
| 620 | use_lld: ?bool = null, |
| 621 | zig_lib_dir: ?LazyPath = null, |
| 622 | /// Deprecated. This functionality will be moved to an external package: |
| 623 | /// https://codeberg.org/ziglang/rc |
| 624 | /// |
| 625 | /// Embed a `.manifest` file in the compilation if the object format supports it. |
| 626 | /// https://learn.microsoft.com/en-us/windows/win32/sbscs/manifest-files-reference |
| 627 | /// Manifest files must have the extension `.manifest`. |
| 628 | /// Can be set regardless of target. The `.manifest` file will be ignored |
| 629 | /// if the target object format does not support embedded manifests. |
| 630 | win32_manifest: ?LazyPath = null, |
| 631 | /// Win32 module definition file (.def). |
| 632 | win32_module_definition: ?LazyPath = null, |
| 633 | }; |
| 634 | |
| 635 | pub fn addLibrary(b: *Build, options: LibraryOptions) *Step.Compile { |
| 636 | return .create(b, .{ |
| 637 | .name = options.name, |
| 638 | .root_module = options.root_module, |
| 639 | .kind = .lib, |
| 640 | .linkage = options.linkage, |
| 641 | .version = options.version, |
| 642 | .max_rss = options.max_rss, |
| 643 | .use_llvm = options.use_llvm, |
| 644 | .use_lld = options.use_lld, |
| 645 | .zig_lib_dir = options.zig_lib_dir, |
| 646 | .win32_manifest = options.win32_manifest, |
| 647 | .win32_module_definition = options.win32_module_definition, |
| 648 | }); |
| 649 | } |
| 650 | |
| 651 | pub const TestOptions = struct { |
| 652 | name: []const u8 = "test", |
| 653 | root_module: *Module, |
| 654 | max_rss: u64 = 0, |
| 655 | filters: []const []const u8 = &.{}, |
| 656 | test_runner: ?Step.Compile.TestRunner = null, |
| 657 | use_llvm: ?bool = null, |
| 658 | use_lld: ?bool = null, |
| 659 | zig_lib_dir: ?LazyPath = null, |
| 660 | /// Emits an object file instead of a test binary. |
| 661 | /// The object must be linked separately. |
| 662 | /// Usually used in conjunction with a custom `test_runner`. |
| 663 | emit_object: bool = false, |
| 664 | }; |
| 665 | |
| 666 | /// Creates an executable containing unit tests. |
| 667 | /// |
| 668 | /// Equivalent to running the command `zig test --test-no-exec ...`. |
| 669 | /// |
| 670 | /// **This step does not run the unit tests**. Typically, the result of this |
| 671 | /// function will be passed to `addRunArtifact`, creating a `Step.Run`. These |
| 672 | /// two steps are separated because they are independently configured and |
| 673 | /// cached. |
| 674 | pub fn addTest(b: *Build, options: TestOptions) *Step.Compile { |
| 675 | return .create(b, .{ |
| 676 | .name = options.name, |
| 677 | .kind = if (options.emit_object) .test_obj else .@"test", |
| 678 | .root_module = options.root_module, |
| 679 | .max_rss = options.max_rss, |
| 680 | .filters = b.graph.dupeStrings(options.filters), |
| 681 | .test_runner = options.test_runner, |
| 682 | .use_llvm = options.use_llvm, |
| 683 | .use_lld = options.use_lld, |
| 684 | .zig_lib_dir = options.zig_lib_dir, |
| 685 | }); |
| 686 | } |
| 687 | |
| 688 | pub const AssemblyOptions = struct { |
| 689 | name: []const u8, |
| 690 | source_file: LazyPath, |
| 691 | /// To choose the same computer as the one building the package, pass the |
| 692 | /// `host` field of the package's `Build` instance. |
| 693 | target: ResolvedTarget, |
| 694 | optimize: std.builtin.Optimize, |
| 695 | max_rss: u64 = 0, |
| 696 | zig_lib_dir: ?LazyPath = null, |
| 697 | }; |
| 698 | |
| 699 | /// This function creates a module and adds it to the package's module set, making |
| 700 | /// it available to other packages which depend on this one. |
| 701 | /// `createModule` can be used instead to create a private module. |
| 702 | pub fn addModule(b: *Build, name: []const u8, options: Module.CreateOptions) *Module { |
| 703 | const graph = b.graph; |
| 704 | const arena = graph.arena; |
| 705 | const module = Module.create(b, options); |
| 706 | const gop = b.modules.getOrPutValue( |
| 707 | arena, |
| 708 | graph.dupeString(name), |
| 709 | module, |
| 710 | ) catch @panic("OOM"); |
| 711 | if (gop.found_existing) { |
| 712 | panic("A module with the name {q} has already been added to the package. Consider creating a private module with std.Build.createModule", .{name}); |
| 713 | } |
| 714 | return module; |
| 715 | } |
| 716 | |
| 717 | /// This function creates a private module, to be used by the current package, |
| 718 | /// but not exposed to other packages depending on this one. |
| 719 | /// `addModule` can be used instead to create a public module. |
| 720 | pub fn createModule(b: *Build, options: Module.CreateOptions) *Module { |
| 721 | return Module.create(b, options); |
| 722 | } |
| 723 | |
| 724 | /// Creates a step that executes a process on the host system. |
| 725 | /// |
| 726 | /// `argv` is one or more command line arguments passed to the executed |
| 727 | /// process. The first element is the name of the executable to run. More |
| 728 | /// command line arguments can be added with methods of `Step.Run`, such as: |
| 729 | /// * `Step.Run.addArgs` |
| 730 | /// * `Step.Run.addArtifactArg` |
| 731 | /// * `Step.Run.addFileArg` |
| 732 | /// * `Step.Run.addOutputFileArg` |
| 733 | /// |
| 734 | /// This function introduces a system dependency, compromising reproducibility |
| 735 | /// and making it more difficult to set up one's computer in order to build the |
| 736 | /// project from source. |
| 737 | /// |
| 738 | /// See also: |
| 739 | /// * `addRunArtifact` |
| 740 | /// * `addRunFile` |
| 741 | pub fn addSystemCommand(b: *Build, argv: []const []const u8) *Step.Run { |
| 742 | assert(argv.len >= 1); |
| 743 | const run_step = Step.Run.create(b, b.fmt("run {s}", .{argv[0]})); |
| 744 | run_step.addArgs(argv); |
| 745 | return run_step; |
| 746 | } |
| 747 | |
| 748 | /// Creates a `Step.Run` with an executable built with `addExecutable`. |
| 749 | /// Add command line arguments with methods of `Step.Run`. |
| 750 | /// |
| 751 | /// It doesn't have to target the host. In some cases cross-compiled binaries |
| 752 | /// can even be executed. |
| 753 | /// |
| 754 | /// This is declarative; it constructs a build step that may or may not be run |
| 755 | /// depending on the options provided by the user to the build command. |
| 756 | /// |
| 757 | /// See also: |
| 758 | /// * `addSystemCommand` |
| 759 | /// * `addRunFile` |
| 760 | pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run { |
| 761 | // Avoid the common case of the step name looking like "run test test". |
| 762 | const step_name = if (exe.kind.isTest() and mem.eql(u8, exe.name, "test")) |
| 763 | b.fmt("run {t}", .{exe.kind}) |
| 764 | else |
| 765 | b.fmt("run {t} {s}", .{ exe.kind, exe.name }); |
| 766 | |
| 767 | const run_step = Step.Run.create(b, step_name); |
| 768 | run_step.producer = exe; |
| 769 | if (exe.kind == .@"test") { |
| 770 | run_step.addArtifactArg(exe); |
| 771 | |
| 772 | const test_server_mode: bool = s: { |
| 773 | if (exe.test_runner) |r| break :s r.mode == .server; |
| 774 | if (exe.use_llvm == false) { |
| 775 | // The default test runner does not use the server protocol if the selected backend |
| 776 | // is too immature to support it. Keep this logic in sync with `need_simple` in the |
| 777 | // default test runner implementation. |
| 778 | switch (exe.rootModuleTarget().cpu.arch) { |
| 779 | // stage2_aarch64 |
| 780 | .aarch64, |
| 781 | .aarch64_be, |
| 782 | // stage2_powerpc |
| 783 | .powerpc, |
| 784 | .powerpcle, |
| 785 | .powerpc64, |
| 786 | .powerpc64le, |
| 787 | // stage2_riscv64 |
| 788 | .riscv64, |
| 789 | => break :s false, |
| 790 | |
| 791 | else => {}, |
| 792 | } |
| 793 | } |
| 794 | break :s true; |
| 795 | }; |
| 796 | if (test_server_mode) { |
| 797 | run_step.enableTestRunnerMode(); |
| 798 | } else if (exe.test_runner == null) { |
| 799 | // If a test runner does not use the `std.zig.Server` protocol, it can instead |
| 800 | // communicate failure via its exit code. |
| 801 | run_step.expectExitCode(0); |
| 802 | } |
| 803 | } else { |
| 804 | run_step.addArtifactArg(exe); |
| 805 | } |
| 806 | |
| 807 | return run_step; |
| 808 | } |
| 809 | |
| 810 | /// Creates a step that executes the provided file. |
| 811 | /// |
| 812 | /// Add more command line arguments via methods of `Step.Run`. |
| 813 | /// |
| 814 | /// See also: |
| 815 | /// * `addSystemCommand` |
| 816 | /// * `addRunArtifact` |
| 817 | pub fn addRunFile(b: *Build, executable: LazyPath) *Step.Run { |
| 818 | const run_step = Step.Run.create(b, b.fmt("run {f}", .{executable})); |
| 819 | run_step.addFileArg(executable); |
| 820 | return run_step; |
| 821 | } |
| 822 | |
| 823 | /// Using the `values` provided, produces a C header file, possibly based on a |
| 824 | /// template input file (e.g. config.h.in). |
| 825 | /// When an input template file is provided, this function will fail the build |
| 826 | /// when an option not found in the input file is provided in `values`, and |
| 827 | /// when an option found in the input file is missing from `values`. |
| 828 | pub fn addConfigHeader( |
| 829 | b: *Build, |
| 830 | options: Step.ConfigHeader.Options, |
| 831 | values: anytype, |
| 832 | ) *Step.ConfigHeader { |
| 833 | var options_copy = options; |
| 834 | if (options_copy.first_ret_addr == null) |
| 835 | options_copy.first_ret_addr = @returnAddress(); |
| 836 | |
| 837 | const config_header_step = Step.ConfigHeader.create(b, options_copy); |
| 838 | config_header_step.addValues(values); |
| 839 | return config_header_step; |
| 840 | } |
| 841 | |
| 842 | /// Deprecated, call `Graph.dupeString` instead. |
| 843 | pub fn dupe(b: *Build, bytes: []const u8) []const u8 { |
| 844 | return b.graph.dupeString(bytes); |
| 845 | } |
| 846 | |
| 847 | /// Deprecated, call `Graph.dupeStrings` instead. |
| 848 | pub fn dupeStrings(b: *Build, strings: []const []const u8) []const []const u8 { |
| 849 | return b.graph.dupeStrings(strings); |
| 850 | } |
| 851 | |
| 852 | /// Deprecated, call `Graph.dupePath` instead. |
| 853 | pub fn dupePath(b: *Build, bytes: []const u8) []const u8 { |
| 854 | return b.graph.dupePath(bytes); |
| 855 | } |
| 856 | |
| 857 | pub fn addWriteFile(b: *Build, file_path: []const u8, data: []const u8) *Step.WriteFile { |
| 858 | const write_file_step = b.addWriteFiles(); |
| 859 | _ = write_file_step.add(file_path, data); |
| 860 | return write_file_step; |
| 861 | } |
| 862 | |
| 863 | pub fn addNamedWriteFiles(b: *Build, name: []const u8) *Step.WriteFile { |
| 864 | const graph = b.graph; |
| 865 | const wf = Step.WriteFile.create(b); |
| 866 | const gop = b.named_writefiles.getOrPutValue( |
| 867 | graph.arena, |
| 868 | graph.dupeString(name), |
| 869 | wf, |
| 870 | ) catch @panic("OOM"); |
| 871 | if (gop.found_existing) { |
| 872 | panic( |
| 873 | "A WriteFile step with the name {q} has already been added to the package. Consider creating a private WriteFile step with std.Build.addWriteFiles", |
| 874 | .{name}, |
| 875 | ); |
| 876 | } |
| 877 | return wf; |
| 878 | } |
| 879 | |
| 880 | pub fn addNamedLazyPath(b: *Build, name: []const u8, lp: LazyPath) void { |
| 881 | const graph = b.graph; |
| 882 | const gop = b.named_lazy_paths.getOrPutValue( |
| 883 | graph.arena, |
| 884 | graph.dupeString(name), |
| 885 | lp.dupe(graph), |
| 886 | ) catch @panic("OOM"); |
| 887 | if (gop.found_existing) { |
| 888 | panic("A LazyPath with the name {q} has already been added to the package.", .{name}); |
| 889 | } |
| 890 | } |
| 891 | |
| 892 | /// Creates a step for mutating files inside a temporary directory created lazily |
| 893 | /// and automatically cleaned up upon successful build. |
| 894 | /// |
| 895 | /// The directory will be placed inside "tmp" rather than "o", and caching will |
| 896 | /// be skipped. During the `make` phase, the step will always do all the file |
| 897 | /// system operations, and on successful build completion, the dir will be |
| 898 | /// deleted along with all other tmp directories. The directory is therefore |
| 899 | /// eligible to be used for mutations by other steps. |
| 900 | /// |
| 901 | /// See also: |
| 902 | /// * `addWriteFiles` |
| 903 | /// * `addMutateFiles` |
| 904 | pub fn addTempFiles(b: *Build) *Step.WriteFile { |
| 905 | const wf = addWriteFiles(b); |
| 906 | wf.mode = .tmp; |
| 907 | return wf; |
| 908 | } |
| 909 | |
| 910 | /// Creates a step for mutating temporary directories created with `addTempFiles`. |
| 911 | /// |
| 912 | /// Consider instead `addWriteFiles` which is for creating a cached directory |
| 913 | /// of files to operate on. |
| 914 | /// |
| 915 | /// This should only be used with a `tmp_path` obtained via `addTempFiles` or |
| 916 | /// `tmpPath`. |
| 917 | pub fn addMutateFiles(b: *Build, tmp_path: LazyPath) *Step.WriteFile { |
| 918 | const wf = addWriteFiles(b); |
| 919 | wf.mode = .{ .mutate = tmp_path }; |
| 920 | tmp_path.addStepDependencies(&wf.step); |
| 921 | return wf; |
| 922 | } |
| 923 | |
| 924 | pub fn addWriteFiles(b: *Build) *Step.WriteFile { |
| 925 | return Step.WriteFile.create(b); |
| 926 | } |
| 927 | |
| 928 | /// Creates a step for writing data to paths relative to the build root, |
| 929 | /// mutating the project's source files. |
| 930 | /// |
| 931 | /// This build step was designed not to be used during the normal build |
| 932 | /// process, but rather as a utility run by a developer with intention to |
| 933 | /// update source files, which will then be committed to version control. |
| 934 | /// |
| 935 | /// Example use cases: |
| 936 | /// * precompiling assets which are tracked by version control |
| 937 | /// * snapshot testing |
| 938 | pub fn addUpdateSourceFiles(b: *Build) *Step.UpdateSourceFiles { |
| 939 | return Step.UpdateSourceFiles.create(b); |
| 940 | } |
| 941 | |
| 942 | pub fn addFail(b: *Build, error_msg: []const u8) *Step.Fail { |
| 943 | return Step.Fail.create(b, error_msg); |
| 944 | } |
| 945 | |
| 946 | pub fn addFmt(b: *Build, options: Step.Fmt.Options) *Step.Fmt { |
| 947 | return Step.Fmt.create(b, options); |
| 948 | } |
| 949 | |
| 950 | pub fn addTranslateC(b: *Build, options: Step.TranslateC.Options) *Step.TranslateC { |
| 951 | return Step.TranslateC.create(b, options); |
| 952 | } |
| 953 | |
| 954 | pub fn getInstallStep(b: *Build) *Step { |
| 955 | return &b.install_tls.step; |
| 956 | } |
| 957 | |
| 958 | pub fn getUninstallStep(b: *Build) *Step { |
| 959 | return &b.uninstall_tls.step; |
| 960 | } |
| 961 | |
| 962 | /// Creates a configuration option to be passed to the build.zig script. |
| 963 | /// When a user directly runs `zig build`, they can set these options with `-D` arguments. |
| 964 | /// When a project depends on a Zig package as a dependency, it programmatically sets |
| 965 | /// these options when calling the dependency's build.zig script as a function. |
| 966 | /// `null` is returned when an option is left to default. |
| 967 | pub fn option(b: *Build, comptime T: type, name_raw: []const u8, description_raw: []const u8) ?T { |
| 968 | const graph = b.graph; |
| 969 | const arena = graph.arena; |
| 970 | const name = graph.dupeString(name_raw); |
| 971 | const description = graph.dupeString(description_raw); |
| 972 | const type_id = comptime typeToEnum(T); |
| 973 | const available_option: AvailableOption = .{ |
| 974 | .type_id = type_id, |
| 975 | .description = description, |
| 976 | .enum_options = if (type_id == .@"enum" or type_id == .enum_list) blk: { |
| 977 | const E = if (type_id == .enum_list) @typeInfo(T).pointer.child else T; |
| 978 | break :blk @typeInfo(E).@"enum".field_names; |
| 979 | } else null, |
| 980 | }; |
| 981 | if ((b.available_options_map.fetchPut(arena, name, available_option) catch @panic("OOM")) != null) { |
| 982 | panic("option {q} declared twice", .{name}); |
| 983 | } |
| 984 | const user_provided = b.user_input_options.get(name) orelse return null; |
| 985 | switch (type_id) { |
| 986 | .bool => switch (user_provided) { |
| 987 | .flag => return true, |
| 988 | .scalar => |s| { |
| 989 | if (mem.eql(u8, s, "true")) { |
| 990 | return true; |
| 991 | } else if (mem.eql(u8, s, "false")) { |
| 992 | return false; |
| 993 | } else { |
| 994 | log.err("expected -D{s} to be a boolean; received: {s}", .{ name, s }); |
| 995 | b.markInvalidUserInput(); |
| 996 | return null; |
| 997 | } |
| 998 | }, |
| 999 | .list, .map, .lazy_path, .lazy_path_list => { |
| 1000 | log.err("expected -D{s} to be a boolean; received: {t}", .{ name, user_provided }); |
| 1001 | b.markInvalidUserInput(); |
| 1002 | return null; |
| 1003 | }, |
| 1004 | }, |
| 1005 | .int => switch (user_provided) { |
| 1006 | .flag, .list, .map, .lazy_path, .lazy_path_list => { |
| 1007 | log.err("expected -D{s} to be an integer; received: {t}", .{ name, user_provided }); |
| 1008 | b.markInvalidUserInput(); |
| 1009 | return null; |
| 1010 | }, |
| 1011 | .scalar => |s| { |
| 1012 | const n = std.fmt.parseInt(T, s, 10) catch |err| switch (err) { |
| 1013 | error.Overflow => { |
| 1014 | log.err("-D{s} value {s} cannot fit into type {s}", .{ name, s, @typeName(T) }); |
| 1015 | b.markInvalidUserInput(); |
| 1016 | return null; |
| 1017 | }, |
| 1018 | else => { |
| 1019 | log.err("expected -D{s} to be an integer of type {s}", .{ name, @typeName(T) }); |
| 1020 | b.markInvalidUserInput(); |
| 1021 | return null; |
| 1022 | }, |
| 1023 | }; |
| 1024 | return n; |
| 1025 | }, |
| 1026 | }, |
| 1027 | .float => switch (user_provided) { |
| 1028 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1029 | log.err("expected -D{s} to be a float; received: {t}", .{ name, user_provided }); |
| 1030 | b.markInvalidUserInput(); |
| 1031 | return null; |
| 1032 | }, |
| 1033 | .scalar => |s| { |
| 1034 | const n = std.fmt.parseFloat(T, s) catch { |
| 1035 | log.err("expected -D{s} to be a float of type {s}", .{ name, @typeName(T) }); |
| 1036 | b.markInvalidUserInput(); |
| 1037 | return null; |
| 1038 | }; |
| 1039 | return n; |
| 1040 | }, |
| 1041 | }, |
| 1042 | .@"enum" => switch (user_provided) { |
| 1043 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1044 | log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided }); |
| 1045 | b.markInvalidUserInput(); |
| 1046 | return null; |
| 1047 | }, |
| 1048 | .scalar => |s| { |
| 1049 | if (T == std.lang.Optimize) { |
| 1050 | if (std.lang.Optimize.fromString(s)) |tag| { |
| 1051 | return tag; |
| 1052 | } |
| 1053 | } else if (std.meta.stringToEnum(T, s)) |tag| { |
| 1054 | return tag; |
| 1055 | } |
| 1056 | log.err("expected -D{s} to be of type {q}", .{ name, @typeName(T) }); |
| 1057 | b.markInvalidUserInput(); |
| 1058 | return null; |
| 1059 | }, |
| 1060 | }, |
| 1061 | .string => switch (user_provided) { |
| 1062 | .flag, .list, .map, .lazy_path, .lazy_path_list => { |
| 1063 | log.err("expected -D{s} to be a string; received: {t}", .{ name, user_provided }); |
| 1064 | b.markInvalidUserInput(); |
| 1065 | return null; |
| 1066 | }, |
| 1067 | .scalar => |s| return s, |
| 1068 | }, |
| 1069 | .build_id => switch (user_provided) { |
| 1070 | .flag, .map, .list, .lazy_path, .lazy_path_list => { |
| 1071 | log.err("expected -D{s} to be an enum; received: {t}.", .{ name, user_provided }); |
| 1072 | b.markInvalidUserInput(); |
| 1073 | return null; |
| 1074 | }, |
| 1075 | .scalar => |s| { |
| 1076 | if (std.zig.BuildId.parse(s)) |build_id| { |
| 1077 | return build_id; |
| 1078 | } else |err| { |
| 1079 | log.err("failed to parse option -D{s}: {t}", .{ name, err }); |
| 1080 | b.markInvalidUserInput(); |
| 1081 | return null; |
| 1082 | } |
| 1083 | }, |
| 1084 | }, |
| 1085 | .list => switch (user_provided) { |
| 1086 | .flag, .map, .lazy_path, .lazy_path_list => { |
| 1087 | log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided }); |
| 1088 | b.markInvalidUserInput(); |
| 1089 | return null; |
| 1090 | }, |
| 1091 | .scalar => |s| { |
| 1092 | return arena.dupe([]const u8, &[_][]const u8{s}) catch @panic("OOM"); |
| 1093 | }, |
| 1094 | .list => |lst| return lst.items, |
| 1095 | }, |
| 1096 | .enum_list => switch (user_provided) { |
| 1097 | .flag, .map, .lazy_path, .lazy_path_list => { |
| 1098 | log.err("expected -D{s} to be a list; received: {t}", .{ name, user_provided }); |
| 1099 | b.markInvalidUserInput(); |
| 1100 | return null; |
| 1101 | }, |
| 1102 | .scalar => |s| { |
| 1103 | const Child = @typeInfo(T).pointer.child; |
| 1104 | if (Child == std.lang.Optimize) { |
| 1105 | if (std.lang.Optimize.fromString(s)) |tag| { |
| 1106 | return arena.dupe(Child, &.{tag}) catch @panic("OOM"); |
| 1107 | } |
| 1108 | } else { |
| 1109 | if (std.meta.stringToEnum(Child, s)) |tag| { |
| 1110 | return arena.dupe(Child, &.{tag}) catch @panic("OOM"); |
| 1111 | } |
| 1112 | } |
| 1113 | log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) }); |
| 1114 | b.markInvalidUserInput(); |
| 1115 | return null; |
| 1116 | }, |
| 1117 | .list => |lst| { |
| 1118 | const Child = @typeInfo(T).pointer.child; |
| 1119 | const new_list = graph.alloc(Child, lst.items.len); |
| 1120 | for (new_list, lst.items) |*new_item, str| { |
| 1121 | if (Child == std.lang.Optimize) { |
| 1122 | if (std.lang.Optimize.fromString(str)) |tag| { |
| 1123 | new_item.* = tag; |
| 1124 | continue; |
| 1125 | } |
| 1126 | } |
| 1127 | if (std.meta.stringToEnum(Child, str)) |tag| { |
| 1128 | new_item.* = tag; |
| 1129 | continue; |
| 1130 | } |
| 1131 | log.err("expected -D{s} to be of type {q}", .{ name, @typeName(Child) }); |
| 1132 | b.markInvalidUserInput(); |
| 1133 | return null; |
| 1134 | } |
| 1135 | return new_list; |
| 1136 | }, |
| 1137 | }, |
| 1138 | .lazy_path => switch (user_provided) { |
| 1139 | .scalar => |s| return .{ .cwd_relative = s }, |
| 1140 | .lazy_path => |lp| return lp, |
| 1141 | .flag, .map, .list, .lazy_path_list => { |
| 1142 | log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided }); |
| 1143 | b.markInvalidUserInput(); |
| 1144 | return null; |
| 1145 | }, |
| 1146 | }, |
| 1147 | .lazy_path_list => switch (user_provided) { |
| 1148 | .scalar => |s| return arena.dupe(LazyPath, &[_]LazyPath{.{ .cwd_relative = s }}) catch @panic("OOM"), |
| 1149 | .lazy_path => |lp| return arena.dupe(LazyPath, &[_]LazyPath{lp}) catch @panic("OOM"), |
| 1150 | .list => |lst| { |
| 1151 | const new_list = graph.alloc(LazyPath, lst.items.len); |
| 1152 | for (new_list, lst.items) |*new_item, str| { |
| 1153 | new_item.* = .{ .cwd_relative = str }; |
| 1154 | } |
| 1155 | return new_list; |
| 1156 | }, |
| 1157 | .lazy_path_list => |lp_list| return lp_list.items, |
| 1158 | .flag, .map => { |
| 1159 | log.err("expected -D{s} to be a path; received: {t}", .{ name, user_provided }); |
| 1160 | b.markInvalidUserInput(); |
| 1161 | return null; |
| 1162 | }, |
| 1163 | }, |
| 1164 | } |
| 1165 | } |
| 1166 | |
| 1167 | /// Creates a top-level build step, exposed to the CLI user and advertised in |
| 1168 | /// the "--help" menu. |
| 1169 | pub fn step(b: *Build, name: []const u8, description: []const u8) *Step { |
| 1170 | const graph = b.graph; |
| 1171 | const arena = graph.arena; |
| 1172 | const step_info = arena.create(Step.TopLevel) catch @panic("OOM"); |
| 1173 | step_info.* = .{ |
| 1174 | .step = .init(.{ |
| 1175 | .tag = .top_level, |
| 1176 | .name = name, |
| 1177 | .owner = b, |
| 1178 | }), |
| 1179 | .description = graph.dupeString(description), |
| 1180 | }; |
| 1181 | const gop = b.top_level_steps.getOrPut(arena, name) catch @panic("OOM"); |
| 1182 | if (gop.found_existing) panic("A top-level step with name \"{s}\" already exists", .{name}); |
| 1183 | |
| 1184 | gop.key_ptr.* = step_info.step.name; |
| 1185 | gop.value_ptr.* = step_info; |
| 1186 | |
| 1187 | return &step_info.step; |
| 1188 | } |
| 1189 | |
| 1190 | pub const StandardOptimizeOptionOptions = struct { |
| 1191 | preferred_optimize_mode: ?std.builtin.Optimize = null, |
| 1192 | }; |
| 1193 | |
| 1194 | pub fn standardOptimizeOption(b: *Build, options: StandardOptimizeOptionOptions) std.builtin.Optimize { |
| 1195 | const graph = b.graph; |
| 1196 | |
| 1197 | if (options.preferred_optimize_mode) |mode| { |
| 1198 | if (b.option(bool, "release", "optimize for end users") orelse (graph.release_mode != .off)) { |
| 1199 | return mode; |
| 1200 | } else { |
| 1201 | return .debug; |
| 1202 | } |
| 1203 | } |
| 1204 | |
| 1205 | if (b.option( |
| 1206 | std.builtin.Optimize, |
| 1207 | "optimize", |
| 1208 | "Prioritize performance, safety, or binary size", |
| 1209 | )) |mode| { |
| 1210 | return mode; |
| 1211 | } |
| 1212 | |
| 1213 | return switch (graph.release_mode) { |
| 1214 | .off => .debug, |
| 1215 | .any => { |
| 1216 | std.debug.print("the project does not declare a preferred optimization mode. choose: --release=fast, --release=safe, or --release=small\n", .{}); |
| 1217 | process.exit(1); |
| 1218 | }, |
| 1219 | .fast => .fast, |
| 1220 | .safe => .safe, |
| 1221 | .small => .small, |
| 1222 | }; |
| 1223 | } |
| 1224 | |
| 1225 | pub const StandardTargetOptionsArgs = struct { |
| 1226 | whitelist: ?[]const Target.Query = null, |
| 1227 | default_target: Target.Query = .{}, |
| 1228 | }; |
| 1229 | |
| 1230 | /// Exposes standard `zig build` options for choosing a target and additionally |
| 1231 | /// resolves the target query. |
| 1232 | pub fn standardTargetOptions(b: *Build, args: StandardTargetOptionsArgs) ResolvedTarget { |
| 1233 | const query = b.standardTargetOptionsQueryOnly(args); |
| 1234 | return b.resolveTargetQuery(query); |
| 1235 | } |
| 1236 | |
| 1237 | /// Obtain a target query from a string, reporting diagnostics to stderr if the |
| 1238 | /// parsing failed. |
| 1239 | /// Asserts that the `diagnostics` field of `options` is `null`. This use case |
| 1240 | /// is handled instead by calling `std.Target.Query.parse` directly. |
| 1241 | pub fn parseTargetQuery(options: std.Target.Query.ParseOptions) error{ParseFailed}!std.Target.Query { |
| 1242 | assert(options.diagnostics == null); |
| 1243 | var diags: Target.Query.ParseOptions.Diagnostics = .{}; |
| 1244 | var opts_copy = options; |
| 1245 | opts_copy.diagnostics = &diags; |
| 1246 | return std.Target.Query.parse(opts_copy) catch |err| switch (err) { |
| 1247 | error.UnknownCpuModel => { |
| 1248 | std.debug.print("unknown CPU: {q}\navailable CPUs for architecture {t}:\n", .{ |
| 1249 | diags.cpu_name.?, diags.arch.?, |
| 1250 | }); |
| 1251 | for (diags.arch.?.allCpuModels()) |cpu| { |
| 1252 | std.debug.print(" {s}\n", .{cpu.name}); |
| 1253 | } |
| 1254 | return error.ParseFailed; |
| 1255 | }, |
| 1256 | error.UnknownCpuFeature => { |
| 1257 | std.debug.print( |
| 1258 | \\unknown CPU feature: {q} |
| 1259 | \\available CPU features for architecture '{t}': |
| 1260 | \\ |
| 1261 | , .{ |
| 1262 | diags.unknown_feature_name.?, diags.arch.?, |
| 1263 | }); |
| 1264 | for (diags.arch.?.allFeaturesList()) |feature| { |
| 1265 | std.debug.print(" {s}: {s}\n", .{ feature.name, feature.description }); |
| 1266 | } |
| 1267 | return error.ParseFailed; |
| 1268 | }, |
| 1269 | error.UnknownOperatingSystem => { |
| 1270 | std.debug.print( |
| 1271 | \\unknown OS: {q} |
| 1272 | \\available operating systems: |
| 1273 | \\ |
| 1274 | , .{diags.os_name.?}); |
| 1275 | inline for (@typeInfo(Target.Os.Tag).@"enum".field_names) |field_name| { |
| 1276 | std.debug.print(" {s}\n", .{field_name}); |
| 1277 | } |
| 1278 | return error.ParseFailed; |
| 1279 | }, |
| 1280 | else => |e| { |
| 1281 | std.debug.print("unable to parse target {q}: {t}\n", .{ options.arch_os_abi, e }); |
| 1282 | return error.ParseFailed; |
| 1283 | }, |
| 1284 | }; |
| 1285 | } |
| 1286 | |
| 1287 | /// Exposes standard `zig build` options for choosing a target. |
| 1288 | pub fn standardTargetOptionsQueryOnly(b: *Build, args: StandardTargetOptionsArgs) Target.Query { |
| 1289 | const graph = b.graph; |
| 1290 | const arena = graph.arena; |
| 1291 | |
| 1292 | const maybe_triple = b.option( |
| 1293 | []const u8, |
| 1294 | "target", |
| 1295 | "The CPU architecture, OS, and ABI to build for", |
| 1296 | ); |
| 1297 | const mcpu = b.option( |
| 1298 | []const u8, |
| 1299 | "cpu", |
| 1300 | "Target CPU features to add or subtract", |
| 1301 | ); |
| 1302 | const ofmt = b.option( |
| 1303 | []const u8, |
| 1304 | "ofmt", |
| 1305 | "Target object format", |
| 1306 | ); |
| 1307 | const dynamic_linker = b.option( |
| 1308 | []const u8, |
| 1309 | "dynamic-linker", |
| 1310 | "Path to interpreter on the target system", |
| 1311 | ); |
| 1312 | |
| 1313 | if (maybe_triple == null and mcpu == null and ofmt == null and dynamic_linker == null) |
| 1314 | return args.default_target; |
| 1315 | |
| 1316 | const triple = maybe_triple orelse "native"; |
| 1317 | |
| 1318 | const selected_target = parseTargetQuery(.{ |
| 1319 | .arch_os_abi = triple, |
| 1320 | .cpu_features = mcpu, |
| 1321 | .object_format = ofmt, |
| 1322 | .dynamic_linker = dynamic_linker, |
| 1323 | }) catch |err| switch (err) { |
| 1324 | error.ParseFailed => { |
| 1325 | b.markInvalidUserInput(); |
| 1326 | return args.default_target; |
| 1327 | }, |
| 1328 | }; |
| 1329 | |
| 1330 | const whitelist = args.whitelist orelse return selected_target; |
| 1331 | |
| 1332 | // Make sure it's a match of one of the list. |
| 1333 | for (whitelist) |q| { |
| 1334 | if (q.eql(selected_target)) |
| 1335 | return selected_target; |
| 1336 | } |
| 1337 | |
| 1338 | for (whitelist) |q| { |
| 1339 | log.info("allowed target: -Dtarget={s} -Dcpu={s}", .{ |
| 1340 | q.zigTriple(arena) catch @panic("OOM"), |
| 1341 | q.serializeCpuAlloc(arena) catch @panic("OOM"), |
| 1342 | }); |
| 1343 | } |
| 1344 | log.err("chosen target {q} does not match one of the allowed targets", .{ |
| 1345 | selected_target.zigTriple(arena) catch @panic("OOM"), |
| 1346 | }); |
| 1347 | b.markInvalidUserInput(); |
| 1348 | return args.default_target; |
| 1349 | } |
| 1350 | |
| 1351 | /// Build system implementation detail. |
| 1352 | pub fn addUserInputOption(b: *Build, name: []const u8, value_raw: []const u8) error{OutOfMemory}!bool { |
| 1353 | const graph = b.graph; |
| 1354 | const arena = graph.arena; |
| 1355 | const value = graph.dupeString(value_raw); |
| 1356 | const gop = try b.user_input_options.getOrPut(arena, name); |
| 1357 | |
| 1358 | if (!gop.found_existing) { |
| 1359 | gop.key_ptr.* = graph.dupeString(name); |
| 1360 | gop.value_ptr.* = .{ .scalar = value }; |
| 1361 | return false; |
| 1362 | } |
| 1363 | |
| 1364 | // Option already exists. |
| 1365 | switch (gop.value_ptr.*) { |
| 1366 | .scalar => |s| { |
| 1367 | // Turn it into a list. |
| 1368 | var list: std.ArrayList([]const u8) = .empty; |
| 1369 | (try list.addManyAsArray(arena, 2)).* = .{ s, value }; |
| 1370 | gop.value_ptr.* = .{ .list = list }; |
| 1371 | }, |
| 1372 | .list => |*list| try list.append(arena, value), |
| 1373 | .flag => { |
| 1374 | log.err("option -D{s}={s} conflicts with flag -D{s}", .{ name, value, name }); |
| 1375 | return true; |
| 1376 | }, |
| 1377 | .map => |*map| { |
| 1378 | _ = map; |
| 1379 | unreachable; // TODO implement maps as command line arguments |
| 1380 | }, |
| 1381 | .lazy_path => unreachable, |
| 1382 | .lazy_path_list => unreachable, |
| 1383 | } |
| 1384 | return false; |
| 1385 | } |
| 1386 | |
| 1387 | /// Build system implementation detail. |
| 1388 | pub fn addUserInputFlag(b: *Build, name: []const u8) error{OutOfMemory}!bool { |
| 1389 | const graph = b.graph; |
| 1390 | const arena = graph.arena; |
| 1391 | const gop = try b.user_input_options.getOrPut(arena, name); |
| 1392 | if (!gop.found_existing) { |
| 1393 | gop.key_ptr.* = graph.dupeString(name); |
| 1394 | gop.value_ptr.* = .{ .flag = {} }; |
| 1395 | return false; |
| 1396 | } |
| 1397 | // Option already exists. |
| 1398 | switch (gop.value_ptr.*) { |
| 1399 | .scalar => |s| { |
| 1400 | log.err("flag -D{s} conflicts with option -D{s}={s}", .{ name, name, s }); |
| 1401 | return true; |
| 1402 | }, |
| 1403 | .list, .map, .lazy_path_list => { |
| 1404 | log.err("flag -D{s} conflicts with multiple options of the same name", .{name}); |
| 1405 | return true; |
| 1406 | }, |
| 1407 | .lazy_path => |lp| { |
| 1408 | log.err("flag -D{s} conflicts with option -D{s}={f}", .{ name, name, lp }); |
| 1409 | return true; |
| 1410 | }, |
| 1411 | |
| 1412 | .flag => {}, |
| 1413 | } |
| 1414 | return false; |
| 1415 | } |
| 1416 | |
| 1417 | fn typeToEnum(comptime T: type) Configuration.AvailableOption.Type { |
| 1418 | return switch (T) { |
| 1419 | std.zig.BuildId => .build_id, |
| 1420 | LazyPath => .lazy_path, |
| 1421 | else => return switch (@typeInfo(T)) { |
| 1422 | .int => .int, |
| 1423 | .float => .float, |
| 1424 | .bool => .bool, |
| 1425 | .@"enum" => .@"enum", |
| 1426 | .pointer => |pointer| switch (pointer.child) { |
| 1427 | u8 => .string, |
| 1428 | []const u8 => .list, |
| 1429 | LazyPath => .lazy_path_list, |
| 1430 | else => switch (@typeInfo(pointer.child)) { |
| 1431 | .@"enum" => .enum_list, |
| 1432 | else => @compileError("Unsupported type: " ++ @typeName(T)), |
| 1433 | }, |
| 1434 | }, |
| 1435 | else => @compileError("Unsupported type: " ++ @typeName(T)), |
| 1436 | }, |
| 1437 | }; |
| 1438 | } |
| 1439 | |
| 1440 | fn markInvalidUserInput(b: *Build) void { |
| 1441 | b.invalid_user_input = true; |
| 1442 | } |
| 1443 | |
| 1444 | fn validateUserInputDidItFail(b: *Build) bool { |
| 1445 | for (b.user_input_options.keys()) |name| { |
| 1446 | if (!b.available_options_map.contains(name)) { |
| 1447 | for (b.available_options_map.keys(), b.available_options_map.values()) |available_name, *available| { |
| 1448 | log.info("available option: {q}: {s}", .{ available_name, available.description }); |
| 1449 | } |
| 1450 | log.err("invalid option: {q}", .{name}); |
| 1451 | b.markInvalidUserInput(); |
| 1452 | } |
| 1453 | } |
| 1454 | return b.invalid_user_input; |
| 1455 | } |
| 1456 | |
| 1457 | /// This creates the install step and adds it to the dependencies of the |
| 1458 | /// top-level install step, using all the default options. |
| 1459 | /// See `addInstallArtifact` for a more flexible function. |
| 1460 | pub fn installArtifact(b: *Build, artifact: *Step.Compile) void { |
| 1461 | b.getInstallStep().dependOn(&b.addInstallArtifact(artifact, .{}).step); |
| 1462 | } |
| 1463 | |
| 1464 | /// This merely creates the step; it does not add it to the dependencies of the |
| 1465 | /// top-level install step. |
| 1466 | pub fn addInstallArtifact( |
| 1467 | b: *Build, |
| 1468 | artifact: *Step.Compile, |
| 1469 | options: Step.InstallArtifact.Options, |
| 1470 | ) *Step.InstallArtifact { |
| 1471 | return Step.InstallArtifact.create(b, artifact, options); |
| 1472 | } |
| 1473 | |
| 1474 | ///`dest_rel_path` is relative to prefix path |
| 1475 | pub fn installFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { |
| 1476 | b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .prefix, dest_rel_path).step); |
| 1477 | } |
| 1478 | |
| 1479 | pub fn installDirectory(b: *Build, options: Step.InstallDir.Options) void { |
| 1480 | b.getInstallStep().dependOn(&b.addInstallDirectory(options).step); |
| 1481 | } |
| 1482 | |
| 1483 | ///`dest_rel_path` is relative to bin path |
| 1484 | pub fn installBinFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { |
| 1485 | b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .bin, dest_rel_path).step); |
| 1486 | } |
| 1487 | |
| 1488 | ///`dest_rel_path` is relative to lib path |
| 1489 | pub fn installLibFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) void { |
| 1490 | b.getInstallStep().dependOn(&b.addInstallFileWithDir(b.path(src_path), .lib, dest_rel_path).step); |
| 1491 | } |
| 1492 | |
| 1493 | pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *Step.ObjCopy { |
| 1494 | return Step.ObjCopy.create(b, source, options); |
| 1495 | } |
| 1496 | |
| 1497 | /// `dest_rel_path` is relative to install prefix path |
| 1498 | pub fn addInstallFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { |
| 1499 | return b.addInstallFileWithDir(source, .prefix, dest_rel_path); |
| 1500 | } |
| 1501 | |
| 1502 | /// `dest_rel_path` is relative to bin path |
| 1503 | pub fn addInstallBinFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { |
| 1504 | return b.addInstallFileWithDir(source, .bin, dest_rel_path); |
| 1505 | } |
| 1506 | |
| 1507 | /// `dest_rel_path` is relative to lib path |
| 1508 | pub fn addInstallLibFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { |
| 1509 | return b.addInstallFileWithDir(source, .lib, dest_rel_path); |
| 1510 | } |
| 1511 | |
| 1512 | /// `dest_rel_path` is relative to header path |
| 1513 | pub fn addInstallHeaderFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile { |
| 1514 | return b.addInstallFileWithDir(source, .header, dest_rel_path); |
| 1515 | } |
| 1516 | |
| 1517 | pub fn addInstallFileWithDir( |
| 1518 | b: *Build, |
| 1519 | source: LazyPath, |
| 1520 | install_dir: InstallDir, |
| 1521 | dest_rel_path: []const u8, |
| 1522 | ) *Step.InstallFile { |
| 1523 | return Step.InstallFile.create(b, source, install_dir, dest_rel_path); |
| 1524 | } |
| 1525 | |
| 1526 | pub fn addInstallDirectory(b: *Build, options: Step.InstallDir.Options) *Step.InstallDir { |
| 1527 | return Step.InstallDir.create(b, options); |
| 1528 | } |
| 1529 | |
| 1530 | pub fn addCheckFile( |
| 1531 | b: *Build, |
| 1532 | file_source: LazyPath, |
| 1533 | options: Step.CheckFile.Options, |
| 1534 | ) *Step.CheckFile { |
| 1535 | return Step.CheckFile.create(b, file_source, options); |
| 1536 | } |
| 1537 | |
| 1538 | /// References a file or directory relative to the source root. |
| 1539 | pub fn path(b: *Build, sub_path: []const u8) LazyPath { |
| 1540 | if (fs.path.isAbsolute(sub_path)) { |
| 1541 | panic("sub_path is expected to be relative to the build root, but was this absolute path: {q}. Absolute paths can cause problems but can be created via Graph.cwdRelativePath", .{sub_path}); |
| 1542 | } |
| 1543 | return .{ .src_path = .{ |
| 1544 | .owner = b, |
| 1545 | .sub_path = sub_path, |
| 1546 | } }; |
| 1547 | } |
| 1548 | |
| 1549 | /// Creates a list of files and/or directories relative to the source root. |
| 1550 | pub fn pathList(b: *Build, sub_paths: []const []const u8) []const LazyPath { |
| 1551 | const graph = b.graph; |
| 1552 | const result = graph.alloc(LazyPath, sub_paths.len); |
| 1553 | for (result, sub_paths) |*d, s| d.* = path(b, s); |
| 1554 | return result; |
| 1555 | } |
| 1556 | |
| 1557 | pub fn pathJoin(b: *Build, paths: []const []const u8) []u8 { |
| 1558 | const graph = b.graph; |
| 1559 | const arena = graph.arena; |
| 1560 | return fs.path.join(arena, paths) catch @panic("OOM"); |
| 1561 | } |
| 1562 | |
| 1563 | pub fn pathResolve(b: *Build, paths: []const []const u8) []u8 { |
| 1564 | const graph = b.graph; |
| 1565 | const arena = graph.arena; |
| 1566 | return fs.path.resolve(arena, paths) catch @panic("OOM"); |
| 1567 | } |
| 1568 | |
| 1569 | pub fn fmt(b: *Build, comptime format: []const u8, args: anytype) []u8 { |
| 1570 | const graph = b.graph; |
| 1571 | const arena = graph.arena; |
| 1572 | return std.fmt.allocPrint(arena, format, args) catch @panic("OOM"); |
| 1573 | } |
| 1574 | |
| 1575 | /// Creates an anonymous `Step` that searches for an executable on the host that |
| 1576 | /// has more than one possible name. |
| 1577 | /// |
| 1578 | /// Returns the `LazyPath` of the found executable. The search only takes place |
| 1579 | /// if the `LazyPath` will be used by a depending `Step`. |
| 1580 | /// |
| 1581 | /// This API is useful in the following cases: |
| 1582 | /// * The binary is not named the same across all systems (for example "python" |
| 1583 | /// vs "python3"). |
| 1584 | /// * The binary may be produced by building from source rather than being |
| 1585 | /// globally installed and will therefore be possibly found in one of the |
| 1586 | /// search prefix paths. |
| 1587 | /// |
| 1588 | /// Names are searched in order, observing search prefixes first and then PATH |
| 1589 | /// environment variable. |
| 1590 | /// |
| 1591 | /// Windows file name extensions are searched automatically, respecting the |
| 1592 | /// PATHEXT environment variable, so they need not be included in this list. |
| 1593 | /// However, even on Windows, the names will be checked without appending |
| 1594 | /// extensions first, so that can be used as a priority system. |
| 1595 | /// |
| 1596 | /// See also: |
| 1597 | /// * `findProgram` |
| 1598 | pub fn findProgramLazy(b: *Build, options: Step.FindProgram.Options) LazyPath { |
| 1599 | return .{ .generated = .{ .index = Step.FindProgram.create(b, options).found_path } }; |
| 1600 | } |
| 1601 | |
| 1602 | pub const FindProgramOptions = Step.FindProgram.Options; |
| 1603 | |
| 1604 | /// Immediately (in the configure phase), searches for an executable on the host |
| 1605 | /// that has more than one possible name. |
| 1606 | /// |
| 1607 | /// Calling this function poisons the configuration cache, so it is only |
| 1608 | /// appropriate when the existence of the program or its output needs to be |
| 1609 | /// observed by configuration logic. For more information, see |
| 1610 | /// `Graph.CachePoison` documentation. |
| 1611 | /// |
| 1612 | /// Names are searched in order, observing search prefixes first and then PATH |
| 1613 | /// environment variable. |
| 1614 | /// |
| 1615 | /// Windows file name extensions are searched automatically, respecting the |
| 1616 | /// PATHEXT environment variable, so they need not be included in this list. |
| 1617 | /// However, even on Windows, the names will be checked without appending |
| 1618 | /// extensions first, so that can be used as a priority system. |
| 1619 | /// |
| 1620 | /// See also: |
| 1621 | /// * `findProgramLazy` |
| 1622 | pub fn findProgram(b: *Build, options: FindProgramOptions) ?[]const u8 { |
| 1623 | const graph = b.graph; |
| 1624 | |
| 1625 | // Because it observes search prefixes and contents of directories in PATH. |
| 1626 | graph.poisonCache(); |
| 1627 | |
| 1628 | for (options.names) |name| { |
| 1629 | if (Io.Dir.path.isAbsolute(name)) { |
| 1630 | if (tryFindProgram(b, name)) |found| return found; |
| 1631 | } |
| 1632 | for (graph.search_prefixes.items) |search_prefix| { |
| 1633 | const full_path = b.pathJoin(&.{ search_prefix, "bin", name }); |
| 1634 | if (tryFindProgram(b, full_path)) |found| return found; |
| 1635 | } |
| 1636 | } |
| 1637 | |
| 1638 | if (b.graph.environ_map.get("PATH")) |PATH| { |
| 1639 | for (options.names) |name| { |
| 1640 | var it = mem.tokenizeScalar(u8, PATH, Io.Dir.path.delimiter); |
| 1641 | while (it.next()) |p| { |
| 1642 | const full_path = b.pathJoin(&.{ p, name }); |
| 1643 | if (tryFindProgram(b, full_path)) |found| return found; |
| 1644 | } |
| 1645 | } |
| 1646 | } |
| 1647 | |
| 1648 | return null; |
| 1649 | } |
| 1650 | |
| 1651 | fn supportedWindowsProgramExtension(ext: []const u8) bool { |
| 1652 | inline for (@typeInfo(std.process.WindowsExtension).@"enum".field_names) |field_name| { |
| 1653 | if (std.ascii.eqlIgnoreCase(ext, "." ++ field_name)) return true; |
| 1654 | } |
| 1655 | return false; |
| 1656 | } |
| 1657 | |
| 1658 | fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { |
| 1659 | const graph = b.graph; |
| 1660 | const io = graph.io; |
| 1661 | const arena = graph.arena; |
| 1662 | |
| 1663 | if (Io.Dir.cwd().access(io, full_path, .{ .execute = true })) |_| { |
| 1664 | return full_path; |
| 1665 | } else |err| switch (err) { |
| 1666 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { |
| 1667 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, full_path }); |
| 1668 | }, |
| 1669 | else => |e| return panic("failed accessing {s}: {t}", .{ full_path, e }), |
| 1670 | } |
| 1671 | |
| 1672 | if (builtin.os.tag == .windows) { |
| 1673 | if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| { |
| 1674 | var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); |
| 1675 | |
| 1676 | const extended_path_buf = arena.alloc(u8, full_path.len + 1 + std.process.WindowsExtension.max_len) catch @panic("OOM"); |
| 1677 | @memcpy(extended_path_buf[0..full_path.len], full_path); |
| 1678 | |
| 1679 | while (it.next()) |ext| { |
| 1680 | if (!supportedWindowsProgramExtension(ext)) continue; |
| 1681 | |
| 1682 | @memcpy(extended_path_buf[full_path.len..][0..ext.len], ext); |
| 1683 | const extended_path = extended_path_buf[0 .. full_path.len + ext.len]; |
| 1684 | |
| 1685 | if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { |
| 1686 | return extended_path; |
| 1687 | } else |err| switch (err) { |
| 1688 | error.FileNotFound, error.AccessDenied, error.PermissionDenied => |e| { |
| 1689 | if (graph.verbose) log.info("searched: {t} {s}", .{ e, extended_path }); |
| 1690 | }, |
| 1691 | else => |e| return panic("failed accessing {s}: {t}", .{ extended_path, e }), |
| 1692 | } |
| 1693 | } |
| 1694 | } |
| 1695 | } |
| 1696 | |
| 1697 | return null; |
| 1698 | } |
| 1699 | |
| 1700 | /// Deprecated; use `runFallible`. |
| 1701 | pub fn runAllowFail( |
| 1702 | b: *Build, |
| 1703 | argv: []const []const u8, |
| 1704 | exit_code: *u8, |
| 1705 | stderr_behavior: process.SpawnOptions.StdIo, |
| 1706 | ) anyerror![]u8 { |
| 1707 | if (!process.can_spawn) return error.ExecNotSupported; |
| 1708 | switch (runFallible(b, argv, .{ |
| 1709 | .stderr_behavior = stderr_behavior, |
| 1710 | })) { |
| 1711 | .success => |stdout| return stdout, |
| 1712 | .spawn_failed => |err| return err, |
| 1713 | .bad_exit_code => |code| { |
| 1714 | exit_code.* = code; |
| 1715 | return error.ExitCodeFailure; |
| 1716 | }, |
| 1717 | .crashed => { |
| 1718 | exit_code.* = 255; |
| 1719 | return error.ProcessTerminated; |
| 1720 | }, |
| 1721 | } |
| 1722 | } |
| 1723 | |
| 1724 | pub const RunOptions = struct { |
| 1725 | stderr_behavior: process.SpawnOptions.StdIo = .inherit, |
| 1726 | /// Fail the configuration if stdout is larger than this. |
| 1727 | stdout_limit: Io.Limit = .limited(1_000_000), |
| 1728 | /// Set to change the current working directory when spawning the child |
| 1729 | /// process. |
| 1730 | cwd: process.Child.Cwd = .inherit, |
| 1731 | /// Replaces the child environment when provided. The PATH value from here |
| 1732 | /// is not used to resolve `argv[0]`; that resolution always uses parent |
| 1733 | /// environment. |
| 1734 | environ_map: ?*const process.Environ.Map = null, |
| 1735 | expand_arg0: process.ArgExpansion = .no_expand, |
| 1736 | }; |
| 1737 | |
| 1738 | pub const RunResult = union(enum) { |
| 1739 | /// Thild process exited with code 0, writing this stdout. |
| 1740 | success: []u8, |
| 1741 | /// The child process could not be created. |
| 1742 | spawn_failed: process.SpawnError, |
| 1743 | /// The child process indicated failure. |
| 1744 | bad_exit_code: u8, |
| 1745 | /// The child process terminated abnormally. |
| 1746 | crashed, |
| 1747 | }; |
| 1748 | |
| 1749 | /// Executes the provided command immediately, allowing failure. |
| 1750 | /// |
| 1751 | /// If the program exits successfully, stdout is returned. Otherwise, returns |
| 1752 | /// an indication of failure. |
| 1753 | /// |
| 1754 | /// See also: |
| 1755 | /// * `run`. |
| 1756 | pub fn runFallible(b: *Build, argv: []const []const u8, options: RunOptions) RunResult { |
| 1757 | assert(argv.len != 0); |
| 1758 | |
| 1759 | const graph = b.graph; |
| 1760 | const io = graph.io; |
| 1761 | const arena = graph.arena; |
| 1762 | |
| 1763 | const print_opts: std.zig.AllocPrintCmdOptions = .{ |
| 1764 | .cwd = switch (options.cwd) { |
| 1765 | .inherit => null, |
| 1766 | .path => |p| p, |
| 1767 | .dir => null, // Unknown without changing function signature of runFallible. |
| 1768 | }, |
| 1769 | .child_env = options.environ_map, |
| 1770 | .parent_env = &graph.environ_map, |
| 1771 | }; |
| 1772 | |
| 1773 | if (graph.verbose) { |
| 1774 | const text = std.zig.allocPrintCmd(arena, argv, print_opts) catch @panic("OOM"); |
| 1775 | std.log.scoped(.verbose).info("{s}", .{text}); |
| 1776 | } |
| 1777 | |
| 1778 | var child = process.spawn(io, .{ |
| 1779 | .argv = argv, |
| 1780 | .stdin = .ignore, |
| 1781 | .stdout = .pipe, |
| 1782 | .stderr = options.stderr_behavior, |
| 1783 | .cwd = options.cwd, |
| 1784 | .environ_map = &graph.environ_map, |
| 1785 | .expand_arg0 = options.expand_arg0, |
| 1786 | }) catch |err| return .{ .spawn_failed = err }; |
| 1787 | |
| 1788 | var stdout_reader = child.stdout.?.readerStreaming(io, &.{}); |
| 1789 | const stdout = stdout_reader.interface.allocRemaining(arena, options.stdout_limit) catch |err| switch (err) { |
| 1790 | error.ReadFailed => panic("failed to read from child: {t}", .{stdout_reader.err.?}), |
| 1791 | else => |e| panic("failed to read from child: {t}", .{e}), |
| 1792 | }; |
| 1793 | |
| 1794 | const term = child.wait(io) catch @panic("unexpected"); |
| 1795 | |
| 1796 | return switch (term) { |
| 1797 | .exited => |code| switch (code) { |
| 1798 | 0 => .{ .success = stdout }, |
| 1799 | else => .{ .bad_exit_code = code }, |
| 1800 | }, |
| 1801 | .signal, .stopped, .unknown => .crashed, |
| 1802 | }; |
| 1803 | } |
| 1804 | |
| 1805 | /// Executes the provided command immediately. |
| 1806 | /// |
| 1807 | /// If the program exits successfully, stdout is returned. Otherwise, fails the |
| 1808 | /// build with a helpful message. |
| 1809 | /// |
| 1810 | /// See also: |
| 1811 | /// * `runFallible`. |
| 1812 | pub fn run(b: *Build, argv: []const []const u8) []u8 { |
| 1813 | const graph = b.graph; |
| 1814 | const arena = graph.arena; |
| 1815 | switch (b.runFallible(argv, .{ |
| 1816 | .stderr_behavior = .inherit, |
| 1817 | })) { |
| 1818 | .success => |stdout| return stdout, |
| 1819 | .spawn_failed => |err| fatal("the following command failed with {t}:\n{s}", .{ |
| 1820 | err, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), |
| 1821 | }), |
| 1822 | .bad_exit_code => |code| fatal("the following command exited with code {d}:\n{s}", .{ |
| 1823 | code, std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), |
| 1824 | }), |
| 1825 | .crashed => fatal("the following command crashed:\n{s}", .{ |
| 1826 | std.zig.allocPrintCmd(arena, argv, .{}) catch @panic("OOM"), |
| 1827 | }), |
| 1828 | } |
| 1829 | } |
| 1830 | |
| 1831 | /// Adds additional paths, equivalent to the `--search-prefix` arguments |
| 1832 | /// provided by the user. Paths added with this function have lower precedence |
| 1833 | /// than the ones specified by the user on the command line. |
| 1834 | /// |
| 1835 | /// It is generally best practice to avoid calling this function, instead |
| 1836 | /// relying on the user to provide these paths via the standard build system |
| 1837 | /// interface. However, when integrating with other build systems, the user may |
| 1838 | /// have already provided the information to the other build system, and thus |
| 1839 | /// it is desirable to use that same information without requiring the user to |
| 1840 | /// provide it again. |
| 1841 | pub fn addSearchPrefix(b: *Build, search_prefix: []const u8) void { |
| 1842 | if (b.isRoot()) { |
| 1843 | const graph = b.graph; |
| 1844 | const wc = &graph.wip_configuration; |
| 1845 | const string = wc.addString(search_prefix) catch @panic("OOM"); |
| 1846 | wc.search_prefixes.append(wc.gpa, string) catch @panic("OOM"); |
| 1847 | } |
| 1848 | } |
| 1849 | |
| 1850 | pub fn isRoot(b: *const Build) bool { |
| 1851 | return b.pkg_hash.len == 0; |
| 1852 | } |
| 1853 | |
| 1854 | pub const Dependency = struct { |
| 1855 | builder: *Build, |
| 1856 | |
| 1857 | pub fn artifact(d: *Dependency, name: []const u8) *Step.Compile { |
| 1858 | var found: ?*Step.Compile = null; |
| 1859 | for (d.builder.install_tls.step.dependencies.items) |dep_step| { |
| 1860 | const inst = dep_step.cast(Step.InstallArtifact) orelse continue; |
| 1861 | if (mem.eql(u8, inst.artifact.name, name)) { |
| 1862 | if (found != null) panic("artifact name {q} is ambiguous", .{name}); |
| 1863 | found = inst.artifact; |
| 1864 | } |
| 1865 | } |
| 1866 | return found orelse { |
| 1867 | for (d.builder.install_tls.step.dependencies.items) |dep_step| { |
| 1868 | const inst = dep_step.cast(Step.InstallArtifact) orelse continue; |
| 1869 | log.info("available artifact: {q}", .{inst.artifact.name}); |
| 1870 | } |
| 1871 | panic("unable to find artifact {q}", .{name}); |
| 1872 | }; |
| 1873 | } |
| 1874 | |
| 1875 | pub fn module(d: *Dependency, name: []const u8) *Module { |
| 1876 | return d.builder.modules.get(name) orelse { |
| 1877 | panic("unable to find module {q}", .{name}); |
| 1878 | }; |
| 1879 | } |
| 1880 | |
| 1881 | pub fn namedWriteFiles(d: *Dependency, name: []const u8) *Step.WriteFile { |
| 1882 | return d.builder.named_writefiles.get(name) orelse { |
| 1883 | panic("unable to find named writefiles {q}", .{name}); |
| 1884 | }; |
| 1885 | } |
| 1886 | |
| 1887 | pub fn namedLazyPath(d: *Dependency, name: []const u8) LazyPath { |
| 1888 | return d.builder.named_lazy_paths.get(name) orelse { |
| 1889 | panic("unable to find named lazypath {q}", .{name}); |
| 1890 | }; |
| 1891 | } |
| 1892 | |
| 1893 | pub fn path(d: *Dependency, sub_path: []const u8) LazyPath { |
| 1894 | return .{ |
| 1895 | .dependency = .{ |
| 1896 | .dependency = d, |
| 1897 | .sub_path = sub_path, |
| 1898 | }, |
| 1899 | }; |
| 1900 | } |
| 1901 | }; |
| 1902 | |
| 1903 | fn findPkgHashOrFatal(b: *Build, name: []const u8) []const u8 { |
| 1904 | for (b.available_deps) |dep| { |
| 1905 | if (mem.eql(u8, dep[0], name)) return dep[1]; |
| 1906 | } |
| 1907 | log.info("all dependencies used by build.zig must be declared in corresponding build.zig.zon", .{}); |
| 1908 | if (b.pkg_hash.len == 0) panic("no dependency named {s}", .{name}); |
| 1909 | panic("no dependency named {s} in {s} ({s})", .{ name, b.dep_prefix, b.pkg_hash }); |
| 1910 | } |
| 1911 | |
| 1912 | inline fn findImportPkgHashOrFatal(b: *Build, comptime asking_build_zig: type, comptime dep_name: []const u8) []const u8 { |
| 1913 | const build_runner = @import("root"); |
| 1914 | const deps = build_runner.dependencies; |
| 1915 | const arena = b.graph.arena; |
| 1916 | |
| 1917 | const b_pkg_hash, const b_pkg_deps = comptime for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| { |
| 1918 | const pkg = @field(deps.packages, pkg_hash); |
| 1919 | if (@hasDecl(pkg, "build_zig") and pkg.build_zig == asking_build_zig) break .{ pkg_hash, pkg.deps }; |
| 1920 | } else .{ "", deps.root_deps }; |
| 1921 | if (!mem.eql(u8, b_pkg_hash, b.pkg_hash)) { |
| 1922 | const build_zig_path = b.root.join(arena, "build.zig") catch @panic("OOM"); |
| 1923 | panic("{} is not the struct that corresponds to {f}", .{ |
| 1924 | asking_build_zig, build_zig_path, |
| 1925 | }); |
| 1926 | } |
| 1927 | comptime for (b_pkg_deps) |dep| { |
| 1928 | if (mem.eql(u8, dep[0], dep_name)) return dep[1]; |
| 1929 | }; |
| 1930 | |
| 1931 | const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); |
| 1932 | panic("no dependency named {s} in {f}. All packages used in build.zig must be declared in this file", .{ |
| 1933 | dep_name, full_path, |
| 1934 | }); |
| 1935 | } |
| 1936 | |
| 1937 | fn markNeededLazyDep(b: *Build, pkg_hash: []const u8) void { |
| 1938 | b.graph.needed_lazy_dependencies.put(b.graph.arena, pkg_hash, {}) catch @panic("OOM"); |
| 1939 | } |
| 1940 | |
| 1941 | /// Deprecated in favor of `dependencyLazy`. |
| 1942 | pub fn lazyDependency(b: *Build, name: []const u8, args: anytype) ?*Dependency { |
| 1943 | return dependencyLazy(b, name, args) catch |err| switch (err) { |
| 1944 | error.LazyDependencyNeeded => null, |
| 1945 | }; |
| 1946 | } |
| 1947 | |
| 1948 | /// Declares that the current configuration does in fact require a potentially |
| 1949 | /// lazy dependency. |
| 1950 | /// |
| 1951 | /// If the dependency is already fetched, it is returned. However if the |
| 1952 | /// dependency is not yet fetched, then when the build script is finished |
| 1953 | /// running, the toolchain will not proceed to the make phase. Instead, the |
| 1954 | /// parent process will additionally fetch all the lazy dependencies that were |
| 1955 | /// actually required by running the build script, recompile the build script, |
| 1956 | /// and then run it again. In other words, if this function returns |
| 1957 | /// `error.LazyDependencyNeeded` it means that the only purpose of completing |
| 1958 | /// the configure phase is to find out all the other lazy dependencies that are |
| 1959 | /// also required. In this case, one must propagate the error all the way up |
| 1960 | /// and return it from the main build function. |
| 1961 | /// |
| 1962 | /// For non-lazy dependencies, this always succeeds. |
| 1963 | pub fn dependencyLazy(b: *Build, name: []const u8, args: anytype) error{LazyDependencyNeeded}!*Dependency { |
| 1964 | const pkg_hash = findPkgHashOrFatal(b, name); |
| 1965 | const entry = package_map.get(pkg_hash) orelse unreachable; |
| 1966 | if (!entry.available) { |
| 1967 | markNeededLazyDep(b, pkg_hash); |
| 1968 | return error.LazyDependencyNeeded; |
| 1969 | } |
| 1970 | var map: PackageOptions.Map = .empty; |
| 1971 | PackageOptions.fromArgs(b.graph.arena, &map, args); |
| 1972 | return dependencyResolved(b, name, entry, &map); |
| 1973 | } |
| 1974 | |
| 1975 | pub const PackageEntry = struct { |
| 1976 | hash: []const u8, |
| 1977 | available: bool, |
| 1978 | build_root: []const u8, |
| 1979 | deps: AvailableDeps, |
| 1980 | run_build: ?*const fn (*Build) void, |
| 1981 | }; |
| 1982 | |
| 1983 | /// Build system implementation detail. |
| 1984 | pub const package_map: std.StaticStringMap(PackageEntry) = blk: { |
| 1985 | const deps = @import("root").dependencies; |
| 1986 | const decl_names = @typeInfo(deps.packages).@"struct".decl_names; |
| 1987 | var kvs: [decl_names.len]struct { []const u8, PackageEntry } = undefined; |
| 1988 | for (decl_names, 0..) |decl_name, i| { |
| 1989 | const pkg = @field(deps.packages, decl_name); |
| 1990 | const available = !@hasDecl(pkg, "available") or pkg.available; |
| 1991 | kvs[i] = .{ decl_name, .{ |
| 1992 | .hash = decl_name, |
| 1993 | .available = available, |
| 1994 | .build_root = if (available) pkg.build_root else "", |
| 1995 | .deps = if (available) pkg.deps else &.{}, |
| 1996 | .run_build = if (available and @hasDecl(pkg, "build_zig")) &struct { |
| 1997 | fn run(sb: *Build) void { |
| 1998 | sb.runPackageScript(pkg.build_zig); |
| 1999 | } |
| 2000 | }.run else null, |
| 2001 | } }; |
| 2002 | } |
| 2003 | const frozen = kvs; |
| 2004 | break :blk .initComptime(&frozen); |
| 2005 | }; |
| 2006 | |
| 2007 | /// Declares that the current configuration does in fact require a potentially |
| 2008 | /// lazy dependency. |
| 2009 | /// |
| 2010 | /// If the dependency is already fetched, it is returned. Otherwise, exits the |
| 2011 | /// configuration phase with intent to fetch the lazy dependency and rerun the |
| 2012 | /// configuration script. |
| 2013 | /// |
| 2014 | /// If it is known to the caller at this point that additional lazy |
| 2015 | /// dependencies are also required, it would save time to call `dependencyLazy` |
| 2016 | /// instead, handling `error.LazyDependencyNeeded` in a way that marks multiple |
| 2017 | /// potentially lazy dependencies as required before eventually returning |
| 2018 | /// that error from the top level build function. |
| 2019 | pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency { |
| 2020 | return dependencyLazy(b, name, args) catch |err| switch (err) { |
| 2021 | error.LazyDependencyNeeded => { |
| 2022 | assert(b.graph.needed_lazy_dependencies.count() != 0); |
| 2023 | serializeConfigurationExiting(b); |
| 2024 | }, |
| 2025 | }; |
| 2026 | } |
| 2027 | |
| 2028 | /// In a build.zig file, this function is to `@import` what `lazyDependency` is to `dependency`. |
| 2029 | /// If the dependency is lazy and has not yet been fetched, it instructs the parent process to fetch |
| 2030 | /// that dependency after the build script has finished running, then returns `null`. |
| 2031 | /// If the dependency is lazy but has already been fetched, or if it is eager, it returns |
| 2032 | /// the build.zig struct of that dependency, just like a regular `@import`. |
| 2033 | pub inline fn lazyImport( |
| 2034 | b: *Build, |
| 2035 | /// The build.zig struct of the package importing the dependency. |
| 2036 | /// When calling this function from the `build` function of a build.zig file's, you normally |
| 2037 | /// pass `@This()`. |
| 2038 | comptime asking_build_zig: type, |
| 2039 | comptime dep_name: []const u8, |
| 2040 | ) ?type { |
| 2041 | const build_runner = @import("root"); |
| 2042 | const deps = build_runner.dependencies; |
| 2043 | const pkg_hash = findImportPkgHashOrFatal(b, asking_build_zig, dep_name); |
| 2044 | |
| 2045 | inline for (@typeInfo(deps.packages).@"struct".decl_names) |decl_name| { |
| 2046 | if (comptime mem.eql(u8, decl_name, pkg_hash)) { |
| 2047 | const pkg = @field(deps.packages, decl_name); |
| 2048 | const available = !@hasDecl(pkg, "available") or pkg.available; |
| 2049 | if (!available) { |
| 2050 | markNeededLazyDep(b, pkg_hash); |
| 2051 | return null; |
| 2052 | } |
| 2053 | return if (@hasDecl(pkg, "build_zig")) |
| 2054 | pkg.build_zig |
| 2055 | else |
| 2056 | @compileError("dependency '" ++ dep_name ++ "' does not have a build.zig"); |
| 2057 | } |
| 2058 | } |
| 2059 | |
| 2060 | comptime unreachable; // Bad @dependencies source |
| 2061 | } |
| 2062 | |
| 2063 | inline fn pkgHashFromBuildZig(comptime build_zig: type) ?[]const u8 { |
| 2064 | comptime { |
| 2065 | const deps = @import("root").dependencies; |
| 2066 | return for (@typeInfo(deps.packages).@"struct".decl_names) |pkg_hash| { |
| 2067 | const pkg = @field(deps.packages, pkg_hash); |
| 2068 | if (@hasDecl(pkg, "build_zig") and pkg.build_zig == build_zig) break pkg_hash; |
| 2069 | } else null; |
| 2070 | } |
| 2071 | } |
| 2072 | |
| 2073 | /// Build system implementation detail. |
| 2074 | pub fn dependencyFromBuildZig( |
| 2075 | b: *Build, |
| 2076 | /// The build.zig struct of the dependency, normally obtained by `@import` of the dependency. |
| 2077 | /// If called from the build.zig file itself, use `@This` to obtain a reference to the struct. |
| 2078 | comptime build_zig: type, |
| 2079 | args: anytype, |
| 2080 | ) *Dependency { |
| 2081 | const arena = b.graph.arena; |
| 2082 | |
| 2083 | find_dep: { |
| 2084 | const pkg_hash = pkgHashFromBuildZig(build_zig) orelse break :find_dep; |
| 2085 | const dep_name = for (b.available_deps) |dep| { |
| 2086 | if (mem.eql(u8, dep[1], pkg_hash)) break dep[1]; |
| 2087 | } else break :find_dep; |
| 2088 | const entry = package_map.get(pkg_hash) orelse break :find_dep; |
| 2089 | var map: PackageOptions.Map = .empty; |
| 2090 | PackageOptions.fromArgs(arena, &map, args); |
| 2091 | return dependencyResolved(b, dep_name, entry, &map); |
| 2092 | } |
| 2093 | |
| 2094 | const full_path = b.root.join(arena, "build.zig.zon") catch @panic("OOM"); |
| 2095 | panic("{} is not a build.zig struct of a dependency in {f}", .{ build_zig, full_path }); |
| 2096 | } |
| 2097 | |
| 2098 | /// Takes ownership of `package_options`, which may be unsorted. |
| 2099 | fn dependencyResolved( |
| 2100 | b: *Build, |
| 2101 | name: []const u8, |
| 2102 | entry: PackageEntry, |
| 2103 | package_options: *PackageOptions.Map, |
| 2104 | ) *Dependency { |
| 2105 | const graph = b.graph; |
| 2106 | const io = graph.io; |
| 2107 | const arena = graph.arena; |
| 2108 | |
| 2109 | PackageOptions.sort(package_options); |
| 2110 | |
| 2111 | if (graph.dependency_cache.getContext(.{ |
| 2112 | .pkg_hash = entry.hash, |
| 2113 | .options = package_options, |
| 2114 | }, .{})) |dep| return dep; |
| 2115 | |
| 2116 | const dep_root: Cache.Path = .{ |
| 2117 | .root_dir = .{ |
| 2118 | .path = entry.build_root, |
| 2119 | .handle = Io.Dir.cwd().openDir(io, entry.build_root, .{}) catch |err| |
| 2120 | fatal("failed to open {q}: {t}", .{ entry.build_root, err }), |
| 2121 | }, |
| 2122 | }; |
| 2123 | |
| 2124 | const sub_builder = b.createChild(name, dep_root, entry.hash, entry.deps, package_options.*) catch @panic("OOM"); |
| 2125 | if (entry.run_build) |run_build| { |
| 2126 | run_build(sub_builder); |
| 2127 | |
| 2128 | if (sub_builder.validateUserInputDidItFail()) { |
| 2129 | std.debug.dumpCurrentStackTrace(.{ .first_address = @returnAddress() }); |
| 2130 | } |
| 2131 | } |
| 2132 | |
| 2133 | const dep = graph.create(Dependency); |
| 2134 | dep.* = .{ .builder = sub_builder }; |
| 2135 | |
| 2136 | graph.dependency_cache.putContext(arena, .{ |
| 2137 | .pkg_hash = entry.hash, |
| 2138 | .options = &sub_builder.user_input_options, |
| 2139 | }, dep, .{}) catch @panic("OOM"); |
| 2140 | return dep; |
| 2141 | } |
| 2142 | |
| 2143 | /// Build system implementation detail. |
| 2144 | pub inline fn runPackageScript(b: *Build, comptime build_zig: anytype) void { |
| 2145 | const result: anyerror!void = build_zig.build(b); |
| 2146 | result catch |err| switch (err) { |
| 2147 | error.LazyDependencyNeeded => assert(b.graph.needed_lazy_dependencies.count() != 0), |
| 2148 | else => { |
| 2149 | if (b.dep_prefix.len == 0) { |
| 2150 | log.err("package {q} configuration failed: {t}", .{ b.dep_prefix, err }); |
| 2151 | } else { |
| 2152 | log.err("configuration failed: {t}", .{err}); |
| 2153 | } |
| 2154 | if (@errorReturnTrace()) |trace| std.debug.dumpErrorReturnTrace(trace); |
| 2155 | const lazy_count = b.graph.needed_lazy_dependencies.count(); |
| 2156 | if (lazy_count == 0) process.exit(1); |
| 2157 | log.info("{d} lazy dependencies detected; fetching and retrying configuration", .{lazy_count}); |
| 2158 | }, |
| 2159 | }; |
| 2160 | } |
| 2161 | |
| 2162 | // dirnameAllowEmpty is a variant of fs.path.dirname |
| 2163 | // that allows "" to refer to the root for relative paths. |
| 2164 | // |
| 2165 | // For context, dirname("foo") and dirname("") are both null. |
| 2166 | // However, for relative paths, we want dirname("foo") to be "" |
| 2167 | // so that we can join it with another path (e.g. build root, cache root, etc.) |
| 2168 | // |
| 2169 | // dirname("") should still be null, because we can't go up any further. |
| 2170 | fn dirnameAllowEmpty(full_path: []const u8) ?[]const u8 { |
| 2171 | return fs.path.dirname(full_path) orelse { |
| 2172 | if (fs.path.isAbsolute(full_path) or full_path.len == 0) return null; |
| 2173 | |
| 2174 | return ""; |
| 2175 | }; |
| 2176 | } |
| 2177 | |
| 2178 | test dirnameAllowEmpty { |
| 2179 | try std.testing.expectEqualStrings( |
| 2180 | "foo", |
| 2181 | dirnameAllowEmpty("foo" ++ fs.path.sep_str ++ "bar") orelse @panic("unexpected null"), |
| 2182 | ); |
| 2183 | |
| 2184 | try std.testing.expectEqualStrings( |
| 2185 | "", |
| 2186 | dirnameAllowEmpty("foo") orelse @panic("unexpected null"), |
| 2187 | ); |
| 2188 | |
| 2189 | try std.testing.expect(dirnameAllowEmpty("") == null); |
| 2190 | } |
| 2191 | |
| 2192 | /// A reference to an existing or future path. |
| 2193 | pub const LazyPath = union(enum) { |
| 2194 | /// A source file path relative to build root. |
| 2195 | src_path: struct { |
| 2196 | owner: *std.Build, |
| 2197 | sub_path: []const u8, |
| 2198 | }, |
| 2199 | |
| 2200 | generated: struct { |
| 2201 | index: Configuration.GeneratedFileIndex, |
| 2202 | |
| 2203 | /// The number of parent directories to go up. |
| 2204 | /// 0 means the generated file itself. |
| 2205 | /// 1 means the directory of the generated file. |
| 2206 | /// 2 means the parent of that directory, and so on. |
| 2207 | up: usize = 0, |
| 2208 | |
| 2209 | /// Applied after `up`. |
| 2210 | sub_path: []const u8 = "", |
| 2211 | }, |
| 2212 | |
| 2213 | /// Deprecated; call `Graph.cwdRelativePath` instead. |
| 2214 | cwd_relative: []const u8, |
| 2215 | |
| 2216 | dependency: struct { |
| 2217 | dependency: *Dependency, |
| 2218 | sub_path: []const u8, |
| 2219 | }, |
| 2220 | |
| 2221 | relative: struct { |
| 2222 | base: Configuration.LazyPath.Relative.Base, |
| 2223 | sub_path: []const u8 = "", |
| 2224 | |
| 2225 | pub fn eql(a: @This(), b: @This()) bool { |
| 2226 | return a.base == b.base and mem.eql(u8, a.sub_path, b.sub_path); |
| 2227 | } |
| 2228 | }, |
| 2229 | |
| 2230 | /// Path to the Zig executable being used to execute "zig build". |
| 2231 | pub const zig_exe: LazyPath = .{ .relative = .{ .base = .zig_exe } }; |
| 2232 | /// Path to the "lib/" directory from the Zig installation being used to |
| 2233 | /// execute "zig build". |
| 2234 | pub const zig_lib: LazyPath = .{ .relative = .{ .base = .zig_lib } }; |
| 2235 | /// Path to the project's local cache directory (usually called ".zig-cache"). |
| 2236 | pub const cache_root: LazyPath = .{ .relative = .{ .base = .local_cache } }; |
| 2237 | |
| 2238 | /// Returns a lazy path referring to the directory containing this path. |
| 2239 | /// |
| 2240 | /// The dirname is not allowed to escape the logical root for underlying |
| 2241 | /// path. For example, if the path is relative to the build root, the |
| 2242 | /// dirname is not allowed to traverse outside of the build root. |
| 2243 | /// Similarly, if the path is a generated file inside zig-cache, the |
| 2244 | /// dirname is not allowed to traverse outside of zig-cache. |
| 2245 | pub fn dirname(lazy_path: LazyPath) LazyPath { |
| 2246 | return switch (lazy_path) { |
| 2247 | .src_path => |sp| .{ .src_path = .{ |
| 2248 | .owner = sp.owner, |
| 2249 | .sub_path = dirnameAllowEmpty(sp.sub_path) orelse { |
| 2250 | dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the build root\n", .{}) catch {}; |
| 2251 | @panic("misconfigured build script"); |
| 2252 | }, |
| 2253 | } }, |
| 2254 | .generated => |generated| .{ .generated = if (dirnameAllowEmpty(generated.sub_path)) |sub_dirname| .{ |
| 2255 | .index = generated.index, |
| 2256 | .up = generated.up, |
| 2257 | .sub_path = sub_dirname, |
| 2258 | } else .{ |
| 2259 | .index = generated.index, |
| 2260 | .up = generated.up + 1, |
| 2261 | .sub_path = "", |
| 2262 | } }, |
| 2263 | .cwd_relative => |rel_path| .{ |
| 2264 | .cwd_relative = dirnameAllowEmpty(rel_path) orelse { |
| 2265 | // If we get null, it means one of two things: |
| 2266 | // - rel_path was absolute, and is now root |
| 2267 | // - rel_path was relative, and is now "" |
| 2268 | // In either case, the build script tried to go too far |
| 2269 | // and we should panic. |
| 2270 | if (fs.path.isAbsolute(rel_path)) { |
| 2271 | dumpBadDirnameHelp(null, null, |
| 2272 | \\dirname() attempted to traverse outside the root. |
| 2273 | \\No more directories left to go up. |
| 2274 | \\ |
| 2275 | , .{}) catch {}; |
| 2276 | @panic("misconfigured build script"); |
| 2277 | } else { |
| 2278 | dumpBadDirnameHelp(null, null, |
| 2279 | \\dirname() attempted to traverse outside the current working directory. |
| 2280 | \\ |
| 2281 | , .{}) catch {}; |
| 2282 | @panic("misconfigured build script"); |
| 2283 | } |
| 2284 | }, |
| 2285 | }, |
| 2286 | .relative => |r| .{ .relative = .{ |
| 2287 | .base = r.base, |
| 2288 | .sub_path = dirnameAllowEmpty(r.sub_path) orelse { |
| 2289 | dumpBadDirnameHelp(null, null, "dirname() attempted to traverse outside the base path\n", .{}) catch {}; |
| 2290 | @panic("misconfigured build script"); |
| 2291 | }, |
| 2292 | } }, |
| 2293 | .dependency => |dep| .{ .dependency = .{ |
| 2294 | .dependency = dep.dependency, |
| 2295 | .sub_path = dirnameAllowEmpty(dep.sub_path) orelse { |
| 2296 | dumpBadDirnameHelp(null, null, |
| 2297 | \\dirname() attempted to traverse outside the dependency root. |
| 2298 | \\ |
| 2299 | , .{}) catch {}; |
| 2300 | @panic("misconfigured build script"); |
| 2301 | }, |
| 2302 | } }, |
| 2303 | }; |
| 2304 | } |
| 2305 | |
| 2306 | pub fn path(lazy_path: LazyPath, b: *Build, sub_path: []const u8) LazyPath { |
| 2307 | const graph = b.graph; |
| 2308 | const arena = graph.arena; |
| 2309 | return lazy_path.join(arena, sub_path) catch @panic("OOM"); |
| 2310 | } |
| 2311 | |
| 2312 | pub fn join(lazy_path: LazyPath, arena: Allocator, sub_path: []const u8) Allocator.Error!LazyPath { |
| 2313 | return switch (lazy_path) { |
| 2314 | .src_path => |src| .{ .src_path = .{ |
| 2315 | .owner = src.owner, |
| 2316 | .sub_path = try fs.path.resolve(arena, &.{ src.sub_path, sub_path }), |
| 2317 | } }, |
| 2318 | .generated => |gen| .{ .generated = .{ |
| 2319 | .index = gen.index, |
| 2320 | .up = gen.up, |
| 2321 | .sub_path = try fs.path.resolve(arena, &.{ gen.sub_path, sub_path }), |
| 2322 | } }, |
| 2323 | .cwd_relative => |cwd_relative| .{ |
| 2324 | .cwd_relative = try fs.path.resolve(arena, &.{ cwd_relative, sub_path }), |
| 2325 | }, |
| 2326 | .relative => |r| .{ .relative = .{ |
| 2327 | .base = r.base, |
| 2328 | .sub_path = try fs.path.resolve(arena, &.{ r.sub_path, sub_path }), |
| 2329 | } }, |
| 2330 | .dependency => |dep| .{ .dependency = .{ |
| 2331 | .dependency = dep.dependency, |
| 2332 | .sub_path = try fs.path.resolve(arena, &.{ dep.sub_path, sub_path }), |
| 2333 | } }, |
| 2334 | }; |
| 2335 | } |
| 2336 | |
| 2337 | /// Deprecated, use `format` instead. |
| 2338 | pub fn getDisplayName(lazy_path: LazyPath) []const u8 { |
| 2339 | return switch (lazy_path) { |
| 2340 | .src_path => |sp| sp.sub_path, |
| 2341 | .cwd_relative => |p| p, |
| 2342 | .generated => "generated", |
| 2343 | .dependency => "dependency", |
| 2344 | .relative => |r| @tagName(r.base), |
| 2345 | }; |
| 2346 | } |
| 2347 | |
| 2348 | pub fn format(lp: LazyPath, w: *Io.Writer) Io.Writer.Error!void { |
| 2349 | switch (lp) { |
| 2350 | .src_path => |sp| try w.writeAll(sp.sub_path), |
| 2351 | .cwd_relative => |p| try w.writeAll(p), |
| 2352 | .generated => try w.writeAll("generated"), |
| 2353 | .dependency => try w.writeAll("dependency"), |
| 2354 | .relative => |r| try w.print("{t} {s}", .{ r.base, r.sub_path }), |
| 2355 | } |
| 2356 | } |
| 2357 | |
| 2358 | /// Adds dependencies this file source implies to the given step. |
| 2359 | pub fn addStepDependencies(lazy_path: LazyPath, other_step: *Step) void { |
| 2360 | switch (lazy_path) { |
| 2361 | .src_path, .cwd_relative, .relative, .dependency => {}, |
| 2362 | .generated => |gen| { |
| 2363 | const graph = other_step.owner.graph; |
| 2364 | const generated_owner_step = graph.generated_files.items[@backingInt(gen.index)]; |
| 2365 | other_step.dependOn(generated_owner_step); |
| 2366 | }, |
| 2367 | } |
| 2368 | } |
| 2369 | |
| 2370 | /// Copies the internal strings. |
| 2371 | /// |
| 2372 | /// The `graph` parameter is only used for the global arena allocator. |
| 2373 | pub fn dupe(lazy_path: LazyPath, graph: *const Graph) LazyPath { |
| 2374 | return dupeInner(lazy_path, graph.arena); |
| 2375 | } |
| 2376 | |
| 2377 | /// Copies the slice of paths and all internal strings. |
| 2378 | /// |
| 2379 | /// The `graph` parameter is only used for the global arena allocator. |
| 2380 | pub fn dupeList(lazy_paths: []const LazyPath, graph: *const Graph) []const LazyPath { |
| 2381 | const arena = graph.arena; |
| 2382 | const result = graph.alloc(LazyPath, lazy_paths.len); |
| 2383 | for (result, lazy_paths) |*d, s| d.* = dupeInner(s, arena); |
| 2384 | return result; |
| 2385 | } |
| 2386 | |
| 2387 | fn dupeInner(lazy_path: LazyPath, arena: Allocator) LazyPath { |
| 2388 | return switch (lazy_path) { |
| 2389 | .src_path => |sp| .{ .src_path = .{ |
| 2390 | .owner = sp.owner, |
| 2391 | .sub_path = sp.owner.graph.dupePath(sp.sub_path), |
| 2392 | } }, |
| 2393 | .cwd_relative => |p| .{ .cwd_relative = Graph.dupePathInner(arena, p) }, |
| 2394 | .relative => |r| .{ .relative = r }, |
| 2395 | .generated => |gen| .{ .generated = .{ |
| 2396 | .index = gen.index, |
| 2397 | .up = gen.up, |
| 2398 | .sub_path = Graph.dupePathInner(arena, gen.sub_path), |
| 2399 | } }, |
| 2400 | .dependency => |dep| .{ .dependency = .{ |
| 2401 | .dependency = dep.dependency, |
| 2402 | .sub_path = Graph.dupePathInner(arena, dep.sub_path), |
| 2403 | } }, |
| 2404 | }; |
| 2405 | } |
| 2406 | |
| 2407 | fn eql(a: LazyPath, b: LazyPath) bool { |
| 2408 | if (std.meta.activeTag(a) != b) return false; |
| 2409 | switch (a) { |
| 2410 | .src_path => |a_sp| { |
| 2411 | const b_sp = b.src_path; |
| 2412 | if (a_sp.owner != b_sp.owner) return false; |
| 2413 | if (mem.eql(u8, a_sp.sub_path, b_sp.sub_path)) return false; |
| 2414 | }, |
| 2415 | .generated => |*a_gen| { |
| 2416 | const b_gen = &b.generated; |
| 2417 | if (a_gen.index != b_gen.index) return false; |
| 2418 | if (a_gen.up != b_gen.up) return false; |
| 2419 | if (mem.eql(u8, a_gen.sub_path, b_gen.sub_path)) return false; |
| 2420 | }, |
| 2421 | .cwd_relative => |a_rel_path| { |
| 2422 | const b_rel_path = b.cwd_relative; |
| 2423 | if (!mem.eql(u8, a_rel_path, b_rel_path)) return false; |
| 2424 | }, |
| 2425 | .relative => |a_relative| return a_relative.eql(b.relative), |
| 2426 | .dependency => |a_dep| { |
| 2427 | const b_dep = b.dependency; |
| 2428 | if (a_dep.dependency != b_dep.dependency) return false; |
| 2429 | if (!mem.eql(u8, a_dep.sub_path, b_dep.sub_path)) return false; |
| 2430 | }, |
| 2431 | } |
| 2432 | return true; |
| 2433 | } |
| 2434 | |
| 2435 | fn hash(lp: LazyPath, hasher: *std.hash.Wyhash) void { |
| 2436 | switch (lp) { |
| 2437 | .src_path => |sp| { |
| 2438 | hasher.update(sp.owner.pkg_hash); |
| 2439 | hasher.update(sp.sub_path); |
| 2440 | }, |
| 2441 | .generated => |gen| { |
| 2442 | hasher.update(@ptrCast(&gen.index)); |
| 2443 | hasher.update(@ptrCast(&gen.up)); |
| 2444 | hasher.update(gen.sub_path); |
| 2445 | }, |
| 2446 | .cwd_relative => |rel_path| { |
| 2447 | hasher.update(rel_path); |
| 2448 | }, |
| 2449 | .relative => |r| { |
| 2450 | hasher.update(@ptrCast(&r.base)); |
| 2451 | hasher.update(@ptrCast(&r.sub_path)); |
| 2452 | }, |
| 2453 | .dependency => |dep| { |
| 2454 | hasher.update(dep.dependency.builder.pkg_hash); |
| 2455 | hasher.update(dep.sub_path); |
| 2456 | }, |
| 2457 | } |
| 2458 | } |
| 2459 | }; |
| 2460 | |
| 2461 | fn dumpBadDirnameHelp( |
| 2462 | fail_step: ?*Step, |
| 2463 | asking_step: ?*Step, |
| 2464 | comptime msg: []const u8, |
| 2465 | args: anytype, |
| 2466 | ) anyerror!void { |
| 2467 | const stderr = std.debug.lockStderr(&.{}).terminal(); |
| 2468 | defer std.debug.unlockStderr(); |
| 2469 | const w = stderr.writer; |
| 2470 | |
| 2471 | try w.print(msg, args); |
| 2472 | |
| 2473 | if (fail_step) |s| { |
| 2474 | stderr.setColor(.red) catch {}; |
| 2475 | try w.writeAll(" The step was created by this stack trace:\n"); |
| 2476 | stderr.setColor(.reset) catch {}; |
| 2477 | |
| 2478 | s.dump(stderr); |
| 2479 | } |
| 2480 | |
| 2481 | if (asking_step) |as| { |
| 2482 | stderr.setColor(.red) catch {}; |
| 2483 | try w.print(" The step {q} that is missing a dependency on the above step was created by this stack trace:\n", .{as.name}); |
| 2484 | stderr.setColor(.reset) catch {}; |
| 2485 | |
| 2486 | as.dump(stderr); |
| 2487 | } |
| 2488 | |
| 2489 | stderr.setColor(.red) catch {}; |
| 2490 | try w.writeAll(" Proceeding to panic.\n"); |
| 2491 | stderr.setColor(.reset) catch {}; |
| 2492 | } |
| 2493 | |
| 2494 | pub const InstallDir = union(enum) { |
| 2495 | prefix: void, |
| 2496 | lib: void, |
| 2497 | bin: void, |
| 2498 | header: void, |
| 2499 | /// A path relative to the prefix |
| 2500 | custom: []const u8, |
| 2501 | |
| 2502 | /// Duplicates the install directory including the path if set to custom. |
| 2503 | pub fn dupe(dir: InstallDir, graph: *const Graph) InstallDir { |
| 2504 | if (dir == .custom) { |
| 2505 | return .{ .custom = graph.dupeString(dir.custom) }; |
| 2506 | } else { |
| 2507 | return dir; |
| 2508 | } |
| 2509 | } |
| 2510 | }; |
| 2511 | |
| 2512 | /// Creates a path leading to a directory inside "tmp" subdirectory of local |
| 2513 | /// cache which is created on demand and cleaned up by the build runner upon |
| 2514 | /// success. |
| 2515 | pub fn tmpPath(b: *Build) LazyPath { |
| 2516 | const wf = b.addTempFiles(); |
| 2517 | return wf.getDirectory(); |
| 2518 | } |
| 2519 | |
| 2520 | /// A pair of target query and fully resolved target. |
| 2521 | /// This type is generally required by build system API that need to be given a |
| 2522 | /// target. The query is kept because the Zig toolchain needs to know which parts |
| 2523 | /// of the target are "native". This can apply to the CPU, the OS, or even the ABI. |
| 2524 | pub const ResolvedTarget = struct { |
| 2525 | query: Target.Query, |
| 2526 | result: Target, |
| 2527 | }; |
| 2528 | |
| 2529 | /// Converts a target query into a fully resolved target that can be passed to |
| 2530 | /// various parts of the API. |
| 2531 | pub fn resolveTargetQuery(b: *Build, query: Target.Query) ResolvedTarget { |
| 2532 | if (query.isNative()) { |
| 2533 | // Hot path. This is faster than querying the native CPU and OS again. |
| 2534 | return b.graph.host; |
| 2535 | } |
| 2536 | const io = b.graph.io; |
| 2537 | return .{ |
| 2538 | .query = query, |
| 2539 | .result = std.zig.system.resolveTargetQuery(io, query) catch |
| 2540 | @panic("unable to resolve target query"), |
| 2541 | }; |
| 2542 | } |
| 2543 | |
| 2544 | pub fn wantSharedLibSymLinks(target: Target) bool { |
| 2545 | return target.os.tag != .windows; |
| 2546 | } |
| 2547 | |
| 2548 | pub const SystemIntegrationOptionConfig = struct { |
| 2549 | /// If left as null, then the default will depend on system_package_mode. |
| 2550 | default: ?bool = null, |
| 2551 | }; |
| 2552 | |
| 2553 | pub fn systemIntegrationOption( |
| 2554 | b: *Build, |
| 2555 | name: []const u8, |
| 2556 | config: SystemIntegrationOptionConfig, |
| 2557 | ) bool { |
| 2558 | const graph = b.graph; |
| 2559 | const arena = graph.arena; |
| 2560 | const gop = graph.system_integration_options.getOrPut(arena, name) catch @panic("OOM"); |
| 2561 | if (gop.found_existing) switch (gop.value_ptr.*) { |
| 2562 | .user_disabled => { |
| 2563 | gop.value_ptr.* = .declared_disabled; |
| 2564 | return false; |
| 2565 | }, |
| 2566 | .user_enabled => { |
| 2567 | gop.value_ptr.* = .declared_enabled; |
| 2568 | return true; |
| 2569 | }, |
| 2570 | .declared_disabled => return false, |
| 2571 | .declared_enabled => return true, |
| 2572 | } else { |
| 2573 | gop.key_ptr.* = graph.dupeString(name); |
| 2574 | if (config.default orelse graph.system_package_mode) { |
| 2575 | gop.value_ptr.* = .declared_enabled; |
| 2576 | return true; |
| 2577 | } else { |
| 2578 | gop.value_ptr.* = .declared_disabled; |
| 2579 | return false; |
| 2580 | } |
| 2581 | } |
| 2582 | } |
| 2583 | |
| 2584 | /// Indicates that the build.zig logic depends on a particular file's contents. |
| 2585 | /// |
| 2586 | /// If the file is created, deleted, or has its contents changed, the configure |
| 2587 | /// phase will be repeated. If the inode or mtime change, but the file contents |
| 2588 | /// remain the same, it will not cause the configure logic to be repeated. |
| 2589 | /// |
| 2590 | /// This is an alternative to `Graph.poisonCache` that avoids making every invocation |
| 2591 | /// of `zig build` into a cache miss. |
| 2592 | /// |
| 2593 | /// Only a subset of `LazyPath` are supported: |
| 2594 | /// - Relative to cwd |
| 2595 | /// - Relative to any package root |
| 2596 | /// - Relative to zig cache or zig installation |
| 2597 | /// |
| 2598 | /// If the file would be inside one of the search prefixes, then the dependency |
| 2599 | /// cannot be tracked; `Graph.poisonCache` must be used instead. |
| 2600 | pub fn dependOnFileContents(b: *Build, lazy_path: LazyPath) void { |
| 2601 | validateConfigureDependency(lazy_path); |
| 2602 | const graph = b.graph; |
| 2603 | graph.configure_dependencies.append(graph.arena, .{ |
| 2604 | .lazy_path = lazy_path.dupe(graph), |
| 2605 | .mode = .contents, |
| 2606 | }) catch @panic("OOM"); |
| 2607 | } |
| 2608 | |
| 2609 | /// Indicates that the build.zig logic depends on a particular file's size, |
| 2610 | /// inode, mtime, and contents. |
| 2611 | /// |
| 2612 | /// If the file is created, deleted, has its contents changed, or the inode |
| 2613 | /// changes, or the mtime changes, the configure phase will be repeated. |
| 2614 | /// |
| 2615 | /// This is an alternative to `Graph.poisonCache` that avoids making every invocation |
| 2616 | /// of `zig build` into a cache miss. |
| 2617 | /// |
| 2618 | /// Only a subset of `LazyPath` are supported: |
| 2619 | /// - Relative to cwd |
| 2620 | /// - Relative to any package root |
| 2621 | /// - Relative to zig cache or zig installation |
| 2622 | /// |
| 2623 | /// If the file would be inside one of the search prefixes, then the dependency |
| 2624 | /// cannot be tracked; `Graph.poisonCache` must be used instead. |
| 2625 | pub fn dependOnFileMetadata(b: *Build, lazy_path: LazyPath) void { |
| 2626 | validateConfigureDependency(lazy_path); |
| 2627 | const graph = b.graph; |
| 2628 | graph.configure_dependencies.append(graph.arena, .{ |
| 2629 | .lazy_path = lazy_path.dupe(graph), |
| 2630 | .mode = .metadata, |
| 2631 | }) catch @panic("OOM"); |
| 2632 | } |
| 2633 | |
| 2634 | /// Indicates that the build.zig logic depends on a particular directory's entries. |
| 2635 | /// |
| 2636 | /// This is an alternative to `Graph.poisonCache` that avoids making every invocation |
| 2637 | /// of `zig build` into a cache miss. |
| 2638 | /// |
| 2639 | /// If any file is created, deleted, or renamed in this directory, the |
| 2640 | /// configure phase will be repeated. |
| 2641 | /// |
| 2642 | /// Only a subset of `LazyPath` are supported: |
| 2643 | /// - Relative to cwd |
| 2644 | /// - Relative to any package root |
| 2645 | /// - Relative to zig cache or zig installation |
| 2646 | /// |
| 2647 | /// If the directory would be inside one of the search prefixes, then the dependency |
| 2648 | /// cannot be tracked; `Graph.poisonCache` must be used instead. |
| 2649 | pub fn dependOnDirectory(b: *Build, lazy_path: LazyPath) void { |
| 2650 | validateConfigureDependency(lazy_path); |
| 2651 | const graph = b.graph; |
| 2652 | graph.configure_dependencies.append(graph.arena, .{ |
| 2653 | .lazy_path = lazy_path.dupe(graph), |
| 2654 | .mode = .directory, |
| 2655 | }) catch @panic("OOM"); |
| 2656 | } |
| 2657 | |
| 2658 | fn validateConfigureDependency(lazy_path: LazyPath) void { |
| 2659 | switch (lazy_path) { |
| 2660 | .src_path, .cwd_relative, .dependency => {}, // OK |
| 2661 | .generated => @panic("configure phase cannot depend on files generated during make phase"), |
| 2662 | .relative => |relative| switch (relative.base) { |
| 2663 | .cwd, .build_root, .local_cache, .global_cache, .zig_exe, .zig_lib => {}, // OK |
| 2664 | .install_prefix, |
| 2665 | .install_lib, |
| 2666 | .install_bin, |
| 2667 | .install_include, |
| 2668 | => @panic("configure phase cannot depend on files installed during make phase"), |
| 2669 | }, |
| 2670 | } |
| 2671 | } |
| 2672 | |
| 2673 | /// Build system implementation detail. |
| 2674 | pub fn serializeConfigurationExiting(b: *Build) noreturn { |
| 2675 | const graph = b.graph; |
| 2676 | const io = graph.io; |
| 2677 | |
| 2678 | var stdout_buffer: [1024]u8 = undefined; |
| 2679 | var file_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer); |
| 2680 | Serialize.write(b, &graph.wip_configuration, &file_writer.interface) catch |err| switch (err) { |
| 2681 | error.WriteFailed => fatal("failed to write configuration output: {t}", .{file_writer.err.?}), |
| 2682 | error.OutOfMemory => @panic("OOM"), |
| 2683 | }; |
| 2684 | file_writer.flush() catch |err| fatal("failed to write configuration output: {t}", .{err}); |
| 2685 | |
| 2686 | // This executable is short-lived and run in Debug mode, so we'd rather |
| 2687 | // have `zig build` run faster than catch resource leaks in the user's |
| 2688 | // build.zig script (or, frankly, this configure runner), therefore we call |
| 2689 | // exit directly here rather than cleanExit. |
| 2690 | process.exit(0); |
| 2691 | } |
| 2692 | |
| 2693 | test { |
| 2694 | _ = Cache; |
| 2695 | _ = Configuration; |
| 2696 | _ = Module; |
| 2697 | _ = Step; |
| 2698 | _ = Configuration; |
| 2699 | _ = &findProgram; |
| 2700 | _ = abi; |
| 2701 | } |