aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/Step/InstallDir.zig
blob: 7472dba2a740483f4610f1f7557da3b2719ac83b (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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const std = @import("std");
const mem = std.mem;
const fs = std.fs;
const Step = std.Build.Step;
const LazyPath = std.Build.LazyPath;
const InstallDir = std.Build.InstallDir;
const InstallDirStep = @This();

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

pub const base_id = .install_dir;

pub const Options = struct {
    source_dir: LazyPath,
    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 = &.{},
    /// Only file paths which end in any of these suffixes will be included
    /// in installation. `null` means all suffixes are valid for this option.
    /// `exclude_extensions` take precedence over `include_extensions`
    include_extensions: ?[]const []const u8 = null,
    /// 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 = self.source_dir.dupe(b),
            .install_dir = self.install_dir.dupe(b),
            .install_subdir = b.dupe(self.install_subdir),
            .exclude_extensions = b.dupeStrings(self.exclude_extensions),
            .include_extensions = if (self.include_extensions) |incs| b.dupeStrings(incs) else null,
            .blank_extensions = b.dupeStrings(self.blank_extensions),
        };
    }
};

pub fn create(owner: *std.Build, options: Options) *InstallDirStep {
    owner.pushInstalledFile(options.install_dir, options.install_subdir);
    const self = owner.allocator.create(InstallDirStep) catch @panic("OOM");
    self.* = .{
        .step = Step.init(.{
            .id = .install_dir,
            .name = owner.fmt("install {s}/", .{options.source_dir.getDisplayName()}),
            .owner = owner,
            .makeFn = make,
        }),
        .options = options.dupe(owner),
        .dest_builder = owner,
    };
    options.source_dir.addStepDependencies(&self.step);
    return self;
}

fn make(step: *Step, prog_node: *std.Progress.Node) !void {
    _ = prog_node;
    const self = @fieldParentPtr(InstallDirStep, "step", step);
    const dest_builder = self.dest_builder;
    const arena = dest_builder.allocator;
    const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir);
    const src_builder = self.step.owner;
    const src_dir_path = self.options.source_dir.getPath2(src_builder, step);
    var src_dir = src_builder.build_root.handle.openDir(src_dir_path, .{ .iterate = true }) catch |err| {
        return step.fail("unable to open source directory '{}{s}': {s}", .{
            src_builder.build_root, src_dir_path, @errorName(err),
        });
    };
    defer src_dir.close();
    var it = try src_dir.walk(arena);
    var all_cached = true;
    next_entry: while (try it.next()) |entry| {
        for (self.options.exclude_extensions) |ext| {
            if (mem.endsWith(u8, entry.path, ext)) {
                continue :next_entry;
            }
        }
        if (self.options.include_extensions) |incs| {
            var found = false;
            for (incs) |inc| {
                if (mem.endsWith(u8, entry.path, inc)) {
                    found = true;
                    break;
                }
            }
            if (!found) continue :next_entry;
        }

        // relative to src build root
        const src_sub_path = try fs.path.join(arena, &.{ src_dir_path, entry.path });
        const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path });
        const cwd = fs.cwd();

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

                const prev_status = fs.Dir.updateFile(
                    src_builder.build_root.handle,
                    src_sub_path,
                    cwd,
                    dest_path,
                    .{},
                ) catch |err| {
                    return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{
                        src_builder.build_root, src_sub_path, dest_path, @errorName(err),
                    });
                };
                all_cached = all_cached and prev_status == .fresh;
            },
            else => continue,
        }
    }

    step.result_cached = all_cached;
}