diff --git a/src/main.cpp b/src/main.cpp index 05576099f23d..e7ad823fceb0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -218,6 +218,7 @@ int main(int argc, char **argv) { " --build-file [file] Override path to build.zig.\n" " --verbose Print commands before executing them.\n" " --debug-build-verbose Print verbose debugging information for the build system itself.\n" + " --prefix [prefix] Override default install prefix.\n" , zig_exe_path); return 0; } diff --git a/std/build.zig b/std/build.zig index bf741b5b95f7..dd1c637a7026 100644 --- a/std/build.zig +++ b/std/build.zig @@ -1,6 +1,7 @@ const io = @import("io.zig"); const mem = @import("mem.zig"); const debug = @import("debug.zig"); +const assert = debug.assert; const List = @import("list.zig").List; const HashMap = @import("hash_map.zig").HashMap; const Allocator = @import("mem.zig").Allocator; @@ -8,13 +9,16 @@ const os = @import("os/index.zig"); const StdIo = os.ChildProcess.StdIo; const Term = os.ChildProcess.Term; const BufSet = @import("buf_set.zig").BufSet; +const BufMap = @import("buf_map.zig").BufMap; error ExtraArg; error UncleanExit; +error InvalidStepName; +error DependencyLoopDetected; +error NoCompilerFound; pub const Builder = struct { allocator: &Allocator, - exe_list: List(&Exe), lib_paths: List([]const u8), include_paths: List([]const u8), rpaths: List([]const u8), @@ -23,6 +27,12 @@ pub const Builder = struct { available_options_list: List(AvailableOption), verbose: bool, invalid_user_input: bool, + zig_exe: []const u8, + default_step: &Step, + env_map: BufMap, + top_level_steps: List(&TopLevelStep), + prefix: []const u8, + out_dir: []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); @@ -53,49 +63,91 @@ pub const Builder = struct { List, }; + const TopLevelStep = struct { + step: Step, + description: []const u8, + }; + pub fn init(allocator: &Allocator) -> Builder { var self = Builder { .verbose = false, .invalid_user_input = false, .allocator = allocator, - .exe_list = List(&Exe).init(allocator), .lib_paths = List([]const u8).init(allocator), .include_paths = List([]const u8).init(allocator), .rpaths = List([]const u8).init(allocator), .user_input_options = UserInputOptionsMap.init(allocator), .available_options_map = AvailableOptionsMap.init(allocator), .available_options_list = List(AvailableOption).init(allocator), + .top_level_steps = List(&TopLevelStep).init(allocator), + .zig_exe = undefined, + .default_step = undefined, + .env_map = %%os.getEnvMap(allocator), + .prefix = undefined, + .out_dir = ".", // TODO organize }; self.processNixOSEnvVars(); + self.default_step = self.step("default", "Build the project"); return self; } pub fn deinit(self: &Builder) { - self.exe_list.deinit(); self.lib_paths.deinit(); self.include_paths.deinit(); self.rpaths.deinit(); + self.env_map.deinit(); + self.top_level_steps.deinit(); } - pub fn addExe(self: &Builder, root_src: []const u8, name: []const u8) -> &Exe { - return self.addExeErr(root_src, name) %% |err| handleErr(err); + 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"; } - pub fn addExeErr(self: &Builder, root_src: []const u8, name: []const u8) -> %&Exe { - const exe = %return self.allocator.create(Exe); - *exe = Exe { - .verbose = false, - .release = false, - .root_src = root_src, - .name = name, - .target = Target.Native, - .linker_script = LinkerScript.None, - .link_libs = BufSet.init(self.allocator), - }; - %return self.exe_list.append(exe); + 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); return exe; } + pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary { + const lib = %%self.allocator.create(CLibrary); + *lib = CLibrary.initStatic(self, name); + return lib; + } + + pub fn addCSharedLibrary(self: &Builder, name: []const u8, ver: &const Version) -> &CLibrary { + const lib = %%self.allocator.create(CLibrary); + *lib = CLibrary.initShared(self, name, ver); + return lib; + } + + pub fn addCExecutable(self: &Builder, name: []const u8) -> &CExecutable { + const exe = %%self.allocator.create(CExecutable); + *exe = CExecutable.init(self, name); + return exe; + } + + pub fn addCommand(self: &Builder, cwd: []const u8, env_map: &const BufMap, + path: []const u8, args: []const []const u8) -> &CommandStep + { + const cmd = %%self.allocator.create(CommandStep); + *cmd = CommandStep.init(self, cwd, env_map, path, args); + return cmd; + } + + pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version { + Version { + .major = major, + .minor = minor, + .patch = patch, + } + } + pub fn addCIncludePath(self: &Builder, path: []const u8) { %%self.include_paths.append(path); } @@ -108,103 +160,53 @@ pub const Builder = struct { %%self.lib_paths.append(path); } - pub fn make(self: &Builder, zig_exe: []const u8, targets: []const []const u8) -> %void { - if (targets.len != 0) { - debug.panic("TODO non default targets"); - } - - var env_map = %return os.getEnvMap(self.allocator); - - for (self.exe_list.toSlice()) |exe| { - var zig_args = List([]const u8).init(self.allocator); - defer zig_args.deinit(); - - %return zig_args.append("build_exe"); - %return zig_args.append(exe.root_src); - - if (exe.verbose) { - %return zig_args.append("--verbose"); - } + pub fn make(self: &Builder, step_names: []const []const u8) -> %void { + var wanted_steps = List(&Step).init(self.allocator); + defer wanted_steps.deinit(); - if (exe.release) { - %return zig_args.append("--release"); + if (step_names.len == 0) { + %%wanted_steps.append(&self.default_step); + } else { + for (step_names) |step_name| { + const s = %return self.getTopLevelStepByName(step_name); + %%wanted_steps.append(s); } + } - %return zig_args.append("--name"); - %return zig_args.append(exe.name); - - switch (exe.target) { - Target.Native => {}, - Target.Cross => |cross_target| { - %return zig_args.append("--target-arch"); - %return zig_args.append(@enumTagName(cross_target.arch)); - - %return zig_args.append("--target-os"); - %return zig_args.append(@enumTagName(cross_target.os)); - - %return zig_args.append("--target-environ"); - %return zig_args.append(@enumTagName(cross_target.environ)); - }, - } + for (wanted_steps.toSliceConst()) |s| { + %return self.makeOneStep(s); + } + } - switch (exe.linker_script) { - LinkerScript.None => {}, - LinkerScript.Embed => |script| { - const tmp_file_name = "linker.ld.tmp"; // TODO issue #298 - io.writeFile(tmp_file_name, script, self.allocator) - %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err)); - %return zig_args.append("--linker-script"); - %return zig_args.append(tmp_file_name); - }, - LinkerScript.Path => |path| { - %return zig_args.append("--linker-script"); - %return zig_args.append(path); - }, - } + fn makeOneStep(self: &Builder, s: &Step) -> %void { + if (s.loop_flag) { + %%io.stderr.printf("Dependency loop detected:\n {}\n", s.name); + return error.DependencyLoopDetected; + } + s.loop_flag = true; - { - var it = exe.link_libs.iterator(); - while (true) { - const entry = it.next() ?? break; - %return zig_args.append("--library"); - %return zig_args.append(entry.key); + for (s.dependencies.toSlice()) |dep| { + self.makeOneStep(dep) %% |err| { + if (err == error.DependencyLoopDetected) { + %%io.stderr.printf(" {}\n", s.name); } - } - - for (self.include_paths.toSliceConst()) |include_path| { - %return zig_args.append("-isystem"); - %return zig_args.append(include_path); - } + return err; + }; + } - for (self.rpaths.toSliceConst()) |rpath| { - %return zig_args.append("-rpath"); - %return zig_args.append(rpath); - } + s.loop_flag = false; - for (self.lib_paths.toSliceConst()) |lib_path| { - %return zig_args.append("--library-path"); - %return zig_args.append(lib_path); - } + %return s.make(); + } - if (self.verbose) { - printInvocation(zig_exe, zig_args); + fn getTopLevelStepByName(self: &Builder, name: []const u8) -> %&Step { + for (self.top_level_steps.toSliceConst()) |top_level_step| { + if (mem.eql(u8, top_level_step.step.name, name)) { + return &top_level_step.step; } - // TODO issue #301 - var child = os.ChildProcess.spawn(zig_exe, zig_args.toSliceConst(), &env_map, - StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator) - %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err)); - const term = %%child.wait(); - switch (term) { - Term.Clean => |code| { - if (code != 0) { - return error.UncleanExit; - } - }, - else => { - return error.UncleanExit; - }, - }; } + %%io.stderr.printf("Cannot run step '{}' because it does not exist.", name); + return error.InvalidStepName; } fn processNixOSEnvVars(self: &Builder) { @@ -286,6 +288,16 @@ pub const Builder = struct { } } + pub fn step(self: &Builder, name: []const u8, description: []const u8) -> &Step { + const step_info = %%self.allocator.create(TopLevelStep); + *step_info = TopLevelStep { + .step = Step.initNoOp(name, self.allocator), + .description = description, + }; + %%self.top_level_steps.append(step_info); + return &step_info.step; + } + pub fn addUserInputOption(self: &Builder, name: []const u8, value: []const u8) -> bool { if (var prev_value ?= %%self.user_input_options.put(name, UserInputOption { .name = name, @@ -381,7 +393,12 @@ pub const Builder = struct { return self.invalid_user_input; } +}; +const Version = struct { + major: u32, + minor: u32, + patch: u32, }; const CrossTarget = struct { @@ -402,6 +419,8 @@ const LinkerScript = enum { }; const Exe = struct { + step: Step, + builder: &Builder, root_src: []const u8, name: []const u8, target: Target, @@ -410,6 +429,20 @@ const Exe = struct { verbose: bool, release: bool, + pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe { + Exe { + .builder = builder, + .verbose = false, + .release = false, + .root_src = root_src, + .name = name, + .target = Target.Native, + .linker_script = LinkerScript.None, + .link_libs = BufSet.init(builder.allocator), + .step = Step.init(name, builder.allocator, make), + } + } + pub fn deinit(self: &Exe) { self.link_libs.deinit(); } @@ -445,11 +478,290 @@ const Exe = struct { pub fn setRelease(self: &Exe, value: bool) { self.release = value; } + + fn make(step: &Step) -> %void { + // TODO issue #320 + //const self = @fieldParentPtr(Exe, "step", step); + const exe = @ptrcast(&Exe, step); + const builder = exe.builder; + + var zig_args = List([]const u8).init(builder.allocator); + defer zig_args.deinit(); + + %return zig_args.append("build_exe"); + %return zig_args.append(exe.root_src); + + if (exe.verbose) { + %return zig_args.append("--verbose"); + } + + if (exe.release) { + %return zig_args.append("--release"); + } + + %return zig_args.append("--name"); + %return zig_args.append(exe.name); + + switch (exe.target) { + Target.Native => {}, + Target.Cross => |cross_target| { + %return zig_args.append("--target-arch"); + %return zig_args.append(@enumTagName(cross_target.arch)); + + %return zig_args.append("--target-os"); + %return zig_args.append(@enumTagName(cross_target.os)); + + %return zig_args.append("--target-environ"); + %return zig_args.append(@enumTagName(cross_target.environ)); + }, + } + + switch (exe.linker_script) { + LinkerScript.None => {}, + LinkerScript.Embed => |script| { + const tmp_file_name = "linker.ld.tmp"; // TODO issue #298 + io.writeFile(tmp_file_name, script, builder.allocator) + %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err)); + %return zig_args.append("--linker-script"); + %return zig_args.append(tmp_file_name); + }, + LinkerScript.Path => |path| { + %return zig_args.append("--linker-script"); + %return zig_args.append(path); + }, + } + + { + var it = exe.link_libs.iterator(); + while (true) { + const entry = it.next() ?? break; + %return zig_args.append("--library"); + %return zig_args.append(entry.key); + } + } + + for (builder.include_paths.toSliceConst()) |include_path| { + %return zig_args.append("-isystem"); + %return zig_args.append(include_path); + } + + for (builder.rpaths.toSliceConst()) |rpath| { + %return zig_args.append("-rpath"); + %return zig_args.append(rpath); + } + + for (builder.lib_paths.toSliceConst()) |lib_path| { + %return zig_args.append("--library-path"); + %return zig_args.append(lib_path); + } + + if (builder.verbose) { + printInvocation(builder.zig_exe, zig_args); + } + // TODO issue #301 + var child = os.ChildProcess.spawn(builder.zig_exe, zig_args.toSliceConst(), &builder.env_map, + StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, builder.allocator) + %% |err| debug.panic("Unable to spawn zig compiler: {}\n", @errorName(err)); + const term = %%child.wait(); + switch (term) { + Term.Clean => |code| { + if (code != 0) { + return error.UncleanExit; + } + }, + else => { + return error.UncleanExit; + }, + }; + } }; -fn handleErr(err: error) -> noreturn { - debug.panic("error: {}\n", @errorName(err)); -} +const CLibrary = struct { + step: Step, + name: []const u8, + static: bool, + version: Version, + cflags: List([]const u8), + source_files: List([]const u8), + link_libs: BufSet, + + pub fn initShared(builder: &Builder, name: []const u8, version: &const Version) -> CLibrary { + return init(builder, name, version, false); + } + + pub fn initStatic(builder: &Builder, name: []const u8) -> CLibrary { + return init(builder, name, undefined, true); + } + + fn init(builder: &Builder, name: []const u8, version: &const Version, static: bool) -> CLibrary { + CLibrary { + .name = name, + .version = *version, + .static = static, + .cflags = List([]const u8).init(builder.allocator), + .source_files = List([]const u8).init(builder.allocator), + .step = Step.init(name, builder.allocator, make), + .link_libs = BufSet.init(builder.allocator), + } + } + + pub fn linkLibrary(self: &CLibrary, name: []const u8) { + %%self.link_libs.put(name); + } + + pub fn linkCLibrary(self: &CLibrary, other: &CLibrary) { + self.step.dependOn(&other.step); + %%self.link_libs.put(other.name); + } + + pub fn addSourceFile(self: &CLibrary, file: []const u8) { + %%self.source_files.append(file); + } + + pub fn addCompileFlagsForRelease(self: &CLibrary, release: bool) { + if (release) { + %%self.cflags.append("-g"); + %%self.cflags.append("-O2"); + } else { + %%self.cflags.append("-g"); + } + } + + pub fn addCompileFlags(self: &CLibrary, flags: []const []const u8) { + for (flags) |flag| { + %%self.cflags.append(flag); + } + } + + fn make(step: &Step) -> %void { + // TODO issue #320 + //const self = @fieldParentPtr(CLibrary, "step", step); + const self = @ptrcast(&CLibrary, step); + + const cc = os.getEnv("CC") ?? { + %%io.stderr.printf("Unable to find C compiler\n"); + return error.NoCompilerFound; + }; + %%io.stderr.printf("TODO: build c library\n"); + } +}; + +const CExecutable = struct { + step: Step, + name: []const u8, + cflags: List([]const u8), + source_files: List([]const u8), + link_libs: BufSet, + + pub fn init(builder: &Builder, name: []const u8) -> CExecutable { + CExecutable { + .name = name, + .cflags = List([]const u8).init(builder.allocator), + .source_files = List([]const u8).init(builder.allocator), + .step = Step.init(name, builder.allocator, make), + .link_libs = BufSet.init(builder.allocator), + } + } + + pub fn linkLibrary(self: &CExecutable, name: []const u8) { + %%self.link_libs.put(name); + } + + pub fn linkCLibrary(self: &CExecutable, clib: &CLibrary) { + self.step.dependOn(&clib.step); + %%self.link_libs.put(clib.name); + } + + pub fn addSourceFile(self: &CExecutable, file: []const u8) { + %%self.source_files.append(file); + } + + pub fn addCompileFlagsForRelease(self: &CExecutable, release: bool) { + if (release) { + %%self.cflags.append("-g"); + %%self.cflags.append("-O2"); + } else { + %%self.cflags.append("-g"); + } + } + + pub fn addCompileFlags(self: &CExecutable, flags: []const []const u8) { + for (flags) |flag| { + %%self.cflags.append(flag); + } + } + + fn make(step: &Step) -> %void { + // TODO issue #320 + //const self = @fieldParentPtr(CExecutable, "step", step); + const self = @ptrcast(&CExecutable, step); + + %%io.stderr.printf("TODO: build c exe\n"); + } +}; + +const CommandStep = struct { + step: Step, + path: []const u8, + args: []const []const u8, + cwd: []const u8, + env_map: &const BufMap, + + pub fn init(builder: &Builder, cwd: []const u8, env_map: &const BufMap, + path: []const u8, args: []const []const u8) -> CommandStep + { + CommandStep { + .step = Step.init(path, builder.allocator, make), + .path = path, + .args = args, + .cwd = cwd, + .env_map = env_map, + } + } + + fn make(step: &Step) -> %void { + // TODO issue #320 + //const self = @fieldParentPtr(CExecutable, "step", step); + const self = @ptrcast(&CommandStep, step); + + %%io.stderr.printf("TODO: exec command\n"); + } +}; + +const Step = struct { + name: []const u8, + makeFn: fn(self: &Step) -> %void, + dependencies: List(&Step), + loop_flag: bool, + done_flag: bool, + + pub fn init(name: []const u8, allocator: &Allocator, makeFn: fn (&Step)->%void) -> Step { + Step { + .name = name, + .makeFn = makeFn, + .dependencies = List(&Step).init(allocator), + .loop_flag = false, + .done_flag = false, + } + } + pub fn initNoOp(name: []const u8, allocator: &Allocator) -> Step { + init(name, allocator, makeNoOp) + } + + pub fn make(self: &Step) -> %void { + if (self.done_flag) + return; + + %return self.makeFn(self); + self.done_flag = true; + } + + pub fn dependOn(self: &Step, other: &Step) { + %%self.dependencies.append(other); + } + + fn makeNoOp(self: &Step) -> %void {} +}; fn printInvocation(exe_name: []const u8, args: &const List([]const u8)) { %%io.stderr.printf("{}", exe_name); diff --git a/std/mem.zig b/std/mem.zig index 878a241195f2..7c9cf5db8a41 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -76,7 +76,7 @@ pub const IncrementingAllocator = struct { } fn alloc(allocator: &Allocator, n: usize) -> %[]u8 { - // TODO + // TODO issue #320 //const self = @fieldParentPtr(IncrementingAllocator, "allocator", allocator); const self = @ptrcast(&IncrementingAllocator, allocator); const new_end_index = self.end_index + n; diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 385c39812432..d56bf1ed1865 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -22,6 +22,8 @@ pub fn main() -> %void { var maybe_zig_exe: ?[]const u8 = null; var targets = List([]const u8).init(allocator); + var prefix: ?[]const u8 = null; + var arg_i: usize = 1; while (arg_i < os.args.count(); arg_i += 1) { const arg = os.args.at(arg_i); @@ -45,6 +47,9 @@ pub fn main() -> %void { builder.verbose = true; } else if (mem.eql(u8, arg, "--help")) { return usage(&builder, maybe_zig_exe, false, &io.stdout); + } else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) { + arg_i += 1; + prefix = os.args.at(arg_i); } else { %%io.stderr.printf("Unrecognized argument: {}\n\n", arg); return usage(&builder, maybe_zig_exe, false, &io.stderr); @@ -56,14 +61,15 @@ pub fn main() -> %void { } } - const zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr); + builder.zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr); + builder.setInstallPrefix(prefix); root.build(&builder); if (builder.validateUserInputDidItFail()) return usage(&builder, maybe_zig_exe, true, &io.stderr); - %return builder.make(zig_exe, targets.toSliceConst()); + %return builder.make(targets.toSliceConst()); } fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, out_stream: &io.OutStream) -> %void { @@ -79,22 +85,33 @@ fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, // This usage text has to be synchronized with src/main.cpp %%out_stream.printf( - \\Usage: {} build [options] + \\Usage: {} build [steps] [options] + \\ + \\Steps: + \\ + , zig_exe); + + const allocator = builder.allocator; + for (builder.top_level_steps.toSliceConst()) |top_level_step| { + %%out_stream.printf(" {s22} {}\n", top_level_step.step.name, top_level_step.description); + } + + %%out_stream.write( \\ \\General Options: - \\ --help Print this help and exit. - \\ --build-file [file] Override path to build.zig. - \\ --verbose Print commands before executing them. - \\ --debug-build-verbose Print verbose debugging information for the build system itself. + \\ --help Print this help and exit + \\ --build-file [file] Override path to build.zig + \\ --verbose Print commands before executing them + \\ --debug-build-verbose Print verbose debugging information for the build system itself + \\ --prefix [prefix] Override default install prefix \\ \\Project-Specific Options: \\ - , zig_exe); + ); if (builder.available_options_list.len == 0) { %%out_stream.printf(" (none)\n"); } else { - const allocator = builder.allocator; for (builder.available_options_list.toSliceConst()) |option| { const name = %%fmt.allocPrint(allocator, " -D{}=({})", option.name, Builder.typeIdName(option.type_id));