Update zig build system to support user defined options

* Fix assertion failure when switching on type.
   Closes #310
 * Update zig build system to support user defined options.
   See #204
 * fmt.format supports {sNNN} to set padding for a buffer arg.
 * add std.fmt.bufPrint and std.fmt.allocPrint
 * std.hash_map.HashMap.put returns the previous value
 * add std.mem.startsWith
master
Andrew Kelley 2017-04-06 05:34:04 -04:00
parent d15bcdce69
commit 6fbe1632d0
10 changed files with 442 additions and 69 deletions

View File

@ -57,6 +57,7 @@ static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *ins
static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, TypeTableEntry *expected_type);
ConstExprValue *const_ptr_pointee(ConstExprValue *const_val) {
assert(const_val->type->id == TypeTableEntryIdPointer);
assert(const_val->special == ConstValSpecialStatic);
switch (const_val->data.x_ptr.special) {
case ConstPtrSpecialInvalid:
@ -10350,10 +10351,21 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira,
if (type_is_invalid(target_value_ptr->value.type))
return ira->codegen->builtin_types.entry_invalid;
if (target_value_ptr->value.type->id == TypeTableEntryIdMetaType) {
assert(instr_is_comptime(target_value_ptr));
TypeTableEntry *ptr_type = target_value_ptr->value.data.x_type;
assert(ptr_type->id == TypeTableEntryIdPointer);
ConstExprValue *out_val = ir_build_const_from(ira, &switch_target_instruction->base);
out_val->type = ira->codegen->builtin_types.entry_type;
out_val->data.x_type = ptr_type->data.pointer.child_type;
return out_val->type;
}
assert(target_value_ptr->value.type->id == TypeTableEntryIdPointer);
TypeTableEntry *target_type = target_value_ptr->value.type->data.pointer.child_type;
ConstExprValue *pointee_val = nullptr;
if (target_value_ptr->value.special != ConstValSpecialRuntime) {
if (instr_is_comptime(target_value_ptr)) {
pointee_val = const_ptr_pointee(&target_value_ptr->value);
if (pointee_val->special == ConstValSpecialRuntime)
pointee_val = nullptr;

View File

@ -167,9 +167,8 @@ int main(int argc, char **argv) {
ZigList<const char *> args = {0};
args.append(zig_exe_path);
for (int i = 2; i < argc; i += 1) {
if (strcmp(argv[i], "--verbose") == 0) {
if (strcmp(argv[i], "--debug-build-verbose") == 0) {
verbose = true;
args.append(argv[i]);
} else {
args.append(argv[i]);
}

View File

@ -11,14 +11,13 @@ pub const BufMap = struct {
pub fn init(allocator: &Allocator) -> BufMap {
var self = BufMap {
.hash_map = undefined,
.hash_map = BufMapHashMap.init(allocator),
};
self.hash_map.init(allocator);
return self;
}
pub fn deinit(self: &BufMap) {
var it = self.hash_map.entryIterator();
var it = self.hash_map.iterator();
while (true) {
const entry = it.next() ?? break;
self.free(entry.key);
@ -54,7 +53,7 @@ pub const BufMap = struct {
}
pub fn iterator(self: &const BufMap) -> BufMapHashMap.Iterator {
return self.hash_map.entryIterator();
return self.hash_map.iterator();
}
fn free(self: &BufMap, value: []const u8) {

View File

@ -9,14 +9,13 @@ pub const BufSet = struct {
pub fn init(allocator: &Allocator) -> BufSet {
var self = BufSet {
.hash_map = undefined,
.hash_map = BufSetHashMap.init(allocator),
};
self.hash_map.init(allocator);
return self;
}
pub fn deinit(self: &BufSet) {
var it = self.hash_map.entryIterator();
var it = self.hash_map.iterator();
while (true) {
const entry = it.next() ?? break;
self.free(entry.key);
@ -43,7 +42,7 @@ pub const BufSet = struct {
}
pub fn iterator(self: &const BufSet) -> BufSetHashMap.Iterator {
return self.hash_map.entryIterator();
return self.hash_map.iterator();
}
fn free(self: &BufSet, value: []const u8) {

View File

@ -2,6 +2,7 @@ const io = @import("io.zig");
const mem = @import("mem.zig");
const debug = @import("debug.zig");
const List = @import("list.zig").List;
const HashMap = @import("hash_map.zig").HashMap;
const Allocator = @import("mem.zig").Allocator;
const os = @import("os/index.zig");
const StdIo = os.ChildProcess.StdIo;
@ -12,21 +13,58 @@ error ExtraArg;
error UncleanExit;
pub const Builder = struct {
zig_exe: []const u8,
allocator: &Allocator,
exe_list: List(&Exe),
lib_paths: List([]const u8),
include_paths: List([]const u8),
rpaths: List([]const u8),
user_input_options: UserInputOptionsMap,
available_options_map: AvailableOptionsMap,
available_options_list: List(AvailableOption),
verbose: bool,
invalid_user_input: bool,
pub fn init(zig_exe: []const u8, allocator: &Allocator) -> Builder {
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);
const AvailableOption = struct {
name: []const u8,
type_id: TypeId,
description: []const u8,
};
const UserInputOption = struct {
name: []const u8,
value: UserValue,
used: bool,
};
const UserValue = enum {
Flag,
Scalar: []const u8,
List: List([]const u8),
};
const TypeId = enum {
Bool,
Int,
Float,
String,
List,
};
pub fn init(allocator: &Allocator) -> Builder {
var self = Builder {
.zig_exe = zig_exe,
.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),
};
self.processNixOSEnvVars();
return self;
@ -46,6 +84,8 @@ pub const Builder = struct {
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,
@ -68,20 +108,13 @@ pub const Builder = struct {
%%self.lib_paths.append(path);
}
pub fn make(self: &Builder, leftover_arg_index: usize) -> %void {
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);
var verbose = false;
var arg_i: usize = leftover_arg_index;
while (arg_i < os.args.count(); arg_i += 1) {
const arg = os.args.at(arg_i);
if (mem.eql(u8, arg, "--verbose")) {
verbose = true;
} else {
%%io.stderr.printf("Unrecognized argument: '{}'\n", arg);
return error.ExtraArg;
}
}
for (self.exe_list.toSlice()) |exe| {
var zig_args = List([]const u8).init(self.allocator);
defer zig_args.deinit();
@ -89,10 +122,14 @@ pub const Builder = struct {
%return zig_args.append("build_exe"[0...]); // TODO issue #296
%return zig_args.append(exe.root_src);
if (verbose) {
if (exe.verbose) {
%return zig_args.append("--verbose"[0...]); // TODO issue #296
}
if (exe.release) {
%return zig_args.append("--release"[0...]); // TODO issue #296
}
%return zig_args.append("--name"[0...]); // TODO issue #296
%return zig_args.append(exe.name);
@ -149,22 +186,21 @@ pub const Builder = struct {
%return zig_args.append(lib_path);
}
if (self.verbose) {
printInvocation(zig_exe, zig_args);
}
// TODO issue #301
var child = os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), &env_map,
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();
const exe_result = switch (term) {
switch (term) {
Term.Clean => |code| {
if (code != 0) {
%%io.stderr.printf("\nCompile failed with code {}. To reproduce:\n", code);
printInvocation(self.zig_exe, zig_args);
return error.UncleanExit;
}
},
else => {
%%io.stderr.printf("\nCompile crashed. To reproduce:\n");
printInvocation(self.zig_exe, zig_args);
return error.UncleanExit;
},
};
@ -208,6 +244,144 @@ pub const Builder = struct {
}
}
}
pub fn option(self: &Builder, comptime T: type, name: []const u8, description: []const u8) -> ?T {
const type_id = typeToEnum(T);
const available_option = AvailableOption {
.name = name,
.type_id = type_id,
.description = description,
};
if (const _ ?= %%self.available_options_map.put(name, available_option)) {
debug.panic("Option '{}' declared twice", name);
}
%%self.available_options_list.append(available_option);
const entry = self.user_input_options.get(name) ?? return null;
entry.value.used = true;
switch (type_id) {
TypeId.Bool => switch (entry.value.value) {
UserValue.Flag => return true,
UserValue.Scalar => |s| {
if (mem.eql(u8, s, "true")) {
return true;
} else if (mem.eql(u8, s, "false")) {
return false;
} else {
%%io.stderr.printf("Expected -O{} to be a boolean, but received '{}'\n", name, s);
self.markInvalidUserInput();
return null;
}
},
UserValue.List => {
%%io.stderr.printf("Expected -O{} to be a boolean, but received a list.\n", name);
self.markInvalidUserInput();
return null;
},
},
TypeId.Int => debug.panic("TODO integer options to build script"),
TypeId.Float => debug.panic("TODO float options to build script"),
TypeId.String => debug.panic("TODO string options to build script"),
TypeId.List => debug.panic("TODO list options to build script"),
}
}
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,
.value = UserValue.Scalar { value },
.used = false,
})) {
switch (prev_value.value) {
UserValue.Scalar => |s| {
var list = List([]const u8).init(self.allocator);
%%list.append(s);
%%list.append(value);
%%self.user_input_options.put(name, UserInputOption {
.name = name,
.value = UserValue.List { list },
.used = false,
});
},
UserValue.List => |*list| {
%%list.append(value);
%%self.user_input_options.put(name, UserInputOption {
.name = name,
.value = UserValue.List { *list },
.used = false,
});
},
UserValue.Flag => {
%%io.stderr.printf("Option '-O{}={}' conflicts with flag '-O{}'.\n", name, value, name);
return true;
},
}
}
return false;
}
pub fn addUserInputFlag(self: &Builder, name: []const u8) -> bool {
if (const prev_value ?= %%self.user_input_options.put(name, UserInputOption {
.name = name,
.value = UserValue.Flag,
.used = false,
})) {
switch (prev_value.value) {
UserValue.Scalar => |s| {
%%io.stderr.printf("Flag '-O{}' conflicts with option '-O{}={}'.\n", name, name, s);
return true;
},
UserValue.List => {
%%io.stderr.printf("Flag '-O{}' conflicts with multiple options of the same name.\n", name);
return true;
},
UserValue.Flag => {},
}
}
return false;
}
fn typeToEnum(comptime T: type) -> TypeId {
if (@isInteger(T)) {
TypeId.Int
} else if (@isFloat(T)) {
TypeId.Float
} else switch (T) {
bool => TypeId.Bool,
[]const u8 => TypeId.String,
[]const []const u8 => TypeId.List,
else => @compileError("Unsupported type: " ++ @typeName(T)),
}
}
fn markInvalidUserInput(self: &Builder) {
self.invalid_user_input = true;
}
pub fn typeIdName(id: TypeId) -> []const u8 {
return switch (id) {
TypeId.Bool => ([]const u8)("bool"), // TODO issue #125
TypeId.Int => ([]const u8)("int"), // TODO issue #125
TypeId.Float => ([]const u8)("float"), // TODO issue #125
TypeId.String => ([]const u8)("string"), // TODO issue #125
TypeId.List => ([]const u8)("list"), // TODO issue #125
};
}
pub fn validateUserInputDidItFail(self: &Builder) -> bool {
// make sure all args are used
var it = self.user_input_options.iterator();
while (true) {
const entry = it.next() ?? break;
if (!entry.value.used) {
%%io.stderr.printf("Invalid option: -O{}\n\n", entry.key);
self.markInvalidUserInput();
}
}
return self.invalid_user_input;
}
};
const CrossTarget = struct {
@ -233,12 +407,14 @@ const Exe = struct {
target: Target,
linker_script: LinkerScript,
link_libs: BufSet,
verbose: bool,
release: bool,
fn deinit(self: &Exe) {
pub fn deinit(self: &Exe) {
self.link_libs.deinit();
}
fn setTarget(self: &Exe, target_arch: Arch, target_os: Os, target_environ: Environ) {
pub fn setTarget(self: &Exe, target_arch: Arch, target_os: Os, target_environ: Environ) {
self.target = Target.Cross {
CrossTarget {
.arch = target_arch,
@ -250,17 +426,25 @@ const Exe = struct {
/// Exe keeps a reference to script for its lifetime or until this function
/// is called again.
fn setLinkerScriptContents(self: &Exe, script: []const u8) {
pub fn setLinkerScriptContents(self: &Exe, script: []const u8) {
self.linker_script = LinkerScript.Embed { script };
}
fn setLinkerScriptPath(self: &Exe, path: []const u8) {
pub fn setLinkerScriptPath(self: &Exe, path: []const u8) {
self.linker_script = LinkerScript.Path { path };
}
fn linkLibrary(self: &Exe, name: []const u8) {
pub fn linkLibrary(self: &Exe, name: []const u8) {
%%self.link_libs.put(name);
}
pub fn setVerbose(self: &Exe, value: bool) {
self.verbose = value;
}
pub fn setRelease(self: &Exe, value: bool) {
self.release = value;
}
};
fn handleErr(err: error) -> noreturn {
@ -401,3 +585,4 @@ fn targetEnvironName(target_environ: Environ) -> []const u8 {
Environ.coreclr => ([]const u8)("coreclr"),
};
}

View File

@ -13,6 +13,8 @@ const State = enum { // TODO put inside format function and make sure the name a
Integer,
IntegerWidth,
Character,
Buf,
BufWidth,
};
/// Renders fmt string with args, calling output with slices of bytes.
@ -82,8 +84,21 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
'c' => {
state = State.Character;
},
's' => {
state = State.Buf;
},
else => @compileError("Unknown format character: " ++ []u8{c}),
},
State.Buf => switch (c) {
'}' => {
return output(context, args[next_arg]);
},
'0' ... '9' => {
width_start = i;
state = State.BufWidth;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.CloseBrace => switch (c) {
'}' => {
state = State.Start;
@ -117,6 +132,18 @@ pub fn format(context: var, output: fn(@typeOf(context), []const u8)->bool,
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.BufWidth => switch (c) {
'}' => {
width = comptime %%parseUnsigned(usize, fmt[width_start...i], 10);
if (!formatBuf(args[next_arg], width, context, output))
return false;
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.Character => switch (c) {
'}' => {
if (!formatAsciiChar(args[next_arg], context, output))
@ -165,6 +192,23 @@ pub fn formatAsciiChar(c: u8, context: var, output: fn(@typeOf(context), []const
return output(context, (&c)[0...1]);
}
pub fn formatBuf(buf: []const u8, width: usize,
context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
{
if (!output(context, buf))
return false;
var leftover_padding = if (width > buf.len) (width - buf.len) else return true;
const pad_byte: u8 = ' ';
while (leftover_padding > 0; leftover_padding -= 1) {
if (!output(context, (&pad_byte)[0...1]))
return false;
}
return true;
}
pub fn formatInt(value: var, base: u8, uppercase: bool, width: usize,
context: var, output: fn(@typeOf(context), []const u8)->bool) -> bool
{
@ -291,6 +335,34 @@ fn digitToChar(digit: u8, uppercase: bool) -> u8 {
};
}
const BufPrintContext = struct {
remaining: []u8,
};
fn bufPrintWrite(context: &BufPrintContext, bytes: []const u8) -> bool {
mem.copy(u8, context.remaining, bytes);
context.remaining = context.remaining[bytes.len...];
return true;
}
pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) {
var context = BufPrintContext { .remaining = buf, };
_ = format(&context, bufPrintWrite, fmt, args);
}
pub fn allocPrint(allocator: &mem.Allocator, comptime fmt: []const u8, args: ...) -> %[]u8 {
var size: usize = 0;
_ = format(&size, countSize, fmt, args);
const buf = %return allocator.alloc(u8, size);
bufPrint(buf, fmt, args);
return buf;
}
fn countSize(size: &usize, bytes: []const u8) -> bool {
*size += bytes.len;
return true;
}
test "testBufPrintInt" {
var buffer: [max_int_digits]u8 = undefined;
const buf = buffer[0...];

View File

@ -54,13 +54,15 @@ pub fn HashMap(comptime K: type, comptime V: type,
}
};
pub fn init(hm: &Self, allocator: &Allocator) {
hm.entries = []Entry{};
hm.allocator = allocator;
hm.size = 0;
hm.max_distance_from_start_index = 0;
// it doesn't actually matter what we set this to since we use wrapping integer arithmetic
hm.modification_count = undefined;
pub fn init(allocator: &Allocator) -> Self {
Self {
.entries = []Entry{},
.allocator = allocator,
.size = 0,
.max_distance_from_start_index = 0,
// it doesn't actually matter what we set this to since we use wrapping integer arithmetic
.modification_count = undefined,
}
}
pub fn deinit(hm: &Self) {
@ -76,7 +78,8 @@ pub fn HashMap(comptime K: type, comptime V: type,
hm.incrementModificationCount();
}
pub fn put(hm: &Self, key: K, value: V) -> %void {
/// Returns the value that was already there.
pub fn put(hm: &Self, key: K, value: &const V) -> %?V {
if (hm.entries.len == 0) {
%return hm.initCapacity(16);
}
@ -89,13 +92,13 @@ pub fn HashMap(comptime K: type, comptime V: type,
// dump all of the old elements into the new table
for (old_entries) |*old_entry| {
if (old_entry.used) {
hm.internalPut(old_entry.key, old_entry.value);
_ = hm.internalPut(old_entry.key, old_entry.value);
}
}
hm.allocator.free(old_entries);
}
hm.internalPut(key, value);
return hm.internalPut(key, value);
}
pub fn get(hm: &Self, key: K) -> ?&Entry {
@ -134,7 +137,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
return null;
}
pub fn entryIterator(hm: &const Self) -> Iterator {
pub fn iterator(hm: &const Self) -> Iterator {
return Iterator {
.hm = hm,
.count = 0,
@ -158,9 +161,10 @@ pub fn HashMap(comptime K: type, comptime V: type,
}
}
fn internalPut(hm: &Self, orig_key: K, orig_value: V) {
/// Returns the value that was already there.
fn internalPut(hm: &Self, orig_key: K, orig_value: &const V) -> ?V {
var key = orig_key;
var value = orig_value;
var value = *orig_value;
const start_index = hm.keyToIndex(key);
var roll_over: usize = 0;
var distance_from_start_index: usize = 0;
@ -187,7 +191,10 @@ pub fn HashMap(comptime K: type, comptime V: type,
continue;
}
if (!entry.used) {
var result: ?V = null;
if (entry.used) {
result = entry.value;
} else {
// adding an entry. otherwise overwriting old value with
// same key
hm.size += 1;
@ -200,7 +207,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
.key = key,
.value = value,
};
return;
return result;
}
unreachable // put into a full map
}
@ -224,15 +231,18 @@ pub fn HashMap(comptime K: type, comptime V: type,
}
test "basicHashMapTest" {
var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;
map.init(&debug.global_allocator);
var map = HashMap(i32, i32, hash_i32, eql_i32).init(&debug.global_allocator);
defer map.deinit();
%%map.put(1, 11);
%%map.put(2, 22);
%%map.put(3, 33);
%%map.put(4, 44);
%%map.put(5, 55);
// TODO issue #311
assert(%%map.put(1, i32(11)) == null);
assert(%%map.put(2, i32(22)) == null);
assert(%%map.put(3, i32(33)) == null);
assert(%%map.put(4, i32(44)) == null);
assert(%%map.put(5, i32(55)) == null);
assert(??%%map.put(5, i32(66)) == 55);
assert(??%%map.put(5, i32(55)) == 66);
assert((??map.get(2)).value == 22);
_ = map.remove(2);
@ -243,6 +253,7 @@ test "basicHashMapTest" {
fn hash_i32(x: i32) -> u32 {
*@ptrcast(&u32, &x)
}
fn eql_i32(a: i32, b: i32) -> bool {
a == b
}

View File

@ -208,6 +208,10 @@ pub fn split(s: []const u8, c: u8) -> SplitIterator {
}
}
pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) -> bool {
return if (needle.len > haystack.len) false else eql(T, haystack[0...needle.len], needle);
}
const SplitIterator = struct {
s: []const u8,
c: u8,

View File

@ -1,26 +1,106 @@
const root = @import("@build");
const std = @import("std");
const io = std.io;
const fmt = std.fmt;
const os = std.os;
const Builder = std.build.Builder;
const mem = std.mem;
const List = std.list.List;
error InvalidArgs;
pub fn main() -> %void {
if (os.args.count() < 2) {
%%io.stderr.printf("Expected first argument to be path to zig compiler\n");
return error.InvalidArgs;
}
const zig_exe = os.args.at(1);
const leftover_arg_index = 2;
// TODO use a more general purpose allocator here
var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024);
defer inc_allocator.deinit();
var builder = Builder.init(zig_exe, &inc_allocator.allocator);
const allocator = &inc_allocator.allocator;
var builder = Builder.init(allocator);
defer builder.deinit();
var maybe_zig_exe: ?[]const u8 = null;
var targets = List([]const u8).init(allocator);
var arg_i: usize = 1;
while (arg_i < os.args.count(); arg_i += 1) {
const arg = os.args.at(arg_i);
if (mem.startsWith(u8, arg, "-O")) {
const option_contents = arg[2...];
if (option_contents.len == 0) {
%%io.stderr.printf("Expected option name after '-O'\n\n");
return usage(&builder, maybe_zig_exe, false, &io.stderr);
}
if (const name_end ?= mem.indexOfScalar(u8, option_contents, '=')) {
const option_name = option_contents[0...name_end];
const option_value = option_contents[name_end...];
if (builder.addUserInputOption(option_name, option_value))
return usage(&builder, maybe_zig_exe, false, &io.stderr);
} else {
if (builder.addUserInputFlag(option_contents))
return usage(&builder, maybe_zig_exe, false, &io.stderr);
}
} else if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--verbose")) {
builder.verbose = true;
} else if (mem.eql(u8, arg, "--help")) {
return usage(&builder, maybe_zig_exe, false, &io.stdout);
} else {
%%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
return usage(&builder, maybe_zig_exe, false, &io.stderr);
}
} else if (maybe_zig_exe == null) {
maybe_zig_exe = arg;
} else {
%%targets.append(arg);
}
}
const zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
root.build(&builder);
%return builder.make(leftover_arg_index);
if (builder.validateUserInputDidItFail())
return usage(&builder, maybe_zig_exe, true, &io.stderr);
%return builder.make(zig_exe, targets.toSliceConst());
}
fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, out_stream: &io.OutStream) -> %void {
const zig_exe = maybe_zig_exe ?? {
%%out_stream.printf("Expected first argument to be path to zig compiler\n");
return error.InvalidArgs;
};
// run the build script to collect the options
if (!already_ran_build) {
root.build(builder);
}
%%out_stream.printf(
\\Usage: {} build [options]
\\
\\General Options:
\\ --help Print this help and exit.
\\ --verbose Print commands before executing them.
\\ --debug-build-verbose Print verbose debugging information for the build system itself.
\\
\\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,
" -O{}=({})", option.name, Builder.typeIdName(option.type_id));
defer allocator.free(name);
%%out_stream.printf("{s24} {}\n", name, option.description);
}
}
if (out_stream == &io.stderr)
return error.InvalidArgs;
}

View File

@ -138,3 +138,15 @@ fn returnsFalse() -> bool {
test "switchOnConstEnumWithVar" {
assert(!returnsFalse());
}
test "switch on type" {
assert(trueIfBoolFalseOtherwise(bool));
assert(!trueIfBoolFalseOtherwise(i32));
}
fn trueIfBoolFalseOtherwise(comptime T: type) -> bool {
switch (T) {
bool => true,
else => false,
}
}