move some of the installation from cmake to zig build
This moves the installation of shipped source files from large CMakeLists.txt lists to zig build recursive directory installation. On my computer a cmake `make install` takes 2.4 seconds even when it has to do nothing, and prints a lot of unnecessary lines to stdout that say "up-to-date: [some file it is installing]". After this commit, the default output of `make` is down to 1 second, and it does not print any junk to stdout. Further, a `make install` is no longer required and `make` is sufficient. This closes #2874. It also closes #2585. `make` now always invokes `zig build` for installing files and libuserland.a, and zig's own caching system makes that go fast.master
parent
7d9ee5d6d5
commit
6096dc5f94
6505
CMakeLists.txt
6505
CMakeLists.txt
File diff suppressed because it is too large
Load Diff
66
build.zig
66
build.zig
|
@ -9,6 +9,7 @@ const ArrayList = std.ArrayList;
|
|||
const Buffer = std.Buffer;
|
||||
const io = std.io;
|
||||
const fs = std.fs;
|
||||
const InstallDirectoryOptions = std.build.InstallDirectoryOptions;
|
||||
|
||||
pub fn build(b: *Builder) !void {
|
||||
b.setPreferredReleaseMode(.ReleaseFast);
|
||||
|
@ -46,8 +47,6 @@ pub fn build(b: *Builder) !void {
|
|||
.llvm_config_exe = nextValue(&index, build_info),
|
||||
.lld_include_dir = nextValue(&index, build_info),
|
||||
.lld_libraries = nextValue(&index, build_info),
|
||||
.std_files = nextValue(&index, build_info),
|
||||
.c_header_files = nextValue(&index, build_info),
|
||||
.dia_guids_lib = nextValue(&index, build_info),
|
||||
.llvm = undefined,
|
||||
};
|
||||
|
@ -64,8 +63,6 @@ pub fn build(b: *Builder) !void {
|
|||
try configureStage2(b, test_stage2, ctx);
|
||||
try configureStage2(b, exe, ctx);
|
||||
|
||||
b.default_step.dependOn(&exe.step);
|
||||
|
||||
addLibUserlandStep(b);
|
||||
|
||||
const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
|
||||
|
@ -79,9 +76,38 @@ pub fn build(b: *Builder) !void {
|
|||
//test_step.dependOn(&exe.step);
|
||||
}
|
||||
|
||||
exe.install();
|
||||
installStdLib(b, ctx.std_files);
|
||||
installCHeaders(b, ctx.c_header_files);
|
||||
const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
|
||||
if (!only_install_lib_files) {
|
||||
b.default_step.dependOn(&exe.step);
|
||||
exe.install();
|
||||
}
|
||||
|
||||
b.installDirectory(InstallDirectoryOptions{
|
||||
.source_dir = "c_headers",
|
||||
.install_dir = .Lib,
|
||||
.install_subdir = "zig" ++ fs.path.sep_str ++ "include",
|
||||
});
|
||||
b.installDirectory(InstallDirectoryOptions{
|
||||
.source_dir = "libc",
|
||||
.install_dir = .Lib,
|
||||
.install_subdir = "zig" ++ fs.path.sep_str ++ "libc",
|
||||
});
|
||||
b.installDirectory(InstallDirectoryOptions{
|
||||
.source_dir = "libcxx",
|
||||
.install_dir = .Lib,
|
||||
.install_subdir = "zig" ++ fs.path.sep_str ++ "libcxx",
|
||||
});
|
||||
b.installDirectory(InstallDirectoryOptions{
|
||||
.source_dir = "libunwind",
|
||||
.install_dir = .Lib,
|
||||
.install_subdir = "zig" ++ fs.path.sep_str ++ "libunwind",
|
||||
});
|
||||
b.installDirectory(InstallDirectoryOptions{
|
||||
.source_dir = "std",
|
||||
.install_dir = .Lib,
|
||||
.install_subdir = "zig" ++ fs.path.sep_str ++ "std",
|
||||
.exclude_extensions = [_][]const u8{"test.zig"},
|
||||
});
|
||||
|
||||
const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
|
||||
|
||||
|
@ -251,30 +277,6 @@ fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep {
|
|||
return result;
|
||||
}
|
||||
|
||||
pub fn installStdLib(b: *Builder, stdlib_files: []const u8) void {
|
||||
var it = mem.tokenize(stdlib_files, ";");
|
||||
while (it.next()) |stdlib_file| {
|
||||
const src_path = fs.path.join(b.allocator, [_][]const u8{ "std", stdlib_file }) catch unreachable;
|
||||
const dest_path = fs.path.join(
|
||||
b.allocator,
|
||||
[_][]const u8{ "lib", "zig", "std", stdlib_file },
|
||||
) catch unreachable;
|
||||
b.installFile(src_path, dest_path);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn installCHeaders(b: *Builder, c_header_files: []const u8) void {
|
||||
var it = mem.tokenize(c_header_files, ";");
|
||||
while (it.next()) |c_header_file| {
|
||||
const src_path = fs.path.join(b.allocator, [_][]const u8{ "c_headers", c_header_file }) catch unreachable;
|
||||
const dest_path = fs.path.join(
|
||||
b.allocator,
|
||||
[_][]const u8{ "lib", "zig", "include", c_header_file },
|
||||
) catch unreachable;
|
||||
b.installFile(src_path, dest_path);
|
||||
}
|
||||
}
|
||||
|
||||
fn nextValue(index: *usize, build_info: []const u8) []const u8 {
|
||||
const start = index.*;
|
||||
while (true) : (index.* += 1) {
|
||||
|
@ -375,8 +377,6 @@ const Context = struct {
|
|||
llvm_config_exe: []const u8,
|
||||
lld_include_dir: []const u8,
|
||||
lld_libraries: []const u8,
|
||||
std_files: []const u8,
|
||||
c_header_files: []const u8,
|
||||
dia_guids_lib: []const u8,
|
||||
llvm: LibraryDep,
|
||||
};
|
||||
|
|
|
@ -23,7 +23,5 @@
|
|||
#define ZIG_LLD_LIBRARIES "@LLD_LIBRARIES@"
|
||||
#define ZIG_LLVM_CONFIG_EXE "@LLVM_CONFIG_EXE@"
|
||||
#define ZIG_DIA_GUIDS_LIB "@ZIG_DIA_GUIDS_LIB_ESCAPED@"
|
||||
#define ZIG_STD_FILES "@ZIG_STD_FILES@"
|
||||
#define ZIG_C_HEADER_FILES "@ZIG_C_HEADER_FILES@"
|
||||
|
||||
#endif
|
||||
|
|
|
@ -305,14 +305,12 @@ int main(int argc, char **argv) {
|
|||
Error err;
|
||||
|
||||
if (argc == 2 && strcmp(argv[1], "BUILD_INFO") == 0) {
|
||||
printf("%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
|
||||
printf("%s\n%s\n%s\n%s\n%s\n%s\n",
|
||||
ZIG_CMAKE_BINARY_DIR,
|
||||
ZIG_CXX_COMPILER,
|
||||
ZIG_LLVM_CONFIG_EXE,
|
||||
ZIG_LLD_INCLUDE_PATH,
|
||||
ZIG_LLD_LIBRARIES,
|
||||
ZIG_STD_FILES,
|
||||
ZIG_C_HEADER_FILES,
|
||||
ZIG_DIA_GUIDS_LIB);
|
||||
return 0;
|
||||
}
|
||||
|
|
115
std/build.zig
115
std/build.zig
|
@ -296,7 +296,7 @@ pub const Builder = struct {
|
|||
if (self.verbose) {
|
||||
warn("rm {}\n", full_path);
|
||||
}
|
||||
fs.deleteFile(full_path) catch {};
|
||||
fs.deleteTree(self.allocator, full_path) catch {};
|
||||
}
|
||||
|
||||
// TODO remove empty directories
|
||||
|
@ -704,6 +704,10 @@ pub const Builder = struct {
|
|||
self.getInstallStep().dependOn(&self.addInstallFileWithDir(src_path, .Prefix, dest_rel_path).step);
|
||||
}
|
||||
|
||||
pub fn installDirectory(self: *Builder, options: InstallDirectoryOptions) void {
|
||||
self.getInstallStep().dependOn(&self.addInstallDirectory(options).step);
|
||||
}
|
||||
|
||||
///`dest_rel_path` is relative to bin path
|
||||
pub fn installBinFile(self: *Builder, src_path: []const u8, dest_rel_path: []const u8) void {
|
||||
self.getInstallStep().dependOn(&self.addInstallFileWithDir(src_path, .Bin, dest_rel_path).step);
|
||||
|
@ -740,6 +744,12 @@ pub const Builder = struct {
|
|||
return install_step;
|
||||
}
|
||||
|
||||
pub fn addInstallDirectory(self: *Builder, options: InstallDirectoryOptions) *InstallDirStep {
|
||||
const install_step = self.allocator.create(InstallDirStep) catch unreachable;
|
||||
install_step.* = InstallDirStep.init(self, options);
|
||||
return install_step;
|
||||
}
|
||||
|
||||
pub fn pushInstalledFile(self: *Builder, dir: InstallDir, dest_rel_path: []const u8) void {
|
||||
self.installed_files.append(InstalledFile{
|
||||
.dir = dir,
|
||||
|
@ -747,24 +757,14 @@ pub const Builder = struct {
|
|||
}) catch unreachable;
|
||||
}
|
||||
|
||||
fn copyFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void {
|
||||
return self.copyFileMode(source_path, dest_path, File.default_mode);
|
||||
}
|
||||
|
||||
fn copyFileMode(self: *Builder, source_path: []const u8, dest_path: []const u8, mode: File.Mode) !void {
|
||||
fn updateFile(self: *Builder, source_path: []const u8, dest_path: []const u8) !void {
|
||||
if (self.verbose) {
|
||||
warn("cp {} {}\n", source_path, dest_path);
|
||||
warn("cp {} {} ", source_path, dest_path);
|
||||
}
|
||||
|
||||
const dirname = fs.path.dirname(dest_path) orelse ".";
|
||||
const abs_source_path = self.pathFromRoot(source_path);
|
||||
fs.makePath(self.allocator, dirname) catch |err| {
|
||||
warn("Unable to create path {}: {}\n", dirname, @errorName(err));
|
||||
return err;
|
||||
};
|
||||
fs.copyFileMode(abs_source_path, dest_path, mode) catch |err| {
|
||||
warn("Unable to copy {} to {}: {}\n", abs_source_path, dest_path, @errorName(err));
|
||||
return err;
|
||||
const prev_status = try fs.updateFile(source_path, dest_path);
|
||||
if (self.verbose) switch (prev_status) {
|
||||
.stale => warn("# installed\n"),
|
||||
.fresh => warn("# up-to-date\n"),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1892,12 +1892,7 @@ pub const LibExeObjStep = struct {
|
|||
try zig_args.append(builder.pathFromRoot(dir));
|
||||
}
|
||||
|
||||
if (self.output_dir) |output_dir| {
|
||||
try zig_args.append("--output-dir");
|
||||
try zig_args.append(output_dir);
|
||||
|
||||
try builder.spawnChild(zig_args.toSliceConst());
|
||||
} else if (self.kind == Kind.Test) {
|
||||
if (self.kind == Kind.Test) {
|
||||
try builder.spawnChild(zig_args.toSliceConst());
|
||||
} else {
|
||||
try zig_args.append("--cache");
|
||||
|
@ -1905,7 +1900,16 @@ pub const LibExeObjStep = struct {
|
|||
|
||||
const output_path_nl = try builder.exec(zig_args.toSliceConst());
|
||||
const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
|
||||
self.output_dir = fs.path.dirname(output_path).?;
|
||||
|
||||
if (self.output_dir) |output_dir| {
|
||||
const full_dest = try fs.path.join(builder.allocator, [_][]const u8{
|
||||
output_dir,
|
||||
fs.path.basename(output_path),
|
||||
});
|
||||
try builder.updateFile(output_path, full_dest);
|
||||
} else {
|
||||
self.output_dir = fs.path.dirname(output_path).?;
|
||||
}
|
||||
}
|
||||
|
||||
if (self.kind == Kind.Lib and self.is_dynamic and self.target.wantSharedLibSymLinks()) {
|
||||
|
@ -2077,23 +2081,14 @@ const InstallArtifactStep = struct {
|
|||
const self = @fieldParentPtr(Self, "step", step);
|
||||
const builder = self.builder;
|
||||
|
||||
const mode = switch (builtin.os) {
|
||||
.windows => {},
|
||||
else => switch (self.artifact.kind) {
|
||||
.Obj => unreachable,
|
||||
.Test => unreachable,
|
||||
.Exe => u32(0o755),
|
||||
.Lib => if (!self.artifact.is_dynamic) u32(0o666) else u32(0o755),
|
||||
},
|
||||
};
|
||||
const full_dest_path = builder.getInstallPath(self.dest_dir, self.artifact.out_filename);
|
||||
try builder.copyFileMode(self.artifact.getOutputPath(), full_dest_path, mode);
|
||||
try builder.updateFile(self.artifact.getOutputPath(), full_dest_path);
|
||||
if (self.artifact.isDynamicLibrary()) {
|
||||
try doAtomicSymLinks(builder.allocator, full_dest_path, self.artifact.major_only_filename, self.artifact.name_only_filename);
|
||||
}
|
||||
if (self.pdb_dir) |pdb_dir| {
|
||||
const full_pdb_path = builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
|
||||
try builder.copyFile(self.artifact.getOutputPdbPath(), full_pdb_path);
|
||||
try builder.updateFile(self.artifact.getOutputPdbPath(), full_pdb_path);
|
||||
}
|
||||
self.artifact.installed_path = full_dest_path;
|
||||
}
|
||||
|
@ -2125,7 +2120,55 @@ pub const InstallFileStep = struct {
|
|||
fn make(step: *Step) !void {
|
||||
const self = @fieldParentPtr(InstallFileStep, "step", step);
|
||||
const full_dest_path = self.builder.getInstallPath(self.dir, self.dest_rel_path);
|
||||
try self.builder.copyFile(self.src_path, full_dest_path);
|
||||
const full_src_path = self.builder.pathFromRoot(self.src_path);
|
||||
try self.builder.updateFile(full_src_path, full_dest_path);
|
||||
}
|
||||
};
|
||||
|
||||
pub const InstallDirectoryOptions = struct {
|
||||
source_dir: []const u8,
|
||||
install_dir: InstallDir,
|
||||
install_subdir: []const u8,
|
||||
exclude_extensions: ?[]const []const u8 = null,
|
||||
};
|
||||
|
||||
pub const InstallDirStep = struct {
|
||||
step: Step,
|
||||
builder: *Builder,
|
||||
options: InstallDirectoryOptions,
|
||||
|
||||
pub fn init(
|
||||
builder: *Builder,
|
||||
options: InstallDirectoryOptions,
|
||||
) InstallDirStep {
|
||||
builder.pushInstalledFile(options.install_dir, options.install_subdir);
|
||||
return InstallDirStep{
|
||||
.builder = builder,
|
||||
.step = Step.init(builder.fmt("install {}/", options.source_dir), builder.allocator, make),
|
||||
.options = options,
|
||||
};
|
||||
}
|
||||
|
||||
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 full_src_dir = self.builder.pathFromRoot(self.options.source_dir);
|
||||
var it = try fs.walkPath(self.builder.allocator, full_src_dir);
|
||||
next_entry: while (try it.next()) |entry| {
|
||||
if (self.options.exclude_extensions) |ext_list| for (ext_list) |ext| {
|
||||
if (mem.endsWith(u8, entry.path, ext)) {
|
||||
continue :next_entry;
|
||||
}
|
||||
};
|
||||
|
||||
const rel_path = entry.path[full_src_dir.len + 1 ..];
|
||||
const dest_path = try fs.path.join(self.builder.allocator, [_][]const u8{ dest_prefix, rel_path });
|
||||
switch (entry.kind) {
|
||||
.Directory => try fs.makePath(self.builder.allocator, dest_path),
|
||||
.File => try self.builder.updateFile(entry.path, dest_path),
|
||||
else => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -124,6 +124,9 @@ pub extern "c" fn realloc(?*c_void, usize) ?*c_void;
|
|||
pub extern "c" fn free(*c_void) void;
|
||||
pub extern "c" fn posix_memalign(memptr: **c_void, alignment: usize, size: usize) c_int;
|
||||
|
||||
pub extern "c" fn utimensat(dirfd: fd_t, pathname: [*]const u8, times: *[2]timespec, flags: u32) c_int;
|
||||
pub extern "c" fn futimens(fd: fd_t, times: *const [2]timespec) c_int;
|
||||
|
||||
pub extern "c" fn pthread_create(noalias newthread: *pthread_t, noalias attr: ?*const pthread_attr_t, start_routine: extern fn (?*c_void) ?*c_void, noalias arg: ?*c_void) c_int;
|
||||
pub extern "c" fn pthread_attr_init(attr: *pthread_attr_t) c_int;
|
||||
pub extern "c" fn pthread_attr_setstack(attr: *pthread_attr_t, stackaddr: *c_void, stacksize: usize) c_int;
|
||||
|
|
151
std/fs.zig
151
std/fs.zig
|
@ -70,6 +70,61 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
|
|||
}
|
||||
}
|
||||
|
||||
// TODO fix enum literal not casting to error union
|
||||
const PrevStatus = enum {
|
||||
stale,
|
||||
fresh,
|
||||
};
|
||||
|
||||
pub fn updateFile(source_path: []const u8, dest_path: []const u8) !PrevStatus {
|
||||
return updateFileMode(source_path, dest_path, null);
|
||||
}
|
||||
|
||||
/// Check the file size and mtime of `source_path` and `dest_path`. If they are equal, does nothing.
|
||||
/// Otherwise, atomically copies `source_path` to `dest_path`. The destination file gains the mtime
|
||||
/// and atime of the source file so that the next call to `updateFile` will not need a copy.
|
||||
/// TODO https://github.com/ziglang/zig/issues/2885
|
||||
/// Returns the previous status of the file before updating.
|
||||
pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?File.Mode) !PrevStatus {
|
||||
var src_file = try File.openRead(source_path);
|
||||
defer src_file.close();
|
||||
|
||||
const src_stat = try src_file.stat();
|
||||
check_dest_stat: {
|
||||
const dest_stat = blk: {
|
||||
var dest_file = File.openRead(dest_path) catch |err| switch (err) {
|
||||
error.FileNotFound => break :check_dest_stat,
|
||||
else => |e| return e,
|
||||
};
|
||||
defer dest_file.close();
|
||||
|
||||
break :blk try dest_file.stat();
|
||||
};
|
||||
|
||||
if (src_stat.size == dest_stat.size and
|
||||
src_stat.mtime == dest_stat.mtime and
|
||||
src_stat.mode == dest_stat.mode)
|
||||
{
|
||||
return PrevStatus.fresh;
|
||||
}
|
||||
}
|
||||
const in_stream = &src_file.inStream().stream;
|
||||
|
||||
var atomic_file = try AtomicFile.init(dest_path, mode orelse src_stat.mode);
|
||||
defer atomic_file.deinit();
|
||||
|
||||
var buf: [mem.page_size * 6]u8 = undefined;
|
||||
while (true) {
|
||||
const amt = try in_stream.readFull(buf[0..]);
|
||||
try atomic_file.file.write(buf[0..amt]);
|
||||
if (amt != buf.len) {
|
||||
try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
|
||||
try atomic_file.finish();
|
||||
return PrevStatus.stale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Guaranteed to be atomic. However until https://patchwork.kernel.org/patch/9636735/ is
|
||||
/// merged and readily available,
|
||||
/// there is a possibility of power loss or application termination leaving temporary files present
|
||||
|
@ -105,7 +160,7 @@ pub fn copyFileMode(source_path: []const u8, dest_path: []const u8, mode: File.M
|
|||
var atomic_file = try AtomicFile.init(dest_path, mode);
|
||||
defer atomic_file.deinit();
|
||||
|
||||
var buf: [mem.page_size]u8 = undefined;
|
||||
var buf: [mem.page_size * 6]u8 = undefined;
|
||||
while (true) {
|
||||
const amt = try in_file.read(buf[0..]);
|
||||
try atomic_file.file.write(buf[0..amt]);
|
||||
|
@ -256,9 +311,6 @@ pub fn deleteDirW(dir_path: [*]const u16) !void {
|
|||
return os.rmdirW(dir_path);
|
||||
}
|
||||
|
||||
/// Whether ::full_path describes a symlink, file, or directory, this function
|
||||
/// removes it. If it cannot be removed because it is a non-empty directory,
|
||||
/// this function recursively removes its entries and then tries again.
|
||||
const DeleteTreeError = error{
|
||||
OutOfMemory,
|
||||
AccessDenied,
|
||||
|
@ -290,7 +342,11 @@ const DeleteTreeError = error{
|
|||
Unexpected,
|
||||
};
|
||||
|
||||
/// Whether `full_path` describes a symlink, file, or directory, this function
|
||||
/// removes it. If it cannot be removed because it is a non-empty directory,
|
||||
/// this function recursively removes its entries and then tries again.
|
||||
/// TODO determine if we can remove the allocator requirement
|
||||
/// https://github.com/ziglang/zig/issues/2886
|
||||
pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
|
||||
start_over: while (true) {
|
||||
var got_access_denied = false;
|
||||
|
@ -427,7 +483,9 @@ pub const Dir = struct {
|
|||
Unexpected,
|
||||
};
|
||||
|
||||
/// Call close when done.
|
||||
/// TODO remove the allocator requirement from this API
|
||||
/// https://github.com/ziglang/zig/issues/2885
|
||||
pub fn open(allocator: *Allocator, dir_path: []const u8) OpenError!Dir {
|
||||
return Dir{
|
||||
.allocator = allocator,
|
||||
|
@ -683,13 +741,98 @@ pub const Dir = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const Walker = struct {
|
||||
stack: std.ArrayList(StackItem),
|
||||
name_buffer: std.Buffer,
|
||||
|
||||
pub const Entry = struct {
|
||||
path: []const u8,
|
||||
basename: []const u8,
|
||||
kind: Dir.Entry.Kind,
|
||||
};
|
||||
|
||||
const StackItem = struct {
|
||||
dir_it: Dir,
|
||||
dirname_len: usize,
|
||||
};
|
||||
|
||||
/// After each call to this function, and on deinit(), the memory returned
|
||||
/// from this function becomes invalid. A copy must be made in order to keep
|
||||
/// a reference to the path.
|
||||
pub fn next(self: *Walker) !?Entry {
|
||||
while (true) {
|
||||
if (self.stack.len == 0) return null;
|
||||
// `top` becomes invalid after appending to `self.stack`.
|
||||
const top = &self.stack.toSlice()[self.stack.len - 1];
|
||||
const dirname_len = top.dirname_len;
|
||||
if (try top.dir_it.next()) |base| {
|
||||
self.name_buffer.shrink(dirname_len);
|
||||
try self.name_buffer.appendByte(path.sep);
|
||||
try self.name_buffer.append(base.name);
|
||||
if (base.kind == .Directory) {
|
||||
// TODO https://github.com/ziglang/zig/issues/2888
|
||||
var new_dir = try Dir.open(self.stack.allocator, self.name_buffer.toSliceConst());
|
||||
{
|
||||
errdefer new_dir.close();
|
||||
try self.stack.append(StackItem{
|
||||
.dir_it = new_dir,
|
||||
.dirname_len = self.name_buffer.len(),
|
||||
});
|
||||
}
|
||||
}
|
||||
return Entry{
|
||||
.basename = self.name_buffer.toSliceConst()[dirname_len + 1 ..],
|
||||
.path = self.name_buffer.toSliceConst(),
|
||||
.kind = base.kind,
|
||||
};
|
||||
} else {
|
||||
self.stack.pop().dir_it.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Walker) void {
|
||||
while (self.stack.popOrNull()) |*item| item.dir_it.close();
|
||||
self.stack.deinit();
|
||||
self.name_buffer.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
/// Recursively iterates over a directory.
|
||||
/// Must call `Walker.deinit` when done.
|
||||
/// `dir_path` must not end in a path separator.
|
||||
/// TODO: https://github.com/ziglang/zig/issues/2888
|
||||
pub fn walkPath(allocator: *Allocator, dir_path: []const u8) !Walker {
|
||||
assert(!mem.endsWith(u8, dir_path, path.sep_str));
|
||||
|
||||
var dir_it = try Dir.open(allocator, dir_path);
|
||||
errdefer dir_it.close();
|
||||
|
||||
var name_buffer = try std.Buffer.init(allocator, dir_path);
|
||||
errdefer name_buffer.deinit();
|
||||
|
||||
var walker = Walker{
|
||||
.stack = std.ArrayList(Walker.StackItem).init(allocator),
|
||||
.name_buffer = name_buffer,
|
||||
};
|
||||
|
||||
try walker.stack.append(Walker.StackItem{
|
||||
.dir_it = dir_it,
|
||||
.dirname_len = dir_path.len,
|
||||
});
|
||||
|
||||
return walker;
|
||||
}
|
||||
|
||||
/// Read value of a symbolic link.
|
||||
/// The return value is a slice of buffer, from index `0`.
|
||||
/// TODO https://github.com/ziglang/zig/issues/2888
|
||||
pub fn readLink(pathname: []const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
|
||||
return os.readlink(pathname, buffer);
|
||||
}
|
||||
|
||||
/// Same as `readLink`, except the `pathname` parameter is null-terminated.
|
||||
/// TODO https://github.com/ziglang/zig/issues/2888
|
||||
pub fn readLinkC(pathname: [*]const u8, buffer: *[os.PATH_MAX]u8) ![]u8 {
|
||||
return os.readlinkC(pathname, buffer);
|
||||
}
|
||||
|
|
|
@ -207,8 +207,7 @@ pub const File = struct {
|
|||
if (windows.is_the_target) {
|
||||
return windows.GetFileSizeEx(self.handle);
|
||||
}
|
||||
const stat = try os.fstat(self.handle);
|
||||
return @bitCast(u64, stat.size);
|
||||
return (try self.stat()).size;
|
||||
}
|
||||
|
||||
pub const ModeError = os.FStatError;
|
||||
|
@ -217,10 +216,63 @@ pub const File = struct {
|
|||
if (windows.is_the_target) {
|
||||
return {};
|
||||
}
|
||||
const stat = try os.fstat(self.handle);
|
||||
// TODO: we should be able to cast u16 to ModeError!u32, making this
|
||||
// explicit cast not necessary
|
||||
return Mode(stat.mode);
|
||||
return (try self.stat()).mode;
|
||||
}
|
||||
|
||||
pub const Stat = struct {
|
||||
size: u64,
|
||||
mode: Mode,
|
||||
|
||||
/// access time in nanoseconds
|
||||
atime: u64,
|
||||
|
||||
/// last modification time in nanoseconds
|
||||
mtime: u64,
|
||||
|
||||
/// creation time in nanoseconds
|
||||
ctime: u64,
|
||||
};
|
||||
|
||||
pub const StatError = os.FStatError;
|
||||
|
||||
pub fn stat(self: File) StatError!Stat {
|
||||
if (windows.is_the_target) {
|
||||
const info = try windows.GetFileInformationByHandle(self.handle);
|
||||
return Stat{
|
||||
.size = (u64(info.nFileSizeHigh) << 32) | u64(info.nFileSizeLow),
|
||||
.mode = {},
|
||||
.atime = windows.fileTimeToNanoSeconds(info.ftLastAccessTime),
|
||||
.mtime = windows.fileTimeToNanoSeconds(info.ftLastWriteTime),
|
||||
.ctime = windows.fileTimeToNanoSeconds(info.ftCreationTime),
|
||||
};
|
||||
}
|
||||
|
||||
const st = try os.fstat(self.handle);
|
||||
return Stat{
|
||||
.size = @bitCast(u64, st.size),
|
||||
.mode = st.mode,
|
||||
.atime = @bitCast(usize, st.atim.tv_sec) * std.time.ns_per_s + @bitCast(usize, st.atim.tv_nsec),
|
||||
.mtime = @bitCast(usize, st.mtim.tv_sec) * std.time.ns_per_s + @bitCast(usize, st.mtim.tv_nsec),
|
||||
.ctime = @bitCast(usize, st.ctim.tv_sec) * std.time.ns_per_s + @bitCast(usize, st.ctim.tv_nsec),
|
||||
};
|
||||
}
|
||||
|
||||
pub const UpdateTimesError = os.FutimensError;
|
||||
|
||||
/// `atime`: access timestamp in nanoseconds
|
||||
/// `mtime`: last modification timestamp in nanoseconds
|
||||
pub fn updateTimes(self: File, atime: u64, mtime: u64) UpdateTimesError!void {
|
||||
const times = [2]os.timespec{
|
||||
os.timespec{
|
||||
.tv_sec = @bitCast(isize, atime / std.time.ns_per_s),
|
||||
.tv_nsec = @bitCast(isize, atime % std.time.ns_per_s),
|
||||
},
|
||||
os.timespec{
|
||||
.tv_sec = @bitCast(isize, mtime / std.time.ns_per_s),
|
||||
.tv_nsec = @bitCast(isize, mtime % std.time.ns_per_s),
|
||||
},
|
||||
};
|
||||
try os.futimens(self.handle, ×);
|
||||
}
|
||||
|
||||
pub const ReadError = os.ReadError;
|
||||
|
|
39
std/os.zig
39
std/os.zig
|
@ -2546,6 +2546,45 @@ pub fn sigaction(sig: u6, act: *const Sigaction, oact: ?*Sigaction) void {
|
|||
}
|
||||
}
|
||||
|
||||
pub const FutimensError = error{
|
||||
/// times is NULL, or both tv_nsec values are UTIME_NOW, and either:
|
||||
/// * the effective user ID of the caller does not match the owner
|
||||
/// of the file, the caller does not have write access to the
|
||||
/// file, and the caller is not privileged (Linux: does not have
|
||||
/// either the CAP_FOWNER or the CAP_DAC_OVERRIDE capability);
|
||||
/// or,
|
||||
/// * the file is marked immutable (see chattr(1)).
|
||||
AccessDenied,
|
||||
|
||||
/// The caller attempted to change one or both timestamps to a value
|
||||
/// other than the current time, or to change one of the timestamps
|
||||
/// to the current time while leaving the other timestamp unchanged,
|
||||
/// (i.e., times is not NULL, neither tv_nsec field is UTIME_NOW,
|
||||
/// and neither tv_nsec field is UTIME_OMIT) and either:
|
||||
/// * the caller's effective user ID does not match the owner of
|
||||
/// file, and the caller is not privileged (Linux: does not have
|
||||
/// the CAP_FOWNER capability); or,
|
||||
/// * the file is marked append-only or immutable (see chattr(1)).
|
||||
PermissionDenied,
|
||||
|
||||
ReadOnlyFileSystem,
|
||||
|
||||
Unexpected,
|
||||
};
|
||||
|
||||
pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void {
|
||||
switch (errno(system.futimens(fd, times))) {
|
||||
0 => return,
|
||||
EACCES => return error.AccessDenied,
|
||||
EPERM => return error.PermissionDenied,
|
||||
EBADF => unreachable, // always a race condition
|
||||
EFAULT => unreachable,
|
||||
EINVAL => unreachable,
|
||||
EROFS => return error.ReadOnlyFileSystem,
|
||||
else => |err| return unexpectedErrno(err),
|
||||
}
|
||||
}
|
||||
|
||||
test "" {
|
||||
_ = @import("os/darwin.zig");
|
||||
_ = @import("os/freebsd.zig");
|
||||
|
|
|
@ -94,6 +94,15 @@ pub inline fn vfork() usize {
|
|||
return @inlineCall(syscall0, SYS_vfork);
|
||||
}
|
||||
|
||||
pub fn futimens(fd: i32, times: *const [2]timespec) usize {
|
||||
return utimensat(fd, null, times, 0);
|
||||
}
|
||||
|
||||
// TODO https://github.com/ziglang/zig/issues/265
|
||||
pub fn utimensat(dirfd: i32, path: ?[*]const u8, times: *const [2]timespec, flags: u32) usize {
|
||||
return syscall4(SYS_utimensat, @bitCast(usize, isize(dirfd)), @ptrToInt(path), @ptrToInt(times), flags);
|
||||
}
|
||||
|
||||
pub fn futex_wait(uaddr: *const i32, futex_op: u32, val: i32, timeout: ?*timespec) usize {
|
||||
return syscall4(SYS_futex, @ptrToInt(uaddr), futex_op, @bitCast(u32, val), @ptrToInt(timeout));
|
||||
}
|
||||
|
|
|
@ -752,6 +752,26 @@ pub fn HeapDestroy(hHeap: HANDLE) void {
|
|||
assert(kernel32.HeapDestroy(hHeap) != 0);
|
||||
}
|
||||
|
||||
pub const GetFileInformationByHandleError = error{Unexpected};
|
||||
|
||||
pub fn GetFileInformationByHandle(
|
||||
hFile: HANDLE,
|
||||
) GetFileInformationByHandleError!BY_HANDLE_FILE_INFORMATION {
|
||||
var info: BY_HANDLE_FILE_INFORMATION = undefined;
|
||||
const rc = kernel32.GetFileInformationByHandle(hFile, &info);
|
||||
if (rc == 0) {
|
||||
switch (kernel32.GetLastError()) {
|
||||
else => |err| return unexpectedError(err),
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
pub fn fileTimeToNanoSeconds(ft: FILETIME) u64 {
|
||||
const sec = (u64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
|
||||
return sec * std.time.ns_per_s;
|
||||
}
|
||||
|
||||
pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE + 1]u16 {
|
||||
return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
|
||||
}
|
||||
|
|
|
@ -104,6 +104,19 @@ pub const FileIdInfo = 18;
|
|||
pub const FileIdExtdDirectoryInfo = 19;
|
||||
pub const FileIdExtdDirectoryRestartInfo = 20;
|
||||
|
||||
pub const BY_HANDLE_FILE_INFORMATION = extern struct {
|
||||
dwFileAttributes: DWORD,
|
||||
ftCreationTime: FILETIME,
|
||||
ftLastAccessTime: FILETIME,
|
||||
ftLastWriteTime: FILETIME,
|
||||
dwVolumeSerialNumber: DWORD,
|
||||
nFileSizeHigh: DWORD,
|
||||
nFileSizeLow: DWORD,
|
||||
nNumberOfLinks: DWORD,
|
||||
nFileIndexHigh: DWORD,
|
||||
nFileIndexLow: DWORD,
|
||||
};
|
||||
|
||||
pub const FILE_NAME_INFO = extern struct {
|
||||
FileNameLength: DWORD,
|
||||
FileName: [1]WCHAR,
|
||||
|
|
|
@ -83,6 +83,11 @@ pub extern "kernel32" stdcallcc fn GetModuleHandleW(lpModuleName: ?[*]const WCHA
|
|||
|
||||
pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
|
||||
|
||||
pub extern "kernel32" stdcallcc fn GetFileInformationByHandle(
|
||||
hFile: HANDLE,
|
||||
lpFileInformation: *BY_HANDLE_FILE_INFORMATION,
|
||||
) BOOL;
|
||||
|
||||
pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(
|
||||
in_hFile: HANDLE,
|
||||
in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS,
|
||||
|
|
Loading…
Reference in New Issue