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 {...@@ -73,8 +73,6 @@ pub const Builder = struct {
73 build_root: []const u8,73 build_root: []const u8,
74 cache_root: []const u8,74 cache_root: []const u8,
75 global_cache_root: []const u8,75 global_cache_root: []const u8,
76 release_mode: ?std.builtin.Mode,
77 is_release: bool,
78 /// zig lib dir76 /// zig lib dir
79 override_lib_dir: ?[]const u8,77 override_lib_dir: ?[]const u8,
80 vcpkg_root: VcpkgRoot = .unattempted,78 vcpkg_root: VcpkgRoot = .unattempted,
...@@ -150,6 +148,7 @@ pub const Builder = struct {...@@ -150,6 +148,7 @@ pub const Builder = struct {
150 flag: void,148 flag: void,
151 scalar: []const u8,149 scalar: []const u8,
152 list: ArrayList([]const u8),150 list: ArrayList([]const u8),
151 map: StringHashMap(*const UserValue),
153 };152 };
154153
155 const TypeId = enum {154 const TypeId = enum {
...@@ -223,8 +222,6 @@ pub const Builder = struct {...@@ -223,8 +222,6 @@ pub const Builder = struct {
223 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),222 .step = Step.init(.top_level, "uninstall", allocator, makeUninstall),
224 .description = "Remove build artifacts from prefix path",223 .description = "Remove build artifacts from prefix path",
225 },224 },
226 .release_mode = null,
227 .is_release = false,
228 .override_lib_dir = null,225 .override_lib_dir = null,
229 .install_path = undefined,226 .install_path = undefined,
230 .args = null,227 .args = null,
...@@ -291,8 +288,6 @@ pub const Builder = struct {...@@ -291,8 +288,6 @@ pub const Builder = struct {
291 .build_root = build_root,288 .build_root = build_root,
292 .cache_root = parent.cache_root,289 .cache_root = parent.cache_root,
293 .global_cache_root = parent.global_cache_root,290 .global_cache_root = parent.global_cache_root,
294 .release_mode = parent.release_mode,
295 .is_release = parent.is_release,
296 .override_lib_dir = parent.override_lib_dir,291 .override_lib_dir = parent.override_lib_dir,
297 .debug_log_scopes = parent.debug_log_scopes,292 .debug_log_scopes = parent.debug_log_scopes,
298 .debug_compile_errors = parent.debug_compile_errors,293 .debug_compile_errors = parent.debug_compile_errors,
...@@ -312,10 +307,55 @@ pub const Builder = struct {...@@ -312,10 +307,55 @@ pub const Builder = struct {
312 }307 }
313308
314 fn applyArgs(b: *Builder, args: anytype) !void {309 fn applyArgs(b: *Builder, args: anytype) !void {
315 // TODO this function is the way that a build.zig file communicates310 inline for (@typeInfo(@TypeOf(args)).Struct.fields) |field| {
316 // options to its dependencies. It is the programmatic way to give311 const v = @field(args, field.name);
317 // command line arguments to a build.zig script.312 const T = @TypeOf(v);
318 _ = args;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 }
319 const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);359 const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
320 // Random bytes to make unique. Refresh this with new random bytes when360 // Random bytes to make unique. Refresh this with new random bytes when
321 // implementation is modified in a non-backwards-compatible way.361 // implementation is modified in a non-backwards-compatible way.
...@@ -679,15 +719,19 @@ pub const Builder = struct {...@@ -679,15 +719,19 @@ pub const Builder = struct {
679 return null;719 return null;
680 }720 }
681 },721 },
682 .list => {722 .list, .map => {
683 log.err("Expected -D{s} to be a boolean, but received a list.\n", .{name});723 log.err("Expected -D{s} to be a boolean, but received a {s}.\n", .{
724 name, @tagName(option_ptr.value),
725 });
684 self.markInvalidUserInput();726 self.markInvalidUserInput();
685 return null;727 return null;
686 },728 },
687 },729 },
688 .int => switch (option_ptr.value) {730 .int => switch (option_ptr.value) {
689 .flag => {731 .flag, .list, .map => {
690 log.err("Expected -D{s} to be an integer, but received a boolean.\n", .{name});732 log.err("Expected -D{s} to be an integer, but received a {s}.\n", .{
733 name, @tagName(option_ptr.value),
734 });
691 self.markInvalidUserInput();735 self.markInvalidUserInput();
692 return null;736 return null;
693 },737 },
...@@ -706,15 +750,12 @@ pub const Builder = struct {...@@ -706,15 +750,12 @@ pub const Builder = struct {
706 };750 };
707 return n;751 return n;
708 },752 },
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 },
714 },753 },
715 .float => switch (option_ptr.value) {754 .float => switch (option_ptr.value) {
716 .flag => {755 .flag, .map, .list => {
717 log.err("Expected -D{s} to be a float, but received a boolean.\n", .{name});756 log.err("Expected -D{s} to be a float, but received a {s}.\n", .{
757 name, @tagName(option_ptr.value),
758 });
718 self.markInvalidUserInput();759 self.markInvalidUserInput();
719 return null;760 return null;
720 },761 },
...@@ -726,15 +767,12 @@ pub const Builder = struct {...@@ -726,15 +767,12 @@ pub const Builder = struct {
726 };767 };
727 return n;768 return n;
728 },769 },
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 },
734 },770 },
735 .@"enum" => switch (option_ptr.value) {771 .@"enum" => switch (option_ptr.value) {
736 .flag => {772 .flag, .map, .list => {
737 log.err("Expected -D{s} to be a string, but received a boolean.\n", .{name});773 log.err("Expected -D{s} to be an enum, but received a {s}.\n", .{
774 name, @tagName(option_ptr.value),
775 });
738 self.markInvalidUserInput();776 self.markInvalidUserInput();
739 return null;777 return null;
740 },778 },
...@@ -747,28 +785,22 @@ pub const Builder = struct {...@@ -747,28 +785,22 @@ pub const Builder = struct {
747 return null;785 return null;
748 }786 }
749 },787 },
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 },
755 },788 },
756 .string => switch (option_ptr.value) {789 .string => switch (option_ptr.value) {
757 .flag => {790 .flag, .list, .map => {
758 log.err("Expected -D{s} to be a string, but received a boolean.\n", .{name});791 log.err("Expected -D{s} to be a string, but received a {s}.\n", .{
759 self.markInvalidUserInput();792 name, @tagName(option_ptr.value),
760 return null;793 });
761 },
762 .list => {
763 log.err("Expected -D{s} to be a string, but received a list.\n", .{name});
764 self.markInvalidUserInput();794 self.markInvalidUserInput();
765 return null;795 return null;
766 },796 },
767 .scalar => |s| return s,797 .scalar => |s| return s,
768 },798 },
769 .list => switch (option_ptr.value) {799 .list => switch (option_ptr.value) {
770 .flag => {800 .flag, .map => {
771 log.err("Expected -D{s} to be a list, but received a boolean.\n", .{name});801 log.err("Expected -D{s} to be a list, but received a {s}.\n", .{
802 name, @tagName(option_ptr.value),
803 });
772 self.markInvalidUserInput();804 self.markInvalidUserInput();
773 return null;805 return null;
774 },806 },
...@@ -790,41 +822,24 @@ pub const Builder = struct {...@@ -790,41 +822,24 @@ pub const Builder = struct {
790 return &step_info.step;822 return &step_info.step;
791 }823 }
792824
793 /// This provides the -Drelease option to the build user and does not give them the choice.825 pub const StandardOptimizeOptionOptions = struct {
794 pub fn setPreferredReleaseMode(self: *Builder, mode: std.builtin.Mode) void {826 preferred_optimize_mode: ?std.builtin.Mode = null,
795 if (self.release_mode != null) {827 };
796 @panic("setPreferredReleaseMode must be called before standardReleaseOptions and may not be called twice");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;
797 }842 }
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;
828 }843 }
829844
830 pub const StandardTargetOptionsArgs = struct {845 pub const StandardTargetOptionsArgs = struct {
...@@ -1004,6 +1019,11 @@ pub const Builder = struct {...@@ -1004,6 +1019,11 @@ pub const Builder = struct {
1004 log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });1019 log.warn("Option '-D{s}={s}' conflicts with flag '-D{s}'.", .{ name, value, name });
1005 return true;1020 return true;
1006 },1021 },
1022 .map => |*map| {
1023 _ = map;
1024 log.warn("TODO maps as command line arguments is not implemented yet.", .{});
1025 return true;
1026 },
1007 }1027 }
1008 return false;1028 return false;
1009 }1029 }
...@@ -1026,7 +1046,7 @@ pub const Builder = struct {...@@ -1026,7 +1046,7 @@ pub const Builder = struct {
1026 log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });1046 log.err("Flag '-D{s}' conflicts with option '-D{s}={s}'.", .{ name, name, s });
1027 return true;1047 return true;
1028 },1048 },
1029 .list => {1049 .list, .map => {
1030 log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});1050 log.err("Flag '-D{s}' conflicts with multiple options of the same name.", .{name});
1031 return true;1051 return true;
1032 },1052 },
...@@ -1058,7 +1078,7 @@ pub const Builder = struct {...@@ -1058,7 +1078,7 @@ pub const Builder = struct {
1058 var it = self.user_input_options.iterator();1078 var it = self.user_input_options.iterator();
1059 while (it.next()) |entry| {1079 while (it.next()) |entry| {
1060 if (!entry.value_ptr.used) {1080 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.*});
1062 self.markInvalidUserInput();1082 self.markInvalidUserInput();
1063 }1083 }
1064 }1084 }
...@@ -1456,6 +1476,11 @@ pub const Builder = struct {...@@ -1456,6 +1476,11 @@ pub const Builder = struct {
1456 ) *Dependency {1476 ) *Dependency {
1457 const sub_builder = b.createChild(name, build_root, args) catch unreachable;1477 const sub_builder = b.createChild(name, build_root, args) catch unreachable;
1458 sub_builder.runBuild(build_zig) catch unreachable;1478 sub_builder.runBuild(build_zig) catch unreachable;
1479
1480 if (sub_builder.validateUserInputDidItFail()) {
1481 std.debug.dumpCurrentStackTrace(@returnAddress());
1482 }
1483
1459 const dep = b.allocator.create(Dependency) catch unreachable;1484 const dep = b.allocator.create(Dependency) catch unreachable;
1460 dep.* = .{ .builder = sub_builder };1485 dep.* = .{ .builder = sub_builder };
1461 return dep;1486 return dep;
...@@ -1718,6 +1743,34 @@ pub const InstalledFile = struct {...@@ -1718,6 +1743,34 @@ pub const InstalledFile = struct {
1718 }1743 }
1719};1744};
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
1721test "dupePkg()" {1774test "dupePkg()" {
1722 if (builtin.os.tag == .wasi) return error.SkipZigTest;1775 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 {...@@ -1495,37 +1495,10 @@ fn make(step: *Step) !void {
1495 }1495 }
14961496
1497 if (!self.target.isNative()) {1497 if (!self.target.isNative()) {
1498 try zig_args.append("-target");1498 try zig_args.appendSlice(&.{
1499 try zig_args.append(try self.target.zigTriple(builder.allocator));1499 "-target", try self.target.zigTriple(builder.allocator),
15001500 "-mcpu", try build.serializeCpu(builder.allocator, self.target.getCpu()),
1501 // TODO this logic can disappear if cpu model + features becomes part of the target triple1501 });
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 }
15291502
1530 if (self.target.dynamic_linker.get()) |dynamic_linker| {1503 if (self.target.dynamic_linker.get()) |dynamic_linker| {
1531 try zig_args.append("--dynamic-linker");1504 try zig_args.append("--dynamic-linker");