authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-27 19:46:45-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-31 15:09:35-07:00
log063888afff75f9d91fd221d84e1b74b111304ac3
tree335e09f72c8180540940b3bea70a4176f875c14f
parentef8f694d777029caaa48c50c28ff805c058ccccb

std.build: implement passing options to dependency packages

* introduce the concept of maps to user input options, but don't implement it for command line arg parsing yet. * remove setPreferredReleaseMode and standardReleaseOptions in favor of standardOptimizeOption which has a future-proof options parameter.

2 files changed, 135 insertions(+), 109 deletions(-)

lib/std/build.zig+131-78
......@@ -73,8 +73,6 @@ pub const Builder = struct {
7373 build_root: []const u8,
7474 cache_root: []const u8,
7575 global_cache_root: []const u8,
76 release_mode: ?std.builtin.Mode,
77 is_release: bool,
7876 /// zig lib dir
7977 override_lib_dir: ?[]const u8,
8078 vcpkg_root: VcpkgRoot = .unattempted,
......@@ -150,6 +148,7 @@ pub const Builder = struct {
150148 flag: void,
151149 scalar: []const u8,
152150 list: ArrayList([]const u8),
151 map: StringHashMap(*const UserValue),
153152 };
154153
155154 const TypeId = enum {
......@@ -223,8 +222,6 @@ pub const Builder = struct {
223222 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
224223 .description = "Remove build artifacts from prefix path",
225224 },
226 .release_mode = null,
227 .is_release = false,
228225 .override_lib_dir = null,
229226 .install_path = undefined,
230227 .args = null,
......@@ -291,8 +288,6 @@ pub const Builder = struct {
291288 .build_root = build_root,
292289 .cache_root = parent.cache_root,
293290 .global_cache_root = parent.global_cache_root,
294 .release_mode = parent.release_mode,
295 .is_release = parent.is_release,
296291 .override_lib_dir = parent.override_lib_dir,
297292 .debug_log_scopes = parent.debug_log_scopes,
298293 .debug_compile_errors = parent.debug_compile_errors,
......@@ -312,10 +307,55 @@ pub const Builder = struct {
312307 }
313308
314309 fn applyArgs(b: *Builder, args: anytype) !void {
315 // TODO this function is the way that a build.zig file communicates
316 // options to its dependencies. It is the programmatic way to give
317 // command line arguments to a build.zig script.
318 _ = args;
310 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
311 const v = @field(args, field.name);
312 const T = @TypeOf(v);
313 switch (T) {
314 CrossTarget => {
315 try b.user_input_options.put(field.name, .{
316 .name = field.name,
317 .value = .{ .scalar = try v.zigTriple(b.allocator) },
318 .used = false,
319 });
320 try b.user_input_options.put("cpu", .{
321 .name = "cpu",
322 .value = .{ .scalar = try serializeCpu(b.allocator, v.getCpu()) },
323 .used = false,
324 });
325 },
326 []const u8 => {
327 try b.user_input_options.put(field.name, .{
328 .name = field.name,
329 .value = .{ .scalar = v },
330 .used = false,
331 });
332 },
333 else => switch (@typeInfo(T)) {
334 .Bool => {
335 try b.user_input_options.put(field.name, .{
336 .name = field.name,
337 .value = .{ .scalar = if (v) "true" else "false" },
338 .used = false,
339 });
340 },
341 .Enum => {
342 try b.user_input_options.put(field.name, .{
343 .name = field.name,
344 .value = .{ .scalar = @tagName(v) },
345 .used = false,
346 });
347 },
348 .Int => {
349 try b.user_input_options.put(field.name, .{
350 .name = field.name,
351 .value = .{ .scalar = try std.fmt.allocPrint(b.allocator, "{d}", .{v}) },
352 .used = false,
353 });
354 },
355 else => @compileError("option '" ++ field.name ++ "' has unsupported type: " ++ @typeName(T)),
356 },
357 }
358 }
319359 const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
320360 // Random bytes to make unique. Refresh this with new random bytes when
321361 // implementation is modified in a non-backwards-compatible way.
......@@ -679,15 +719,19 @@ pub const Builder = struct {
679719 return null;
680720 }
681721 },
682 .list => {
683 log.err("Expected -D{s} to be a boolean, but received a list.\n", .{name});
722 .list, .map => {
723 log.err("Expected -D{s} to be a boolean, but received a {s}.\n", .{
724 name, @tagName(option_ptr.value),
725 });
684726 self.markInvalidUserInput();
685727 return null;
686728 },
687729 },
688730 .int => switch (option_ptr.value) {
689 .flag => {
690 log.err("Expected -D{s} to be an integer, but received a boolean.\n", .{name});
731 .flag, .list, .map => {
732 log.err("Expected -D{s} to be an integer, but received a {s}.\n", .{
733 name, @tagName(option_ptr.value),
734 });
691735 self.markInvalidUserInput();
692736 return null;
693737 },
......@@ -706,15 +750,12 @@ pub const Builder = struct {
706750 };
707751 return n;
708752 },
709 .list => {
710 log.err("Expected -D{s} to be an integer, but received a list.\n", .{name});
711 self.markInvalidUserInput();
712 return null;
713 },
714753 },
715754 .float => switch (option_ptr.value) {
716 .flag => {
717 log.err("Expected -D{s} to be a float, but received a boolean.\n", .{name});
755 .flag, .map, .list => {
756 log.err("Expected -D{s} to be a float, but received a {s}.\n", .{
757 name, @tagName(option_ptr.value),
758 });
718759 self.markInvalidUserInput();
719760 return null;
720761 },
......@@ -726,15 +767,12 @@ pub const Builder = struct {
726767 };
727768 return n;
728769 },
729 .list => {
730 log.err("Expected -D{s} to be a float, but received a list.\n", .{name});
731 self.markInvalidUserInput();
732 return null;
733 },
734770 },
735771 .@"enum" => switch (option_ptr.value) {
736 .flag => {
737 log.err("Expected -D{s} to be a string, but received a boolean.\n", .{name});
772 .flag, .map, .list => {
773 log.err("Expected -D{s} to be an enum, but received a {s}.\n", .{
774 name, @tagName(option_ptr.value),
775 });
738776 self.markInvalidUserInput();
739777 return null;
740778 },
......@@ -747,28 +785,22 @@ pub const Builder = struct {
747785 return null;
748786 }
749787 },
750 .list => {
751 log.err("Expected -D{s} to be a string, but received a list.\n", .{name});
752 self.markInvalidUserInput();
753 return null;
754 },
755788 },
756789 .string => switch (option_ptr.value) {
757 .flag => {
758 log.err("Expected -D{s} to be a string, but received a boolean.\n", .{name});
759 self.markInvalidUserInput();
760 return null;
761 },
762 .list => {
763 log.err("Expected -D{s} to be a string, but received a list.\n", .{name});
790 .flag, .list, .map => {
791 log.err("Expected -D{s} to be a string, but received a {s}.\n", .{
792 name, @tagName(option_ptr.value),
793 });
764794 self.markInvalidUserInput();
765795 return null;
766796 },
767797 .scalar => |s| return s,
768798 },
769799 .list => switch (option_ptr.value) {
770 .flag => {
771 log.err("Expected -D{s} to be a list, but received a boolean.\n", .{name});
800 .flag, .map => {
801 log.err("Expected -D{s} to be a list, but received a {s}.\n", .{
802 name, @tagName(option_ptr.value),
803 });
772804 self.markInvalidUserInput();
773805 return null;
774806 },
......@@ -790,41 +822,24 @@ pub const Builder = struct {
790822 return &step_info.step;
791823 }
792824
793 /// This provides the -Drelease option to the build user and does not give them the choice.
794 pub fn setPreferredReleaseMode(self: *Builder, mode: std.builtin.Mode) void {
795 if (self.release_mode != null) {
796 @panic("setPreferredReleaseMode must be called before standardReleaseOptions and may not be called twice");
825 pub const StandardOptimizeOptionOptions = struct {
826 preferred_optimize_mode: ?std.builtin.Mode = null,
827 };
828
829 pub fn standardOptimizeOption(self: *Builder, options: StandardOptimizeOptionOptions) std.builtin.Mode {
830 if (options.preferred_optimize_mode) |mode| {
831 if (self.option(bool, "release", "optimize for end users") orelse false) {
832 return mode;
833 } else {
834 return .Debug;
835 }
836 } else {
837 return self.option(
838 std.builtin.Mode,
839 "optimize",
840 "prioritize performance, safety, or binary size (-O flag)",
841 ) orelse .Debug;
797842 }
798 const description = self.fmt("Create a release build ({s})", .{@tagName(mode)});
799 self.is_release = self.option(bool, "release", description) orelse false;
800 self.release_mode = if (self.is_release) mode else std.builtin.Mode.Debug;
801 }
802
803 /// If you call this without first calling `setPreferredReleaseMode` then it gives the build user
804 /// the choice of what kind of release.
805 pub fn standardReleaseOptions(self: *Builder) std.builtin.Mode {
806 if (self.release_mode) |mode| return mode;
807
808 const release_safe = self.option(bool, "release-safe", "Optimizations on and safety on") orelse false;
809 const release_fast = self.option(bool, "release-fast", "Optimizations on and safety off") orelse false;
810 const release_small = self.option(bool, "release-small", "Size optimizations on and safety off") orelse false;
811
812 const mode = if (release_safe and !release_fast and !release_small)
813 std.builtin.Mode.ReleaseSafe
814 else if (release_fast and !release_safe and !release_small)
815 std.builtin.Mode.ReleaseFast
816 else if (release_small and !release_fast and !release_safe)
817 std.builtin.Mode.ReleaseSmall
818 else if (!release_fast and !release_safe and !release_small)
819 std.builtin.Mode.Debug
820 else x: {
821 log.err("Multiple release modes (of -Drelease-safe, -Drelease-fast and -Drelease-small)\n", .{});
822 self.markInvalidUserInput();
823 break :x std.builtin.Mode.Debug;
824 };
825 self.is_release = mode != .Debug;
826 self.release_mode = mode;
827 return mode;
828843 }
829844
830845 pub const StandardTargetOptionsArgs = struct {
......@@ -1004,6 +1019,11 @@ pub const Builder = struct {
10041019 log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
10051020 return true;
10061021 },
1022 .map => |*map| {
1023 _ = map;
1024 log.warn("TODO maps as command line arguments is not implemented yet.", .{});
1025 return true;
1026 },
10071027 }
10081028 return false;
10091029 }
......@@ -1026,7 +1046,7 @@ pub const Builder = struct {
10261046 log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });
10271047 return true;
10281048 },
1029 .list => {
1049 .list, .map => {
10301050 log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});
10311051 return true;
10321052 },
......@@ -1058,7 +1078,7 @@ pub const Builder = struct {
10581078 var it = self.user_input_options.iterator();
10591079 while (it.next()) |entry| {
10601080 if (!entry.value_ptr.used) {
1061 log.err("Invalid option: -D{s}\n", .{entry.key_ptr.*});
1081 log.err("Invalid option: -D{s}", .{entry.key_ptr.*});
10621082 self.markInvalidUserInput();
10631083 }
10641084 }
......@@ -1456,6 +1476,11 @@ pub const Builder = struct {
14561476 ) *Dependency {
14571477 const sub_builder = b.createChild(name, build_root, args) catch unreachable;
14581478 sub_builder.runBuild(build_zig) catch unreachable;
1479
1480 if (sub_builder.validateUserInputDidItFail()) {
1481 std.debug.dumpCurrentStackTrace(@returnAddress());
1482 }
1483
14591484 const dep = b.allocator.create(Dependency) catch unreachable;
14601485 dep.* = .{ .builder = sub_builder };
14611486 return dep;
......@@ -1718,6 +1743,34 @@ pub const InstalledFile = struct {
17181743 }
17191744};
17201745
1746pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
1747 // TODO this logic can disappear if cpu model + features becomes part of the target triple
1748 const all_features = cpu.arch.allFeaturesList();
1749 var populated_cpu_features = cpu.model.features;
1750 populated_cpu_features.populateDependencies(all_features);
1751
1752 if (populated_cpu_features.eql(cpu.features)) {
1753 // The CPU name alone is sufficient.
1754 return cpu.model.name;
1755 } else {
1756 var mcpu_buffer = ArrayList(u8).init(allocator);
1757 try mcpu_buffer.appendSlice(cpu.model.name);
1758
1759 for (all_features) |feature, i_usize| {
1760 const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize);
1761 const in_cpu_set = populated_cpu_features.isEnabled(i);
1762 const in_actual_set = cpu.features.isEnabled(i);
1763 if (in_cpu_set and !in_actual_set) {
1764 try mcpu_buffer.writer().print("-{s}", .{feature.name});
1765 } else if (!in_cpu_set and in_actual_set) {
1766 try mcpu_buffer.writer().print("+{s}", .{feature.name});
1767 }
1768 }
1769
1770 return try mcpu_buffer.toOwnedSlice();
1771 }
1772}
1773
17211774test "dupePkg()" {
17221775 if (builtin.os.tag == .wasi) return error.SkipZigTest;
17231776
lib/std/build/LibExeObjStep.zig+4-31
......@@ -1495,37 +1495,10 @@ fn make(step: *Step) !void {
14951495 }
14961496
14971497 if (!self.target.isNative()) {
1498 try zig_args.append("-target");
1499 try zig_args.append(try self.target.zigTriple(builder.allocator));
1500
1501 // TODO this logic can disappear if cpu model + features becomes part of the target triple
1502 const cross = self.target.toTarget();
1503 const all_features = cross.cpu.arch.allFeaturesList();
1504 var populated_cpu_features = cross.cpu.model.features;
1505 populated_cpu_features.populateDependencies(all_features);
1506
1507 if (populated_cpu_features.eql(cross.cpu.features)) {
1508 // The CPU name alone is sufficient.
1509 try zig_args.append("-mcpu");
1510 try zig_args.append(cross.cpu.model.name);
1511 } else {
1512 var mcpu_buffer = ArrayList(u8).init(builder.allocator);
1513
1514 try mcpu_buffer.writer().print("-mcpu={s}", .{cross.cpu.model.name});
1515
1516 for (all_features) |feature, i_usize| {
1517 const i = @intCast(std.Target.Cpu.Feature.Set.Index, i_usize);
1518 const in_cpu_set = populated_cpu_features.isEnabled(i);
1519 const in_actual_set = cross.cpu.features.isEnabled(i);
1520 if (in_cpu_set and !in_actual_set) {
1521 try mcpu_buffer.writer().print("-{s}", .{feature.name});
1522 } else if (!in_cpu_set and in_actual_set) {
1523 try mcpu_buffer.writer().print("+{s}", .{feature.name});
1524 }
1525 }
1526
1527 try zig_args.append(try mcpu_buffer.toOwnedSlice());
1528 }
1498 try zig_args.appendSlice(&.{
1499 "-target", try self.target.zigTriple(builder.allocator),
1500 "-mcpu", try build.serializeCpu(builder.allocator, self.target.getCpu()),
1501 });
15291502
15301503 if (self.target.dynamic_linker.get()) |dynamic_linker| {
15311504 try zig_args.append("--dynamic-linker");