authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-15 17:28:12-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-03-15 17:28:12-04:00
loga2432b6755c3f5c7f05ed219aefbf290aeb358cb
treefa1bd38109ba5ecc932d4766da2b4b25b5303b5c
parent0a69a10f2a1997edaeae047c44eca7b260ab72b9
parenta27a8561e9387b22d7c694af924e8f2ed0f17290
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4735 from ziglang/renameat

zig build system: correctly handle multiple output artifacts

12 files changed, 407 insertions(+), 176 deletions(-)

doc/docgen.zig+18-2
...@@ -1096,6 +1096,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var...@@ -1096,6 +1096,9 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
1096 try build_args.append("-lc");1096 try build_args.append("-lc");
1097 try out.print(" -lc", .{});1097 try out.print(" -lc", .{});
1098 }1098 }
1099 const target = try std.zig.CrossTarget.parse(.{
1100 .arch_os_abi = code.target_str orelse "native",
1101 });
1099 if (code.target_str) |triple| {1102 if (code.target_str) |triple| {
1100 try build_args.appendSlice(&[_][]const u8{ "-target", triple });1103 try build_args.appendSlice(&[_][]const u8{ "-target", triple });
1101 if (!code.is_inline) {1104 if (!code.is_inline) {
...@@ -1150,7 +1153,15 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var...@@ -1150,7 +1153,15 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
1150 }1153 }
1151 }1154 }
11521155
1153 const path_to_exe = mem.trim(u8, exec_result.stdout, " \r\n");1156 const path_to_exe_dir = mem.trim(u8, exec_result.stdout, " \r\n");
1157 const path_to_exe_basename = try std.fmt.allocPrint(allocator, "{}{}", .{
1158 code.name,
1159 target.exeFileExt(),
1160 });
1161 const path_to_exe = try fs.path.join(allocator, &[_][]const u8{
1162 path_to_exe_dir,
1163 path_to_exe_basename,
1164 });
1154 const run_args = &[_][]const u8{path_to_exe};1165 const run_args = &[_][]const u8{path_to_exe};
11551166
1156 var exited_with_signal = false;1167 var exited_with_signal = false;
...@@ -1486,7 +1497,12 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var...@@ -1486,7 +1497,12 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var
1486}1497}
14871498
1488fn exec(allocator: *mem.Allocator, env_map: *std.BufMap, args: []const []const u8) !ChildProcess.ExecResult {1499fn exec(allocator: *mem.Allocator, env_map: *std.BufMap, args: []const []const u8) !ChildProcess.ExecResult {
1489 const result = try ChildProcess.exec(allocator, args, null, env_map, max_doc_file_size);1500 const result = try ChildProcess.exec2(.{
1501 .allocator = allocator,
1502 .argv = args,
1503 .env_map = env_map,
1504 .max_output_bytes = max_doc_file_size,
1505 });
1490 switch (result.term) {1506 switch (result.term) {
1491 .Exited => |exit_code| {1507 .Exited => |exit_code| {
1492 if (exit_code != 0) {1508 if (exit_code != 0) {
lib/std/build.zig+13-8
...@@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {...@@ -2144,17 +2144,22 @@ pub const LibExeObjStep = struct {
2144 try zig_args.append("--cache");2144 try zig_args.append("--cache");
2145 try zig_args.append("on");2145 try zig_args.append("on");
21462146
2147 const output_path_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);2147 const output_dir_nl = try builder.execFromStep(zig_args.toSliceConst(), &self.step);
2148 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");2148 const build_output_dir = mem.trimRight(u8, output_dir_nl, "\r\n");
21492149
2150 if (self.output_dir) |output_dir| {2150 if (self.output_dir) |output_dir| {
2151 const full_dest = try fs.path.join(builder.allocator, &[_][]const u8{2151 var src_dir = try std.fs.cwd().openDirTraverse(build_output_dir);
2152 output_dir,2152 defer src_dir.close();
2153 fs.path.basename(output_path),2153
2154 });2154 var dest_dir = try std.fs.cwd().openDirList(output_dir);
2155 try builder.updateFile(output_path, full_dest);2155 defer dest_dir.close();
2156
2157 var it = src_dir.iterate();
2158 while (try it.next()) |entry| {
2159 _ = try src_dir.updateFile(entry.name, dest_dir, entry.name, .{});
2160 }
2156 } else {2161 } else {
2157 self.output_dir = fs.path.dirname(output_path).?;2162 self.output_dir = build_output_dir;
2158 }2163 }
2159 }2164 }
21602165
lib/std/c.zig+1
...@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;...@@ -106,6 +106,7 @@ pub extern "c" fn mkdir(path: [*:0]const u8, mode: c_uint) c_int;
106pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;106pub extern "c" fn mkdirat(dirfd: fd_t, path: [*:0]const u8, mode: u32) c_int;
107pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;107pub extern "c" fn symlink(existing: [*:0]const u8, new: [*:0]const u8) c_int;
108pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;108pub extern "c" fn rename(old: [*:0]const u8, new: [*:0]const u8) c_int;
109pub extern "c" fn renameat(olddirfd: fd_t, old: [*:0]const u8, newdirfd: fd_t, new: [*:0]const u8) c_int;
109pub extern "c" fn chdir(path: [*:0]const u8) c_int;110pub extern "c" fn chdir(path: [*:0]const u8) c_int;
110pub extern "c" fn fchdir(fd: fd_t) c_int;111pub extern "c" fn fchdir(fd: fd_t) c_int;
111pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;112pub extern "c" fn execve(path: [*:0]const u8, argv: [*:null]const ?[*:0]const u8, envp: [*:null]const ?[*:0]const u8) c_int;
lib/std/fs.zig+119-142
...@@ -81,60 +81,21 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:...@@ -81,60 +81,21 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
81 }81 }
82}82}
8383
84// TODO fix enum literal not casting to error union84pub const PrevStatus = enum {
85const PrevStatus = enum {
86 stale,85 stale,
87 fresh,86 fresh,
88};87};
8988
89/// Deprecated; use `Dir.updateFile`.
90pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {90pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {
91 return updateFileMode(source_path, dest_path, null);91 const my_cwd = cwd();
92 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{});
92}93}
9394
94/// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.95/// Deprecated; use `Dir.updateFile`.
95/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,96pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !Dir.PrevStatus {
96/// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
97/// Returns the previous status of the file before updating.
98/// If any of the directories do not exist for dest_path, they are created.
99/// TODO rework this to integrate with Dir
100pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus {
101 const my_cwd = cwd();97 const my_cwd = cwd();
10298 return Dir.updateFile(my_cwd, source_path, my_cwd, dest_path, .{ .override_mode = mode });
103 var src_file = try my_cwd.openFile(source_path, .{});
104 defer src_file.close();
105
106 const src_stat = try src_file.stat();
107 check_dest_stat: {
108 const dest_stat = blk: {
109 var dest_file = my_cwd.openFile(dest_path, .{}) catch |err| switch (err) {
110 error.FileNotFound => break :check_dest_stat,
111 else => |e| return e,
112 };
113 defer dest_file.close();
114
115 break :blk try dest_file.stat();
116 };
117
118 if (src_stat.size == dest_stat.size and
119 src_stat.mtime == dest_stat.mtime and
120 src_stat.mode == dest_stat.mode)
121 {
122 return PrevStatus.fresh;
123 }
124 }
125 const actual_mode = mode orelse src_stat.mode;
126
127 if (path.dirname(dest_path)) |dirname| {
128 try cwd().makePath(dirname);
129 }
130
131 var atomic_file = try AtomicFile.init(dest_path, actual_mode);
132 defer atomic_file.deinit();
133
134 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
135 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
136 try atomic_file.finish();
137 return PrevStatus.stale;
138}99}
139100
140/// Guaranteed to be atomic.101/// Guaranteed to be atomic.
...@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M...@@ -172,43 +133,40 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
172 return atomic_file.finish();133 return atomic_file.finish();
173}134}
174135
175/// TODO update this API to avoid a getrandom syscall for every operation. It136/// TODO update this API to avoid a getrandom syscall for every operation.
176/// should accept a random interface.
177/// TODO rework this to integrate with Dir
178pub const AtomicFile = struct {137pub const AtomicFile = struct {
179 file: File,138 file: File,
180 tmp_path_buf: [MAX_PATH_BYTES]u8,139 tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8,
181 dest_path: []const u8,140 dest_path: []const u8,
182 finished: bool,141 file_open: bool,
142 file_exists: bool,
143 dir: Dir,
183144
184 const InitError = File.OpenError;145 const InitError = File.OpenError;
185146
186 /// dest_path must remain valid for the lifetime of AtomicFile147 /// TODO rename this. Callers should go through Dir API
187 /// call finish to atomically replace dest_path with contents148 pub fn init2(dest_path: []const u8, mode: File.Mode, dir: Dir) InitError!AtomicFile {
188 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
189 const dirname = path.dirname(dest_path);149 const dirname = path.dirname(dest_path);
190 var rand_buf: [12]u8 = undefined;150 var rand_buf: [12]u8 = undefined;
191 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;151 const dirname_component_len = if (dirname) |d| d.len + 1 else 0;
192 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);152 const encoded_rand_len = comptime base64.Base64Encoder.calcSize(rand_buf.len);
193 const tmp_path_len = dirname_component_len + encoded_rand_len;153 const tmp_path_len = dirname_component_len + encoded_rand_len;
194 var tmp_path_buf: [MAX_PATH_BYTES]u8 = undefined;154 var tmp_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined;
195 if (tmp_path_len >= tmp_path_buf.len) return error.NameTooLong;155 if (tmp_path_len > tmp_path_buf.len) return error.NameTooLong;
196156
197 if (dirname) |dir| {157 if (dirname) |dn| {
198 mem.copy(u8, tmp_path_buf[0..], dir);158 mem.copy(u8, tmp_path_buf[0..], dn);
199 tmp_path_buf[dir.len] = path.sep;159 tmp_path_buf[dn.len] = path.sep;
200 }160 }
201161
202 tmp_path_buf[tmp_path_len] = 0;162 tmp_path_buf[tmp_path_len] = 0;
203 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];163 const tmp_path_slice = tmp_path_buf[0..tmp_path_len :0];
204164
205 const my_cwd = cwd();
206
207 while (true) {165 while (true) {
208 try crypto.randomBytes(rand_buf[0..]);166 try crypto.randomBytes(rand_buf[0..]);
209 base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);167 base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);
210168
211 const file = my_cwd.createFileC(169 const file = dir.createFileC(
212 tmp_path_slice,170 tmp_path_slice,
213 .{ .mode = mode, .exclusive = true },171 .{ .mode = mode, .exclusive = true },
214 ) catch |err| switch (err) {172 ) catch |err| switch (err) {
...@@ -220,33 +178,46 @@ pub const AtomicFile = struct {...@@ -220,33 +178,46 @@ pub const AtomicFile = struct {
220 .file = file,178 .file = file,
221 .tmp_path_buf = tmp_path_buf,179 .tmp_path_buf = tmp_path_buf,
222 .dest_path = dest_path,180 .dest_path = dest_path,
223 .finished = false,181 .file_open = true,
182 .file_exists = true,
183 .dir = dir,
224 };184 };
225 }185 }
226 }186 }
227187
188 /// Deprecated. Use `Dir.atomicFile`.
189 pub fn init(dest_path: []const u8, mode: File.Mode) InitError!AtomicFile {
190 return init2(dest_path, mode, cwd());
191 }
192
228 /// always call deinit, even after successful finish()193 /// always call deinit, even after successful finish()
229 pub fn deinit(self: *AtomicFile) void {194 pub fn deinit(self: *AtomicFile) void {
230 if (!self.finished) {195 if (self.file_open) {
231 self.file.close();196 self.file.close();
232 cwd().deleteFileC(@ptrCast([*:0]u8, &self.tmp_path_buf)) catch {};197 self.file_open = false;
233 self.finished = true;198 }
199 if (self.file_exists) {
200 self.dir.deleteFileC(&self.tmp_path_buf) catch {};
201 self.file_exists = false;
234 }202 }
203 self.* = undefined;
235 }204 }
236205
237 pub fn finish(self: *AtomicFile) !void {206 pub fn finish(self: *AtomicFile) !void {
238 assert(!self.finished);207 assert(self.file_exists);
208 if (self.file_open) {
209 self.file.close();
210 self.file_open = false;
211 }
239 if (std.Target.current.os.tag == .windows) {212 if (std.Target.current.os.tag == .windows) {
240 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);213 const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_path);
241 const tmp_path_w = try os.windows.cStrToPrefixedFileW(@ptrCast([*:0]u8, &self.tmp_path_buf));214 const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf);
242 self.file.close();215 try os.renameatW(self.dir.fd, &tmp_path_w, self.dir.fd, &dest_path_w, os.windows.TRUE);
243 self.finished = true;216 self.file_exists = false;
244 return os.renameW(&tmp_path_w, &dest_path_w);
245 } else {217 } else {
246 const dest_path_c = try os.toPosixPath(self.dest_path);218 const dest_path_c = try os.toPosixPath(self.dest_path);
247 self.file.close();219 try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c);
248 self.finished = true;220 self.file_exists = false;
249 return os.renameC(@ptrCast([*:0]u8, &self.tmp_path_buf), &dest_path_c);
250 }221 }
251 }222 }
252};223};
...@@ -694,7 +665,10 @@ pub const Dir = struct {...@@ -694,7 +665,10 @@ pub const Dir = struct {
694 const access_mask = w.SYNCHRONIZE |665 const access_mask = w.SYNCHRONIZE |
695 (if (flags.read) @as(u32, w.GENERIC_READ) else 0) |666 (if (flags.read) @as(u32, w.GENERIC_READ) else 0) |
696 (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0);667 (if (flags.write) @as(u32, w.GENERIC_WRITE) else 0);
697 return self.openFileWindows(sub_path_w, access_mask, w.FILE_OPEN);668 return @as(File, .{
669 .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, w.FILE_OPEN),
670 .io_mode = .blocking,
671 });
698 }672 }
699673
700 /// Creates, opens, or overwrites a file with write access.674 /// Creates, opens, or overwrites a file with write access.
...@@ -739,7 +713,10 @@ pub const Dir = struct {...@@ -739,7 +713,10 @@ pub const Dir = struct {
739 @as(u32, w.FILE_OVERWRITE_IF)713 @as(u32, w.FILE_OVERWRITE_IF)
740 else714 else
741 @as(u32, w.FILE_OPEN_IF);715 @as(u32, w.FILE_OPEN_IF);
742 return self.openFileWindows(sub_path_w, access_mask, creation);716 return @as(File, .{
717 .handle = try os.windows.OpenFileW(self.fd, sub_path_w, null, access_mask, creation),
718 .io_mode = .blocking,
719 });
743 }720 }
744721
745 /// Deprecated; call `openFile` directly.722 /// Deprecated; call `openFile` directly.
...@@ -757,72 +734,6 @@ pub const Dir = struct {...@@ -757,72 +734,6 @@ pub const Dir = struct {
757 return self.openFileW(sub_path, .{});734 return self.openFileW(sub_path, .{});
758 }735 }
759736
760 pub fn openFileWindows(
761 self: Dir,
762 sub_path_w: [*:0]const u16,
763 access_mask: os.windows.ACCESS_MASK,
764 creation: os.windows.ULONG,
765 ) File.OpenError!File {
766 const w = os.windows;
767
768 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
769 return error.IsDir;
770 }
771 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
772 return error.IsDir;
773 }
774
775 var result = File{
776 .handle = undefined,
777 .io_mode = .blocking,
778 };
779
780 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
781 error.Overflow => return error.NameTooLong,
782 };
783 var nt_name = w.UNICODE_STRING{
784 .Length = path_len_bytes,
785 .MaximumLength = path_len_bytes,
786 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
787 };
788 var attr = w.OBJECT_ATTRIBUTES{
789 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
790 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
791 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
792 .ObjectName = &nt_name,
793 .SecurityDescriptor = null,
794 .SecurityQualityOfService = null,
795 };
796 var io: w.IO_STATUS_BLOCK = undefined;
797 const rc = w.ntdll.NtCreateFile(
798 &result.handle,
799 access_mask,
800 &attr,
801 &io,
802 null,
803 w.FILE_ATTRIBUTE_NORMAL,
804 w.FILE_SHARE_WRITE | w.FILE_SHARE_READ | w.FILE_SHARE_DELETE,
805 creation,
806 w.FILE_NON_DIRECTORY_FILE | w.FILE_SYNCHRONOUS_IO_NONALERT,
807 null,
808 0,
809 );
810 switch (rc) {
811 .SUCCESS => return result,
812 .OBJECT_NAME_INVALID => unreachable,
813 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
814 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
815 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
816 .INVALID_PARAMETER => unreachable,
817 .SHARING_VIOLATION => return error.SharingViolation,
818 .ACCESS_DENIED => return error.AccessDenied,
819 .PIPE_BUSY => return error.PipeBusy,
820 .OBJECT_PATH_SYNTAX_BAD => unreachable,
821 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
822 else => return w.unexpectedStatus(rc),
823 }
824 }
825
826 pub fn makeDir(self: Dir, sub_path: []const u8) !void {737 pub fn makeDir(self: Dir, sub_path: []const u8) !void {
827 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);738 try os.mkdirat(self.fd, sub_path, default_new_dir_mode);
828 }739 }
...@@ -898,6 +809,7 @@ pub const Dir = struct {...@@ -898,6 +809,7 @@ pub const Dir = struct {
898 /// Call `close` on the result when done.809 /// Call `close` on the result when done.
899 ///810 ///
900 /// Asserts that the path parameter has no null bytes.811 /// Asserts that the path parameter has no null bytes.
812 /// TODO collapse this and `openDirList` into one function with an options parameter
901 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {813 pub fn openDirTraverse(self: Dir, sub_path: []const u8) OpenError!Dir {
902 if (builtin.os.tag == .windows) {814 if (builtin.os.tag == .windows) {
903 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);815 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
...@@ -915,6 +827,7 @@ pub const Dir = struct {...@@ -915,6 +827,7 @@ pub const Dir = struct {
915 /// Call `close` on the result when done.827 /// Call `close` on the result when done.
916 ///828 ///
917 /// Asserts that the path parameter has no null bytes.829 /// Asserts that the path parameter has no null bytes.
830 /// TODO collapse this and `openDirTraverse` into one function with an options parameter
918 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {831 pub fn openDirList(self: Dir, sub_path: []const u8) OpenError!Dir {
919 if (builtin.os.tag == .windows) {832 if (builtin.os.tag == .windows) {
920 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);833 const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path);
...@@ -1370,6 +1283,70 @@ pub const Dir = struct {...@@ -1370,6 +1283,70 @@ pub const Dir = struct {
1370 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {1283 pub fn accessW(self: Dir, sub_path_w: [*:0]const u16, flags: File.OpenFlags) AccessError!void {
1371 return os.faccessatW(self.fd, sub_path_w, 0, 0);1284 return os.faccessatW(self.fd, sub_path_w, 0, 0);
1372 }1285 }
1286
1287 pub const UpdateFileOptions = struct {
1288 override_mode: ?File.Mode = null,
1289 };
1290
1291 /// Check the file size, mtime, and mode of `source_path` and `dest_path`. If they are equal, does nothing.
1292 /// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime,
1293 /// atime, and mode of the source file so that the next call to `updateFile` will not need a copy.
1294 /// Returns the previous status of the file before updating.
1295 /// If any of the directories do not exist for dest_path, they are created.
1296 /// If `override_mode` is provided, then that value is used rather than the source path's mode.
1297 pub fn updateFile(
1298 source_dir: Dir,
1299 source_path: []const u8,
1300 dest_dir: Dir,
1301 dest_path: []const u8,
1302 options: UpdateFileOptions,
1303 ) !PrevStatus {
1304 var src_file = try source_dir.openFile(source_path, .{});
1305 defer src_file.close();
1306
1307 const src_stat = try src_file.stat();
1308 const actual_mode = options.override_mode orelse src_stat.mode;
1309 check_dest_stat: {
1310 const dest_stat = blk: {
1311 var dest_file = dest_dir.openFile(dest_path, .{}) catch |err| switch (err) {
1312 error.FileNotFound => break :check_dest_stat,
1313 else => |e| return e,
1314 };
1315 defer dest_file.close();
1316
1317 break :blk try dest_file.stat();
1318 };
1319
1320 if (src_stat.size == dest_stat.size and
1321 src_stat.mtime == dest_stat.mtime and
1322 actual_mode == dest_stat.mode)
1323 {
1324 return PrevStatus.fresh;
1325 }
1326 }
1327
1328 if (path.dirname(dest_path)) |dirname| {
1329 try dest_dir.makePath(dirname);
1330 }
1331
1332 var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = actual_mode });
1333 defer atomic_file.deinit();
1334
1335 try atomic_file.file.writeFileAll(src_file, .{ .in_len = src_stat.size });
1336 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
1337 try atomic_file.finish();
1338 return PrevStatus.stale;
1339 }
1340
1341 pub const AtomicFileOptions = struct {
1342 mode: File.Mode = File.default_mode,
1343 };
1344
1345 /// `dest_path` must remain valid for the lifetime of `AtomicFile`.
1346 /// Call `AtomicFile.finish` to atomically replace `dest_path` with contents.
1347 pub fn atomicFile(self: Dir, dest_path: []const u8, options: AtomicFileOptions) !AtomicFile {
1348 return AtomicFile.init2(dest_path, options.mode, self);
1349 }
1373};1350};
13741351
1375/// Returns an handle to the current working directory that is open for traversal.1352/// Returns an handle to the current working directory that is open for traversal.
lib/std/mem.zig+17-3
...@@ -116,7 +116,7 @@ pub const Allocator = struct {...@@ -116,7 +116,7 @@ pub const Allocator = struct {
116 pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem {116 pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem {
117 var ptr = try self.alloc(Elem, n + 1);117 var ptr = try self.alloc(Elem, n + 1);
118 ptr[n] = sentinel;118 ptr[n] = sentinel;
119 return ptr[0 .. n :sentinel];119 return ptr[0..n :sentinel];
120 }120 }
121121
122 pub fn alignedAlloc(122 pub fn alignedAlloc(
...@@ -567,12 +567,20 @@ test "span" {...@@ -567,12 +567,20 @@ test "span" {
567567
568/// Takes a pointer to an array, an array, a sentinel-terminated pointer,568/// Takes a pointer to an array, an array, a sentinel-terminated pointer,
569/// or a slice, and returns the length.569/// or a slice, and returns the length.
570/// In the case of a sentinel-terminated array, it scans the array
571/// for a sentinel and uses that for the length, rather than using the array length.
570pub fn len(ptr: var) usize {572pub fn len(ptr: var) usize {
571 return switch (@typeInfo(@TypeOf(ptr))) {573 return switch (@typeInfo(@TypeOf(ptr))) {
572 .Array => |info| info.len,574 .Array => |info| if (info.sentinel) |sentinel|
575 indexOfSentinel(info.child, sentinel, &ptr)
576 else
577 info.len,
573 .Pointer => |info| switch (info.size) {578 .Pointer => |info| switch (info.size) {
574 .One => switch (@typeInfo(info.child)) {579 .One => switch (@typeInfo(info.child)) {
575 .Array => |x| x.len,580 .Array => |x| if (x.sentinel) |sentinel|
581 indexOfSentinel(x.child, sentinel, ptr)
582 else
583 ptr.len,
576 else => @compileError("invalid type given to std.mem.length"),584 else => @compileError("invalid type given to std.mem.length"),
577 },585 },
578 .Many => if (info.sentinel) |sentinel|586 .Many => if (info.sentinel) |sentinel|
...@@ -597,6 +605,12 @@ test "len" {...@@ -597,6 +605,12 @@ test "len" {
597 const ptr = array[0..2 :0].ptr;605 const ptr = array[0..2 :0].ptr;
598 testing.expect(len(ptr) == 2);606 testing.expect(len(ptr) == 2);
599 }607 }
608 {
609 var array: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
610 testing.expect(len(&array) == 5);
611 array[2] = 0;
612 testing.expect(len(&array) == 2);
613 }
600}614}
601615
602pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize {616pub fn indexOfSentinel(comptime Elem: type, comptime sentinel: Elem, ptr: [*:sentinel]const Elem) usize {
lib/std/os.zig+116-3
...@@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {...@@ -461,13 +461,11 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void {
461 );461 );
462462
463 switch (rc) {463 switch (rc) {
464 .SUCCESS => {},464 .SUCCESS => return,
465 .INVALID_HANDLE => unreachable, // Handle not open for writing465 .INVALID_HANDLE => unreachable, // Handle not open for writing
466 .ACCESS_DENIED => return error.CannotTruncate,466 .ACCESS_DENIED => return error.CannotTruncate,
467 else => return windows.unexpectedStatus(rc),467 else => return windows.unexpectedStatus(rc),
468 }468 }
469
470 return;
471 }469 }
472470
473 while (true) {471 while (true) {
...@@ -852,6 +850,7 @@ pub const OpenError = error{...@@ -852,6 +850,7 @@ pub const OpenError = error{
852850
853/// Open and possibly create a file. Keeps trying if it gets interrupted.851/// Open and possibly create a file. Keeps trying if it gets interrupted.
854/// See also `openC`.852/// See also `openC`.
853/// TODO support windows
855pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {854pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
856 const file_path_c = try toPosixPath(file_path);855 const file_path_c = try toPosixPath(file_path);
857 return openC(&file_path_c, flags, perm);856 return openC(&file_path_c, flags, perm);
...@@ -859,6 +858,7 @@ pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {...@@ -859,6 +858,7 @@ pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
859858
860/// Open and possibly create a file. Keeps trying if it gets interrupted.859/// Open and possibly create a file. Keeps trying if it gets interrupted.
861/// See also `open`.860/// See also `open`.
861/// TODO support windows
862pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {862pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
863 while (true) {863 while (true) {
864 const rc = system.open(file_path, flags, perm);864 const rc = system.open(file_path, flags, perm);
...@@ -892,6 +892,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {...@@ -892,6 +892,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
892/// Open and possibly create a file. Keeps trying if it gets interrupted.892/// Open and possibly create a file. Keeps trying if it gets interrupted.
893/// `file_path` is relative to the open directory handle `dir_fd`.893/// `file_path` is relative to the open directory handle `dir_fd`.
894/// See also `openatC`.894/// See also `openatC`.
895/// TODO support windows
895pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {896pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
896 const file_path_c = try toPosixPath(file_path);897 const file_path_c = try toPosixPath(file_path);
897 return openatC(dir_fd, &file_path_c, flags, mode);898 return openatC(dir_fd, &file_path_c, flags, mode);
...@@ -900,6 +901,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope...@@ -900,6 +901,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope
900/// Open and possibly create a file. Keeps trying if it gets interrupted.901/// Open and possibly create a file. Keeps trying if it gets interrupted.
901/// `file_path` is relative to the open directory handle `dir_fd`.902/// `file_path` is relative to the open directory handle `dir_fd`.
902/// See also `openat`.903/// See also `openat`.
904/// TODO support windows
903pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {905pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
904 while (true) {906 while (true) {
905 const rc = system.openat(dir_fd, file_path, flags, mode);907 const rc = system.openat(dir_fd, file_path, flags, mode);
...@@ -1527,6 +1529,9 @@ const RenameError = error{...@@ -1527,6 +1529,9 @@ const RenameError = error{
1527 RenameAcrossMountPoints,1529 RenameAcrossMountPoints,
1528 InvalidUtf8,1530 InvalidUtf8,
1529 BadPathName,1531 BadPathName,
1532 NoDevice,
1533 SharingViolation,
1534 PipeBusy,
1530} || UnexpectedError;1535} || UnexpectedError;
15311536
1532/// Change the name or location of a file.1537/// Change the name or location of a file.
...@@ -1580,6 +1585,113 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v...@@ -1580,6 +1585,113 @@ pub fn renameW(old_path: [*:0]const u16, new_path: [*:0]const u16) RenameError!v
1580 return windows.MoveFileExW(old_path, new_path, flags);1585 return windows.MoveFileExW(old_path, new_path, flags);
1581}1586}
15821587
1588/// Change the name or location of a file based on an open directory handle.
1589pub fn renameat(
1590 old_dir_fd: fd_t,
1591 old_path: []const u8,
1592 new_dir_fd: fd_t,
1593 new_path: []const u8,
1594) RenameError!void {
1595 if (builtin.os.tag == .windows) {
1596 const old_path_w = try windows.sliceToPrefixedFileW(old_path);
1597 const new_path_w = try windows.sliceToPrefixedFileW(new_path);
1598 return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
1599 } else {
1600 const old_path_c = try toPosixPath(old_path);
1601 const new_path_c = try toPosixPath(new_path);
1602 return renameatZ(old_dir_fd, &old_path_c, new_dir_fd, &new_path_c);
1603 }
1604}
1605
1606/// Same as `renameat` except the parameters are null-terminated byte arrays.
1607pub fn renameatZ(
1608 old_dir_fd: fd_t,
1609 old_path: [*:0]const u8,
1610 new_dir_fd: fd_t,
1611 new_path: [*:0]const u8,
1612) RenameError!void {
1613 if (builtin.os.tag == .windows) {
1614 const old_path_w = try windows.cStrToPrefixedFileW(old_path);
1615 const new_path_w = try windows.cStrToPrefixedFileW(new_path);
1616 return renameatW(old_dir_fd, &old_path_w, new_dir_fd, &new_path_w, windows.TRUE);
1617 }
1618
1619 switch (errno(system.renameat(old_dir_fd, old_path, new_dir_fd, new_path))) {
1620 0 => return,
1621 EACCES => return error.AccessDenied,
1622 EPERM => return error.AccessDenied,
1623 EBUSY => return error.FileBusy,
1624 EDQUOT => return error.DiskQuota,
1625 EFAULT => unreachable,
1626 EINVAL => unreachable,
1627 EISDIR => return error.IsDir,
1628 ELOOP => return error.SymLinkLoop,
1629 EMLINK => return error.LinkQuotaExceeded,
1630 ENAMETOOLONG => return error.NameTooLong,
1631 ENOENT => return error.FileNotFound,
1632 ENOTDIR => return error.NotDir,
1633 ENOMEM => return error.SystemResources,
1634 ENOSPC => return error.NoSpaceLeft,
1635 EEXIST => return error.PathAlreadyExists,
1636 ENOTEMPTY => return error.PathAlreadyExists,
1637 EROFS => return error.ReadOnlyFileSystem,
1638 EXDEV => return error.RenameAcrossMountPoints,
1639 else => |err| return unexpectedErrno(err),
1640 }
1641}
1642
1643/// Same as `renameat` except the parameters are null-terminated UTF16LE encoded byte arrays.
1644/// Assumes target is Windows.
1645/// TODO these args can actually be slices when using ntdll. audit the rest of the W functions too.
1646pub fn renameatW(
1647 old_dir_fd: fd_t,
1648 old_path: [*:0]const u16,
1649 new_dir_fd: fd_t,
1650 new_path_w: [*:0]const u16,
1651 ReplaceIfExists: windows.BOOLEAN,
1652) RenameError!void {
1653 const access_mask = windows.SYNCHRONIZE | windows.GENERIC_WRITE | windows.DELETE;
1654 const src_fd = try windows.OpenFileW(old_dir_fd, old_path, null, access_mask, windows.FILE_OPEN);
1655 defer windows.CloseHandle(src_fd);
1656
1657 const struct_buf_len = @sizeOf(windows.FILE_RENAME_INFORMATION) + (MAX_PATH_BYTES - 1);
1658 var rename_info_buf: [struct_buf_len]u8 align(@alignOf(windows.FILE_RENAME_INFORMATION)) = undefined;
1659 const new_path = mem.span(new_path_w);
1660 const struct_len = @sizeOf(windows.FILE_RENAME_INFORMATION) - 1 + new_path.len * 2;
1661 if (struct_len > struct_buf_len) return error.NameTooLong;
1662
1663 const rename_info = @ptrCast(*windows.FILE_RENAME_INFORMATION, &rename_info_buf);
1664
1665 rename_info.* = .{
1666 .ReplaceIfExists = ReplaceIfExists,
1667 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(new_path_w)) null else new_dir_fd,
1668 .FileNameLength = @intCast(u32, new_path.len * 2), // already checked error.NameTooLong
1669 .FileName = undefined,
1670 };
1671 std.mem.copy(u16, @as([*]u16, &rename_info.FileName)[0..new_path.len], new_path);
1672
1673 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
1674
1675 const rc = windows.ntdll.NtSetInformationFile(
1676 src_fd,
1677 &io_status_block,
1678 rename_info,
1679 @intCast(u32, struct_len), // already checked for error.NameTooLong
1680 .FileRenameInformation,
1681 );
1682
1683 switch (rc) {
1684 .SUCCESS => return,
1685 .INVALID_HANDLE => unreachable,
1686 .INVALID_PARAMETER => unreachable,
1687 .OBJECT_PATH_SYNTAX_BAD => unreachable,
1688 .ACCESS_DENIED => return error.AccessDenied,
1689 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
1690 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
1691 else => return windows.unexpectedStatus(rc),
1692 }
1693}
1694
1583pub const MakeDirError = error{1695pub const MakeDirError = error{
1584 AccessDenied,1696 AccessDenied,
1585 DiskQuota,1697 DiskQuota,
...@@ -3125,6 +3237,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP...@@ -3125,6 +3237,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP
3125}3237}
31263238
3127/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.3239/// Same as `realpath` except `pathname` is null-terminated and UTF16LE-encoded.
3240/// TODO use ntdll for better semantics
3128pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {3241pub fn realpathW(pathname: [*:0]const u16, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 {
3129 const h_file = try windows.CreateFileW(3242 const h_file = try windows.CreateFileW(
3130 pathname,3243 pathname,
lib/std/os/linux.zig+4-4
...@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const...@@ -465,17 +465,17 @@ pub fn renameat(oldfd: i32, oldpath: [*]const u8, newfd: i32, newpath: [*]const
465 return syscall4(465 return syscall4(
466 SYS_renameat,466 SYS_renameat,
467 @bitCast(usize, @as(isize, oldfd)),467 @bitCast(usize, @as(isize, oldfd)),
468 @ptrToInt(old),468 @ptrToInt(oldpath),
469 @bitCast(usize, @as(isize, newfd)),469 @bitCast(usize, @as(isize, newfd)),
470 @ptrToInt(new),470 @ptrToInt(newpath),
471 );471 );
472 } else {472 } else {
473 return syscall5(473 return syscall5(
474 SYS_renameat2,474 SYS_renameat2,
475 @bitCast(usize, @as(isize, oldfd)),475 @bitCast(usize, @as(isize, oldfd)),
476 @ptrToInt(old),476 @ptrToInt(oldpath),
477 @bitCast(usize, @as(isize, newfd)),477 @bitCast(usize, @as(isize, newfd)),
478 @ptrToInt(new),478 @ptrToInt(newpath),
479 0,479 0,
480 );480 );
481 }481 }
lib/std/os/windows.zig+76
...@@ -88,6 +88,82 @@ pub fn CreateFileW(...@@ -88,6 +88,82 @@ pub fn CreateFileW(
88 return result;88 return result;
89}89}
9090
91pub const OpenError = error{
92 IsDir,
93 FileNotFound,
94 NoDevice,
95 SharingViolation,
96 AccessDenied,
97 PipeBusy,
98 PathAlreadyExists,
99 Unexpected,
100 NameTooLong,
101};
102
103/// TODO rename to CreateFileW
104/// TODO actually we don't need the path parameter to be null terminated
105pub fn OpenFileW(
106 dir: ?HANDLE,
107 sub_path_w: [*:0]const u16,
108 sa: ?*SECURITY_ATTRIBUTES,
109 access_mask: ACCESS_MASK,
110 creation: ULONG,
111) OpenError!HANDLE {
112 if (sub_path_w[0] == '.' and sub_path_w[1] == 0) {
113 return error.IsDir;
114 }
115 if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) {
116 return error.IsDir;
117 }
118
119 var result: HANDLE = undefined;
120
121 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
122 error.Overflow => return error.NameTooLong,
123 };
124 var nt_name = UNICODE_STRING{
125 .Length = path_len_bytes,
126 .MaximumLength = path_len_bytes,
127 .Buffer = @intToPtr([*]u16, @ptrToInt(sub_path_w)),
128 };
129 var attr = OBJECT_ATTRIBUTES{
130 .Length = @sizeOf(OBJECT_ATTRIBUTES),
131 .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w)) null else dir,
132 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
133 .ObjectName = &nt_name,
134 .SecurityDescriptor = if (sa) |ptr| ptr.lpSecurityDescriptor else null,
135 .SecurityQualityOfService = null,
136 };
137 var io: IO_STATUS_BLOCK = undefined;
138 const rc = ntdll.NtCreateFile(
139 &result,
140 access_mask,
141 &attr,
142 &io,
143 null,
144 FILE_ATTRIBUTE_NORMAL,
145 FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
146 creation,
147 FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
148 null,
149 0,
150 );
151 switch (rc) {
152 .SUCCESS => return result,
153 .OBJECT_NAME_INVALID => unreachable,
154 .OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
155 .OBJECT_PATH_NOT_FOUND => return error.FileNotFound,
156 .NO_MEDIA_IN_DEVICE => return error.NoDevice,
157 .INVALID_PARAMETER => unreachable,
158 .SHARING_VIOLATION => return error.SharingViolation,
159 .ACCESS_DENIED => return error.AccessDenied,
160 .PIPE_BUSY => return error.PipeBusy,
161 .OBJECT_PATH_SYNTAX_BAD => unreachable,
162 .OBJECT_NAME_COLLISION => return error.PathAlreadyExists,
163 else => return unexpectedStatus(rc),
164 }
165}
166
91pub const CreatePipeError = error{Unexpected};167pub const CreatePipeError = error{Unexpected};
92168
93pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {169pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
lib/std/os/windows/bits.zig+7
...@@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {...@@ -242,6 +242,13 @@ pub const FILE_NAME_INFORMATION = extern struct {
242 FileName: [1]WCHAR,242 FileName: [1]WCHAR,
243};243};
244244
245pub const FILE_RENAME_INFORMATION = extern struct {
246 ReplaceIfExists: BOOLEAN,
247 RootDirectory: ?HANDLE,
248 FileNameLength: ULONG,
249 FileName: [1]WCHAR,
250};
251
245pub const IO_STATUS_BLOCK = extern struct {252pub const IO_STATUS_BLOCK = extern struct {
246 // "DUMMYUNIONNAME" expands to "u"253 // "DUMMYUNIONNAME" expands to "u"
247 u: extern union {254 u: extern union {
src/codegen.cpp+33-13
...@@ -9650,6 +9650,21 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose...@@ -9650,6 +9650,21 @@ Error create_c_object_cache(CodeGen *g, CacheHash **out_cache_hash, bool verbose
9650 return ErrorNone;9650 return ErrorNone;
9651}9651}
96529652
9653static bool need_llvm_module(CodeGen *g) {
9654 return buf_len(&g->main_pkg->root_src_path) != 0;
9655}
9656
9657// before gen_c_objects
9658static bool main_output_dir_is_just_one_c_object_pre(CodeGen *g) {
9659 return g->enable_cache && g->c_source_files.length == 1 && !need_llvm_module(g) &&
9660 g->out_type == OutTypeObj && g->link_objects.length == 0;
9661}
9662
9663// after gen_c_objects
9664static bool main_output_dir_is_just_one_c_object_post(CodeGen *g) {
9665 return g->enable_cache && g->link_objects.length == 1 && !need_llvm_module(g) && g->out_type == OutTypeObj;
9666}
9667
9653// returns true if it was a cache miss9668// returns true if it was a cache miss
9654static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {9669static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
9655 Error err;9670 Error err;
...@@ -9667,7 +9682,12 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {...@@ -9667,7 +9682,12 @@ static void gen_c_object(CodeGen *g, Buf *self_exe_path, CFile *c_file) {
9667 buf_len(c_source_basename), 0);9682 buf_len(c_source_basename), 0);
96689683
9669 Buf *final_o_basename = buf_alloc();9684 Buf *final_o_basename = buf_alloc();
9670 os_path_extname(c_source_basename, final_o_basename, nullptr);9685 // We special case when doing build-obj for just one C file
9686 if (main_output_dir_is_just_one_c_object_pre(g)) {
9687 buf_init_from_buf(final_o_basename, g->root_out_name);
9688 } else {
9689 os_path_extname(c_source_basename, final_o_basename, nullptr);
9690 }
9671 buf_append_str(final_o_basename, target_o_file_ext(g->zig_target));9691 buf_append_str(final_o_basename, target_o_file_ext(g->zig_target));
96729692
9673 CacheHash *cache_hash;9693 CacheHash *cache_hash;
...@@ -10467,10 +10487,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {...@@ -10467,10 +10487,6 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
10467 return ErrorNone;10487 return ErrorNone;
10468}10488}
1046910489
10470static bool need_llvm_module(CodeGen *g) {
10471 return buf_len(&g->main_pkg->root_src_path) != 0;
10472}
10473
10474static void resolve_out_paths(CodeGen *g) {10490static void resolve_out_paths(CodeGen *g) {
10475 assert(g->output_dir != nullptr);10491 assert(g->output_dir != nullptr);
10476 assert(g->root_out_name != nullptr);10492 assert(g->root_out_name != nullptr);
...@@ -10482,10 +10498,6 @@ static void resolve_out_paths(CodeGen *g) {...@@ -10482,10 +10498,6 @@ static void resolve_out_paths(CodeGen *g) {
10482 case OutTypeUnknown:10498 case OutTypeUnknown:
10483 zig_unreachable();10499 zig_unreachable();
10484 case OutTypeObj:10500 case OutTypeObj:
10485 if (g->enable_cache && g->link_objects.length == 1 && !need_llvm_module(g)) {
10486 buf_init_from_buf(&g->bin_file_output_path, g->link_objects.at(0));
10487 return;
10488 }
10489 if (need_llvm_module(g) && g->link_objects.length != 0 && !g->enable_cache &&10501 if (need_llvm_module(g) && g->link_objects.length != 0 && !g->enable_cache &&
10490 buf_eql_buf(o_basename, out_basename))10502 buf_eql_buf(o_basename, out_basename))
10491 {10503 {
...@@ -10580,6 +10592,16 @@ static void output_type_information(CodeGen *g) {...@@ -10580,6 +10592,16 @@ static void output_type_information(CodeGen *g) {
10580 }10592 }
10581}10593}
1058210594
10595static void init_output_dir(CodeGen *g, Buf *digest) {
10596 if (main_output_dir_is_just_one_c_object_post(g)) {
10597 g->output_dir = buf_alloc();
10598 os_path_dirname(g->link_objects.at(0), g->output_dir);
10599 } else {
10600 g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",
10601 buf_ptr(g->cache_dir), buf_ptr(digest));
10602 }
10603}
10604
10583void codegen_build_and_link(CodeGen *g) {10605void codegen_build_and_link(CodeGen *g) {
10584 Error err;10606 Error err;
10585 assert(g->out_type != OutTypeUnknown);10607 assert(g->out_type != OutTypeUnknown);
...@@ -10622,8 +10644,7 @@ void codegen_build_and_link(CodeGen *g) {...@@ -10622,8 +10644,7 @@ void codegen_build_and_link(CodeGen *g) {
10622 }10644 }
1062310645
10624 if (g->enable_cache && buf_len(&digest) != 0) {10646 if (g->enable_cache && buf_len(&digest) != 0) {
10625 g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",10647 init_output_dir(g, &digest);
10626 buf_ptr(g->cache_dir), buf_ptr(&digest));
10627 resolve_out_paths(g);10648 resolve_out_paths(g);
10628 } else {10649 } else {
10629 if (need_llvm_module(g)) {10650 if (need_llvm_module(g)) {
...@@ -10644,8 +10665,7 @@ void codegen_build_and_link(CodeGen *g) {...@@ -10644,8 +10665,7 @@ void codegen_build_and_link(CodeGen *g) {
10644 exit(1);10665 exit(1);
10645 }10666 }
10646 }10667 }
10647 g->output_dir = buf_sprintf("%s" OS_SEP CACHE_OUT_SUBDIR OS_SEP "%s",10668 init_output_dir(g, &digest);
10648 buf_ptr(g->cache_dir), buf_ptr(&digest));
1064910669
10650 if ((err = os_make_path(g->output_dir))) {10670 if ((err = os_make_path(g->output_dir))) {
10651 fprintf(stderr, "Unable to create output directory: %s\n", err_str(err));10671 fprintf(stderr, "Unable to create output directory: %s\n", err_str(err));
src/link.cpp+1
...@@ -566,6 +566,7 @@ static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFil...@@ -566,6 +566,7 @@ static const char *build_libc_object(CodeGen *parent_gen, const char *name, CFil
566 Stage2ProgressNode *progress_node)566 Stage2ProgressNode *progress_node)
567{567{
568 CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr, name, progress_node);568 CodeGen *child_gen = create_child_codegen(parent_gen, nullptr, OutTypeObj, nullptr, name, progress_node);
569 child_gen->root_out_name = buf_create_from_str(name);
569 ZigList<CFile *> c_source_files = {0};570 ZigList<CFile *> c_source_files = {0};
570 c_source_files.append(c_file);571 c_source_files.append(c_file);
571 child_gen->c_source_files = c_source_files;572 child_gen->c_source_files = c_source_files;
src/main.cpp+2-1
...@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {...@@ -1290,6 +1290,7 @@ static int main0(int argc, char **argv) {
1290 if (g->enable_cache) {1290 if (g->enable_cache) {
1291#if defined(ZIG_OS_WINDOWS)1291#if defined(ZIG_OS_WINDOWS)
1292 buf_replace(&g->bin_file_output_path, '/', '\\');1292 buf_replace(&g->bin_file_output_path, '/', '\\');
1293 buf_replace(g->output_dir, '/', '\\');
1293#endif1294#endif
1294 if (final_output_dir_step != nullptr) {1295 if (final_output_dir_step != nullptr) {
1295 Buf *dest_basename = buf_alloc();1296 Buf *dest_basename = buf_alloc();
...@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {...@@ -1303,7 +1304,7 @@ static int main0(int argc, char **argv) {
1303 return main_exit(root_progress_node, EXIT_FAILURE);1304 return main_exit(root_progress_node, EXIT_FAILURE);
1304 }1305 }
1305 } else {1306 } else {
1306 if (g->emit_bin && printf("%s\n", buf_ptr(&g->bin_file_output_path)) < 0)1307 if (printf("%s\n", buf_ptr(g->output_dir)) < 0)
1307 return main_exit(root_progress_node, EXIT_FAILURE);1308 return main_exit(root_progress_node, EXIT_FAILURE);
1308 }1309 }
1309 }1310 }