authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-07 15:07:00-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-04-07 15:07:00-07:00
logfdd6c31e8b25f9eed81c1e78fa71eca17fd29f68
tree61686eb9bc1399946d702e2fe024b6bf259f5b44
parentc78f996ff986b8843f328e1f083547c538ac865b
parenteee5400b7dc37845ea5f42e0841320953e7852b2
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #19167 from castholm/installHeader

std.Build: fix `Compile.installHeader` behavior, add `WriteFile.addCopyDirectory`

14 files changed, 542 insertions(+), 140 deletions(-)

lib/std/Build.zig+13-12
......@@ -1568,23 +1568,24 @@ pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *S
15681568 return Step.ObjCopy.create(b, source, options);
15691569}
15701570
1571///`dest_rel_path` is relative to install prefix path
1572pub fn addInstallFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1573 return self.addInstallFileWithDir(source.dupe(self), .prefix, dest_rel_path);
1571/// `dest_rel_path` is relative to install prefix path
1572pub fn addInstallFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1573 return b.addInstallFileWithDir(source, .prefix, dest_rel_path);
15741574}
15751575
1576///`dest_rel_path` is relative to bin path
1577pub fn addInstallBinFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1578 return self.addInstallFileWithDir(source.dupe(self), .bin, dest_rel_path);
1576/// `dest_rel_path` is relative to bin path
1577pub fn addInstallBinFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1578 return b.addInstallFileWithDir(source, .bin, dest_rel_path);
15791579}
15801580
1581///`dest_rel_path` is relative to lib path
1582pub fn addInstallLibFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1583 return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path);
1581/// `dest_rel_path` is relative to lib path
1582pub fn addInstallLibFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1583 return b.addInstallFileWithDir(source, .lib, dest_rel_path);
15841584}
15851585
1586pub fn addInstallHeaderFile(b: *Build, src_path: []const u8, dest_rel_path: []const u8) *Step.InstallFile {
1587 return b.addInstallFileWithDir(.{ .path = src_path }, .header, dest_rel_path);
1586/// `dest_rel_path` is relative to header path
1587pub fn addInstallHeaderFile(b: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
1588 return b.addInstallFileWithDir(source, .header, dest_rel_path);
15881589}
15891590
15901591pub fn addInstallFileWithDir(
......@@ -1593,7 +1594,7 @@ pub fn addInstallFileWithDir(
15931594 install_dir: InstallDir,
15941595 dest_rel_path: []const u8,
15951596) *Step.InstallFile {
1596 return Step.InstallFile.create(self, source.dupe(self), install_dir, dest_rel_path);
1597 return Step.InstallFile.create(self, source, install_dir, dest_rel_path);
15971598}
15981599
15991600pub fn addInstallDirectory(self: *Build, options: Step.InstallDir.Options) *Step.InstallDir {
lib/std/Build/Module.zig+6-15
......@@ -265,8 +265,7 @@ fn addShallowDependencies(m: *Module, dependee: *Module) void {
265265 for (dependee.link_objects.items) |link_object| switch (link_object) {
266266 .other_step => |compile| {
267267 addStepDependencies(m, dependee, &compile.step);
268 for (compile.installed_headers.items) |install_step|
269 addStepDependenciesOnly(m, install_step);
268 addLazyPathDependenciesOnly(m, compile.getEmittedIncludeTree());
270269 },
271270
272271 .static_path,
......@@ -691,20 +690,14 @@ pub fn appendZigProcessFlags(
691690 },
692691 .other_step => |other| {
693692 if (other.generated_h) |header| {
694 try zig_args.append("-isystem");
695 try zig_args.append(std.fs.path.dirname(header.path.?).?);
693 try zig_args.appendSlice(&.{ "-isystem", std.fs.path.dirname(header.getPath()).? });
696694 }
697 if (other.installed_headers.items.len > 0) {
698 try zig_args.append("-I");
699 try zig_args.append(b.pathJoin(&.{
700 other.step.owner.install_prefix, "include",
701 }));
695 if (other.installed_headers_include_tree) |include_tree| {
696 try zig_args.appendSlice(&.{ "-I", include_tree.generated_directory.getPath() });
702697 }
703698 },
704699 .config_header_step => |config_header| {
705 const full_file_path = config_header.output_file.path.?;
706 const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len];
707 try zig_args.appendSlice(&.{ "-I", header_dir_path });
700 try zig_args.appendSlice(&.{ "-I", std.fs.path.dirname(config_header.output_file.getPath()).? });
708701 },
709702 }
710703 }
......@@ -752,9 +745,7 @@ fn linkLibraryOrObject(m: *Module, other: *Step.Compile) void {
752745 m.link_objects.append(allocator, .{ .other_step = other }) catch @panic("OOM");
753746 m.include_dirs.append(allocator, .{ .other_step = other }) catch @panic("OOM");
754747
755 for (other.installed_headers.items) |install_step| {
756 addStepDependenciesOnly(m, install_step);
757 }
748 addLazyPathDependenciesOnly(m, other.getEmittedIncludeTree());
758749}
759750
760751fn requireKnownTarget(m: *Module) std.Target {
lib/std/Build/Step/Compile.zig+163-66
......@@ -59,7 +59,13 @@ test_runner: ?[]const u8,
5959test_server_mode: bool,
6060wasi_exec_model: ?std.builtin.WasiExecModel = null,
6161
62installed_headers: ArrayList(*Step),
62installed_headers: ArrayList(HeaderInstallation),
63
64/// This step is used to create an include tree that dependent modules can add to their include
65/// search paths. Installed headers are copied to this step.
66/// This step is created the first time a module links with this artifact and is not
67/// created otherwise.
68installed_headers_include_tree: ?*Step.WriteFile = null,
6369
6470// keep in sync with src/Compilation.zig:RcIncludes
6571/// Behavior of automatic detection of include directories when compiling .rc files.
......@@ -249,6 +255,90 @@ pub const Kind = enum {
249255 @"test",
250256};
251257
258pub const HeaderInstallation = union(enum) {
259 file: File,
260 directory: Directory,
261
262 pub const File = struct {
263 source: LazyPath,
264 dest_rel_path: []const u8,
265
266 pub fn dupe(self: File, b: *std.Build) File {
267 // 'path' lazy paths are relative to the build root of some step, inferred from the step
268 // in which they are used. This means that we can't dupe such paths, because they may
269 // come from dependencies with their own build roots and duping the paths as is might
270 // cause the build script to search for the file relative to the wrong root.
271 // As a temporary workaround, we convert build root-relative paths to absolute paths.
272 // If/when the build-root relative paths are updated to encode which build root they are
273 // relative to, this workaround should be removed.
274 const duped_source: LazyPath = switch (self.source) {
275 .path => |root_rel| .{ .cwd_relative = b.pathFromRoot(root_rel) },
276 else => self.source.dupe(b),
277 };
278
279 return .{
280 .source = duped_source,
281 .dest_rel_path = b.dupePath(self.dest_rel_path),
282 };
283 }
284 };
285
286 pub const Directory = struct {
287 source: LazyPath,
288 dest_rel_path: []const u8,
289 options: Directory.Options,
290
291 pub const Options = struct {
292 /// File paths that end in any of these suffixes will be excluded from installation.
293 exclude_extensions: []const []const u8 = &.{},
294 /// Only file paths that end in any of these suffixes will be included in installation.
295 /// `null` means that all suffixes will be included.
296 /// `exclude_extensions` takes precedence over `include_extensions`.
297 include_extensions: ?[]const []const u8 = &.{".h"},
298
299 pub fn dupe(self: Directory.Options, b: *std.Build) Directory.Options {
300 return .{
301 .exclude_extensions = b.dupeStrings(self.exclude_extensions),
302 .include_extensions = if (self.include_extensions) |incs| b.dupeStrings(incs) else null,
303 };
304 }
305 };
306
307 pub fn dupe(self: Directory, b: *std.Build) Directory {
308 // 'path' lazy paths are relative to the build root of some step, inferred from the step
309 // in which they are used. This means that we can't dupe such paths, because they may
310 // come from dependencies with their own build roots and duping the paths as is might
311 // cause the build script to search for the file relative to the wrong root.
312 // As a temporary workaround, we convert build root-relative paths to absolute paths.
313 // If/when the build-root relative paths are updated to encode which build root they are
314 // relative to, this workaround should be removed.
315 const duped_source: LazyPath = switch (self.source) {
316 .path => |root_rel| .{ .cwd_relative = b.pathFromRoot(root_rel) },
317 else => self.source.dupe(b),
318 };
319
320 return .{
321 .source = duped_source,
322 .dest_rel_path = b.dupePath(self.dest_rel_path),
323 .options = self.options.dupe(b),
324 };
325 }
326 };
327
328 pub fn getSource(self: HeaderInstallation) LazyPath {
329 return switch (self) {
330 inline .file, .directory => |x| x.source,
331 };
332 }
333
334 pub fn dupe(self: HeaderInstallation, b: *std.Build) HeaderInstallation {
335 return switch (self) {
336 .file => |f| .{ .file = f.dupe(b) },
337 .directory => |d| .{ .directory = d.dupe(b) },
338 };
339 }
340};
341
252342pub fn create(owner: *std.Build, options: Options) *Compile {
253343 const name = owner.dupe(options.name);
254344 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
......@@ -308,7 +398,7 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
308398 .out_lib_filename = undefined,
309399 .major_only_filename = null,
310400 .name_only_filename = null,
311 .installed_headers = ArrayList(*Step).init(owner.allocator),
401 .installed_headers = ArrayList(HeaderInstallation).init(owner.allocator),
312402 .zig_lib_dir = null,
313403 .exec_cmd_args = null,
314404 .filters = options.filters,
......@@ -380,78 +470,85 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
380470 return self;
381471}
382472
383pub fn installHeader(cs: *Compile, src_path: []const u8, dest_rel_path: []const u8) void {
473/// Marks the specified header for installation alongside this artifact.
474/// When a module links with this artifact, all headers marked for installation are added to that
475/// module's include search path.
476pub fn installHeader(cs: *Compile, source: LazyPath, dest_rel_path: []const u8) void {
384477 const b = cs.step.owner;
385 const install_file = b.addInstallHeaderFile(src_path, dest_rel_path);
386 b.getInstallStep().dependOn(&install_file.step);
387 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
388}
389
390pub const InstallConfigHeaderOptions = struct {
391 install_dir: InstallDir = .header,
392 dest_rel_path: ?[]const u8 = null,
393};
394
395pub fn installConfigHeader(
396 cs: *Compile,
397 config_header: *Step.ConfigHeader,
398 options: InstallConfigHeaderOptions,
399) void {
400 const dest_rel_path = options.dest_rel_path orelse config_header.include_path;
401 const b = cs.step.owner;
402 const install_file = b.addInstallFileWithDir(
403 .{ .generated = &config_header.output_file },
404 options.install_dir,
405 dest_rel_path,
406 );
407 install_file.step.dependOn(&config_header.step);
408 b.getInstallStep().dependOn(&install_file.step);
409 cs.installed_headers.append(&install_file.step) catch @panic("OOM");
410}
411
478 const installation: HeaderInstallation = .{ .file = .{
479 .source = source.dupe(b),
480 .dest_rel_path = b.dupePath(dest_rel_path),
481 } };
482 cs.installed_headers.append(installation) catch @panic("OOM");
483 cs.addHeaderInstallationToIncludeTree(installation);
484 installation.getSource().addStepDependencies(&cs.step);
485}
486
487/// Marks headers from the specified directory for installation alongside this artifact.
488/// When a module links with this artifact, all headers marked for installation are added to that
489/// module's include search path.
412490pub fn installHeadersDirectory(
413 a: *Compile,
414 src_dir_path: []const u8,
415 dest_rel_path: []const u8,
416) void {
417 return installHeadersDirectoryOptions(a, .{
418 .source_dir = .{ .path = src_dir_path },
419 .install_dir = .header,
420 .install_subdir = dest_rel_path,
421 });
422}
423
424pub fn installHeadersDirectoryOptions(
425491 cs: *Compile,
426 options: std.Build.Step.InstallDir.Options,
492 source: LazyPath,
493 dest_rel_path: []const u8,
494 options: HeaderInstallation.Directory.Options,
427495) void {
428496 const b = cs.step.owner;
429 const install_dir = b.addInstallDirectory(options);
430 b.getInstallStep().dependOn(&install_dir.step);
431 cs.installed_headers.append(&install_dir.step) catch @panic("OOM");
497 const installation: HeaderInstallation = .{ .directory = .{
498 .source = source.dupe(b),
499 .dest_rel_path = b.dupePath(dest_rel_path),
500 .options = options.dupe(b),
501 } };
502 cs.installed_headers.append(installation) catch @panic("OOM");
503 cs.addHeaderInstallationToIncludeTree(installation);
504 installation.getSource().addStepDependencies(&cs.step);
505}
506
507/// Marks the specified config header for installation alongside this artifact.
508/// When a module links with this artifact, all headers marked for installation are added to that
509/// module's include search path.
510pub fn installConfigHeader(cs: *Compile, config_header: *Step.ConfigHeader) void {
511 cs.installHeader(config_header.getOutput(), config_header.include_path);
512}
513
514/// Forwards all headers marked for installation from `lib` to this artifact.
515/// When a module links with this artifact, all headers marked for installation are added to that
516/// module's include search path.
517pub fn installLibraryHeaders(cs: *Compile, lib: *Compile) void {
518 assert(lib.kind == .lib);
519 for (lib.installed_headers.items) |installation| {
520 const installation_copy = installation.dupe(lib.step.owner);
521 cs.installed_headers.append(installation_copy) catch @panic("OOM");
522 cs.addHeaderInstallationToIncludeTree(installation_copy);
523 installation_copy.getSource().addStepDependencies(&cs.step);
524 }
525}
526
527fn addHeaderInstallationToIncludeTree(cs: *Compile, installation: HeaderInstallation) void {
528 if (cs.installed_headers_include_tree) |wf| switch (installation) {
529 .file => |file| {
530 _ = wf.addCopyFile(file.source, file.dest_rel_path);
531 },
532 .directory => |dir| {
533 _ = wf.addCopyDirectory(dir.source, dir.dest_rel_path, .{
534 .exclude_extensions = dir.options.exclude_extensions,
535 .include_extensions = dir.options.include_extensions,
536 });
537 },
538 };
432539}
433540
434pub fn installLibraryHeaders(cs: *Compile, l: *Compile) void {
435 assert(l.kind == .lib);
541pub fn getEmittedIncludeTree(cs: *Compile) LazyPath {
542 if (cs.installed_headers_include_tree) |wf| return wf.getDirectory();
436543 const b = cs.step.owner;
437 const install_step = b.getInstallStep();
438 // Copy each element from installed_headers, modifying the builder
439 // to be the new parent's builder.
440 for (l.installed_headers.items) |step| {
441 const step_copy = switch (step.id) {
442 inline .install_file, .install_dir => |id| blk: {
443 const T = id.Type();
444 const ptr = b.allocator.create(T) catch @panic("OOM");
445 ptr.* = step.cast(T).?.*;
446 ptr.dest_builder = b;
447 break :blk &ptr.step;
448 },
449 else => unreachable,
450 };
451 cs.installed_headers.append(step_copy) catch @panic("OOM");
452 install_step.dependOn(step_copy);
453 }
454 cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM");
544 const wf = b.addWriteFiles();
545 cs.installed_headers_include_tree = wf;
546 for (cs.installed_headers.items) |installation| {
547 cs.addHeaderInstallationToIncludeTree(installation);
548 }
549 // The compile step itself does not need to depend on the write files step,
550 // only dependent modules do.
551 return wf.getDirectory();
455552}
456553
457554pub fn addObjCopy(cs: *Compile, options: Step.ObjCopy.Options) *Step.ObjCopy {
lib/std/Build/Step/InstallArtifact.zig+74-21
......@@ -77,12 +77,10 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
7777 },
7878 .h_dir = switch (options.h_dir) {
7979 .disabled => null,
80 // https://github.com/ziglang/zig/issues/9698
81 .default => null,
82 //.default => switch (artifact.kind) {
83 // .lib => .header,
84 // else => null,
85 //},
80 .default => switch (artifact.kind) {
81 .lib => .header,
82 else => null,
83 },
8684 .override => |o| o,
8785 },
8886 .implib_dir = switch (options.implib_dir) {
......@@ -113,7 +111,8 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
113111
114112 if (self.dest_dir != null) self.emitted_bin = artifact.getEmittedBin();
115113 if (self.pdb_dir != null) self.emitted_pdb = artifact.getEmittedPdb();
116 if (self.h_dir != null) self.emitted_h = artifact.getEmittedH();
114 // https://github.com/ziglang/zig/issues/9698
115 //if (self.h_dir != null) self.emitted_h = artifact.getEmittedH();
117116 if (self.implib_dir != null) self.emitted_implib = artifact.getEmittedImplib();
118117
119118 return self;
......@@ -122,14 +121,14 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
122121fn make(step: *Step, prog_node: *std.Progress.Node) !void {
123122 _ = prog_node;
124123 const self: *InstallArtifact = @fieldParentPtr("step", step);
125 const dest_builder = step.owner;
124 const b = step.owner;
126125 const cwd = fs.cwd();
127126
128127 var all_cached = true;
129128
130129 if (self.dest_dir) |dest_dir| {
131 const full_dest_path = dest_builder.getInstallPath(dest_dir, self.dest_sub_path);
132 const full_src_path = self.emitted_bin.?.getPath2(step.owner, step);
130 const full_dest_path = b.getInstallPath(dest_dir, self.dest_sub_path);
131 const full_src_path = self.emitted_bin.?.getPath2(b, step);
133132 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
134133 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
135134 full_src_path, full_dest_path, @errorName(err),
......@@ -145,8 +144,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
145144 }
146145
147146 if (self.implib_dir) |implib_dir| {
148 const full_src_path = self.emitted_implib.?.getPath2(step.owner, step);
149 const full_implib_path = dest_builder.getInstallPath(implib_dir, fs.path.basename(full_src_path));
147 const full_src_path = self.emitted_implib.?.getPath2(b, step);
148 const full_implib_path = b.getInstallPath(implib_dir, fs.path.basename(full_src_path));
150149 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| {
151150 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
152151 full_src_path, full_implib_path, @errorName(err),
......@@ -156,8 +155,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
156155 }
157156
158157 if (self.pdb_dir) |pdb_dir| {
159 const full_src_path = self.emitted_pdb.?.getPath2(step.owner, step);
160 const full_pdb_path = dest_builder.getInstallPath(pdb_dir, fs.path.basename(full_src_path));
158 const full_src_path = self.emitted_pdb.?.getPath2(b, step);
159 const full_pdb_path = b.getInstallPath(pdb_dir, fs.path.basename(full_src_path));
161160 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| {
162161 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
163162 full_src_path, full_pdb_path, @errorName(err),
......@@ -167,14 +166,68 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
167166 }
168167
169168 if (self.h_dir) |h_dir| {
170 const full_src_path = self.emitted_h.?.getPath2(step.owner, step);
171 const full_h_path = dest_builder.getInstallPath(h_dir, fs.path.basename(full_src_path));
172 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
173 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
174 full_src_path, full_h_path, @errorName(err),
175 });
169 if (self.emitted_h) |emitted_h| {
170 const full_src_path = emitted_h.getPath2(b, step);
171 const full_h_path = b.getInstallPath(h_dir, fs.path.basename(full_src_path));
172 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
173 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
174 full_src_path, full_h_path, @errorName(err),
175 });
176 };
177 all_cached = all_cached and p == .fresh;
178 }
179
180 for (self.artifact.installed_headers.items) |installation| switch (installation) {
181 .file => |file| {
182 const full_src_path = file.source.getPath2(b, step);
183 const full_h_path = b.getInstallPath(h_dir, file.dest_rel_path);
184 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
185 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
186 full_src_path, full_h_path, @errorName(err),
187 });
188 };
189 all_cached = all_cached and p == .fresh;
190 },
191 .directory => |dir| {
192 const full_src_dir_path = dir.source.getPath2(b, step);
193 const full_h_prefix = b.getInstallPath(h_dir, dir.dest_rel_path);
194
195 var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| {
196 return step.fail("unable to open source directory '{s}': {s}", .{
197 full_src_dir_path, @errorName(err),
198 });
199 };
200 defer src_dir.close();
201
202 var it = try src_dir.walk(b.allocator);
203 next_entry: while (try it.next()) |entry| {
204 for (dir.options.exclude_extensions) |ext| {
205 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
206 }
207 if (dir.options.include_extensions) |incs| {
208 for (incs) |inc| {
209 if (std.mem.endsWith(u8, entry.path, inc)) break;
210 } else {
211 continue :next_entry;
212 }
213 }
214 const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path });
215 const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path });
216 switch (entry.kind) {
217 .directory => try cwd.makePath(full_dest_path),
218 .file => {
219 const p = fs.Dir.updateFile(cwd, full_src_entry_path, cwd, full_dest_path, .{}) catch |err| {
220 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
221 full_src_entry_path, full_dest_path, @errorName(err),
222 });
223 };
224 all_cached = all_cached and p == .fresh;
225 },
226 else => continue,
227 }
228 }
229 },
176230 };
177 all_cached = all_cached and p == .fresh;
178231 }
179232
180233 step.result_cached = all_cached;
lib/std/Build/Step/InstallDir.zig+9-14
......@@ -8,9 +8,6 @@ const InstallDirStep = @This();
88
99step: Step,
1010options: Options,
11/// This is used by the build system when a file being installed comes from one
12/// package but is being installed by another.
13dest_builder: *std.Build,
1411
1512pub const base_id = .install_dir;
1613
......@@ -55,7 +52,6 @@ pub fn create(owner: *std.Build, options: Options) *InstallDirStep {
5552 .makeFn = make,
5653 }),
5754 .options = options.dupe(owner),
58 .dest_builder = owner,
5955 };
6056 options.source_dir.addStepDependencies(&self.step);
6157 return self;
......@@ -63,15 +59,14 @@ pub fn create(owner: *std.Build, options: Options) *InstallDirStep {
6359
6460fn make(step: *Step, prog_node: *std.Progress.Node) !void {
6561 _ = prog_node;
62 const b = step.owner;
6663 const self: *InstallDirStep = @fieldParentPtr("step", step);
67 const dest_builder = self.dest_builder;
68 const arena = dest_builder.allocator;
69 const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
70 const src_builder = self.step.owner;
71 const src_dir_path = self.options.source_dir.getPath2(src_builder, step);
72 var src_dir = src_builder.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
64 const arena = b.allocator;
65 const dest_prefix = b.getInstallPath(self.options.install_dir, self.options.install_subdir);
66 const src_dir_path = self.options.source_dir.getPath2(b, step);
67 var src_dir = b.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
7368 return step.fail("unable to open source directory '{}{s}': {s}", .{
74 src_builder.build_root, src_dir_path, @errorName(err),
69 b.build_root, src_dir_path, @errorName(err),
7570 });
7671 };
7772 defer src_dir.close();
......@@ -104,20 +99,20 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
10499 .file => {
105100 for (self.options.blank_extensions) |ext| {
106101 if (mem.endsWith(u8, entry.path, ext)) {
107 try dest_builder.truncateFile(dest_path);
102 try b.truncateFile(dest_path);
108103 continue :next_entry;
109104 }
110105 }
111106
112107 const prev_status = fs.Dir.updateFile(
113 src_builder.build_root.handle,
108 b.build_root.handle,
114109 src_sub_path,
115110 cwd,
116111 dest_path,
117112 .{},
118113 ) catch |err| {
119114 return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
120 src_builder.build_root, src_sub_path, dest_path, @errorName(err),
115 b.build_root, src_sub_path, dest_path, @errorName(err),
121116 });
122117 };
123118 all_cached = all_cached and prev_status == .fresh;
lib/std/Build/Step/InstallFile.zig+3-8
......@@ -11,9 +11,6 @@ step: Step,
1111source: LazyPath,
1212dir: InstallDir,
1313dest_rel_path: []const u8,
14/// This is used by the build system when a file being installed comes from one
15/// package but is being installed by another.
16dest_builder: *std.Build,
1714
1815pub fn create(
1916 owner: *std.Build,
......@@ -34,7 +31,6 @@ pub fn create(
3431 .source = source.dupe(owner),
3532 .dir = dir.dupe(owner),
3633 .dest_rel_path = owner.dupePath(dest_rel_path),
37 .dest_builder = owner,
3834 };
3935 source.addStepDependencies(&self.step);
4036 return self;
......@@ -42,11 +38,10 @@ pub fn create(
4238
4339fn make(step: *Step, prog_node: *std.Progress.Node) !void {
4440 _ = prog_node;
45 const src_builder = step.owner;
41 const b = step.owner;
4642 const self: *InstallFile = @fieldParentPtr("step", step);
47 const dest_builder = self.dest_builder;
48 const full_src_path = self.source.getPath2(src_builder, step);
49 const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path);
43 const full_src_path = self.source.getPath2(b, step);
44 const full_dest_path = b.getInstallPath(self.dir, self.dest_rel_path);
5045 const cwd = std.fs.cwd();
5146 const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
5247 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
lib/std/Build/Step/WriteFile.zig+130-4
......@@ -15,9 +15,11 @@ const ArrayList = std.ArrayList;
1515const WriteFile = @This();
1616
1717step: Step,
18/// The elements here are pointers because we need stable pointers for the
19/// GeneratedFile field.
18
19// The elements here are pointers because we need stable pointers for the GeneratedFile field.
2020files: std.ArrayListUnmanaged(*File),
21directories: std.ArrayListUnmanaged(*Directory),
22
2123output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
2224generated_directory: std.Build.GeneratedFile,
2325
......@@ -33,6 +35,33 @@ pub const File = struct {
3335 }
3436};
3537
38pub const Directory = struct {
39 source: std.Build.LazyPath,
40 sub_path: []const u8,
41 options: Options,
42 generated_dir: std.Build.GeneratedFile,
43
44 pub const Options = struct {
45 /// File paths that end in any of these suffixes will be excluded from copying.
46 exclude_extensions: []const []const u8 = &.{},
47 /// Only file paths that end in any of these suffixes will be included in copying.
48 /// `null` means that all suffixes will be included.
49 /// `exclude_extensions` takes precedence over `include_extensions`.
50 include_extensions: ?[]const []const u8 = null,
51
52 pub fn dupe(self: Options, b: *std.Build) Options {
53 return .{
54 .exclude_extensions = b.dupeStrings(self.exclude_extensions),
55 .include_extensions = if (self.include_extensions) |incs| b.dupeStrings(incs) else null,
56 };
57 }
58 };
59
60 pub fn getPath(self: *Directory) std.Build.LazyPath {
61 return .{ .generated = &self.generated_dir };
62 }
63};
64
3665pub const OutputSourceFile = struct {
3766 contents: Contents,
3867 sub_path: []const u8,
......@@ -53,6 +82,7 @@ pub fn create(owner: *std.Build) *WriteFile {
5382 .makeFn = make,
5483 }),
5584 .files = .{},
85 .directories = .{},
5686 .output_source_files = .{},
5787 .generated_directory = .{ .step = &wf.step },
5888 };
......@@ -96,6 +126,31 @@ pub fn addCopyFile(wf: *WriteFile, source: std.Build.LazyPath, sub_path: []const
96126 return file.getPath();
97127}
98128
129/// Copy files matching the specified exclude/include patterns to the specified subdirectory
130/// relative to this step's generated directory.
131/// The returned value is a lazy path to the generated subdirectory.
132pub fn addCopyDirectory(
133 wf: *WriteFile,
134 source: std.Build.LazyPath,
135 sub_path: []const u8,
136 options: Directory.Options,
137) std.Build.LazyPath {
138 const b = wf.step.owner;
139 const gpa = b.allocator;
140 const dir = gpa.create(Directory) catch @panic("OOM");
141 dir.* = .{
142 .source = source.dupe(b),
143 .sub_path = b.dupePath(sub_path),
144 .options = options.dupe(b),
145 .generated_dir = .{ .step = &wf.step },
146 };
147 wf.directories.append(gpa, dir) catch @panic("OOM");
148
149 wf.maybeUpdateName();
150 source.addStepDependencies(&wf.step);
151 return dir.getPath();
152}
153
99154/// A path relative to the package root.
100155/// Be careful with this because it updates source files. This should not be
101156/// used as part of the normal build process, but as a utility occasionally
......@@ -130,11 +185,16 @@ pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
130185}
131186
132187fn maybeUpdateName(wf: *WriteFile) void {
133 if (wf.files.items.len == 1) {
188 if (wf.files.items.len == 1 and wf.directories.items.len == 0) {
134189 // First time adding a file; update name.
135190 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
136191 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path});
137192 }
193 } else if (wf.directories.items.len == 1 and wf.files.items.len == 0) {
194 // First time adding a directory; update name.
195 if (std.mem.eql(u8, wf.step.name, "WriteFile")) {
196 wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.directories.items[0].sub_path});
197 }
138198 }
139199}
140200
......@@ -209,6 +269,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
209269 },
210270 }
211271 }
272 for (wf.directories.items) |dir| {
273 man.hash.addBytes(dir.source.getPath2(b, step));
274 man.hash.addBytes(dir.sub_path);
275 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);
276 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);
277 }
212278
213279 if (try step.cacheHit(&man)) {
214280 const digest = man.final();
......@@ -233,6 +299,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
233299 };
234300 defer cache_dir.close();
235301
302 const cwd = fs.cwd();
303
236304 for (wf.files.items) |file| {
237305 if (fs.path.dirname(file.sub_path)) |dirname| {
238306 cache_dir.makePath(dirname) catch |err| {
......@@ -252,7 +320,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
252320 .copy => |file_source| {
253321 const source_path = file_source.getPath(b);
254322 const prev_status = fs.Dir.updateFile(
255 fs.cwd(),
323 cwd,
256324 source_path,
257325 cache_dir,
258326 file.sub_path,
......@@ -279,6 +347,64 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
279347 cache_path, file.sub_path,
280348 });
281349 }
350 for (wf.directories.items) |dir| {
351 const full_src_dir_path = dir.source.getPath2(b, step);
352 const dest_dirname = dir.sub_path;
353
354 if (dest_dirname.len != 0) {
355 cache_dir.makePath(dest_dirname) catch |err| {
356 return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{
357 b.cache_root, cache_path, fs.path.sep, dest_dirname, @errorName(err),
358 });
359 };
360 }
361
362 var src_dir = b.build_root.handle.openDir(full_src_dir_path, .{ .iterate = true }) catch |err| {
363 return step.fail("unable to open source directory '{s}': {s}", .{
364 full_src_dir_path, @errorName(err),
365 });
366 };
367 defer src_dir.close();
368
369 var it = try src_dir.walk(b.allocator);
370 next_entry: while (try it.next()) |entry| {
371 for (dir.options.exclude_extensions) |ext| {
372 if (std.mem.endsWith(u8, entry.path, ext)) continue :next_entry;
373 }
374 if (dir.options.include_extensions) |incs| {
375 for (incs) |inc| {
376 if (std.mem.endsWith(u8, entry.path, inc)) break;
377 } else {
378 continue :next_entry;
379 }
380 }
381 const full_src_entry_path = b.pathJoin(&.{ full_src_dir_path, entry.path });
382 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });
383 switch (entry.kind) {
384 .directory => try cache_dir.makePath(dest_path),
385 .file => {
386 const prev_status = fs.Dir.updateFile(
387 cwd,
388 full_src_entry_path,
389 cache_dir,
390 dest_path,
391 .{},
392 ) catch |err| {
393 return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{
394 full_src_entry_path,
395 b.cache_root,
396 cache_path,
397 fs.path.sep,
398 dest_path,
399 @errorName(err),
400 });
401 };
402 _ = prev_status;
403 },
404 else => continue,
405 }
406 }
407 }
282408
283409 try step.writeManifest(&man);
284410}
test/standalone.zig+4
......@@ -266,6 +266,10 @@ pub const build_cases = [_]BuildCase{
266266 .build_root = "test/standalone/depend_on_main_mod",
267267 .import = @import("standalone/depend_on_main_mod/build.zig"),
268268 },
269 .{
270 .build_root = "test/standalone/install_headers",
271 .import = @import("standalone/install_headers/build.zig"),
272 },
269273};
270274
271275const std = @import("std");
test/standalone/install_headers/build.zig created+99
......@@ -0,0 +1,99 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test");
5 b.default_step = test_step;
6
7 const empty_c = b.addWriteFiles().add("empty.c", "");
8
9 const libfoo = b.addStaticLibrary(.{
10 .name = "foo",
11 .target = b.resolveTargetQuery(.{}),
12 .optimize = .Debug,
13 });
14 libfoo.addCSourceFile(.{ .file = empty_c });
15
16 const exe = b.addExecutable(.{
17 .name = "exe",
18 .target = b.resolveTargetQuery(.{}),
19 .optimize = .Debug,
20 .link_libc = true,
21 });
22 exe.addCSourceFile(.{ .file = b.addWriteFiles().add("main.c",
23 \\#include <stdio.h>
24 \\#include <foo/a.h>
25 \\#include <foo/sub_dir/b.h>
26 \\#include <foo/d.h>
27 \\#include <foo/config.h>
28 \\#include <bar.h>
29 \\int main(void) {
30 \\ printf(FOO_A FOO_B FOO_D FOO_CONFIG_1 FOO_CONFIG_2 BAR_X);
31 \\ return 0;
32 \\}
33 ) });
34
35 libfoo.installHeadersDirectory(.{ .path = "include" }, "foo", .{ .exclude_extensions = &.{".ignore_me.h"} });
36 libfoo.installHeader(b.addWriteFiles().add("d.h",
37 \\#define FOO_D "D"
38 \\
39 ), "foo/d.h");
40
41 if (libfoo.installed_headers_include_tree != null) std.debug.panic("include tree step was created before linking", .{});
42
43 // Link before we have registered all headers for installation,
44 // to verify that the lazily created write files step is properly taken into account.
45 exe.linkLibrary(libfoo);
46
47 if (libfoo.installed_headers_include_tree == null) std.debug.panic("include tree step was not created after linking", .{});
48
49 libfoo.installConfigHeader(b.addConfigHeader(.{
50 .style = .blank,
51 .include_path = "foo/config.h",
52 }, .{
53 .FOO_CONFIG_1 = "1",
54 .FOO_CONFIG_2 = "2",
55 }));
56
57 const libbar = b.addStaticLibrary(.{
58 .name = "bar",
59 .target = b.resolveTargetQuery(.{}),
60 .optimize = .Debug,
61 });
62 libbar.addCSourceFile(.{ .file = empty_c });
63 libbar.installHeader(b.addWriteFiles().add("bar.h",
64 \\#define BAR_X "X"
65 \\
66 ), "bar.h");
67 libfoo.installLibraryHeaders(libbar);
68
69 const run_exe = b.addRunArtifact(exe);
70 run_exe.expectStdOutEqual("ABD12X");
71 test_step.dependOn(&run_exe.step);
72
73 const install_libfoo = b.addInstallArtifact(libfoo, .{
74 .dest_dir = .{ .override = .{ .custom = "custom" } },
75 .h_dir = .{ .override = .{ .custom = "custom/include" } },
76 .implib_dir = .disabled,
77 .pdb_dir = .disabled,
78 });
79 const check_exists = b.addExecutable(.{
80 .name = "check_exists",
81 .root_source_file = .{ .path = "check_exists.zig" },
82 .target = b.resolveTargetQuery(.{}),
83 .optimize = .Debug,
84 });
85 const run_check_exists = b.addRunArtifact(check_exists);
86 run_check_exists.addArgs(&.{
87 "custom/include/foo/a.h",
88 "!custom/include/foo/ignore_me.txt",
89 "custom/include/foo/sub_dir/b.h",
90 "!custom/include/foo/sub_dir/c.ignore_me.h",
91 "custom/include/foo/d.h",
92 "custom/include/foo/config.h",
93 "custom/include/bar.h",
94 });
95 run_check_exists.setCwd(.{ .cwd_relative = b.getInstallPath(.prefix, "") });
96 run_check_exists.expectExitCode(0);
97 run_check_exists.step.dependOn(&install_libfoo.step);
98 test_step.dependOn(&run_check_exists.step);
99}
test/standalone/install_headers/check_exists.zig created+37
......@@ -0,0 +1,37 @@
1const std = @import("std");
2
3/// Checks the existence of files relative to cwd.
4/// A path starting with ! should not exist.
5pub fn main() !void {
6 var arena_state = std.heap.ArenaAllocator.init(std.heap.page_allocator);
7 defer arena_state.deinit();
8
9 const arena = arena_state.allocator();
10
11 var arg_it = try std.process.argsWithAllocator(arena);
12 _ = arg_it.next();
13
14 const cwd = std.fs.cwd();
15 const cwd_realpath = try cwd.realpathAlloc(arena, ".");
16
17 while (arg_it.next()) |file_path| {
18 if (file_path.len > 0 and file_path[0] == '!') {
19 errdefer std.log.err(
20 "exclusive file check '{s}{c}{s}' failed",
21 .{ cwd_realpath, std.fs.path.sep, file_path[1..] },
22 );
23 if (std.fs.cwd().statFile(file_path[1..])) |_| {
24 return error.FileFound;
25 } else |err| switch (err) {
26 error.FileNotFound => {},
27 else => return err,
28 }
29 } else {
30 errdefer std.log.err(
31 "inclusive file check '{s}{c}{s}' failed",
32 .{ cwd_realpath, std.fs.path.sep, file_path },
33 );
34 _ = try std.fs.cwd().statFile(file_path);
35 }
36 }
37}
test/standalone/install_headers/include/a.h created+1
......@@ -0,0 +1 @@
1#define FOO_A "A"
test/standalone/install_headers/include/ignore_me.txt created+1
......@@ -0,0 +1 @@
1ignore me
test/standalone/install_headers/include/sub_dir/b.h created+1
......@@ -0,0 +1 @@
1#define FOO_B "B"
test/standalone/install_headers/include/sub_dir/c.ignore_me.h created+1
......@@ -0,0 +1 @@
1#error "ignore me"