diff --git a/CMakeLists.txt b/CMakeLists.txt index 86dca3aea5..8b119e1628 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -199,6 +199,7 @@ install(FILES ${C_HEADERS} DESTINATION ${C_HEADERS_DEST}) install(FILES "${CMAKE_SOURCE_DIR}/std/base64.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/buf_map.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/buf_set.zig" DESTINATION "${ZIG_STD_DEST}") +install(FILES "${CMAKE_SOURCE_DIR}/std/buffer.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/build.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/c/darwin.zig" DESTINATION "${ZIG_STD_DEST}/c") install(FILES "${CMAKE_SOURCE_DIR}/std/c/index.zig" DESTINATION "${ZIG_STD_DEST}/c") diff --git a/example/shared_library/build.zig b/example/shared_library/build.zig new file mode 100644 index 0000000000..1212c58ab8 --- /dev/null +++ b/example/shared_library/build.zig @@ -0,0 +1,20 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: &Builder) { + const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0)); + + const exe = b.addCExecutable("test"); + exe.addCompileFlags([][]const u8 { + "-std=c99", + }); + exe.addSourceFile("test.c"); + exe.linkLibrary(lib); + + b.default_step.dependOn(&exe.step); + + const run_cmd = b.addCommand(b.out_dir, b.env_map, "./test", [][]const u8{}); + run_cmd.step.dependOn(&exe.step); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(&run_cmd.step); +} diff --git a/example/shared_library/mathtest.zig b/example/shared_library/mathtest.zig index 9dc33b9555..a11642554f 100644 --- a/example/shared_library/mathtest.zig +++ b/example/shared_library/mathtest.zig @@ -1,7 +1,3 @@ export fn add(a: i32, b: i32) -> i32 { a + b } - -export fn hang() -> unreachable { - while (true) { } -} diff --git a/example/shared_library/test.c b/example/shared_library/test.c index 4fff250f08..b60a6a5a75 100644 --- a/example/shared_library/test.c +++ b/example/shared_library/test.c @@ -1,7 +1,7 @@ #include "mathtest.h" -#include +#include int main(int argc, char **argv) { - printf("%d\n", add(42, 1137)); + assert(add(42, 1337) == 1379); return 0; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 735c3f19a9..d8c3e0e2d6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -155,6 +155,12 @@ void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix) { g->test_name_prefix = prefix; } +void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patch) { + g->version_major = major; + g->version_minor = minor; + g->version_patch = patch; +} + void codegen_set_is_test(CodeGen *g, bool is_test_build) { g->is_test_build = is_test_build; } diff --git a/src/codegen.hpp b/src/codegen.hpp index 03aa6f53e4..d5d7bd0644 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -46,6 +46,7 @@ void codegen_set_linker_script(CodeGen *g, const char *linker_script); void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt); void codegen_set_test_filter(CodeGen *g, Buf *filter); void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix); +void codegen_set_lib_version(CodeGen *g, size_t major, size_t minor, size_t patch); PackageTableEntry *new_package(const char *root_src_dir, const char *root_src_path); void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code); diff --git a/src/main.cpp b/src/main.cpp index 1d37352427..bf2dbcf683 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,6 +67,10 @@ static int usage(const char *arg0) { "Test Options:\n" " --test-filter [text] skip tests that do not match filter\n" " --test-name-prefix [text] add prefix to all tests\n" + "Dynamic Library Options:\n" + " --ver-major [ver] semver major version\n" + " --ver-minor [ver] semver minor version\n" + " --ver-patch [ver] semver patch version\n" , arg0); return EXIT_FAILURE; } @@ -156,6 +160,9 @@ int main(int argc, char **argv) { ZigList objects = {0}; const char *test_filter = nullptr; const char *test_name_prefix = nullptr; + size_t ver_major = 0; + size_t ver_minor = 0; + size_t ver_patch = 0; if (argc >= 2 && strcmp(argv[1], "build") == 0) { const char *zig_exe_path = arg0; @@ -350,6 +357,12 @@ int main(int argc, char **argv) { test_filter = argv[i]; } else if (strcmp(arg, "--test-name-prefix") == 0) { test_name_prefix = argv[i]; + } else if (strcmp(arg, "--ver-major") == 0) { + ver_major = atoi(argv[i]); + } else if (strcmp(arg, "--ver-minor") == 0) { + ver_minor = atoi(argv[i]); + } else if (strcmp(arg, "--ver-patch") == 0) { + ver_patch = atoi(argv[i]); } else { fprintf(stderr, "Invalid argument: %s\n", arg); return usage(arg0); @@ -509,6 +522,7 @@ int main(int argc, char **argv) { } CodeGen *g = codegen_create(&root_source_dir, target); + codegen_set_lib_version(g, ver_major, ver_minor, ver_patch); codegen_set_is_release(g, is_release_build); codegen_set_is_test(g, cmd == CmdTest); codegen_set_linker_script(g, linker_script); diff --git a/std/buffer.zig b/std/buffer.zig new file mode 100644 index 0000000000..c7f0861356 --- /dev/null +++ b/std/buffer.zig @@ -0,0 +1,117 @@ +const debug = @import("debug.zig"); +const mem = @import("mem.zig"); +const Allocator = mem.Allocator; +const assert = debug.assert; +const List = @import("list.zig").List; + +/// A buffer that allocates memory and maintains a null byte at the end. +pub const Buffer = struct { + list: List(u8), + + /// Must deinitialize with deinit. + pub fn init(allocator: &Allocator, m: []const u8) -> %Buffer { + var self = %return initSize(allocator, m.len); + mem.copy(u8, self.list.items, m); + return self; + } + + /// Must deinitialize with deinit. + pub fn initSize(allocator: &Allocator, size: usize) -> %Buffer { + var self = initNull(allocator); + %return self.resize(size); + return self; + } + + /// Must deinitialize with deinit. + /// None of the other operations are valid until you do one of these: + /// * ::replaceContents + /// * ::replaceContentsBuffer + /// * ::resize + pub fn initNull(allocator: &Allocator) -> Buffer { + Buffer { + .list = List(u8).init(allocator), + } + } + + /// Must deinitialize with deinit. + pub fn initFromBuffer(buffer: &const Buffer) -> %Buffer { + return Buffer.init(buffer.list.allocator, buffer.toSliceConst()); + } + + pub fn deinit(self: &Buffer) { + self.list.deinit(); + } + + pub fn toSlice(self: &Buffer) -> []u8 { + return self.list.toSlice()[0...self.len()]; + } + + pub fn toSliceConst(self: &const Buffer) -> []const u8 { + return self.list.toSliceConst()[0...self.len()]; + } + + pub fn resize(self: &Buffer, new_len: usize) -> %void { + %return self.list.resize(new_len + 1); + self.list.items[self.len()] = 0; + } + + pub fn isNull(self: &const Buffer) -> bool { + return self.list.len == 0; + } + + pub fn len(self: &const Buffer) -> usize { + return self.list.len - 1; + } + + pub fn append(self: &Buffer, m: []const u8) -> %void { + const old_len = self.len(); + %return self.resize(old_len + m.len); + mem.copy(u8, self.list.toSlice()[old_len...], m); + } + + pub fn appendByte(self: &Buffer, byte: u8) -> %void { + %return self.resize(self.len() + 1); + self.list.items[self.len() - 1] = byte; + } + + pub fn eql(self: &const Buffer, m: []const u8) -> bool { + mem.eql(u8, self.toSliceConst(), m) + } + + pub fn startsWith(self: &const Buffer, m: []const u8) -> bool { + if (self.len() < m.len) return false; + return mem.eql(u8, self.list.items[0...m.len], m); + } + + pub fn endsWith(self: &const Buffer, m: []const u8) -> bool { + const l = self.len(); + if (l < m.len) return false; + const start = l - m.len; + return mem.eql(u8, self.list.items[start...], m); + } + + pub fn replaceContents(self: &const Buffer, m: []const u8) -> %void { + %return self.resize(m.len); + mem.copy(u8, self.list.toSlice(), m); + } +}; + +test "simple Buffer" { + const cstr = @import("cstr.zig"); + + var buf = %%Buffer.init(&debug.global_allocator, ""); + assert(buf.len() == 0); + %%buf.append("hello"); + %%buf.appendByte(' '); + %%buf.append("world"); + assert(buf.eql("hello world")); + assert(mem.eql(u8, cstr.toSliceConst(buf.toSliceConst().ptr), buf.toSliceConst())); + + var buf2 = %%Buffer.initFromBuffer(&buf); + assert(buf.eql(buf2.toSliceConst())); + + assert(buf.startsWith("hell")); + + %%buf2.resize(4); + assert(buf.startsWith(buf2.toSliceConst())); +} diff --git a/std/build.zig b/std/build.zig index d22d6b660a..29b3350292 100644 --- a/std/build.zig +++ b/std/build.zig @@ -120,12 +120,30 @@ pub const Builder = struct { self.lib_dir = %%os.path.join(self.allocator, self.prefix, "lib"); } - pub fn addExecutable(self: &Builder, name: []const u8, root_src: []const u8) -> &Exe { - const exe = %%self.allocator.create(Exe); - *exe = Exe.init(self, name, root_src); + pub fn addExecutable(self: &Builder, name: []const u8, root_src: []const u8) -> &LibOrExeStep { + const exe = %%self.allocator.create(LibOrExeStep); + *exe = LibOrExeStep.initExecutable(self, name, root_src); return exe; } + pub fn addObject(self: &Builder, name: []const u8, root_src: []const u8) -> &ObjectStep { + const obj_step = %%self.allocator.create(ObjectStep); + *obj_step = ObjectStep.init(self, name, src); + return obj_step; + } + + pub fn addSharedLibrary(self: &Builder, name: []const u8, root_src: []const u8, ver: &const Version) -> &LibOrExeStep { + const lib_step = %%self.allocator.create(LibOrExeStep); + *lib_step = LibOrExeStep.initSharedLibrary(self, name, root_src, ver); + return lib_step; + } + + pub fn addStaticLibrary(self: &Builder, name: []const u8, root_src: []const u8) -> &LibOrExeStep { + const lib_step = %%self.allocator.create(LibOrExeStep); + *lib_step = LibOrExeStep.initStaticLibrary(self, name, root_src); + return lib_step; + } + pub fn addTest(self: &Builder, root_src: []const u8) -> &TestStep { const test_step = %%self.allocator.create(TestStep); *test_step = TestStep.init(self, root_src); @@ -487,11 +505,13 @@ pub const Builder = struct { return self.invalid_user_input; } - fn spawnChild(self: &Builder, exe_path: []const u8, args: []const []const u8) { + fn spawnChild(self: &Builder, exe_path: []const u8, args: []const []const u8) -> %void { return self.spawnChildEnvMap(&self.env_map, exe_path, args); } - fn spawnChildEnvMap(self: &Builder, env_map: &const BufMap, exe_path: []const u8, args: []const []const u8) { + fn spawnChildEnvMap(self: &Builder, env_map: &const BufMap, exe_path: []const u8, + args: []const []const u8) -> %void + { if (self.verbose) { %%io.stderr.printf("{}", exe_path); for (args) |arg| { @@ -501,18 +521,26 @@ pub const Builder = struct { } var child = os.ChildProcess.spawn(exe_path, args, env_map, - StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator) - %% |err| debug.panic("Unable to spawn {}: {}\n", exe_path, @errorName(err)); + StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator) %% |err| + { + %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err)); + return err; + }; - const term = %%child.wait(); + const term = child.wait() %% |err| { + %%io.stderr.printf("Unable to spawn {}: {}\n", exe_path, @errorName(err)); + return err; + }; switch (term) { Term.Clean => |code| { if (code != 0) { - debug.panic("Process {} exited with error code {}\n", exe_path, code); + %%io.stderr.printf("Process {} exited with error code {}\n", exe_path, code); + return error.UncleanExit; } }, else => { - debug.panic("Process {} terminated unexpectedly\n", exe_path); + %%io.stderr.printf("Process {} terminated unexpectedly\n", exe_path); + return error.UncleanExit; }, }; @@ -547,7 +575,7 @@ pub const Builder = struct { } fn pathFromRoot(self: &Builder, rel_path: []const u8) -> []u8 { - return %%os.path.join(self.allocator, self.build_root, rel_path); + return %%os.path.resolve(self.allocator, self.build_root, rel_path); } pub fn fmt(self: &Builder, comptime format: []const u8, args: ...) -> []u8 { @@ -600,7 +628,7 @@ const LinkerScript = enum { Path: []const u8, }; -pub const Exe = struct { +pub const LibOrExeStep = struct { step: Step, builder: &Builder, root_src: []const u8, @@ -610,13 +638,42 @@ pub const Exe = struct { link_libs: BufSet, verbose: bool, release: bool, + static: bool, output_path: ?[]const u8, + kind: Kind, + version: Version, + out_filename: []const u8, + out_filename_major_only: []const u8, + out_filename_name_only: []const u8, - pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe { - Exe { + const Kind = enum { + Exe, + Lib, + }; + + pub fn initExecutable(builder: &Builder, name: []const u8, root_src: []const u8) -> LibOrExeStep { + return initExtraArgs(builder, name, root_src, Kind.Exe, false, builder.version(0, 0, 0)); + } + + pub fn initSharedLibrary(builder: &Builder, name: []const u8, root_src: []const u8, + ver: &const Version) -> LibOrExeStep + { + return initExtraArgs(builder, name, root_src, Kind.Lib, false, ver); + } + + pub fn initStaticLibrary(builder: &Builder, name: []const u8, root_src: []const u8) -> LibOrExeStep { + return initExtraArgs(builder, name, root_src, Kind.Lib, true, builder.version(0, 0, 0)); + } + + fn initExtraArgs(builder: &Builder, name: []const u8, root_src: []const u8, kind: Kind, + static: bool, ver: &const Version) -> LibOrExeStep + { + var self = LibOrExeStep { .builder = builder, .verbose = false, .release = false, + .static = static, + .kind = kind, .root_src = root_src, .name = name, .target = Target.Native, @@ -624,14 +681,34 @@ pub const Exe = struct { .link_libs = BufSet.init(builder.allocator), .step = Step.init(name, builder.allocator, make), .output_path = null, + .version = *ver, + .out_filename = undefined, + .out_filename_major_only = undefined, + .out_filename_name_only = undefined, + }; + self.computeOutFileNames(); + return self; + } + + fn computeOutFileNames(self: &LibOrExeStep) { + switch (self.kind) { + Kind.Exe => { + self.out_filename = self.builder.fmt("{}{}", self.name, self.target.exeFileExt()); + }, + Kind.Lib => { + if (self.static) { + self.out_filename = self.builder.fmt("lib{}.a", self.name); + } else { + self.out_filename = self.builder.fmt("lib{}.so.{d}.{d}.{d}", + self.name, self.version.major, self.version.minor, self.version.patch); + self.out_filename_major_only = self.builder.fmt("lib{}.so.{d}", self.name, self.version.major); + self.out_filename_name_only = self.builder.fmt("lib{}.so", self.name); + } + }, } } - pub fn deinit(self: &Exe) { - self.link_libs.deinit(); - } - - pub fn setTarget(self: &Exe, target_arch: Arch, target_os: Os, target_environ: Environ) { + pub fn setTarget(self: &LibOrExeStep, target_arch: Arch, target_os: Os, target_environ: Environ) { self.target = Target.Cross { CrossTarget { .arch = target_arch, @@ -641,59 +718,74 @@ pub const Exe = struct { }; } - /// Exe keeps a reference to script for its lifetime or until this function + /// LibOrExeStep keeps a reference to script for its lifetime or until this function /// is called again. - pub fn setLinkerScriptContents(self: &Exe, script: []const u8) { + pub fn setLinkerScriptContents(self: &LibOrExeStep, script: []const u8) { self.linker_script = LinkerScript.Embed { script }; } - pub fn setLinkerScriptPath(self: &Exe, path: []const u8) { + pub fn setLinkerScriptPath(self: &LibOrExeStep, path: []const u8) { self.linker_script = LinkerScript.Path { path }; } - pub fn linkLibrary(self: &Exe, name: []const u8) { + pub fn linkSystemLibrary(self: &LibOrExeStep, name: []const u8) { %%self.link_libs.put(name); } - pub fn setVerbose(self: &Exe, value: bool) { + pub fn setVerbose(self: &LibOrExeStep, value: bool) { self.verbose = value; } - pub fn setRelease(self: &Exe, value: bool) { + pub fn setRelease(self: &LibOrExeStep, value: bool) { self.release = value; } - pub fn setOutputPath(self: &Exe, value: []const u8) { + pub fn setOutputPath(self: &LibOrExeStep, value: []const u8) { self.output_path = value; } fn make(step: &Step) -> %void { - const exe = @fieldParentPtr(Exe, "step", step); - const builder = exe.builder; + const self = @fieldParentPtr(LibOrExeStep, "step", step); + const builder = self.builder; var zig_args = List([]const u8).init(builder.allocator); defer zig_args.deinit(); - %%zig_args.append("build_exe"); - %%zig_args.append(builder.pathFromRoot(exe.root_src)); + const cmd = switch (self.kind) { + Kind.Lib => "build_lib", + Kind.Exe => "build_exe", + }; + %%zig_args.append(cmd); + %%zig_args.append(builder.pathFromRoot(self.root_src)); - if (exe.verbose) { + if (self.verbose) { %%zig_args.append("--verbose"); } - if (exe.release) { + if (self.release) { %%zig_args.append("--release"); } - if (const output_path ?= exe.output_path) { + if (const output_path ?= self.output_path) { %%zig_args.append("--output"); %%zig_args.append(builder.pathFromRoot(output_path)); } %%zig_args.append("--name"); - %%zig_args.append(exe.name); + %%zig_args.append(self.name); - switch (exe.target) { + if (self.kind == Kind.Lib and !self.static) { + %%zig_args.append("--ver-major"); + %%zig_args.append(builder.fmt("{}", self.version.major)); + + %%zig_args.append("--ver-minor"); + %%zig_args.append(builder.fmt("{}", self.version.minor)); + + %%zig_args.append("--ver-patch"); + %%zig_args.append(builder.fmt("{}", self.version.patch)); + } + + switch (self.target) { Target.Native => {}, Target.Cross => |cross_target| { %%zig_args.append("--target-arch"); @@ -707,7 +799,7 @@ pub const Exe = struct { }, } - switch (exe.linker_script) { + switch (self.linker_script) { LinkerScript.None => {}, LinkerScript.Embed => |script| { const tmp_file_name = "linker.ld.tmp"; // TODO issue #298 @@ -723,7 +815,7 @@ pub const Exe = struct { } { - var it = exe.link_libs.iterator(); + var it = self.link_libs.iterator(); while (true) { const entry = it.next() ?? break; %%zig_args.append("--library"); @@ -746,7 +838,118 @@ pub const Exe = struct { %%zig_args.append(lib_path); } - builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + %%builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + + if (self.kind == Kind.Lib and !self.static) { + // sym link for libfoo.so.1 to libfoo.so.1.2.3 + %%os.atomicSymLink(builder.allocator, self.out_filename, self.out_filename_major_only); + // sym link for libfoo.so to libfoo.so.1 + %%os.atomicSymLink(builder.allocator, self.out_filename_major_only, self.out_filename_name_only); + } + } +}; + +pub const ObjectStep = struct { + step: Step, + builder: &Builder, + root_src: []const u8, + name: []const u8, + target: Target, + verbose: bool, + release: bool, + output_path: ?[]const u8, + + pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> ObjectStep { + ObjectStep { + .builder = builder, + .verbose = false, + .release = false, + .root_src = root_src, + .name = name, + .target = Target.Native, + .step = Step.init(name, builder.allocator, make), + .output_path = null, + } + } + + pub fn setTarget(self: &ObjectStep, target_arch: Arch, target_os: Os, target_environ: Environ) { + self.target = Target.Cross { + CrossTarget { + .arch = target_arch, + .os = target_os, + .environ = target_environ, + } + }; + } + + pub fn setVerbose(self: &ObjectStep, value: bool) { + self.verbose = value; + } + + pub fn setRelease(self: &ObjectStep, value: bool) { + self.release = value; + } + + pub fn setOutputPath(self: &ObjectStep, value: []const u8) { + self.output_path = value; + } + + fn make(step: &Step) -> %void { + const self = @fieldParentPtr(ObjectStep, "step", step); + const builder = self.builder; + + var zig_args = List([]const u8).init(builder.allocator); + defer zig_args.deinit(); + + %%zig_args.append("build_obj"); + %%zig_args.append(builder.pathFromRoot(self.root_src)); + + if (self.verbose) { + %%zig_args.append("--verbose"); + } + + if (self.release) { + %%zig_args.append("--release"); + } + + if (const output_path ?= self.output_path) { + %%zig_args.append("--output"); + %%zig_args.append(builder.pathFromRoot(output_path)); + } + + %%zig_args.append("--name"); + %%zig_args.append(self.name); + + switch (self.target) { + Target.Native => {}, + Target.Cross => |cross_target| { + %%zig_args.append("--target-arch"); + %%zig_args.append(@enumTagName(cross_target.arch)); + + %%zig_args.append("--target-os"); + %%zig_args.append(@enumTagName(cross_target.os)); + + %%zig_args.append("--target-environ"); + %%zig_args.append(@enumTagName(cross_target.environ)); + }, + } + + for (builder.include_paths.toSliceConst()) |include_path| { + %%zig_args.append("-isystem"); + %%zig_args.append(include_path); + } + + for (builder.rpaths.toSliceConst()) |rpath| { + %%zig_args.append("-rpath"); + %%zig_args.append(rpath); + } + + for (builder.lib_paths.toSliceConst()) |lib_path| { + %%zig_args.append("--library-path"); + %%zig_args.append(lib_path); + } + + %%builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); } }; @@ -836,7 +1039,7 @@ pub const AsmStep = struct { }, } - builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + %%builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); } }; @@ -941,7 +1144,7 @@ pub const LinkStep = struct { self.linker_script = LinkerScript.Path { path }; } - pub fn linkLibrary(self: &LinkStep, name: []const u8) { + pub fn linkSystemLibrary(self: &LinkStep, name: []const u8) { %%self.link_libs.put(name); } @@ -1047,7 +1250,7 @@ pub const LinkStep = struct { %%zig_args.append(lib_path); } - builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + %%builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); } }; @@ -1083,7 +1286,7 @@ pub const TestStep = struct { self.release = value; } - pub fn linkLibrary(self: &TestStep, name: []const u8) { + pub fn linkSystemLibrary(self: &TestStep, name: []const u8) { %%self.link_libs.put(name); } @@ -1147,7 +1350,7 @@ pub const TestStep = struct { %%zig_args.append(lib_path); } - builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + %%builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); } }; @@ -1207,7 +1410,7 @@ pub const CLibrary = struct { } } - pub fn linkLibrary(self: &CLibrary, name: []const u8) { + pub fn linkSystemLibrary(self: &CLibrary, name: []const u8) { %%self.link_libs.put(name); } @@ -1276,7 +1479,7 @@ pub const CLibrary = struct { %%cc_args.append(dir); } - builder.spawnChild(cc, cc_args.toSliceConst()); + %return builder.spawnChild(cc, cc_args.toSliceConst()); %%self.object_files.append(o_file); } @@ -1300,7 +1503,7 @@ pub const CLibrary = struct { %%cc_args.append(builder.pathFromRoot(object_file)); } - builder.spawnChild(cc, cc_args.toSliceConst()); + %return builder.spawnChild(cc, cc_args.toSliceConst()); // sym link for libfoo.so.1 to libfoo.so.1.2.3 %%os.atomicSymLink(builder.allocator, self.out_filename, self.major_only_filename); @@ -1347,7 +1550,7 @@ pub const CExecutable = struct { } } - pub fn linkLibrary(self: &CExecutable, name: []const u8) { + pub fn linkSystemLibrary(self: &CExecutable, name: []const u8) { %%self.link_libs.put(name); } @@ -1356,6 +1559,14 @@ pub const CExecutable = struct { %%self.full_path_libs.append(clib.out_filename); } + pub fn linkLibrary(self: &CExecutable, lib: &LibOrExeStep) { + assert(lib.kind == LibOrExeStep.Kind.Lib); + self.step.dependOn(&lib.step); + %%self.full_path_libs.append(lib.out_filename); + // TODO should be some kind of isolated directory that only has this header in it + %%self.include_dirs.append(self.builder.out_dir); + } + pub fn addSourceFile(self: &CExecutable, file: []const u8) { %%self.source_files.append(file); } @@ -1395,7 +1606,7 @@ pub const CExecutable = struct { %%cc_args.resize(0); %%cc_args.append("-c"); - %%cc_args.append(source_file); + %%cc_args.append(builder.pathFromRoot(source_file)); // TODO don't dump the .o file in the same place as the source file const o_file = builder.fmt("{}{}", source_file, self.target.oFileExt()); @@ -1409,10 +1620,10 @@ pub const CExecutable = struct { for (self.include_dirs.toSliceConst()) |dir| { %%cc_args.append("-I"); - %%cc_args.append(dir); + %%cc_args.append(builder.pathFromRoot(dir)); } - builder.spawnChild(cc, cc_args.toSliceConst()); + %return builder.spawnChild(cc, cc_args.toSliceConst()); %%self.object_files.append(o_file); } @@ -1436,7 +1647,7 @@ pub const CExecutable = struct { %%cc_args.append(full_path_lib); } - builder.spawnChild(cc, cc_args.toSliceConst()); + %return builder.spawnChild(cc, cc_args.toSliceConst()); } pub fn setTarget(self: &CExecutable, target_arch: Arch, target_os: Os, target_environ: Environ) { @@ -1475,7 +1686,7 @@ pub const CommandStep = struct { const self = @fieldParentPtr(CommandStep, "step", step); // TODO set cwd - self.builder.spawnChildEnvMap(self.env_map, self.exe_path, self.args); + return self.builder.spawnChildEnvMap(self.env_map, self.exe_path, self.args); } }; @@ -1552,7 +1763,7 @@ pub const WriteFileStep = struct { fn make(step: &Step) -> %void { const self = @fieldParentPtr(WriteFileStep, "step", step); const full_path = self.builder.pathFromRoot(self.file_path); - const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path); + const full_path_dir = os.path.dirname(full_path); os.makePath(self.builder.allocator, full_path_dir) %% |err| { %%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err)); return err; diff --git a/std/cstr.zig b/std/cstr.zig index 21cf393d46..0934b9967f 100644 --- a/std/cstr.zig +++ b/std/cstr.zig @@ -1,11 +1,6 @@ -const List = @import("list.zig").List; -const mem = @import("mem.zig"); -const Allocator = mem.Allocator; const debug = @import("debug.zig"); const assert = debug.assert; -const strlen = len; - pub fn len(ptr: &const u8) -> usize { var count: usize = 0; while (ptr[count] != 0; count += 1) {} @@ -25,139 +20,11 @@ pub fn cmp(a: &const u8, b: &const u8) -> i8 { } pub fn toSliceConst(str: &const u8) -> []const u8 { - return str[0...strlen(str)]; + return str[0...len(str)]; } pub fn toSlice(str: &u8) -> []u8 { - return str[0...strlen(str)]; -} - - -/// A buffer that allocates memory and maintains a null byte at the end. -pub const Buffer0 = struct { - list: List(u8), - - /// Must deinitialize with deinit. - pub fn initEmpty(allocator: &Allocator) -> %Buffer0 { - return initSize(allocator, 0); - } - - /// Must deinitialize with deinit. - pub fn initFromMem(allocator: &Allocator, m: []const u8) -> %Buffer0 { - var self = %return initSize(allocator, m.len); - mem.copy(u8, self.list.items, m); - return self; - } - - /// Must deinitialize with deinit. - pub fn initFromCStr(allocator: &Allocator, s: &const u8) -> %Buffer0 { - return Buffer0.initFromMem(allocator, s[0...strlen(s)]); - } - - /// Must deinitialize with deinit. - pub fn initFromOther(cbuf: &const Buffer0) -> %Buffer0 { - return Buffer0.initFromMem(cbuf.list.allocator, cbuf.list.items[0...cbuf.len()]); - } - - /// Must deinitialize with deinit. - pub fn initFromSlice(other: &const Buffer0, start: usize, end: usize) -> %Buffer0 { - return Buffer0.initFromMem(other.list.allocator, other.list.items[start...end]); - } - - /// Must deinitialize with deinit. - pub fn initSize(allocator: &Allocator, size: usize) -> %Buffer0 { - var self = Buffer0 { - .list = List(u8).init(allocator), - }; - %return self.resize(size); - return self; - } - - pub fn deinit(self: &Buffer0) { - self.list.deinit(); - } - - pub fn toSlice(self: &Buffer0) -> []u8 { - return self.list.toSlice()[0...self.len()]; - } - - pub fn toSliceConst(self: &const Buffer0) -> []const u8 { - return self.list.toSliceConst()[0...self.len()]; - } - - pub fn resize(self: &Buffer0, new_len: usize) -> %void { - %return self.list.resize(new_len + 1); - self.list.items[self.len()] = 0; - } - - pub fn len(self: &const Buffer0) -> usize { - return self.list.len - 1; - } - - pub fn appendMem(self: &Buffer0, m: []const u8) -> %void { - const old_len = self.len(); - %return self.resize(old_len + m.len); - mem.copy(u8, self.list.toSlice()[old_len...], m); - } - - pub fn appendOther(self: &Buffer0, other: &const Buffer0) -> %void { - return self.appendMem(other.toSliceConst()); - } - - pub fn appendCStr(self: &Buffer0, s: &const u8) -> %void { - self.appendMem(s[0...strlen(s)]) - } - - pub fn appendByte(self: &Buffer0, byte: u8) -> %void { - %return self.resize(self.len() + 1); - self.list.items[self.len() - 1] = byte; - } - - pub fn eqlMem(self: &const Buffer0, m: []const u8) -> bool { - if (self.len() != m.len) return false; - return mem.cmp(u8, self.list.items[0...m.len], m) == mem.Cmp.Equal; - } - - pub fn eqlCStr(self: &const Buffer0, s: &const u8) -> bool { - self.eqlMem(s[0...strlen(s)]) - } - - pub fn eqlOther(self: &const Buffer0, other: &const Buffer0) -> bool { - self.eqlMem(other.list.items[0...other.len()]) - } - - pub fn startsWithMem(self: &const Buffer0, m: []const u8) -> bool { - if (self.len() < m.len) return false; - return mem.cmp(u8, self.list.items[0...m.len], m) == mem.Cmp.Equal; - } - - pub fn startsWithOther(self: &const Buffer0, other: &const Buffer0) -> bool { - self.startsWithMem(other.list.items[0...other.len()]) - } - - pub fn startsWithCStr(self: &const Buffer0, s: &const u8) -> bool { - self.startsWithMem(s[0...strlen(s)]) - } -}; - -test "simple Buffer0" { - var buf = %%Buffer0.initEmpty(&debug.global_allocator); - assert(buf.len() == 0); - %%buf.appendCStr(c"hello"); - %%buf.appendByte(' '); - %%buf.appendMem("world"); - assert(buf.eqlCStr(c"hello world")); - assert(buf.eqlMem("hello world")); - assert(mem.eql(u8, buf.toSliceConst(), "hello world")); - - var buf2 = %%Buffer0.initFromOther(&buf); - assert(buf.eqlOther(&buf2)); - - assert(buf.startsWithMem("hell")); - assert(buf.startsWithCStr(c"hell")); - - %%buf2.resize(4); - assert(buf.startsWithOther(&buf2)); + return str[0...len(str)]; } test "cstr fns" { diff --git a/std/index.zig b/std/index.zig index 26518d6de9..5deb3d37c4 100644 --- a/std/index.zig +++ b/std/index.zig @@ -1,4 +1,5 @@ pub const base64 = @import("base64.zig"); +pub const buffer = @import("buffer.zig"); pub const build = @import("build.zig"); pub const c = @import("c/index.zig"); pub const cstr = @import("cstr.zig"); diff --git a/std/io.zig b/std/io.zig index e9c70d9611..2b48e1d899 100644 --- a/std/io.zig +++ b/std/io.zig @@ -10,7 +10,7 @@ const debug = @import("debug.zig"); const assert = debug.assert; const os = @import("os/index.zig"); const mem = @import("mem.zig"); -const Buffer0 = @import("cstr.zig").Buffer0; +const Buffer = @import("buffer.zig").Buffer; const fmt = @import("fmt.zig"); pub var stdin = InStream { @@ -326,7 +326,7 @@ pub const InStream = struct { return usize(stat.size); } - pub fn readAll(is: &InStream, buf: &Buffer0) -> %void { + pub fn readAll(is: &InStream, buf: &Buffer) -> %void { %return buf.resize(os.page_size); var actual_buf_len: usize = 0; diff --git a/std/list.zig b/std/list.zig index b20cd06fef..3052fb3243 100644 --- a/std/list.zig +++ b/std/list.zig @@ -44,6 +44,11 @@ pub fn List(comptime T: type) -> type{ l.len = new_len; } + pub fn resizeDown(l: &Self, new_len: usize) { + assert(new_len <= l.len); + l.len = new_len; + } + pub fn ensureCapacity(l: &Self, new_capacity: usize) -> %void { var better_capacity = l.items.len; if (better_capacity >= new_capacity) return; diff --git a/std/os/index.zig b/std/os/index.zig index 0113706796..52e26d9e2b 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -226,11 +226,11 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) -> %void { pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &const BufMap, allocator: &Allocator) -> %void { - const argv_buf = %return allocator.alloc(?&const u8, argv.len + 2); - mem.set(?&const u8, argv_buf, null); + const argv_buf = %return allocator.alloc(?&u8, argv.len + 2); + mem.set(?&u8, argv_buf, null); defer { for (argv_buf) |arg| { - const arg_buf = if (const ptr ?= arg) ptr[0...cstr.len(ptr)] else break; + const arg_buf = if (const ptr ?= arg) cstr.toSlice(ptr) else break; allocator.free(arg_buf); } allocator.free(argv_buf); @@ -253,11 +253,11 @@ pub fn posixExecve(exe_path: []const u8, argv: []const []const u8, env_map: &con argv_buf[argv.len + 1] = null; const envp_count = env_map.count(); - const envp_buf = %return allocator.alloc(?&const u8, envp_count + 1); - mem.set(?&const u8, envp_buf, null); + const envp_buf = %return allocator.alloc(?&u8, envp_count + 1); + mem.set(?&u8, envp_buf, null); defer { for (envp_buf) |env| { - const env_buf = if (const ptr ?= env) ptr[0...cstr.len(ptr)] else break; + const env_buf = if (const ptr ?= env) cstr.toSlice(ptr) else break; allocator.free(env_buf); } allocator.free(envp_buf); @@ -380,7 +380,7 @@ pub const args = struct { } pub fn at(i: usize) -> []const u8 { const s = raw[i]; - return s[0...cstr.len(s)]; + return cstr.toSlice(s); } }; @@ -397,7 +397,7 @@ pub fn getCwd(allocator: &Allocator) -> %[]u8 { return error.Unexpected; } - return buf; + return cstr.toSlice(buf.ptr); } } @@ -572,22 +572,39 @@ pub fn makeDir(allocator: &Allocator, dir_path: []const u8) -> %void { /// Calls makeDir recursively to make an entire path. Returns success if the path /// already exists and is a directory. pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void { - const child_dir = %return path.dirname(allocator, full_path); - defer allocator.free(child_dir); + const resolved_path = %return path.resolve(allocator, full_path); + defer allocator.free(resolved_path); - if (mem.eql(u8, child_dir, full_path)) - return; - - makePath(allocator, child_dir) %% |err| { - if (err != error.PathAlreadyExists) - return err; - }; - - makeDir(allocator, full_path) %% |err| { - if (err != error.PathAlreadyExists) - return err; - // TODO stat the file and return an error if it's not a directory - }; + var end_index: usize = resolved_path.len; + while (true) { + makeDir(allocator, resolved_path[0...end_index]) %% |err| { + if (err == error.PathAlreadyExists) { + // TODO stat the file and return an error if it's not a directory + // this is important because otherwise a dangling symlink + // could cause an infinite loop + if (end_index == resolved_path.len) + return; + } else if (err == error.FileNotFound) { + // march end_index backward until next path component + while (true) { + end_index -= 1; + if (resolved_path[end_index] == '/') + break; + } + continue; + } else { + return err; + } + }; + if (end_index == resolved_path.len) + return; + // march end_index forward until next path component + while (true) { + end_index += 1; + if (end_index == resolved_path.len or resolved_path[end_index] == '/') + break; + } + } } /// Returns ::error.DirNotEmpty if the directory is not empty. @@ -739,7 +756,7 @@ pub const Dir = struct { const next_index = self.index + linux_entry.d_reclen; self.index = next_index; - const name = (&linux_entry.d_name)[0...cstr.len(&linux_entry.d_name)]; + const name = cstr.toSlice(&linux_entry.d_name); // skip . and .. entries if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { diff --git a/std/os/path.zig b/std/os/path.zig index 66c07ad6de..1f82153a89 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -2,7 +2,11 @@ const debug = @import("../debug.zig"); const assert = debug.assert; const mem = @import("../mem.zig"); const Allocator = mem.Allocator; +const os = @import("index.zig"); +pub const sep = '/'; + +/// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 { assert(paths.len >= 2); @@ -26,8 +30,8 @@ pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 { mem.copy(u8, buf[buf_index...], arg); buf_index += arg.len; if (path_i >= paths.len) break; - if (arg[arg.len - 1] != '/') { - buf[buf_index] = '/'; + if (arg[arg.len - 1] != sep) { + buf[buf_index] = sep; buf_index += 1; } } @@ -43,22 +47,128 @@ test "os.path.join" { assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c")); } -pub fn dirname(allocator: &Allocator, path: []const u8) -> %[]u8 { - if (path.len != 0) { - var last_index: usize = path.len - 1; - if (path[last_index] == '/') - last_index -= 1; +pub fn isAbsolute(path: []const u8) -> bool { + switch (@compileVar("os")) { + Os.windows => @compileError("Unsupported OS"), + else => return path[0] == sep, + } +} - var i: usize = last_index; +/// This function is like a series of `cd` statements executed one after another. +/// The result does not have a trailing path separator. +pub fn resolve(allocator: &Allocator, args: ...) -> %[]u8 { + var paths: [args.len][]const u8 = undefined; + comptime var arg_i = 0; + inline while (arg_i < args.len; arg_i += 1) { + paths[arg_i] = args[arg_i]; + } + return resolveSlice(allocator, paths); +} + +pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) -> %[]u8 { + if (paths.len == 0) + return os.getCwd(allocator); + + var first_index: usize = 0; + var have_abs = false; + var max_size: usize = 0; + for (paths) |p, i| { + if (isAbsolute(p)) { + first_index = i; + have_abs = true; + max_size = 0; + } + max_size += p.len + 1; + } + + var result: []u8 = undefined; + var result_index: usize = 0; + + if (have_abs) { + result = %return allocator.alloc(u8, max_size); + } else { + const cwd = %return os.getCwd(allocator); + defer allocator.free(cwd); + result = %return allocator.alloc(u8, max_size + cwd.len + 1); + mem.copy(u8, result, cwd); + result_index += cwd.len; + } + %defer allocator.free(result); + + for (paths[first_index...]) |p, i| { + var it = mem.split(p, '/'); while (true) { - const c = path[i]; - if (c == '/') - return mem.dupe(allocator, u8, path[0...i]); - if (i == 0) - break; - i -= 1; + const component = it.next() ?? break; + if (mem.eql(u8, component, ".")) { + continue; + } else if (mem.eql(u8, component, "..")) { + while (true) { + if (result_index == 0) + break; + result_index -= 1; + if (result[result_index] == '/') + break; + } + } else { + result[result_index] = '/'; + result_index += 1; + mem.copy(u8, result[result_index...], component); + result_index += component.len; + } } } - return mem.dupe(allocator, u8, "."); + if (result_index == 0) { + result[0] = '/'; + result_index += 1; + } + + return result[0...result_index]; +} + +test "os.path.resolve" { + assert(mem.eql(u8, testResolve("/a/b", "c"), "/a/b/c")); + assert(mem.eql(u8, testResolve("/a/b", "c", "//d", "e///"), "/d/e")); + assert(mem.eql(u8, testResolve("/a/b/c", "..", "../"), "/a")); + assert(mem.eql(u8, testResolve("/", "..", ".."), "/")); +} +fn testResolve(args: ...) -> []u8 { + return %%resolve(&debug.global_allocator, args); +} + +pub fn dirname(path: []const u8) -> []const u8 { + if (path.len == 0) + return path[0...0]; + var end_index: usize = path.len - 1; + while (path[end_index] == '/') { + if (end_index == 0) + return path[0...1]; + end_index -= 1; + } + + while (path[end_index] != '/') { + if (end_index == 0) + return path[0...0]; + end_index -= 1; + } + + if (end_index == 0 and path[end_index] == '/') + return path[0...1]; + + return path[0...end_index]; +} + +test "os.path.dirname" { + testDirname("/a/b/c", "/a/b"); + testDirname("/a/b/c///", "/a/b"); + testDirname("/a", "/"); + testDirname("/", "/"); + testDirname("////", "/"); + testDirname("", ""); + testDirname("a", ""); + testDirname("a/", ""); + testDirname("a//", ""); +} +fn testDirname(input: []const u8, expected_output: []const u8) { + assert(mem.eql(u8, dirname(input), expected_output)); } diff --git a/test/build_examples.zig b/test/build_examples.zig index 72bed9d296..dc2e793da4 100644 --- a/test/build_examples.zig +++ b/test/build_examples.zig @@ -5,4 +5,5 @@ pub fn addCases(cases: &tests.BuildExamplesContext) { cases.addC("example/hello_world/hello_libc.zig"); cases.add("example/cat/main.zig"); cases.add("example/guess_number/main.zig"); + cases.addBuildFile("example/shared_library/build.zig"); } diff --git a/test/tests.zig b/test/tests.zig index d760856339..3340e5913c 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -4,7 +4,7 @@ const build = std.build; const os = std.os; const StdIo = os.ChildProcess.StdIo; const Term = os.ChildProcess.Term; -const Buffer0 = std.cstr.Buffer0; +const Buffer = std.buffer.Buffer; const io = std.io; const mem = std.mem; const fmt = std.fmt; @@ -116,7 +116,7 @@ pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []cons these_tests.setFilter(test_filter); these_tests.setRelease(release); if (link_libc) { - these_tests.linkLibrary("c"); + these_tests.linkSystemLibrary("c"); } step.dependOn(&these_tests.step); } @@ -211,8 +211,8 @@ pub const CompareOutputContext = struct { }, }; - var stdout = %%Buffer0.initEmpty(b.allocator); - var stderr = %%Buffer0.initEmpty(b.allocator); + var stdout = Buffer.initNull(b.allocator); + var stderr = Buffer.initNull(b.allocator); %%(??child.stdout).readAll(&stdout); %%(??child.stderr).readAll(&stderr); @@ -388,7 +388,7 @@ pub const CompareOutputContext = struct { exe.setOutputPath(exe_path); exe.setRelease(release); if (case.link_libc) { - exe.linkLibrary("c"); + exe.linkSystemLibrary("c"); } for (case.sources.toSliceConst()) |src_file| { @@ -415,7 +415,7 @@ pub const CompareOutputContext = struct { const exe = b.addExecutable("test", root_src); exe.setOutputPath(exe_path); if (case.link_libc) { - exe.linkLibrary("c"); + exe.linkSystemLibrary("c"); } for (case.sources.toSliceConst()) |src_file| { @@ -537,8 +537,8 @@ pub const CompileErrorContext = struct { }, }; - var stdout_buf = %%Buffer0.initEmpty(b.allocator); - var stderr_buf = %%Buffer0.initEmpty(b.allocator); + var stdout_buf = Buffer.initNull(b.allocator); + var stderr_buf = Buffer.initNull(b.allocator); %%(??child.stdout).readAll(&stdout_buf); %%(??child.stderr).readAll(&stderr_buf); @@ -657,6 +657,35 @@ pub const BuildExamplesContext = struct { self.addAllArgs(root_src, false); } + pub fn addBuildFile(self: &BuildExamplesContext, build_file: []const u8) { + const b = self.b; + + const annotated_case_name = b.fmt("build {}", build_file); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; + } + + var zig_args = List([]const u8).init(b.allocator); + %%zig_args.append("build"); + + %%zig_args.append("--build-file"); + %%zig_args.append(b.pathFromRoot(build_file)); + + %%zig_args.append("test"); + + if (b.verbose) { + %%zig_args.append("--verbose"); + } + + const run_cmd = b.addCommand(b.out_dir, b.env_map, b.zig_exe, zig_args.toSliceConst()); + + const log_step = b.addLog("PASS {}\n", annotated_case_name); + log_step.step.dependOn(&run_cmd.step); + + self.step.dependOn(&log_step.step); + } + pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) { const b = self.b; @@ -671,7 +700,7 @@ pub const BuildExamplesContext = struct { const exe = b.addExecutable("test", root_src); exe.setRelease(release); if (link_libc) { - exe.linkLibrary("c"); + exe.linkSystemLibrary("c"); } const log_step = b.addLog("PASS {}\n", annotated_case_name); @@ -774,8 +803,8 @@ pub const ParseHContext = struct { }, }; - var stdout_buf = %%Buffer0.initEmpty(b.allocator); - var stderr_buf = %%Buffer0.initEmpty(b.allocator); + var stdout_buf = Buffer.initNull(b.allocator); + var stderr_buf = Buffer.initNull(b.allocator); %%(??child.stdout).readAll(&stdout_buf); %%(??child.stderr).readAll(&stderr_buf);