Commit 05b3082121

Andrew Kelley <superjoe30@gmail.com>
2017-04-17 12:45:44
zig build system: progress toward install and uninstall
also: * add std.os.path.join * add std.os.deleteFile
1 parent e4ec2d1
std/os/index.zig
@@ -10,6 +10,7 @@ pub const posix = switch(@compileVar("os")) {
 
 pub const max_noalloc_path_len = 1024;
 pub const ChildProcess = @import("child_process.zig").ChildProcess;
+pub const path = @import("path.zig");
 
 const debug = @import("../debug.zig");
 const assert = debug.assert;
@@ -24,6 +25,8 @@ const Allocator = mem.Allocator;
 const BufMap = @import("../buf_map.zig").BufMap;
 const cstr = @import("../cstr.zig");
 
+const io = @import("../io.zig");
+
 error Unexpected;
 error SystemResources;
 error AccessDenied;
@@ -134,21 +137,21 @@ pub fn posixWrite(fd: i32, bytes: []const u8) -> %void {
 }
 
 
-/// ::path may need to be copied in memory to add a null terminating byte. In this case
+/// ::file_path may need to be copied in memory to add a null terminating byte. In this case
 /// a fixed size buffer of size ::max_noalloc_path_len is an attempted solution. If the fixed
 /// size buffer is too small, and the provided allocator is null, ::error.NameTooLong is returned.
 /// otherwise if the fixed size buffer is too small, allocator is used to obtain the needed memory.
 /// Calls POSIX open, keeps trying if it gets interrupted, and translates
 /// the return value into zig errors.
-pub fn posixOpen(path: []const u8, flags: usize, perm: usize, allocator: ?&Allocator) -> %i32 {
+pub fn posixOpen(file_path: []const u8, flags: usize, perm: usize, allocator: ?&Allocator) -> %i32 {
     var stack_buf: [max_noalloc_path_len]u8 = undefined;
     var path0: []u8 = undefined;
     var need_free = false;
 
-    if (path.len < stack_buf.len) {
-        path0 = stack_buf[0...path.len + 1];
+    if (file_path.len < stack_buf.len) {
+        path0 = stack_buf[0...file_path.len + 1];
     } else if (const a ?= allocator) {
-        path0 = %return a.alloc(u8, path.len + 1);
+        path0 = %return a.alloc(u8, file_path.len + 1);
         need_free = true;
     } else {
         return error.NameTooLong;
@@ -156,8 +159,8 @@ pub fn posixOpen(path: []const u8, flags: usize, perm: usize, allocator: ?&Alloc
     defer if (need_free) {
         (??allocator).free(path0);
     };
-    mem.copy(u8, path0, path);
-    path0[path.len] = 0;
+    mem.copy(u8, path0, file_path);
+    path0[file_path.len] = 0;
 
     while (true) {
         const result = posix.open(path0.ptr, flags, perm);
@@ -416,12 +419,12 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
     }
 }
 
-pub fn deleteFile(allocator: &Allocator, path: []const u8) -> %void {
-    const buf = %return allocator.alloc(u8, path.len + 1);
+pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void {
+    const buf = %return allocator.alloc(u8, file_path.len + 1);
     defer allocator.free(buf);
 
-    mem.copy(u8, buf, path);
-    buf[path.len] = 0;
+    mem.copy(u8, buf, file_path);
+    buf[file_path.len] = 0;
 
     const err = posix.getErrno(posix.unlink(buf.ptr));
     if (err > 0) {
@@ -440,3 +443,19 @@ pub fn deleteFile(allocator: &Allocator, path: []const u8) -> %void {
         };
     }
 }
+
+pub fn copyFile(allocator: &Allocator, source_path: []const u8, dest_path: []const u8) -> %void {
+    var in_stream = %return io.InStream.open(source_path, allocator);
+    defer in_stream.close();
+    var out_stream = %return io.OutStream.open(dest_path, allocator);
+    defer out_stream.close();
+
+    const buf = out_stream.buffer[0...];
+    while (true) {
+        const amt = %return in_stream.read(buf);
+        out_stream.index = amt;
+        %return out_stream.flush();
+        if (amt != out_stream.buffer.len)
+            return;
+    }
+}
std/os/path.zig
@@ -0,0 +1,25 @@
+const debug = @import("../debug.zig");
+const assert = debug.assert;
+const mem = @import("../mem.zig");
+const Allocator = mem.Allocator;
+
+/// Allocates memory for the result, which must be freed by the caller.
+pub fn join(allocator: &Allocator, dirname: []const u8, basename: []const u8) -> %[]const u8 {
+    const buf = %return allocator.alloc(u8, dirname.len + basename.len + 1);
+    %defer allocator.free(buf);
+
+    mem.copy(u8, buf, dirname);
+    if (dirname[dirname.len - 1] == '/') {
+        mem.copy(u8, buf[dirname.len...], basename);
+        return buf[0...buf.len - 1];
+    } else {
+        buf[dirname.len] = '/';
+        mem.copy(u8, buf[dirname.len + 1 ...], basename);
+        return buf;
+    }
+}
+
+test "os.path.join" {
+    assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));
+    assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c"));
+}
std/special/build_runner.zig
@@ -80,6 +80,7 @@ fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool,
 
     // run the build script to collect the options
     if (!already_ran_build) {
+        builder.setInstallPrefix(null);
         root.build(builder);
     }
 
std/build.zig
@@ -19,6 +19,8 @@ error DependencyLoopDetected;
 error NoCompilerFound;
 
 pub const Builder = struct {
+    uninstall_tls: TopLevelStep,
+    have_uninstall_step: bool,
     allocator: &Allocator,
     lib_paths: List([]const u8),
     include_paths: List([]const u8),
@@ -33,7 +35,9 @@ pub const Builder = struct {
     env_map: BufMap,
     top_level_steps: List(&TopLevelStep),
     prefix: []const u8,
+    lib_dir: []const u8,
     out_dir: []u8,
+    installed_files: List([]const u8),
 
     const UserInputOptionsMap = HashMap([]const u8, UserInputOption, mem.hash_slice_u8, mem.eql_slice_u8);
     const AvailableOptionsMap = HashMap([]const u8, AvailableOption, mem.hash_slice_u8, mem.eql_slice_u8);
@@ -85,7 +89,14 @@ pub const Builder = struct {
             .default_step = undefined,
             .env_map = %%os.getEnvMap(allocator),
             .prefix = undefined,
+            .lib_dir = undefined,
             .out_dir = %%os.getCwd(allocator),
+            .installed_files = List([]const u8).init(allocator),
+            .uninstall_tls = TopLevelStep {
+                .step = Step.init("uninstall", allocator, makeUninstall),
+                .description = "Remove build artifacts from prefix path",
+            },
+            .have_uninstall_step = false,
         };
         self.processNixOSEnvVars();
         self.default_step = self.step("default", "Build the project");
@@ -102,12 +113,8 @@ pub const Builder = struct {
     }
 
     pub fn setInstallPrefix(self: &Builder, maybe_prefix: ?[]const u8) {
-        if (const prefix ?= maybe_prefix) {
-            self.prefix = prefix;
-            return;
-        }
-        // TODO better default
-        self.prefix = "/usr/local";
+        self.prefix = maybe_prefix ?? "/usr/local"; // TODO better default
+        self.lib_dir = %%os.path.join(self.allocator, self.prefix, "lib");
     }
 
     pub fn addExecutable(self: &Builder, name: []const u8, root_src: []const u8) -> &Exe {
@@ -180,6 +187,27 @@ pub const Builder = struct {
         }
     }
 
+    pub fn getUninstallStep(self: &Builder) -> &Step {
+        if (self.have_uninstall_step)
+            return &self.uninstall_tls.step;
+
+        %%self.top_level_steps.append(&self.uninstall_tls);
+        self.have_uninstall_step = true;
+        return &self.uninstall_tls.step;
+    }
+
+    fn makeUninstall(uninstall_step: &Step) -> %void {
+        // TODO
+        // const self = @fieldParentPtr(Exe, "step", step);
+        const self = @ptrcast(&Builder, uninstall_step);
+
+        for (self.installed_files.toSliceConst()) |installed_file| {
+            _ = os.deleteFile(self.allocator, installed_file);
+        }
+
+        // TODO remove empty directories
+    }
+
     fn makeOneStep(self: &Builder, s: &Step) -> %void {
         if (s.loop_flag) {
             %%io.stderr.printf("Dependency loop detected:\n  {}\n", s.name);
@@ -426,6 +454,34 @@ pub const Builder = struct {
         };
 
     }
+
+    pub fn installCLibrary(self: &Builder, lib: &CLibrary) -> &InstallCLibraryStep {
+        const install_step = %%self.allocator.create(InstallCLibraryStep);
+        *install_step = InstallCLibraryStep.init(self, lib);
+        install_step.step.dependOn(&lib.step);
+        return install_step;
+    }
+
+    ///::dest_rel_path is relative to prefix path
+    pub fn installFile(self: &Builder, src_path: []const u8, dest_rel_path: []const u8) -> &InstallFileStep {
+        const full_dest_path = %%os.path.join(self.allocator, self.prefix, dest_rel_path);
+        self.addInstalledFile(full_dest_path);
+
+        const install_step = %%self.allocator.create(InstallFileStep);
+        *install_step = InstallFileStep.init(self, src_path, full_dest_path);
+        return install_step;
+    }
+
+    pub fn addInstalledFile(self: &Builder, full_path: []const u8) {
+        _ = self.getUninstallStep();
+        %%self.installed_files.append(full_path);
+    }
+
+    fn copyFile(self: &Builder, source_path: []const u8, dest_path: []const u8) {
+        os.copyFile(self.allocator, source_path, dest_path) %% |err| {
+            debug.panic("Unable to copy {} to {}: {}", source_path, dest_path, @errorName(err));
+        };
+    }
 };
 
 const Version = struct {
@@ -616,6 +672,8 @@ const CLibrary = struct {
     target: Target,
     builder: &Builder,
     include_dirs: List([]const u8),
+    major_only_filename: []const u8,
+    name_only_filename: []const u8,
 
     pub fn initShared(builder: &Builder, name: []const u8, version: &const Version) -> CLibrary {
         return init(builder, name, version, false);
@@ -639,6 +697,8 @@ const CLibrary = struct {
             .link_libs = BufSet.init(builder.allocator),
             .include_dirs = List([]const u8).init(builder.allocator),
             .out_filename = undefined,
+            .major_only_filename = undefined,
+            .name_only_filename = undefined,
         };
         clib.computeOutFileName();
         return clib;
@@ -650,6 +710,10 @@ const CLibrary = struct {
         } else {
             self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.so.{d}.{d}.{d}",
                 self.name, self.version.major, self.version.minor, self.version.patch);
+            self.major_only_filename = %%fmt.allocPrint(self.builder.allocator,
+                "lib{}.so.{d}", self.name, self.version.major);
+            self.name_only_filename = %%fmt.allocPrint(self.builder.allocator,
+                "lib{}.so", self.name);
         }
     }
 
@@ -752,15 +816,11 @@ const CLibrary = struct {
             builder.spawnChild(cc, cc_args.toSliceConst());
 
             // sym link for libfoo.so.1 to libfoo.so.1.2.3
-            const major_only = %%fmt.allocPrint(builder.allocator, "lib{}.so.{d}", self.name, self.version.major);
-            defer builder.allocator.free(major_only);
-            _ = os.deleteFile(builder.allocator, major_only);
-            %%os.symLink(builder.allocator, self.out_filename, major_only);
+            _ = os.deleteFile(builder.allocator, self.major_only_filename);
+            %%os.symLink(builder.allocator, self.out_filename, self.major_only_filename);
             // sym link for libfoo.so to libfoo.so.1
-            const name_only = %%fmt.allocPrint(builder.allocator, "lib{}.so", self.name);
-            defer builder.allocator.free(name_only);
-            _ = os.deleteFile(builder.allocator, name_only);
-            %%os.symLink(builder.allocator, major_only, name_only);
+            _ = os.deleteFile(builder.allocator, self.name_only_filename);
+            %%os.symLink(builder.allocator, self.major_only_filename, self.name_only_filename);
         }
     }
 
@@ -938,6 +998,71 @@ const CommandStep = struct {
     }
 };
 
+const InstallCLibraryStep = struct {
+    step: Step,
+    builder: &Builder,
+    lib: &CLibrary,
+    dest_file: []const u8,
+
+    pub fn init(builder: &Builder, lib: &CLibrary) -> InstallCLibraryStep {
+        var self = InstallCLibraryStep {
+            .builder = builder,
+            .step = Step.init(
+                %%fmt.allocPrint(builder.allocator, "install {}", lib.step.name),
+                builder.allocator, make),
+            .lib = lib,
+            .dest_file = undefined,
+        };
+        self.dest_file = %%os.path.join(builder.allocator, builder.lib_dir, lib.out_filename);
+        builder.addInstalledFile(self.dest_file);
+        if (!self.lib.static) {
+            builder.addInstalledFile(%%os.path.join(builder.allocator, builder.lib_dir, lib.major_only_filename));
+            builder.addInstalledFile(%%os.path.join(builder.allocator, builder.lib_dir, lib.name_only_filename));
+        }
+        return self;
+    }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(InstallCLibraryStep, "step", step);
+        const self = @ptrcast(&InstallCLibraryStep, step);
+
+        self.builder.copyFile(self.lib.out_filename, self.dest_file);
+        if (!self.lib.static) {
+            _ = os.deleteFile(self.builder.allocator, self.lib.major_only_filename);
+            %%os.symLink(self.builder.allocator, self.lib.out_filename, self.lib.major_only_filename);
+            _ = os.deleteFile(self.builder.allocator, self.lib.name_only_filename);
+            %%os.symLink(self.builder.allocator, self.lib.major_only_filename, self.lib.name_only_filename);
+        }
+    }
+};
+
+const InstallFileStep = struct {
+    step: Step,
+    builder: &Builder,
+    src_path: []const u8,
+    dest_path: []const u8,
+
+    pub fn init(builder: &Builder, src_path: []const u8, dest_path: []const u8) -> InstallFileStep {
+        return InstallFileStep {
+            .builder = builder,
+            .step = Step.init(
+                %%fmt.allocPrint(builder.allocator, "install {}", src_path),
+                builder.allocator, make),
+            .src_path = src_path,
+            .dest_path = dest_path,
+        };
+    }
+
+    fn make(step: &Step) -> %void {
+        // TODO issue #320
+        //const self = @fieldParentPtr(InstallFileStep, "step", step);
+        const self = @ptrcast(&InstallFileStep, step);
+
+        debug.panic("TODO install file");
+    }
+};
+
 const Step = struct {
     name: []const u8,
     makeFn: fn(self: &Step) -> %void,
@@ -972,25 +1097,3 @@ const Step = struct {
 
     fn makeNoOp(self: &Step) -> %void {}
 };
-
-fn printInvocation(exe_name: []const u8, args: &const List([]const u8)) {
-    %%io.stderr.printf("{}", exe_name);
-    for (args.toSliceConst()) |arg| {
-        %%io.stderr.printf(" {}", arg);
-    }
-    %%io.stderr.printf("\n");
-}
-
-fn waitForCleanExit(child: &os.ChildProcess) -> %void {
-    const term = %%child.wait();
-    switch (term) {
-        Term.Clean => |code| {
-            if (code != 0) {
-                return error.UncleanExit;
-            }
-        },
-        else => {
-            return error.UncleanExit;
-        },
-    };
-}
CMakeLists.txt
@@ -233,6 +233,7 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/os/index.zig" DESTINATION "${ZIG_STD_DEST
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux_i386.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/linux_x86_64.zig" DESTINATION "${ZIG_STD_DEST}/os")
+install(FILES "${CMAKE_SOURCE_DIR}/std/os/path.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/os/windows.zig" DESTINATION "${ZIG_STD_DEST}/os")
 install(FILES "${CMAKE_SOURCE_DIR}/std/rand.zig" DESTINATION "${ZIG_STD_DEST}")
 install(FILES "${CMAKE_SOURCE_DIR}/std/rand_test.zig" DESTINATION "${ZIG_STD_DEST}")