zig build system understands the concept of dependencies

See #204
This commit is contained in:
Andrew Kelley 2017-04-13 17:21:00 -04:00
parent a6bd3d8ca2
commit 5fdefe58e4
4 changed files with 450 additions and 120 deletions

View File

@ -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;
}

View File

@ -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");
pub fn make(self: &Builder, step_names: []const []const u8) -> %void {
var wanted_steps = List(&Step).init(self.allocator);
defer wanted_steps.deinit();
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);
}
}
var env_map = %return os.getEnvMap(self.allocator);
for (wanted_steps.toSliceConst()) |s| {
%return self.makeOneStep(s);
}
}
for (self.exe_list.toSlice()) |exe| {
var zig_args = List([]const u8).init(self.allocator);
defer zig_args.deinit();
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;
%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, 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);
},
}
{
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);
}
for (self.rpaths.toSliceConst()) |rpath| {
%return zig_args.append("-rpath");
%return zig_args.append(rpath);
}
for (self.lib_paths.toSliceConst()) |lib_path| {
%return zig_args.append("--library-path");
%return zig_args.append(lib_path);
}
if (self.verbose) {
printInvocation(zig_exe, zig_args);
}
// 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;
},
return err;
};
}
s.loop_flag = false;
%return s.make();
}
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;
}
}
%%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);

View File

@ -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;

View File

@ -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));