aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/InstallDirStep.zig
blob: 41dbb3e35a5f1d455a6dac09f6d127e113989625 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const std = @import("../std.zig");
const mem = std.mem;
const fs = std.fs;
const Step = std.Build.Step;
const InstallDir = std.Build.InstallDir;
const InstallDirStep = @This();
const log = std.log;

step: Step,
builder: *std.Build,
options: Options,
/// This is used by the build system when a file being installed comes from one
/// package but is being installed by another.
override_source_builder: ?*std.Build = null,

pub const base_id = .install_dir;

pub const Options = struct {
    source_dir: []const u8,
    install_dir: InstallDir,
    install_subdir: []const u8,
    /// File paths which end in any of these suffixes will be excluded
    /// from being installed.
    exclude_extensions: []const []const u8 = &.{},
    /// File paths which end in any of these suffixes will result in
    /// empty files being installed. This is mainly intended for large
    /// test.zig files in order to prevent needless installation bloat.
    /// However if the files were not present at all, then
    /// `@import("test.zig")` would be a compile error.
    blank_extensions: []const []const u8 = &.{},

    fn dupe(self: Options, b: *std.Build) Options {
        return .{
            .source_dir = b.dupe(self.source_dir),
            .install_dir = self.install_dir.dupe(b),
            .install_subdir = b.dupe(self.install_subdir),
            .exclude_extensions = b.dupeStrings(self.exclude_extensions),
            .blank_extensions = b.dupeStrings(self.blank_extensions),
        };
    }
};

pub fn init(
    builder: *std.Build,
    options: Options,
) InstallDirStep {
    builder.pushInstalledFile(options.install_dir, options.install_subdir);
    return InstallDirStep{
        .builder = builder,
        .step = Step.init(.install_dir, builder.fmt("install {s}/", .{options.source_dir}), builder.allocator, make),
        .options = options.dupe(builder),
    };
}

fn make(step: *Step) !void {
    const self = @fieldParentPtr(InstallDirStep, "step", step);
    const dest_prefix = self.builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
    const src_builder = self.override_source_builder orelse self.builder;
    const full_src_dir = src_builder.pathFromRoot(self.options.source_dir);
    var src_dir = std.fs.cwd().openIterableDir(full_src_dir, .{}) catch |err| {
        log.err("InstallDirStep: unable to open source directory '{s}': {s}", .{
            full_src_dir, @errorName(err),
        });
        return error.StepFailed;
    };
    defer src_dir.close();
    var it = try src_dir.walk(self.builder.allocator);
    next_entry: while (try it.next()) |entry| {
        for (self.options.exclude_extensions) |ext| {
            if (mem.endsWith(u8, entry.path, ext)) {
                continue :next_entry;
            }
        }

        const full_path = self.builder.pathJoin(&.{ full_src_dir, entry.path });
        const dest_path = self.builder.pathJoin(&.{ dest_prefix, entry.path });

        switch (entry.kind) {
            .Directory => try fs.cwd().makePath(dest_path),
            .File => {
                for (self.options.blank_extensions) |ext| {
                    if (mem.endsWith(u8, entry.path, ext)) {
                        try self.builder.truncateFile(dest_path);
                        continue :next_entry;
                    }
                }

                try self.builder.updateFile(full_path, dest_path);
            },
            else => continue,
        }
    }
}