authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-03 17:22:31-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-02-04 01:55:39-05:00
log693b12f8e1c1d99d33c01961c1fdc972a5255eb6
treeda0106bbc81d8830be509ffda0d7c0107a7400db
parent4009e0d2b1ac5b0a9a0caff676d5c73ae18c6e9e

std.Build: support exposing and depending on zig modules

New API introduced: std.Build.addModule This function exposes a zig module with the given name, which can be used by packages that depend on this one via std.Build.Dependency.module. std.Build.Pkg and related functionality is deleted. Every use case has a straightforward upgrade path using the new Module struct. std.Build.OptionsStep.getPackage is replaced by std.Build.OptionsStep.createModule. std.Build.CompileStep.addPackagePath is replaced by std.Build.CompileStep.addAnonymousModule. This partially addresses #14307 by renaming some of the instances of "package" to "module". Closes #14278

4 files changed, 106 insertions(+), 144 deletions(-)

lib/std/Build.zig+60-70
...@@ -109,6 +109,8 @@ host: NativeTargetInfo,...@@ -109,6 +109,8 @@ host: NativeTargetInfo,
109109
110dep_prefix: []const u8 = "",110dep_prefix: []const u8 = "",
111111
112modules: std.StringArrayHashMap(*Module),
113
112pub const ExecError = error{114pub const ExecError = error{
113 ReadFailure,115 ReadFailure,
114 ExitCodeFailure,116 ExitCodeFailure,
...@@ -232,6 +234,7 @@ pub fn create(...@@ -232,6 +234,7 @@ pub fn create(
232 .install_path = undefined,234 .install_path = undefined,
233 .args = null,235 .args = null,
234 .host = host,236 .host = host,
237 .modules = std.StringArrayHashMap(*Module).init(allocator),
235 };238 };
236 try self.top_level_steps.append(&self.install_tls);239 try self.top_level_steps.append(&self.install_tls);
237 try self.top_level_steps.append(&self.uninstall_tls);240 try self.top_level_steps.append(&self.uninstall_tls);
...@@ -305,6 +308,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: []const u8)...@@ -305,6 +308,7 @@ fn createChildOnly(parent: *Build, dep_name: []const u8, build_root: []const u8)
305 .glibc_runtimes_dir = parent.glibc_runtimes_dir,308 .glibc_runtimes_dir = parent.glibc_runtimes_dir,
306 .host = parent.host,309 .host = parent.host,
307 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),310 .dep_prefix = parent.fmt("{s}{s}.", .{ parent.dep_prefix, dep_name }),
311 .modules = std.StringArrayHashMap(*Module).init(allocator),
308 };312 };
309 try child.top_level_steps.append(&child.install_tls);313 try child.top_level_steps.append(&child.install_tls);
310 try child.top_level_steps.append(&child.uninstall_tls);314 try child.top_level_steps.append(&child.uninstall_tls);
...@@ -539,6 +543,49 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {...@@ -539,6 +543,49 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *CompileStep {
539 return obj_step;543 return obj_step;
540}544}
541545
546pub const AddModuleOptions = struct {
547 name: []const u8,
548 source_file: FileSource,
549 dependencies: []const ModuleDependency = &.{},
550};
551
552pub fn addModule(b: *Build, options: AddModuleOptions) void {
553 b.modules.put(b.dupe(options.name), b.createModule(.{
554 .source_file = options.source_file,
555 .dependencies = options.dependencies,
556 })) catch @panic("OOM");
557}
558
559pub const ModuleDependency = struct {
560 name: []const u8,
561 module: *Module,
562};
563
564pub const CreateModuleOptions = struct {
565 source_file: FileSource,
566 dependencies: []const ModuleDependency = &.{},
567};
568
569/// Prefer to use `addModule` which will make the module available to other
570/// packages which depend on this package.
571pub fn createModule(b: *Build, options: CreateModuleOptions) *Module {
572 const module = b.allocator.create(Module) catch @panic("OOM");
573 module.* = .{
574 .builder = b,
575 .source_file = options.source_file,
576 .dependencies = moduleDependenciesToArrayHashMap(b.allocator, options.dependencies),
577 };
578 return module;
579}
580
581fn moduleDependenciesToArrayHashMap(arena: Allocator, deps: []const ModuleDependency) std.StringArrayHashMap(*Module) {
582 var result = std.StringArrayHashMap(*Module).init(arena);
583 for (deps) |dep| {
584 result.put(dep.name, dep.module) catch @panic("OOM");
585 }
586 return result;
587}
588
542/// Initializes a RunStep with argv, which must at least have the path to the589/// Initializes a RunStep with argv, which must at least have the path to the
543/// executable. More command line arguments can be added with `addArg`,590/// executable. More command line arguments can be added with `addArg`,
544/// `addArgs`, and `addArtifactArg`.591/// `addArgs`, and `addArtifactArg`.
...@@ -588,24 +635,6 @@ pub fn dupePath(self: *Build, bytes: []const u8) []u8 {...@@ -588,24 +635,6 @@ pub fn dupePath(self: *Build, bytes: []const u8) []u8 {
588 return the_copy;635 return the_copy;
589}636}
590637
591/// Duplicates a package recursively.
592pub fn dupePkg(self: *Build, package: Pkg) Pkg {
593 var the_copy = Pkg{
594 .name = self.dupe(package.name),
595 .source = package.source.dupe(self),
596 };
597
598 if (package.dependencies) |dependencies| {
599 const new_dependencies = self.allocator.alloc(Pkg, dependencies.len) catch @panic("OOM");
600 the_copy.dependencies = new_dependencies;
601
602 for (dependencies) |dep_package, i| {
603 new_dependencies[i] = self.dupePkg(dep_package);
604 }
605 }
606 return the_copy;
607}
608
609pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *WriteFileStep {638pub fn addWriteFile(self: *Build, file_path: []const u8, data: []const u8) *WriteFileStep {
610 const write_file_step = self.addWriteFiles();639 const write_file_step = self.addWriteFiles();
611 write_file_step.add(file_path, data);640 write_file_step.add(file_path, data);
...@@ -1479,6 +1508,12 @@ pub const Dependency = struct {...@@ -1479,6 +1508,12 @@ pub const Dependency = struct {
1479 panic("unable to find artifact '{s}'", .{name});1508 panic("unable to find artifact '{s}'", .{name});
1480 };1509 };
1481 }1510 }
1511
1512 pub fn module(d: *Dependency, name: []const u8) *Module {
1513 return d.builder.modules.get(name) orelse {
1514 panic("unable to find module '{s}'", .{name});
1515 };
1516 }
1482};1517};
14831518
1484pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {1519pub fn dependency(b: *Build, name: []const u8, args: anytype) *Dependency {
...@@ -1548,10 +1583,13 @@ test "builder.findProgram compiles" {...@@ -1548,10 +1583,13 @@ test "builder.findProgram compiles" {
1548 _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null;1583 _ = builder.findProgram(&[_][]const u8{}, &[_][]const u8{}) catch null;
1549}1584}
15501585
1551pub const Pkg = struct {1586pub const Module = struct {
1552 name: []const u8,1587 builder: *Build,
1553 source: FileSource,1588 /// This could either be a generated file, in which case the module
1554 dependencies: ?[]const Pkg = null,1589 /// contains exactly one file, or it could be a path to the root source
1590 /// file of directory of files which constitute the module.
1591 source_file: FileSource,
1592 dependencies: std.StringArrayHashMap(*Module),
1555};1593};
15561594
1557/// A file that is generated by a build step.1595/// A file that is generated by a build step.
...@@ -1713,54 +1751,6 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {...@@ -1713,54 +1751,6 @@ pub fn serializeCpu(allocator: Allocator, cpu: std.Target.Cpu) ![]const u8 {
1713 }1751 }
1714}1752}
17151753
1716test "dupePkg()" {
1717 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1718
1719 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
1720 defer arena.deinit();
1721
1722 const host = try NativeTargetInfo.detect(.{});
1723
1724 var builder = try Build.create(
1725 arena.allocator(),
1726 "test",
1727 "test",
1728 "test",
1729 "test",
1730 host,
1731 );
1732 defer builder.destroy();
1733
1734 var pkg_dep = Pkg{
1735 .name = "pkg_dep",
1736 .source = .{ .path = "/not/a/pkg_dep.zig" },
1737 };
1738 var pkg_top = Pkg{
1739 .name = "pkg_top",
1740 .source = .{ .path = "/not/a/pkg_top.zig" },
1741 .dependencies = &[_]Pkg{pkg_dep},
1742 };
1743 const duped = builder.dupePkg(pkg_top);
1744
1745 const original_deps = pkg_top.dependencies.?;
1746 const dupe_deps = duped.dependencies.?;
1747
1748 // probably the same top level package details
1749 try std.testing.expectEqualStrings(pkg_top.name, duped.name);
1750
1751 // probably the same dependencies
1752 try std.testing.expectEqual(original_deps.len, dupe_deps.len);
1753 try std.testing.expectEqual(original_deps[0].name, pkg_dep.name);
1754
1755 // could segfault otherwise if pointers in duplicated package's fields are
1756 // the same as those in stack allocated package's fields
1757 try std.testing.expect(dupe_deps.ptr != original_deps.ptr);
1758 try std.testing.expect(duped.name.ptr != pkg_top.name.ptr);
1759 try std.testing.expect(duped.source.path.ptr != pkg_top.source.path.ptr);
1760 try std.testing.expect(dupe_deps[0].name.ptr != pkg_dep.name.ptr);
1761 try std.testing.expect(dupe_deps[0].source.path.ptr != pkg_dep.source.path.ptr);
1762}
1763
1764test {1754test {
1765 _ = CheckFileStep;1755 _ = CheckFileStep;
1766 _ = CheckObjectStep;1756 _ = CheckObjectStep;
lib/std/Build/CompileStep.zig+40-71
...@@ -16,7 +16,7 @@ const FileSource = std.Build.FileSource;...@@ -16,7 +16,7 @@ const FileSource = std.Build.FileSource;
16const PkgConfigPkg = std.Build.PkgConfigPkg;16const PkgConfigPkg = std.Build.PkgConfigPkg;
17const PkgConfigError = std.Build.PkgConfigError;17const PkgConfigError = std.Build.PkgConfigError;
18const ExecError = std.Build.ExecError;18const ExecError = std.Build.ExecError;
19const Pkg = std.Build.Pkg;19const Module = std.Build.Module;
20const VcpkgRoot = std.Build.VcpkgRoot;20const VcpkgRoot = std.Build.VcpkgRoot;
21const InstallDir = std.Build.InstallDir;21const InstallDir = std.Build.InstallDir;
22const InstallArtifactStep = std.Build.InstallArtifactStep;22const InstallArtifactStep = std.Build.InstallArtifactStep;
...@@ -99,7 +99,7 @@ root_src: ?FileSource,...@@ -99,7 +99,7 @@ root_src: ?FileSource,
99out_h_filename: []const u8,99out_h_filename: []const u8,
100out_lib_filename: []const u8,100out_lib_filename: []const u8,
101out_pdb_filename: []const u8,101out_pdb_filename: []const u8,
102packages: ArrayList(Pkg),102modules: std.StringArrayHashMap(*Module),
103103
104object_src: []const u8,104object_src: []const u8,
105105
...@@ -334,7 +334,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {...@@ -334,7 +334,7 @@ pub fn create(builder: *std.Build, options: Options) *CompileStep {
334 .out_pdb_filename = builder.fmt("{s}.pdb", .{name}),334 .out_pdb_filename = builder.fmt("{s}.pdb", .{name}),
335 .major_only_filename = null,335 .major_only_filename = null,
336 .name_only_filename = null,336 .name_only_filename = null,
337 .packages = ArrayList(Pkg).init(builder.allocator),337 .modules = std.StringArrayHashMap(*Module).init(builder.allocator),
338 .include_dirs = ArrayList(IncludeDir).init(builder.allocator),338 .include_dirs = ArrayList(IncludeDir).init(builder.allocator),
339 .link_objects = ArrayList(LinkObject).init(builder.allocator),339 .link_objects = ArrayList(LinkObject).init(builder.allocator),
340 .c_macros = ArrayList([]const u8).init(builder.allocator),340 .c_macros = ArrayList([]const u8).init(builder.allocator),
...@@ -946,29 +946,29 @@ pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void {...@@ -946,29 +946,29 @@ pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void {
946 self.framework_dirs.append(self.builder.dupe(dir_path)) catch @panic("OOM");946 self.framework_dirs.append(self.builder.dupe(dir_path)) catch @panic("OOM");
947}947}
948948
949pub fn addPackage(self: *CompileStep, package: Pkg) void {949/// Adds a module to be used with `@import` and exposing it in the current
950 self.packages.append(self.builder.dupePkg(package)) catch @panic("OOM");950/// package's module table using `name`.
951 self.addRecursiveBuildDeps(package);951pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void {
952 cs.modules.put(cs.builder.dupe(name), module) catch @panic("OOM");
953 cs.addRecursiveBuildDeps(module);
952}954}
953955
954pub fn addOptions(self: *CompileStep, package_name: []const u8, options: *OptionsStep) void {956/// Adds a module to be used with `@import` without exposing it in the current
955 self.addPackage(options.getPackage(package_name));957/// package's module table.
958pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void {
959 const module = cs.builder.createModule(options);
960 return addModule(cs, name, module);
956}961}
957962
958fn addRecursiveBuildDeps(self: *CompileStep, package: Pkg) void {963pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void {
959 package.source.addStepDependencies(&self.step);964 addModule(cs, module_name, options.createModule());
960 if (package.dependencies) |deps| {
961 for (deps) |dep| {
962 self.addRecursiveBuildDeps(dep);
963 }
964 }
965}965}
966966
967pub fn addPackagePath(self: *CompileStep, name: []const u8, pkg_index_path: []const u8) void {967fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module) void {
968 self.addPackage(Pkg{968 module.source_file.addStepDependencies(&cs.step);
969 .name = self.builder.dupe(name),969 for (module.dependencies.values()) |dep| {
970 .source = .{ .path = self.builder.dupe(pkg_index_path) },970 cs.addRecursiveBuildDeps(dep);
971 });971 }
972}972}
973973
974/// If Vcpkg was found on the system, it will be added to include and lib974/// If Vcpkg was found on the system, it will be added to include and lib
...@@ -1023,16 +1023,21 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void {...@@ -1023,16 +1023,21 @@ fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void {
1023 self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");1023 self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");
1024}1024}
10251025
1026fn makePackageCmd(self: *CompileStep, pkg: Pkg, zig_args: *ArrayList([]const u8)) error{OutOfMemory}!void {1026fn appendModuleArgs(
1027 const builder = self.builder;1027 cs: *CompileStep,
10281028 zig_args: *ArrayList([]const u8),
1029 name: []const u8,
1030 module: *Module,
1031) error{OutOfMemory}!void {
1029 try zig_args.append("--pkg-begin");1032 try zig_args.append("--pkg-begin");
1030 try zig_args.append(pkg.name);1033 try zig_args.append(name);
1031 try zig_args.append(builder.pathFromRoot(pkg.source.getPath(self.builder)));1034 try zig_args.append(module.builder.pathFromRoot(module.source_file.getPath(module.builder)));
10321035
1033 if (pkg.dependencies) |dependencies| {1036 {
1034 for (dependencies) |sub_pkg| {1037 const keys = module.dependencies.keys();
1035 try self.makePackageCmd(sub_pkg, zig_args);1038 for (module.dependencies.values()) |sub_module, i| {
1039 const sub_name = keys[i];
1040 try cs.appendModuleArgs(zig_args, sub_name, sub_module);
1036 }1041 }
1037 }1042 }
10381043
...@@ -1563,8 +1568,12 @@ fn make(step: *Step) !void {...@@ -1563,8 +1568,12 @@ fn make(step: *Step) !void {
1563 try zig_args.append("--test-no-exec");1568 try zig_args.append("--test-no-exec");
1564 }1569 }
15651570
1566 for (self.packages.items) |pkg| {1571 {
1567 try self.makePackageCmd(pkg, &zig_args);1572 const keys = self.modules.keys();
1573 for (self.modules.values()) |module, i| {
1574 const name = keys[i];
1575 try self.appendModuleArgs(&zig_args, name, module);
1576 }
1568 }1577 }
15691578
1570 for (self.include_dirs.items) |include_dir| {1579 for (self.include_dirs.items) |include_dir| {
...@@ -1942,46 +1951,6 @@ fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg {...@@ -1942,46 +1951,6 @@ fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg {
1942 }1951 }
1943}1952}
19441953
1945test "addPackage" {
1946 if (builtin.os.tag == .wasi) return error.SkipZigTest;
1947
1948 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
1949 defer arena.deinit();
1950
1951 const host = try NativeTargetInfo.detect(.{});
1952
1953 var builder = try std.Build.create(
1954 arena.allocator(),
1955 "test",
1956 "test",
1957 "test",
1958 "test",
1959 host,
1960 );
1961 defer builder.destroy();
1962
1963 const pkg_dep = Pkg{
1964 .name = "pkg_dep",
1965 .source = .{ .path = "/not/a/pkg_dep.zig" },
1966 };
1967 const pkg_top = Pkg{
1968 .name = "pkg_dep",
1969 .source = .{ .path = "/not/a/pkg_top.zig" },
1970 .dependencies = &[_]Pkg{pkg_dep},
1971 };
1972
1973 var exe = builder.addExecutable(.{
1974 .name = "not_an_executable",
1975 .root_source_file = .{ .path = "/not/an/executable.zig" },
1976 });
1977 exe.addPackage(pkg_top);
1978
1979 try std.testing.expectEqual(@as(usize, 1), exe.packages.items.len);
1980
1981 const dupe = exe.packages.items[0];
1982 try std.testing.expectEqualStrings(pkg_top.name, dupe.name);
1983}
1984
1985fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void {1954fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void {
1986 const cond = opt orelse return;1955 const cond = opt orelse return;
1987 try args.ensureUnusedCapacity(1);1956 try args.ensureUnusedCapacity(1);
lib/std/Build/OptionsStep.zig+5-2
...@@ -204,8 +204,11 @@ pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *Compil...@@ -204,8 +204,11 @@ pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *Compil
204 self.step.dependOn(&artifact.step);204 self.step.dependOn(&artifact.step);
205}205}
206206
207pub fn getPackage(self: *OptionsStep, package_name: []const u8) std.Build.Pkg {207pub fn createModule(self: *OptionsStep) *std.Build.Module {
208 return .{ .name = package_name, .source = self.getSource() };208 return self.builder.createModule(.{
209 .source_file = self.getSource(),
210 .dependencies = &.{},
211 });
209}212}
210213
211pub fn getSource(self: *OptionsStep) FileSource {214pub fn getSource(self: *OptionsStep) FileSource {
test/standalone/pkg_import/build.zig+1-1
...@@ -8,7 +8,7 @@ pub fn build(b: *std.Build) void {...@@ -8,7 +8,7 @@ pub fn build(b: *std.Build) void {
8 .root_source_file = .{ .path = "test.zig" },8 .root_source_file = .{ .path = "test.zig" },
9 .optimize = optimize,9 .optimize = optimize,
10 });10 });
11 exe.addPackagePath("my_pkg", "pkg.zig");11 exe.addAnonymousModule("my_pkg", .{ .source_file = .{ .path = "pkg.zig" } });
1212
13 const run = exe.run();13 const run = exe.run();
1414