Merge branch 'self-hosted-tests'

Now instead of:

```
./run_tests
```

Do this:

```
./zig build --build-file ../build.zig test
```

For more options, see:

```
./zig build --build-file ../build.zig --help
```
master
Andrew Kelley 2017-04-20 02:31:28 -04:00
commit 037a9d937d
29 changed files with 4459 additions and 3161 deletions

View File

@ -63,14 +63,6 @@ set(ZIG_SOURCES
"${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp" "${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp"
) )
set(TEST_SOURCES
"${CMAKE_SOURCE_DIR}/src/buffer.cpp"
"${CMAKE_SOURCE_DIR}/src/util.cpp"
"${CMAKE_SOURCE_DIR}/src/os.cpp"
"${CMAKE_SOURCE_DIR}/src/error.cpp"
"${CMAKE_SOURCE_DIR}/test/run_tests.cpp"
)
set(C_HEADERS set(C_HEADERS
"${CMAKE_SOURCE_DIR}/c_headers/Intrin.h" "${CMAKE_SOURCE_DIR}/c_headers/Intrin.h"
"${CMAKE_SOURCE_DIR}/c_headers/__stddef_max_align_t.h" "${CMAKE_SOURCE_DIR}/c_headers/__stddef_max_align_t.h"
@ -248,19 +240,12 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${Z
install(FILES "${CMAKE_SOURCE_DIR}/std/special/zigrt.zig" DESTINATION "${ZIG_STD_DEST}/special") install(FILES "${CMAKE_SOURCE_DIR}/std/special/zigrt.zig" DESTINATION "${ZIG_STD_DEST}/special")
install(FILES "${CMAKE_SOURCE_DIR}/std/target.zig" DESTINATION "${ZIG_STD_DEST}") install(FILES "${CMAKE_SOURCE_DIR}/std/target.zig" DESTINATION "${ZIG_STD_DEST}")
add_executable(run_tests ${TEST_SOURCES})
target_link_libraries(run_tests)
set_target_properties(run_tests PROPERTIES
COMPILE_FLAGS ${EXE_CFLAGS}
LINK_FLAGS ${EXE_LDFLAGS}
)
if (ZIG_TEST_COVERAGE) if (ZIG_TEST_COVERAGE)
add_custom_target(coverage add_custom_target(coverage
DEPENDS run_tests DEPENDS run_tests
WORKING_DIRECTORY ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND lcov --directory . --zerocounters --rc lcov_branch_coverage=1 COMMAND lcov --directory . --zerocounters --rc lcov_branch_coverage=1
COMMAND ./run_tests COMMAND ./zig build --build-file ../build.zig test
COMMAND lcov --directory . --capture --output-file coverage.info --rc lcov_branch_coverage=1 COMMAND lcov --directory . --capture --output-file coverage.info --rc lcov_branch_coverage=1
COMMAND lcov --remove coverage.info '/usr/*' --output-file coverage.info.cleaned --rc lcov_branch_coverage=1 COMMAND lcov --remove coverage.info '/usr/*' --output-file coverage.info.cleaned --rc lcov_branch_coverage=1
COMMAND genhtml -o coverage coverage.info.cleaned --rc lcov_branch_coverage=1 COMMAND genhtml -o coverage coverage.info.cleaned --rc lcov_branch_coverage=1

View File

@ -45,8 +45,8 @@ compromises backward compatibility.
* Release mode produces heavily optimized code. What other projects call * Release mode produces heavily optimized code. What other projects call
"Link Time Optimization" Zig does automatically. "Link Time Optimization" Zig does automatically.
* Mark functions as tests and automatically run them with `zig test`. * Mark functions as tests and automatically run them with `zig test`.
* Currently supported architectures: `x86_64`, `i386` * Currently supported architectures: `x86_64`
* Currently supported operating systems: linux, macosx * Currently supported operating systems: linux
* Friendly toward package maintainers. Reproducible build, bootstrapping * Friendly toward package maintainers. Reproducible build, bootstrapping
process carefully documented. Issues filed by package maintainers are process carefully documented. Issues filed by package maintainers are
considered especially important. considered especially important.
@ -103,7 +103,7 @@ cd build
cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o))
make make
make install make install
./run_tests ./zig build --build-file ../build.zig test
``` ```
### Release / Install Build ### Release / Install Build

23
build.zig Normal file
View File

@ -0,0 +1,23 @@
const Builder = @import("std").build.Builder;
const tests = @import("test/tests.zig");
pub fn build(b: &Builder) {
const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
const test_step = b.step("test", "Run all the tests");
const cleanup = b.addRemoveDirTree("test_artifacts");
test_step.dependOn(&cleanup.step);
cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
"test/behavior.zig", "behavior", "Run the behavior tests"));
cleanup.step.dependOn(tests.addPkgTests(b, test_filter,
"std/index.zig", "std", "Run the standard library tests"));
cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter));
cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter));
cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter));
cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter));
cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter));
cleanup.step.dependOn(tests.addParseHTests(b, test_filter));
}

View File

@ -1458,6 +1458,9 @@ struct CodeGen {
ZigList<Buf *> link_objects; ZigList<Buf *> link_objects;
ZigList<TypeTableEntry *> name_table_enums; ZigList<TypeTableEntry *> name_table_enums;
Buf *test_filter;
Buf *test_name_prefix;
}; };
enum VarLinkage { enum VarLinkage {

View File

@ -1959,7 +1959,14 @@ static void preview_test_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope
if (import->package != g->root_package) if (import->package != g->root_package)
return; return;
Buf *test_name = node->data.test_decl.name; Buf *decl_name_buf = node->data.test_decl.name;
Buf *test_name = g->test_name_prefix ?
buf_sprintf("%s%s", buf_ptr(g->test_name_prefix), buf_ptr(decl_name_buf)) : decl_name_buf;
if (g->test_filter != nullptr && strstr(buf_ptr(test_name), buf_ptr(g->test_filter)) == nullptr) {
return;
}
TldFn *tld_fn = allocate<TldFn>(1); TldFn *tld_fn = allocate<TldFn>(1);
init_tld(&tld_fn->base, TldIdFn, test_name, VisibModPrivate, node, &decls_scope->base); init_tld(&tld_fn->base, TldIdFn, test_name, VisibModPrivate, node, &decls_scope->base);

View File

@ -147,6 +147,14 @@ void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt) {
g->omit_zigrt = omit_zigrt; g->omit_zigrt = omit_zigrt;
} }
void codegen_set_test_filter(CodeGen *g, Buf *filter) {
g->test_filter = filter;
}
void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix) {
g->test_name_prefix = prefix;
}
void codegen_set_is_test(CodeGen *g, bool is_test_build) { void codegen_set_is_test(CodeGen *g, bool is_test_build) {
g->is_test_build = is_test_build; g->is_test_build = is_test_build;
} }

View File

@ -44,6 +44,8 @@ void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min);
void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min); void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min);
void codegen_set_linker_script(CodeGen *g, const char *linker_script); void codegen_set_linker_script(CodeGen *g, const char *linker_script);
void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt); 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);
PackageTableEntry *new_package(const char *root_src_dir, const char *root_src_path); 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); void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code);

View File

@ -770,6 +770,14 @@ void codegen_link(CodeGen *g, const char *out_file) {
if (g->want_h_file) { if (g->want_h_file) {
codegen_generate_h_file(g); codegen_generate_h_file(g);
} }
if (override_out_file) {
assert(g->link_objects.length == 1);
Buf *o_file_path = g->link_objects.at(0);
int err;
if ((err = os_rename(o_file_path, &lj.out_file))) {
zig_panic("unable to rename object file into final output: %s", err_str(err));
}
}
if (g->verbose) { if (g->verbose) {
fprintf(stderr, "OK\n"); fprintf(stderr, "OK\n");
} }

View File

@ -64,6 +64,9 @@ static int usage(const char *arg0) {
" -mwindows (windows only) --subsystem windows to the linker\n" " -mwindows (windows only) --subsystem windows to the linker\n"
" -rdynamic add all symbols to the dynamic symbol table\n" " -rdynamic add all symbols to the dynamic symbol table\n"
" -rpath [path] add directory to the runtime library search path\n" " -rpath [path] add directory to the runtime library search path\n"
"Test Options:\n"
" --test-filter [text] skip tests that do not match filter\n"
" --test-name-prefix [text] add prefix to all tests\n"
, arg0); , arg0);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -151,6 +154,8 @@ int main(int argc, char **argv) {
ZigList<const char *> rpath_list = {0}; ZigList<const char *> rpath_list = {0};
bool each_lib_rpath = false; bool each_lib_rpath = false;
ZigList<const char *> objects = {0}; ZigList<const char *> objects = {0};
const char *test_filter = nullptr;
const char *test_name_prefix = nullptr;
if (argc >= 2 && strcmp(argv[1], "build") == 0) { if (argc >= 2 && strcmp(argv[1], "build") == 0) {
const char *zig_exe_path = arg0; const char *zig_exe_path = arg0;
@ -168,6 +173,7 @@ int main(int argc, char **argv) {
ZigList<const char *> args = {0}; ZigList<const char *> args = {0};
args.append(zig_exe_path); args.append(zig_exe_path);
args.append(NULL); // placeholder
for (int i = 2; i < argc; i += 1) { for (int i = 2; i < argc; i += 1) {
if (strcmp(argv[i], "--debug-build-verbose") == 0) { if (strcmp(argv[i], "--debug-build-verbose") == 0) {
verbose = true; verbose = true;
@ -202,6 +208,8 @@ int main(int argc, char **argv) {
Buf build_file_dirname = BUF_INIT; Buf build_file_dirname = BUF_INIT;
os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename); os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename);
args.items[1] = buf_ptr(&build_file_dirname);
bool build_file_exists; bool build_file_exists;
if ((err = os_file_exists(&build_file_abs, &build_file_exists))) { if ((err = os_file_exists(&build_file_abs, &build_file_exists))) {
fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&build_file_abs), err_str(err)); fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&build_file_abs), err_str(err));
@ -214,11 +222,14 @@ int main(int argc, char **argv) {
"Usage: %s build [options]\n" "Usage: %s build [options]\n"
"\n" "\n"
"General Options:\n" "General Options:\n"
" --help Print this help and exit.\n" " --help Print this help and exit\n"
" --build-file [file] Override path to build.zig.\n" " --build-file [file] Override path to build.zig\n"
" --verbose Print commands before executing them.\n" " --verbose Print commands before executing them\n"
" --debug-build-verbose Print verbose debugging information for the build system itself.\n" " --debug-build-verbose Print verbose debugging information for the build system itself\n"
" --prefix [prefix] Override default install prefix.\n" " --prefix [prefix] Override default install prefix\n"
"\n"
"More options become available when the build file is found.\n"
"Run this command with no options to generate a build.zig template.\n"
, zig_exe_path); , zig_exe_path);
return 0; return 0;
} }
@ -335,6 +346,10 @@ int main(int argc, char **argv) {
linker_script = argv[i]; linker_script = argv[i];
} else if (strcmp(arg, "-rpath") == 0) { } else if (strcmp(arg, "-rpath") == 0) {
rpath_list.append(argv[i]); rpath_list.append(argv[i]);
} else if (strcmp(arg, "--test-filter") == 0) {
test_filter = argv[i];
} else if (strcmp(arg, "--test-name-prefix") == 0) {
test_name_prefix = argv[i];
} else { } else {
fprintf(stderr, "Invalid argument: %s\n", arg); fprintf(stderr, "Invalid argument: %s\n", arg);
return usage(arg0); return usage(arg0);
@ -556,6 +571,14 @@ int main(int argc, char **argv) {
codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min)); codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min));
} }
if (test_filter) {
codegen_set_test_filter(g, buf_create_from_str(test_filter));
}
if (test_name_prefix) {
codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
}
if (cmd == CmdBuild) { if (cmd == CmdBuild) {
codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code); codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code);
codegen_link(g, out_file); codegen_link(g, out_file);

View File

@ -683,3 +683,10 @@ int os_delete_file(Buf *path) {
void os_init(void) { void os_init(void) {
srand((unsigned)time(NULL)); srand((unsigned)time(NULL));
} }
int os_rename(Buf *src_path, Buf *dest_path) {
if (rename(buf_ptr(src_path), buf_ptr(dest_path)) == -1) {
return ErrorFileSystem;
}
return 0;
}

View File

@ -55,6 +55,8 @@ int os_delete_file(Buf *path);
int os_file_exists(Buf *full_path, bool *result); int os_file_exists(Buf *full_path, bool *result);
int os_rename(Buf *src_path, Buf *dest_path);
#if defined(__APPLE__) #if defined(__APPLE__)
#define ZIG_OS_DARWIN #define ZIG_OS_DARWIN
#elif defined(_WIN32) #elif defined(_WIN32)

View File

@ -10,13 +10,14 @@ const StdIo = os.ChildProcess.StdIo;
const Term = os.ChildProcess.Term; const Term = os.ChildProcess.Term;
const BufSet = @import("buf_set.zig").BufSet; const BufSet = @import("buf_set.zig").BufSet;
const BufMap = @import("buf_map.zig").BufMap; const BufMap = @import("buf_map.zig").BufMap;
const fmt = @import("fmt.zig"); const fmt_lib = @import("fmt.zig");
error ExtraArg; error ExtraArg;
error UncleanExit; error UncleanExit;
error InvalidStepName; error InvalidStepName;
error DependencyLoopDetected; error DependencyLoopDetected;
error NoCompilerFound; error NoCompilerFound;
error NeedAnObject;
pub const Builder = struct { pub const Builder = struct {
uninstall_tls: TopLevelStep, uninstall_tls: TopLevelStep,
@ -38,6 +39,7 @@ pub const Builder = struct {
lib_dir: []const u8, lib_dir: []const u8,
out_dir: []u8, out_dir: []u8,
installed_files: List([]const u8), installed_files: List([]const u8),
build_root: []const u8,
const UserInputOptionsMap = HashMap([]const u8, UserInputOption, mem.hash_slice_u8, mem.eql_slice_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); const AvailableOptionsMap = HashMap([]const u8, AvailableOption, mem.hash_slice_u8, mem.eql_slice_u8);
@ -73,8 +75,10 @@ pub const Builder = struct {
description: []const u8, description: []const u8,
}; };
pub fn init(allocator: &Allocator) -> Builder { pub fn init(allocator: &Allocator, zig_exe: []const u8, build_root: []const u8) -> Builder {
var self = Builder { var self = Builder {
.zig_exe = zig_exe,
.build_root = build_root,
.verbose = false, .verbose = false,
.invalid_user_input = false, .invalid_user_input = false,
.allocator = allocator, .allocator = allocator,
@ -85,7 +89,6 @@ pub const Builder = struct {
.available_options_map = AvailableOptionsMap.init(allocator), .available_options_map = AvailableOptionsMap.init(allocator),
.available_options_list = List(AvailableOption).init(allocator), .available_options_list = List(AvailableOption).init(allocator),
.top_level_steps = List(&TopLevelStep).init(allocator), .top_level_steps = List(&TopLevelStep).init(allocator),
.zig_exe = undefined,
.default_step = undefined, .default_step = undefined,
.env_map = %%os.getEnvMap(allocator), .env_map = %%os.getEnvMap(allocator),
.prefix = undefined, .prefix = undefined,
@ -123,6 +126,36 @@ pub const Builder = struct {
return exe; return exe;
} }
pub fn addTest(self: &Builder, root_src: []const u8) -> &TestStep {
const test_step = %%self.allocator.create(TestStep);
*test_step = TestStep.init(self, root_src);
return test_step;
}
pub fn addAssemble(self: &Builder, name: []const u8, src: []const u8) -> &AsmStep {
const asm_step = %%self.allocator.create(AsmStep);
*asm_step = AsmStep.init(self, name, src);
return asm_step;
}
pub fn addLinkExecutable(self: &Builder, name: []const u8) -> &LinkStep {
const exe = %%self.allocator.create(LinkStep);
*exe = LinkStep.initExecutable(self, name);
return exe;
}
pub fn addLinkStaticLibrary(self: &Builder, name: []const u8) -> &LinkStep {
const exe = %%self.allocator.create(LinkStep);
*exe = LinkStep.initStaticLibrary(self, name);
return exe;
}
pub fn addLinkSharedLibrary(self: &Builder, name: []const u8, ver: &const Version) -> &LinkStep {
const exe = %%self.allocator.create(LinkStep);
*exe = LinkStep.initSharedLibrary(self, name, ver);
return exe;
}
pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary { pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary {
const lib = %%self.allocator.create(CLibrary); const lib = %%self.allocator.create(CLibrary);
*lib = CLibrary.initStatic(self, name); *lib = CLibrary.initStatic(self, name);
@ -149,6 +182,25 @@ pub const Builder = struct {
return cmd; return cmd;
} }
pub fn addWriteFile(self: &Builder, file_path: []const u8, data: []const u8) -> &WriteFileStep {
const write_file_step = %%self.allocator.create(WriteFileStep);
*write_file_step = WriteFileStep.init(self, file_path, data);
return write_file_step;
}
pub fn addLog(self: &Builder, comptime format: []const u8, args: ...) -> &LogStep {
const data = self.fmt(format, args);
const log_step = %%self.allocator.create(LogStep);
*log_step = LogStep.init(self, data);
return log_step;
}
pub fn addRemoveDirTree(self: &Builder, dir_path: []const u8) -> &RemoveDirStep {
const remove_dir_step = %%self.allocator.create(RemoveDirStep);
*remove_dir_step = RemoveDirStep.init(self, dir_path);
return remove_dir_step;
}
pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version { pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
Version { Version {
.major = major, .major = major,
@ -197,9 +249,8 @@ pub const Builder = struct {
} }
fn makeUninstall(uninstall_step: &Step) -> %void { fn makeUninstall(uninstall_step: &Step) -> %void {
// TODO const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
// const self = @fieldParentPtr(Exe, "step", step); const self = @fieldParentPtr(Builder, "uninstall_tls", uninstall_tls);
const self = @ptrcast(&Builder, uninstall_step);
for (self.installed_files.toSliceConst()) |installed_file| { for (self.installed_files.toSliceConst()) |installed_file| {
_ = os.deleteFile(self.allocator, installed_file); _ = os.deleteFile(self.allocator, installed_file);
@ -278,7 +329,7 @@ pub const Builder = struct {
} }
pub fn option(self: &Builder, comptime T: type, name: []const u8, description: []const u8) -> ?T { pub fn option(self: &Builder, comptime T: type, name: []const u8, description: []const u8) -> ?T {
const type_id = typeToEnum(T); const type_id = comptime typeToEnum(T);
const available_option = AvailableOption { const available_option = AvailableOption {
.name = name, .name = name,
.type_id = type_id, .type_id = type_id,
@ -313,7 +364,19 @@ pub const Builder = struct {
}, },
TypeId.Int => debug.panic("TODO integer options to build script"), TypeId.Int => debug.panic("TODO integer options to build script"),
TypeId.Float => debug.panic("TODO float 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.String => switch (entry.value.value) {
UserValue.Flag => {
%%io.stderr.printf("Expected -D{} to be a string, but received a boolean.\n", name);
self.markInvalidUserInput();
return null;
},
UserValue.List => {
%%io.stderr.printf("Expected -D{} to be a string, but received a list.\n", name);
self.markInvalidUserInput();
return null;
},
UserValue.Scalar => |s| return s,
},
TypeId.List => debug.panic("TODO list options to build script"), TypeId.List => debug.panic("TODO list options to build script"),
} }
} }
@ -482,6 +545,14 @@ pub const Builder = struct {
debug.panic("Unable to copy {} to {}: {}", source_path, dest_path, @errorName(err)); debug.panic("Unable to copy {} to {}: {}", source_path, dest_path, @errorName(err));
}; };
} }
fn pathFromRoot(self: &Builder, rel_path: []const u8) -> []u8 {
return %%os.path.join(self.allocator, self.build_root, rel_path);
}
pub fn fmt(self: &Builder, comptime format: []const u8, args: ...) -> []u8 {
return %%fmt_lib.allocPrint(self.allocator, format, args);
}
}; };
const Version = struct { const Version = struct {
@ -510,6 +581,17 @@ const Target = enum {
else => ".o", else => ".o",
}; };
} }
pub fn exeFileExt(self: &const Target) -> []const u8 {
const target_os = switch (*self) {
Target.Native => @compileVar("os"),
Target.Cross => |t| t.os,
};
return switch (target_os) {
Os.windows => ".exe",
else => "",
};
}
}; };
const LinkerScript = enum { const LinkerScript = enum {
@ -518,7 +600,7 @@ const LinkerScript = enum {
Path: []const u8, Path: []const u8,
}; };
const Exe = struct { pub const Exe = struct {
step: Step, step: Step,
builder: &Builder, builder: &Builder,
root_src: []const u8, root_src: []const u8,
@ -528,6 +610,7 @@ const Exe = struct {
link_libs: BufSet, link_libs: BufSet,
verbose: bool, verbose: bool,
release: bool, release: bool,
output_path: ?[]const u8,
pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe { pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe {
Exe { Exe {
@ -540,6 +623,7 @@ const Exe = struct {
.linker_script = LinkerScript.None, .linker_script = LinkerScript.None,
.link_libs = BufSet.init(builder.allocator), .link_libs = BufSet.init(builder.allocator),
.step = Step.init(name, builder.allocator, make), .step = Step.init(name, builder.allocator, make),
.output_path = null,
} }
} }
@ -579,6 +663,10 @@ const Exe = struct {
self.release = value; self.release = value;
} }
pub fn setOutputPath(self: &Exe, value: []const u8) {
self.output_path = value;
}
fn make(step: &Step) -> %void { fn make(step: &Step) -> %void {
const exe = @fieldParentPtr(Exe, "step", step); const exe = @fieldParentPtr(Exe, "step", step);
const builder = exe.builder; const builder = exe.builder;
@ -586,31 +674,36 @@ const Exe = struct {
var zig_args = List([]const u8).init(builder.allocator); var zig_args = List([]const u8).init(builder.allocator);
defer zig_args.deinit(); defer zig_args.deinit();
%return zig_args.append("build_exe"); %%zig_args.append("build_exe");
%return zig_args.append(exe.root_src); %%zig_args.append(builder.pathFromRoot(exe.root_src));
if (exe.verbose) { if (exe.verbose) {
%return zig_args.append("--verbose"); %%zig_args.append("--verbose");
} }
if (exe.release) { if (exe.release) {
%return zig_args.append("--release"); %%zig_args.append("--release");
} }
%return zig_args.append("--name"); if (const output_path ?= exe.output_path) {
%return zig_args.append(exe.name); %%zig_args.append("--output");
%%zig_args.append(builder.pathFromRoot(output_path));
}
%%zig_args.append("--name");
%%zig_args.append(exe.name);
switch (exe.target) { switch (exe.target) {
Target.Native => {}, Target.Native => {},
Target.Cross => |cross_target| { Target.Cross => |cross_target| {
%return zig_args.append("--target-arch"); %%zig_args.append("--target-arch");
%return zig_args.append(@enumTagName(cross_target.arch)); %%zig_args.append(@enumTagName(cross_target.arch));
%return zig_args.append("--target-os"); %%zig_args.append("--target-os");
%return zig_args.append(@enumTagName(cross_target.os)); %%zig_args.append(@enumTagName(cross_target.os));
%return zig_args.append("--target-environ"); %%zig_args.append("--target-environ");
%return zig_args.append(@enumTagName(cross_target.environ)); %%zig_args.append(@enumTagName(cross_target.environ));
}, },
} }
@ -620,12 +713,12 @@ const Exe = struct {
const tmp_file_name = "linker.ld.tmp"; // TODO issue #298 const tmp_file_name = "linker.ld.tmp"; // TODO issue #298
io.writeFile(tmp_file_name, script, builder.allocator) io.writeFile(tmp_file_name, script, builder.allocator)
%% |err| debug.panic("unable to write linker script: {}\n", @errorName(err)); %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err));
%return zig_args.append("--linker-script"); %%zig_args.append("--linker-script");
%return zig_args.append(tmp_file_name); %%zig_args.append(tmp_file_name);
}, },
LinkerScript.Path => |path| { LinkerScript.Path => |path| {
%return zig_args.append("--linker-script"); %%zig_args.append("--linker-script");
%return zig_args.append(path); %%zig_args.append(path);
}, },
} }
@ -633,31 +726,432 @@ const Exe = struct {
var it = exe.link_libs.iterator(); var it = exe.link_libs.iterator();
while (true) { while (true) {
const entry = it.next() ?? break; const entry = it.next() ?? break;
%return zig_args.append("--library"); %%zig_args.append("--library");
%return zig_args.append(entry.key); %%zig_args.append(entry.key);
} }
} }
for (builder.include_paths.toSliceConst()) |include_path| { for (builder.include_paths.toSliceConst()) |include_path| {
%return zig_args.append("-isystem"); %%zig_args.append("-isystem");
%return zig_args.append(include_path); %%zig_args.append(include_path);
} }
for (builder.rpaths.toSliceConst()) |rpath| { for (builder.rpaths.toSliceConst()) |rpath| {
%return zig_args.append("-rpath"); %%zig_args.append("-rpath");
%return zig_args.append(rpath); %%zig_args.append(rpath);
} }
for (builder.lib_paths.toSliceConst()) |lib_path| { for (builder.lib_paths.toSliceConst()) |lib_path| {
%return zig_args.append("--library-path"); %%zig_args.append("--library-path");
%return zig_args.append(lib_path); %%zig_args.append(lib_path);
} }
builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
} }
}; };
const CLibrary = struct { pub const AsmStep = struct {
step: Step,
builder: &Builder,
name: []const u8,
target: Target,
verbose: bool,
release: bool,
output_path: ?[]const u8,
src_path: []const u8,
pub fn init(builder: &Builder, name: []const u8, src_path: []const u8) -> AsmStep {
var self = AsmStep {
.step = Step.init(name, builder.allocator, make),
.builder = builder,
.name = name,
.target = Target.Native,
.verbose = false,
.release = false,
.output_path = null,
.src_path = src_path,
};
return self;
}
pub fn setTarget(self: &AsmStep, 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: &AsmStep, value: bool) {
self.verbose = value;
}
pub fn setRelease(self: &AsmStep, value: bool) {
self.release = value;
}
pub fn setOutputPath(self: &AsmStep, value: []const u8) {
self.output_path = value;
}
fn make(step: &Step) -> %void {
const self = @fieldParentPtr(AsmStep, "step", step);
const builder = self.builder;
var zig_args = List([]const u8).init(builder.allocator);
defer zig_args.deinit();
%%zig_args.append("asm");
%%zig_args.append(builder.pathFromRoot(self.src_path));
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));
},
}
builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
}
};
pub const LinkStep = struct {
step: Step,
builder: &Builder,
name: []const u8,
target: Target,
linker_script: LinkerScript,
link_libs: BufSet,
verbose: bool,
release: bool,
output_path: ?[]const u8,
object_files: List([]const u8),
static: bool,
out_filename: []const u8,
out_type: OutType,
version: Version,
major_only_filename: []const u8,
name_only_filename: []const u8,
const OutType = enum {
Exe,
Lib,
};
pub fn initExecutable(builder: &Builder, name: []const u8) -> LinkStep {
return init(builder, name, OutType.Exe, builder.version(0, 0, 0), false)
}
pub fn initSharedLibrary(builder: &Builder, name: []const u8, version: &const Version) -> LinkStep {
return init(builder, name, OutType.Lib, version, false)
}
pub fn initStaticLibrary(builder: &Builder, name: []const u8) -> LinkStep {
return init(builder, name, OutType.Lib, builder.version(0, 0, 0), true)
}
fn init(builder: &Builder, name: []const u8, out_type: OutType, version: &const Version, static: bool) -> LinkStep {
var self = LinkStep {
.builder = builder,
.verbose = false,
.release = false,
.name = name,
.target = Target.Native,
.linker_script = LinkerScript.None,
.link_libs = BufSet.init(builder.allocator),
.step = Step.init(name, builder.allocator, make),
.output_path = null,
.object_files = List([]const u8).init(builder.allocator),
.out_type = out_type,
.version = *version,
.static = static,
.out_filename = undefined,
.major_only_filename = undefined,
.name_only_filename = undefined,
};
self.computeOutFileName();
return self;
}
fn computeOutFileName(self: &LinkStep) {
switch (self.out_type) {
OutType.Exe => {
self.out_filename = self.builder.fmt("{}{}", self.name, self.target.exeFileExt());
},
OutType.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.major_only_filename = self.builder.fmt("lib{}.so.{d}", self.name, self.version.major);
self.name_only_filename = self.builder.fmt("lib{}.so", self.name);
}
},
}
}
pub fn addObjectFile(self: &LinkStep, file: []const u8) {
%%self.object_files.append(file);
}
pub fn setTarget(self: &LinkStep, target_arch: Arch, target_os: Os, target_environ: Environ) {
self.target = Target.Cross {
CrossTarget {
.arch = target_arch,
.os = target_os,
.environ = target_environ,
}
};
self.computeOutFileName();
}
/// LinkStep keeps a reference to script for its lifetime or until this function
/// is called again.
pub fn setLinkerScriptContents(self: &LinkStep, script: []const u8) {
self.linker_script = LinkerScript.Embed { script };
}
pub fn setLinkerScriptPath(self: &LinkStep, path: []const u8) {
self.linker_script = LinkerScript.Path { path };
}
pub fn linkLibrary(self: &LinkStep, name: []const u8) {
%%self.link_libs.put(name);
}
pub fn setVerbose(self: &LinkStep, value: bool) {
self.verbose = value;
}
pub fn setRelease(self: &LinkStep, value: bool) {
self.release = value;
}
pub fn setOutputPath(self: &LinkStep, value: []const u8) {
self.output_path = value;
}
fn make(step: &Step) -> %void {
const self = @fieldParentPtr(LinkStep, "step", step);
const builder = self.builder;
if (self.object_files.len == 0) {
%%io.stderr.printf("{}: linker needs 1 or more objects to link\n", step.name);
return error.NeedAnObject;
}
var zig_args = List([]const u8).init(builder.allocator);
defer zig_args.deinit();
const cmd = switch (self.out_type) {
OutType.Exe => "link_exe",
OutType.Lib => "link_lib",
};
%%zig_args.append(cmd);
for (self.object_files.toSliceConst()) |object_file| {
%%zig_args.append(builder.pathFromRoot(object_file));
}
if (self.verbose) {
%%zig_args.append("--verbose");
}
if (self.release) {
%%zig_args.append("--release");
}
if (self.static) {
%%zig_args.append("--static");
}
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));
},
}
switch (self.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));
%%zig_args.append("--linker-script");
%%zig_args.append(tmp_file_name);
},
LinkerScript.Path => |path| {
%%zig_args.append("--linker-script");
%%zig_args.append(path);
},
}
{
var it = self.link_libs.iterator();
while (true) {
const entry = it.next() ?? break;
%%zig_args.append("--library");
%%zig_args.append(entry.key);
}
}
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());
}
};
pub const TestStep = struct {
step: Step,
builder: &Builder,
root_src: []const u8,
release: bool,
verbose: bool,
link_libs: BufSet,
name_prefix: []const u8,
filter: ?[]const u8,
pub fn init(builder: &Builder, root_src: []const u8) -> TestStep {
const step_name = builder.fmt("test {}", root_src);
TestStep {
.step = Step.init(step_name, builder.allocator, make),
.builder = builder,
.root_src = root_src,
.release = false,
.verbose = false,
.name_prefix = "",
.filter = null,
.link_libs = BufSet.init(builder.allocator),
}
}
pub fn setVerbose(self: &TestStep, value: bool) {
self.verbose = value;
}
pub fn setRelease(self: &TestStep, value: bool) {
self.release = value;
}
pub fn linkLibrary(self: &TestStep, name: []const u8) {
%%self.link_libs.put(name);
}
pub fn setNamePrefix(self: &TestStep, text: []const u8) {
self.name_prefix = text;
}
pub fn setFilter(self: &TestStep, text: ?[]const u8) {
self.filter = text;
}
fn make(step: &Step) -> %void {
const self = @fieldParentPtr(TestStep, "step", step);
const builder = self.builder;
var zig_args = List([]const u8).init(builder.allocator);
defer zig_args.deinit();
%%zig_args.append("test");
%%zig_args.append(builder.pathFromRoot(self.root_src));
if (self.verbose) {
%%zig_args.append("--verbose");
}
if (self.release) {
%%zig_args.append("--release");
}
if (const filter ?= self.filter) {
%%zig_args.append("--test-filter");
%%zig_args.append(filter);
}
if (self.name_prefix.len != 0) {
%%zig_args.append("--test-name-prefix");
%%zig_args.append(self.name_prefix);
}
{
var it = self.link_libs.iterator();
while (true) {
const entry = it.next() ?? break;
%%zig_args.append("--library");
%%zig_args.append(entry.key);
}
}
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());
}
};
pub const CLibrary = struct {
step: Step, step: Step,
name: []const u8, name: []const u8,
out_filename: []const u8, out_filename: []const u8,
@ -678,7 +1172,7 @@ const CLibrary = struct {
} }
pub fn initStatic(builder: &Builder, name: []const u8) -> CLibrary { pub fn initStatic(builder: &Builder, name: []const u8) -> CLibrary {
return init(builder, name, undefined, true); return init(builder, name, builder.version(0, 0, 0), true);
} }
fn init(builder: &Builder, name: []const u8, version: &const Version, static: bool) -> CLibrary { fn init(builder: &Builder, name: []const u8, version: &const Version, static: bool) -> CLibrary {
@ -704,14 +1198,12 @@ const CLibrary = struct {
fn computeOutFileName(self: &CLibrary) { fn computeOutFileName(self: &CLibrary) {
if (self.static) { if (self.static) {
self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.a", self.name); self.out_filename = self.builder.fmt("lib{}.a", self.name);
} else { } else {
self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.so.{d}.{d}.{d}", self.out_filename = self.builder.fmt("lib{}.so.{d}.{d}.{d}",
self.name, self.version.major, self.version.minor, self.version.patch); self.name, self.version.major, self.version.minor, self.version.patch);
self.major_only_filename = %%fmt.allocPrint(self.builder.allocator, self.major_only_filename = self.builder.fmt("lib{}.so.{d}", self.name, self.version.major);
"lib{}.so.{d}", self.name, self.version.major); self.name_only_filename = self.builder.fmt("lib{}.so", self.name);
self.name_only_filename = %%fmt.allocPrint(self.builder.allocator,
"lib{}.so", self.name);
} }
} }
@ -770,7 +1262,7 @@ const CLibrary = struct {
%%cc_args.append(source_file); %%cc_args.append(source_file);
// TODO don't dump the .o file in the same place as the source file // TODO don't dump the .o file in the same place as the source file
const o_file = %%fmt.allocPrint(builder.allocator, "{}{}", source_file, self.target.oFileExt()); const o_file = builder.fmt("{}{}", source_file, self.target.oFileExt());
defer builder.allocator.free(o_file); defer builder.allocator.free(o_file);
%%cc_args.append("-o"); %%cc_args.append("-o");
%%cc_args.append(o_file); %%cc_args.append(o_file);
@ -797,8 +1289,7 @@ const CLibrary = struct {
%%cc_args.append("-fPIC"); %%cc_args.append("-fPIC");
%%cc_args.append("-shared"); %%cc_args.append("-shared");
const soname_arg = %%fmt.allocPrint(builder.allocator, "-Wl,-soname,lib{}.so.{d}", const soname_arg = builder.fmt("-Wl,-soname,lib{}.so.{d}", self.name, self.version.major);
self.name, self.version.major);
defer builder.allocator.free(soname_arg); defer builder.allocator.free(soname_arg);
%%cc_args.append(soname_arg); %%cc_args.append(soname_arg);
@ -806,7 +1297,7 @@ const CLibrary = struct {
%%cc_args.append(self.out_filename); %%cc_args.append(self.out_filename);
for (self.object_files.toSliceConst()) |object_file| { for (self.object_files.toSliceConst()) |object_file| {
%%cc_args.append(object_file); %%cc_args.append(builder.pathFromRoot(object_file));
} }
builder.spawnChild(cc, cc_args.toSliceConst()); builder.spawnChild(cc, cc_args.toSliceConst());
@ -829,7 +1320,7 @@ const CLibrary = struct {
} }
}; };
const CExecutable = struct { pub const CExecutable = struct {
step: Step, step: Step,
builder: &Builder, builder: &Builder,
name: []const u8, name: []const u8,
@ -907,7 +1398,7 @@ const CExecutable = struct {
%%cc_args.append(source_file); %%cc_args.append(source_file);
// TODO don't dump the .o file in the same place as the source file // TODO don't dump the .o file in the same place as the source file
const o_file = %%fmt.allocPrint(builder.allocator, "{}{}", source_file, self.target.oFileExt()); const o_file = builder.fmt("{}{}", source_file, self.target.oFileExt());
defer builder.allocator.free(o_file); defer builder.allocator.free(o_file);
%%cc_args.append("-o"); %%cc_args.append("-o");
%%cc_args.append(o_file); %%cc_args.append(o_file);
@ -935,7 +1426,7 @@ const CExecutable = struct {
%%cc_args.append("-o"); %%cc_args.append("-o");
%%cc_args.append(self.name); %%cc_args.append(self.name);
const rpath_arg = %%fmt.allocPrint(builder.allocator, "-Wl,-rpath,{}", builder.out_dir); const rpath_arg = builder.fmt("-Wl,-rpath,{}", builder.out_dir);
defer builder.allocator.free(rpath_arg); defer builder.allocator.free(rpath_arg);
%%cc_args.append(rpath_arg); %%cc_args.append(rpath_arg);
@ -959,7 +1450,7 @@ const CExecutable = struct {
} }
}; };
const CommandStep = struct { pub const CommandStep = struct {
step: Step, step: Step,
builder: &Builder, builder: &Builder,
exe_path: []const u8, exe_path: []const u8,
@ -988,7 +1479,7 @@ const CommandStep = struct {
} }
}; };
const InstallCLibraryStep = struct { pub const InstallCLibraryStep = struct {
step: Step, step: Step,
builder: &Builder, builder: &Builder,
lib: &CLibrary, lib: &CLibrary,
@ -997,9 +1488,7 @@ const InstallCLibraryStep = struct {
pub fn init(builder: &Builder, lib: &CLibrary) -> InstallCLibraryStep { pub fn init(builder: &Builder, lib: &CLibrary) -> InstallCLibraryStep {
var self = InstallCLibraryStep { var self = InstallCLibraryStep {
.builder = builder, .builder = builder,
.step = Step.init( .step = Step.init(builder.fmt("install {}", lib.step.name), builder.allocator, make),
%%fmt.allocPrint(builder.allocator, "install {}", lib.step.name),
builder.allocator, make),
.lib = lib, .lib = lib,
.dest_file = undefined, .dest_file = undefined,
}; };
@ -1023,7 +1512,7 @@ const InstallCLibraryStep = struct {
} }
}; };
const InstallFileStep = struct { pub const InstallFileStep = struct {
step: Step, step: Step,
builder: &Builder, builder: &Builder,
src_path: []const u8, src_path: []const u8,
@ -1032,9 +1521,7 @@ const InstallFileStep = struct {
pub fn init(builder: &Builder, src_path: []const u8, dest_path: []const u8) -> InstallFileStep { pub fn init(builder: &Builder, src_path: []const u8, dest_path: []const u8) -> InstallFileStep {
return InstallFileStep { return InstallFileStep {
.builder = builder, .builder = builder,
.step = Step.init( .step = Step.init(builder.fmt("install {}", src_path), builder.allocator, make),
%%fmt.allocPrint(builder.allocator, "install {}", src_path),
builder.allocator, make),
.src_path = src_path, .src_path = src_path,
.dest_path = dest_path, .dest_path = dest_path,
}; };
@ -1047,7 +1534,81 @@ const InstallFileStep = struct {
} }
}; };
const Step = struct { pub const WriteFileStep = struct {
step: Step,
builder: &Builder,
file_path: []const u8,
data: []const u8,
pub fn init(builder: &Builder, file_path: []const u8, data: []const u8) -> WriteFileStep {
return WriteFileStep {
.builder = builder,
.step = Step.init(builder.fmt("writefile {}", file_path), builder.allocator, make),
.file_path = file_path,
.data = data,
};
}
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);
os.makePath(self.builder.allocator, full_path_dir) %% |err| {
%%io.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err));
return err;
};
io.writeFile(full_path, self.data, self.builder.allocator) %% |err| {
%%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err));
return err;
};
}
};
pub const LogStep = struct {
step: Step,
builder: &Builder,
data: []const u8,
pub fn init(builder: &Builder, data: []const u8) -> LogStep {
return LogStep {
.builder = builder,
.step = Step.init(builder.fmt("log {}", data), builder.allocator, make),
.data = data,
};
}
fn make(step: &Step) -> %void {
const self = @fieldParentPtr(LogStep, "step", step);
%%io.stderr.write(self.data);
%%io.stderr.flush();
}
};
pub const RemoveDirStep = struct {
step: Step,
builder: &Builder,
dir_path: []const u8,
pub fn init(builder: &Builder, dir_path: []const u8) -> RemoveDirStep {
return RemoveDirStep {
.builder = builder,
.step = Step.init(builder.fmt("RemoveDir {}", dir_path), builder.allocator, make),
.dir_path = dir_path,
};
}
fn make(step: &Step) -> %void {
const self = @fieldParentPtr(RemoveDirStep, "step", step);
const full_path = self.builder.pathFromRoot(self.dir_path);
os.deleteTree(self.builder.allocator, full_path) %% |err| {
%%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err));
return err;
};
}
};
pub const Step = struct {
name: []const u8, name: []const u8,
makeFn: fn(self: &Step) -> %void, makeFn: fn(self: &Step) -> %void,
dependencies: List(&Step), dependencies: List(&Step),

View File

@ -57,8 +57,6 @@ error NoMem;
error Unseekable; error Unseekable;
error Eof; error Eof;
const buffer_size = 4 * 1024;
pub const OpenRead = 0b0001; pub const OpenRead = 0b0001;
pub const OpenWrite = 0b0010; pub const OpenWrite = 0b0010;
pub const OpenCreate = 0b0100; pub const OpenCreate = 0b0100;
@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000;
pub const OutStream = struct { pub const OutStream = struct {
fd: i32, fd: i32,
buffer: [buffer_size]u8, buffer: [os.page_size]u8,
index: usize, index: usize,
/// `path` may need to be copied in memory to add a null terminating byte. In this case /// `path` may need to be copied in memory to add a null terminating byte. In this case
@ -97,7 +95,7 @@ pub const OutStream = struct {
} }
pub fn write(self: &OutStream, bytes: []const u8) -> %void { pub fn write(self: &OutStream, bytes: []const u8) -> %void {
if (bytes.len >= buffer_size) { if (bytes.len >= self.buffer.len) {
%return self.flush(); %return self.flush();
return os.posixWrite(self.fd, bytes); return os.posixWrite(self.fd, bytes);
} }
@ -329,7 +327,7 @@ pub const InStream = struct {
} }
pub fn readAll(is: &InStream, buf: &Buffer0) -> %void { pub fn readAll(is: &InStream, buf: &Buffer0) -> %void {
%return buf.resize(buffer_size); %return buf.resize(os.page_size);
var actual_buf_len: usize = 0; var actual_buf_len: usize = 0;
while (true) { while (true) {
@ -341,7 +339,7 @@ pub const InStream = struct {
return buf.resize(actual_buf_len); return buf.resize(actual_buf_len);
} }
%return buf.resize(actual_buf_len + buffer_size); %return buf.resize(actual_buf_len + os.page_size);
} }
} }
}; };

View File

@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{
l.len = new_length; l.len = new_length;
return result; return result;
} }
pub fn pop(self: &Self) -> T {
self.len -= 1;
return self.items[self.len];
}
} }
} }
test "basicListTest" { test "basic list test" {
var list = List(i32).init(&debug.global_allocator); var list = List(i32).init(&debug.global_allocator);
defer list.deinit(); defer list.deinit();
@ -75,4 +80,7 @@ test "basicListTest" {
{var i: usize = 0; while (i < 10; i += 1) { {var i: usize = 0; while (i < 10; i += 1) {
assert(list.items[i] == i32(i + 1)); assert(list.items[i] == i32(i + 1));
}} }}
assert(list.pop() == 10);
assert(list.len == 9);
} }

View File

@ -9,6 +9,8 @@ error NoMem;
pub const Allocator = struct { pub const Allocator = struct {
allocFn: fn (self: &Allocator, n: usize) -> %[]u8, allocFn: fn (self: &Allocator, n: usize) -> %[]u8,
/// Note that old_mem may be a slice of length 0, in which case reallocFn
/// should simply call allocFn
reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8, reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8,
freeFn: fn (self: &Allocator, mem: []u8), freeFn: fn (self: &Allocator, mem: []u8),
@ -134,6 +136,13 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) -> bool {
return true; return true;
} }
/// Copies ::m to newly allocated memory. Caller is responsible to free it.
pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) -> %[]T {
const new_buf = %return allocator.alloc(T, m.len);
copy(T, new_buf, m);
return new_buf;
}
/// Linear search for the index of a scalar value inside a slice. /// Linear search for the index of a scalar value inside a slice.
pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize { pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
for (slice) |item, i| { for (slice) |item, i| {
@ -144,6 +153,27 @@ pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
return null; return null;
} }
// TODO boyer-moore algorithm
pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
if (needle.len > haystack.len)
return null;
var i: usize = 0;
const end = haystack.len - needle.len;
while (i <= end; i += 1) {
if (eql(T, haystack[i...i + needle.len], needle))
return i;
}
return null;
}
test "mem.indexOf" {
assert(??indexOf(u8, "one two three four", "four") == 14);
assert(indexOf(u8, "one two three four", "gour") == null);
assert(??indexOf(u8, "foo", "foo") == 0);
assert(indexOf(u8, "foo", "fool") == null);
}
/// Reads an integer from memory with size equal to bytes.len. /// Reads an integer from memory with size equal to bytes.len.
/// T specifies the return type, which must be large enough to store /// T specifies the return type, which must be large enough to store
/// the result. /// the result.

View File

@ -12,6 +12,13 @@ pub const max_noalloc_path_len = 1024;
pub const ChildProcess = @import("child_process.zig").ChildProcess; pub const ChildProcess = @import("child_process.zig").ChildProcess;
pub const path = @import("path.zig"); pub const path = @import("path.zig");
pub const line_sep = switch (@compileVar("os")) {
Os.windows => "\r\n",
else => "\n",
};
pub const page_size = 4 * 1024;
const debug = @import("../debug.zig"); const debug = @import("../debug.zig");
const assert = debug.assert; const assert = debug.assert;
@ -27,6 +34,7 @@ const cstr = @import("../cstr.zig");
const io = @import("../io.zig"); const io = @import("../io.zig");
const base64 = @import("../base64.zig"); const base64 = @import("../base64.zig");
const List = @import("../list.zig").List;
error Unexpected; error Unexpected;
error SystemResources; error SystemResources;
@ -41,6 +49,7 @@ error SymLinkLoop;
error ReadOnlyFileSystem; error ReadOnlyFileSystem;
error LinkQuotaExceeded; error LinkQuotaExceeded;
error RenameAcrossMountPoints; error RenameAcrossMountPoints;
error DirNotEmpty;
/// Fills `buf` with random bytes. If linking against libc, this calls the /// Fills `buf` with random bytes. If linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard /// appropriate OS-specific library call. Otherwise it uses the zig standard
@ -319,7 +328,8 @@ fn posixExecveErrnoToErr(err: usize) -> error {
errno.EINVAL, errno.ENOEXEC => error.InvalidExe, errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
errno.EIO, errno.ELOOP => error.FileSystem, errno.EIO, errno.ELOOP => error.FileSystem,
errno.EISDIR => error.IsDir, errno.EISDIR => error.IsDir,
errno.ENOENT, errno.ENOTDIR => error.FileNotFound, errno.ENOENT => error.FileNotFound,
errno.ENOTDIR => error.NotDir,
errno.ETXTBSY => error.FileBusy, errno.ETXTBSY => error.FileBusy,
else => error.Unexpected, else => error.Unexpected,
}; };
@ -413,7 +423,8 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
errno.EIO => error.FileSystem, errno.EIO => error.FileSystem,
errno.ELOOP => error.SymLinkLoop, errno.ELOOP => error.SymLinkLoop,
errno.ENAMETOOLONG => error.NameTooLong, errno.ENAMETOOLONG => error.NameTooLong,
errno.ENOENT, errno.ENOTDIR => error.FileNotFound, errno.ENOENT => error.FileNotFound,
errno.ENOTDIR => error.NotDir,
errno.ENOMEM => error.SystemResources, errno.ENOMEM => error.SystemResources,
errno.ENOSPC => error.NoSpaceLeft, errno.ENOSPC => error.NoSpaceLeft,
errno.EROFS => error.ReadOnlyFileSystem, errno.EROFS => error.ReadOnlyFileSystem,
@ -471,7 +482,8 @@ pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void {
errno.EISDIR => error.IsDir, errno.EISDIR => error.IsDir,
errno.ELOOP => error.SymLinkLoop, errno.ELOOP => error.SymLinkLoop,
errno.ENAMETOOLONG => error.NameTooLong, errno.ENAMETOOLONG => error.NameTooLong,
errno.ENOENT, errno.ENOTDIR => error.FileNotFound, errno.ENOENT => error.FileNotFound,
errno.ENOTDIR => error.NotDir,
errno.ENOMEM => error.SystemResources, errno.ENOMEM => error.SystemResources,
errno.EROFS => error.ReadOnlyFileSystem, errno.EROFS => error.ReadOnlyFileSystem,
else => error.Unexpected, else => error.Unexpected,
@ -518,7 +530,8 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
errno.ELOOP => error.SymLinkLoop, errno.ELOOP => error.SymLinkLoop,
errno.EMLINK => error.LinkQuotaExceeded, errno.EMLINK => error.LinkQuotaExceeded,
errno.ENAMETOOLONG => error.NameTooLong, errno.ENAMETOOLONG => error.NameTooLong,
errno.ENOENT, errno.ENOTDIR => error.FileNotFound, errno.ENOENT => error.FileNotFound,
errno.ENOTDIR => error.NotDir,
errno.ENOMEM => error.SystemResources, errno.ENOMEM => error.SystemResources,
errno.ENOSPC => error.NoSpaceLeft, errno.ENOSPC => error.NoSpaceLeft,
errno.EEXIST, errno.ENOTEMPTY => error.PathAlreadyExists, errno.EEXIST, errno.ENOTEMPTY => error.PathAlreadyExists,
@ -528,3 +541,225 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
}; };
} }
} }
pub fn makeDir(allocator: &Allocator, dir_path: []const u8) -> %void {
const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
defer allocator.free(path_buf);
mem.copy(u8, path_buf, dir_path);
path_buf[dir_path.len] = 0;
const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
if (err > 0) {
return switch (err) {
errno.EACCES, errno.EPERM => error.AccessDenied,
errno.EDQUOT => error.DiskQuota,
errno.EEXIST => error.PathAlreadyExists,
errno.EFAULT => unreachable,
errno.ELOOP => error.SymLinkLoop,
errno.EMLINK => error.LinkQuotaExceeded,
errno.ENAMETOOLONG => error.NameTooLong,
errno.ENOENT => error.FileNotFound,
errno.ENOMEM => error.SystemResources,
errno.ENOSPC => error.NoSpaceLeft,
errno.ENOTDIR => error.NotDir,
errno.EROFS => error.ReadOnlyFileSystem,
else => error.Unexpected,
};
}
}
/// 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);
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
};
}
/// Returns ::error.DirNotEmpty if the directory is not empty.
/// To delete a directory recursively, see ::deleteTree
pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) -> %void {
const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
defer allocator.free(path_buf);
mem.copy(u8, path_buf, dir_path);
path_buf[dir_path.len] = 0;
const err = posix.getErrno(posix.rmdir(path_buf.ptr));
if (err > 0) {
return switch (err) {
errno.EACCES, errno.EPERM => error.AccessDenied,
errno.EBUSY => error.FileBusy,
errno.EFAULT, errno.EINVAL => unreachable,
errno.ELOOP => error.SymLinkLoop,
errno.ENAMETOOLONG => error.NameTooLong,
errno.ENOENT => error.FileNotFound,
errno.ENOMEM => error.SystemResources,
errno.ENOTDIR => error.NotDir,
errno.EEXIST, errno.ENOTEMPTY => error.DirNotEmpty,
errno.EROFS => error.ReadOnlyFileSystem,
else => error.Unexpected,
};
}
}
/// Whether ::full_path describes a symlink, file, or directory, this function
/// removes it. If it cannot be removed because it is a non-empty directory,
/// this function recursively removes its entries and then tries again.
// TODO non-recursive implementation
pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void {
start_over:
// First, try deleting the item as a file. This way we don't follow sym links.
try (deleteFile(allocator, full_path)) {
return;
} else |err| {
if (err == error.FileNotFound)
return;
if (err != error.IsDir)
return err;
}
{
var dir = Dir.open(allocator, full_path) %% |err| {
if (err == error.FileNotFound)
return;
if (err == error.NotDir)
goto start_over;
return err;
};
defer dir.close();
var full_entry_buf = List(u8).init(allocator);
defer full_entry_buf.deinit();
while (true) {
const entry = (%return dir.next()) ?? break;
%return full_entry_buf.resize(full_path.len + entry.name.len + 1);
const full_entry_path = full_entry_buf.toSlice();
mem.copy(u8, full_entry_path, full_path);
full_entry_path[full_path.len] = '/';
mem.copy(u8, full_entry_path[full_path.len + 1...], entry.name);
%return deleteTree(allocator, full_entry_path);
}
}
return deleteDir(allocator, full_path);
}
pub const Dir = struct {
fd: i32,
allocator: &Allocator,
buf: []u8,
index: usize,
end_index: usize,
const LinuxEntry = extern struct {
d_ino: usize,
d_off: usize,
d_reclen: u16,
d_name: u8, // field address is the address of first byte of name
};
pub const Entry = struct {
name: []const u8,
kind: Kind,
pub const Kind = enum {
BlockDevice,
CharacterDevice,
Directory,
NamedPipe,
SymLink,
File,
UnixDomainSocket,
Unknown,
};
};
pub fn open(allocator: &Allocator, dir_path: []const u8) -> %Dir {
const fd = %return posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator);
return Dir {
.allocator = allocator,
.fd = fd,
.index = 0,
.end_index = 0,
.buf = []u8{},
};
}
pub fn close(self: &Dir) {
self.allocator.free(self.buf);
posixClose(self.fd);
}
/// Memory such as file names referenced in this returned entry becomes invalid
/// with subsequent calls to next, as well as when this ::Dir is deinitialized.
pub fn next(self: &Dir) -> %?Entry {
start_over:
if (self.index >= self.end_index) {
if (self.buf.len == 0) {
self.buf = %return self.allocator.alloc(u8, 2); //page_size);
}
while (true) {
const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len);
const err = linux.getErrno(result);
if (err > 0) {
switch (err) {
errno.EBADF, errno.EFAULT, errno.ENOTDIR => unreachable,
errno.EINVAL => {
self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2);
continue;
},
else => return error.Unexpected,
};
}
if (result == 0)
return null;
self.index = 0;
self.end_index = result;
break;
}
}
const linux_entry = @ptrcast(&LinuxEntry, &self.buf[self.index]);
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)];
// skip . and .. entries
if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
goto start_over;
}
const type_char = self.buf[next_index - 1];
const entry_kind = switch (type_char) {
posix.DT_BLK => Entry.Kind.BlockDevice,
posix.DT_CHR => Entry.Kind.CharacterDevice,
posix.DT_DIR => Entry.Kind.Directory,
posix.DT_FIFO => Entry.Kind.NamedPipe,
posix.DT_LNK => Entry.Kind.SymLink,
posix.DT_REG => Entry.Kind.File,
posix.DT_SOCK => Entry.Kind.UnixDomainSocket,
else => Entry.Kind.Unknown,
};
return Entry {
.name = name,
.kind = entry_kind,
};
}
};

View File

@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC;
pub const AF_VSOCK = PF_VSOCK; pub const AF_VSOCK = PF_VSOCK;
pub const AF_MAX = PF_MAX; pub const AF_MAX = PF_MAX;
pub const DT_UNKNOWN = 0;
pub const DT_FIFO = 1;
pub const DT_CHR = 2;
pub const DT_DIR = 4;
pub const DT_BLK = 6;
pub const DT_REG = 8;
pub const DT_LNK = 10;
pub const DT_SOCK = 12;
pub const DT_WHT = 14;
fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) } fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) }
fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) } fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) }
@ -273,6 +282,14 @@ pub fn getcwd(buf: &u8, size: usize) -> usize {
arch.syscall2(arch.SYS_getcwd, usize(buf), size) arch.syscall2(arch.SYS_getcwd, usize(buf), size)
} }
pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize {
arch.syscall3(arch.SYS_getdents, usize(fd), usize(dirp), usize(count))
}
pub fn mkdir(path: &const u8, mode: usize) -> usize {
arch.syscall2(arch.SYS_mkdir, usize(path), mode)
}
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize) pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize)
-> usize -> usize
{ {
@ -287,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize {
arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count) arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count)
} }
pub fn rmdir(path: &const u8) -> usize {
arch.syscall1(arch.SYS_rmdir, usize(path))
}
pub fn symlink(existing: &const u8, new: &const u8) -> usize { pub fn symlink(existing: &const u8, new: &const u8) -> usize {
arch.syscall2(arch.SYS_symlink, usize(existing), usize(new)) arch.syscall2(arch.SYS_symlink, usize(existing), usize(new))
} }

View File

@ -4,22 +4,61 @@ const mem = @import("../mem.zig");
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
/// Allocates memory for the result, which must be freed by the caller. /// Allocates memory for the result, which must be freed by the caller.
pub fn join(allocator: &Allocator, dirname: []const u8, basename: []const u8) -> %[]const u8 { pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 {
const buf = %return allocator.alloc(u8, dirname.len + basename.len + 1); assert(paths.len >= 2);
var total_paths_len: usize = paths.len; // 1 slash per path
{
comptime var path_i = 0;
inline while (path_i < paths.len; path_i += 1) {
const arg = ([]const u8)(paths[path_i]);
total_paths_len += arg.len;
}
}
const buf = %return allocator.alloc(u8, total_paths_len);
%defer allocator.free(buf); %defer allocator.free(buf);
mem.copy(u8, buf, dirname); var buf_index: usize = 0;
if (dirname[dirname.len - 1] == '/') { comptime var path_i = 0;
mem.copy(u8, buf[dirname.len...], basename); inline while (true) {
return buf[0...buf.len - 1]; const arg = ([]const u8)(paths[path_i]);
} else { path_i += 1;
buf[dirname.len] = '/'; mem.copy(u8, buf[buf_index...], arg);
mem.copy(u8, buf[dirname.len + 1 ...], basename); buf_index += arg.len;
return buf; if (path_i >= paths.len) break;
if (arg[arg.len - 1] != '/') {
buf[buf_index] = '/';
buf_index += 1;
} }
} }
return buf[0...buf_index];
}
test "os.path.join" { test "os.path.join" {
assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c")); assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));
assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c")); assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c"));
assert(mem.eql(u8, %%join(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));
assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));
}
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;
var i: usize = last_index;
while (true) {
const c = path[i];
if (c == '/')
return mem.dupe(allocator, u8, path[0...i]);
if (i == 0)
break;
i -= 1;
}
}
return mem.dupe(allocator, u8, ".");
} }

View File

@ -3,6 +3,8 @@ const Builder = @import("std").build.Builder;
pub fn build(b: &Builder) { pub fn build(b: &Builder) {
const release = b.option(bool, "release", "optimizations on and safety off") ?? false; const release = b.option(bool, "release", "optimizations on and safety off") ?? false;
var exe = b.addExe("src/main.zig", "YOUR_NAME_HERE"); const exe = b.addExecutable("YOUR_NAME_HERE", "src/main.zig");
exe.setRelease(release); exe.setRelease(release);
b.default_step.dependOn(&exe.step);
} }

View File

@ -10,74 +10,85 @@ const List = std.list.List;
error InvalidArgs; error InvalidArgs;
pub fn main() -> %void { pub fn main() -> %void {
var arg_i: usize = 1;
const zig_exe = {
if (arg_i >= os.args.count()) {
%%io.stderr.printf("Expected first argument to be path to zig compiler\n");
return error.InvalidArgs;
}
const result = os.args.at(arg_i);
arg_i += 1;
result
};
const build_root = {
if (arg_i >= os.args.count()) {
%%io.stderr.printf("Expected second argument to be build root directory path\n");
return error.InvalidArgs;
}
const result = os.args.at(arg_i);
arg_i += 1;
result
};
// TODO use a more general purpose allocator here // TODO use a more general purpose allocator here
var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024); var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024);
defer inc_allocator.deinit(); defer inc_allocator.deinit();
const allocator = &inc_allocator.allocator; const allocator = &inc_allocator.allocator;
var builder = Builder.init(allocator); var builder = Builder.init(allocator, zig_exe, build_root);
defer builder.deinit(); defer builder.deinit();
var maybe_zig_exe: ?[]const u8 = null;
var targets = List([]const u8).init(allocator); var targets = List([]const u8).init(allocator);
var prefix: ?[]const u8 = null; var prefix: ?[]const u8 = null;
var arg_i: usize = 1;
while (arg_i < os.args.count(); arg_i += 1) { while (arg_i < os.args.count(); arg_i += 1) {
const arg = os.args.at(arg_i); const arg = os.args.at(arg_i);
if (mem.startsWith(u8, arg, "-D")) { if (mem.startsWith(u8, arg, "-D")) {
const option_contents = arg[2...]; const option_contents = arg[2...];
if (option_contents.len == 0) { if (option_contents.len == 0) {
%%io.stderr.printf("Expected option name after '-D'\n\n"); %%io.stderr.printf("Expected option name after '-D'\n\n");
return usage(&builder, maybe_zig_exe, false, &io.stderr); return usage(&builder, false, &io.stderr);
} }
if (const name_end ?= mem.indexOfScalar(u8, option_contents, '=')) { if (const name_end ?= mem.indexOfScalar(u8, option_contents, '=')) {
const option_name = option_contents[0...name_end]; const option_name = option_contents[0...name_end];
const option_value = option_contents[name_end...]; const option_value = option_contents[name_end + 1...];
if (builder.addUserInputOption(option_name, option_value)) if (builder.addUserInputOption(option_name, option_value))
return usage(&builder, maybe_zig_exe, false, &io.stderr); return usage(&builder, false, &io.stderr);
} else { } else {
if (builder.addUserInputFlag(option_contents)) if (builder.addUserInputFlag(option_contents))
return usage(&builder, maybe_zig_exe, false, &io.stderr); return usage(&builder, false, &io.stderr);
} }
} else if (mem.startsWith(u8, arg, "-")) { } else if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--verbose")) { if (mem.eql(u8, arg, "--verbose")) {
builder.verbose = true; builder.verbose = true;
} else if (mem.eql(u8, arg, "--help")) { } else if (mem.eql(u8, arg, "--help")) {
return usage(&builder, maybe_zig_exe, false, &io.stdout); return usage(&builder, false, &io.stdout);
} else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) { } else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) {
arg_i += 1; arg_i += 1;
prefix = os.args.at(arg_i); prefix = os.args.at(arg_i);
} else { } else {
%%io.stderr.printf("Unrecognized argument: {}\n\n", arg); %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
return usage(&builder, maybe_zig_exe, false, &io.stderr); return usage(&builder, false, &io.stderr);
} }
} else if (maybe_zig_exe == null) {
maybe_zig_exe = arg;
} else { } else {
%%targets.append(arg); %%targets.append(arg);
} }
} }
builder.zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
builder.setInstallPrefix(prefix); builder.setInstallPrefix(prefix);
root.build(&builder); root.build(&builder);
if (builder.validateUserInputDidItFail()) if (builder.validateUserInputDidItFail())
return usage(&builder, maybe_zig_exe, true, &io.stderr); return usage(&builder, true, &io.stderr);
%return builder.make(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 { fn usage(builder: &Builder, 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 // run the build script to collect the options
if (!already_ran_build) { if (!already_ran_build) {
builder.setInstallPrefix(null); builder.setInstallPrefix(null);
@ -90,7 +101,7 @@ fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool,
\\ \\
\\Steps: \\Steps:
\\ \\
, zig_exe); , builder.zig_exe);
const allocator = builder.allocator; const allocator = builder.allocator;
for (builder.top_level_steps.toSliceConst()) |top_level_step| { for (builder.top_level_steps.toSliceConst()) |top_level_step| {

View File

@ -0,0 +1,26 @@
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.CompareOutputContext) {
if (@compileVar("os") == Os.linux and @compileVar("arch") == Arch.x86_64) {
cases.addAsm("hello world linux x86_64",
\\.text
\\.globl _start
\\
\\_start:
\\ mov rax, 1
\\ mov rdi, 1
\\ lea rsi, msg
\\ mov rdx, 14
\\ syscall
\\
\\ mov rax, 60
\\ mov rdi, 0
\\ syscall
\\
\\.data
\\
\\msg:
\\ .ascii "Hello, world!\n"
, "Hello, world!\n");
}
}

8
test/build_examples.zig Normal file
View File

@ -0,0 +1,8 @@
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.BuildExamplesContext) {
cases.add("example/hello_world/hello.zig");
cases.addC("example/hello_world/hello_libc.zig");
cases.add("example/cat/main.zig");
cases.add("example/guess_number/main.zig");
}

404
test/compare_output.zig Normal file
View File

@ -0,0 +1,404 @@
const os = @import("std").os;
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.CompareOutputContext) {
cases.addC("hello world with libc",
\\const c = @cImport(@cInclude("stdio.h"));
\\export fn main(argc: c_int, argv: &&u8) -> c_int {
\\ _ = c.puts(c"Hello, world!");
\\ return 0;
\\}
, "Hello, world!" ++ os.line_sep);
cases.addCase({
var tc = cases.create("multiple files with private function",
\\use @import("std").io;
\\use @import("foo.zig");
\\
\\pub fn main() -> %void {
\\ privateFunction();
\\ %%stdout.printf("OK 2\n");
\\}
\\
\\fn privateFunction() {
\\ printText();
\\}
, "OK 1\nOK 2\n");
tc.addSourceFile("foo.zig",
\\use @import("std").io;
\\
\\// purposefully conflicting function with main.zig
\\// but it's private so it should be OK
\\fn privateFunction() {
\\ %%stdout.printf("OK 1\n");
\\}
\\
\\pub fn printText() {
\\ privateFunction();
\\}
);
tc
});
cases.addCase({
var tc = cases.create("import segregation",
\\use @import("foo.zig");
\\use @import("bar.zig");
\\
\\pub fn main() -> %void {
\\ foo_function();
\\ bar_function();
\\}
, "OK\nOK\n");
tc.addSourceFile("foo.zig",
\\use @import("std").io;
\\pub fn foo_function() {
\\ %%stdout.printf("OK\n");
\\}
);
tc.addSourceFile("bar.zig",
\\use @import("other.zig");
\\use @import("std").io;
\\
\\pub fn bar_function() {
\\ if (foo_function()) {
\\ %%stdout.printf("OK\n");
\\ }
\\}
);
tc.addSourceFile("other.zig",
\\pub fn foo_function() -> bool {
\\ // this one conflicts with the one from foo
\\ return true;
\\}
);
tc
});
cases.addCase({
var tc = cases.create("two files use import each other",
\\use @import("a.zig");
\\
\\pub fn main() -> %void {
\\ ok();
\\}
, "OK\n");
tc.addSourceFile("a.zig",
\\use @import("b.zig");
\\const io = @import("std").io;
\\
\\pub const a_text = "OK\n";
\\
\\pub fn ok() {
\\ %%io.stdout.printf(b_text);
\\}
);
tc.addSourceFile("b.zig",
\\use @import("a.zig");
\\
\\pub const b_text = a_text;
);
tc
});
cases.add("hello world without libc",
\\const io = @import("std").io;
\\
\\pub fn main() -> %void {
\\ %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
\\}
, "Hello, world!\n0012 012 a\n");
cases.addC("number literals",
\\const c = @cImport(@cInclude("stdio.h"));
\\
\\export fn main(argc: c_int, argv: &&u8) -> c_int {
\\ _ = c.printf(c"0: %llu\n",
\\ u64(0));
\\ _ = c.printf(c"320402575052271: %llu\n",
\\ u64(320402575052271));
\\ _ = c.printf(c"0x01236789abcdef: %llu\n",
\\ u64(0x01236789abcdef));
\\ _ = c.printf(c"0xffffffffffffffff: %llu\n",
\\ u64(0xffffffffffffffff));
\\ _ = c.printf(c"0x000000ffffffffffffffff: %llu\n",
\\ u64(0x000000ffffffffffffffff));
\\ _ = c.printf(c"0o1777777777777777777777: %llu\n",
\\ u64(0o1777777777777777777777));
\\ _ = c.printf(c"0o0000001777777777777777777777: %llu\n",
\\ u64(0o0000001777777777777777777777));
\\ _ = c.printf(c"0b1111111111111111111111111111111111111111111111111111111111111111: %llu\n",
\\ u64(0b1111111111111111111111111111111111111111111111111111111111111111));
\\ _ = c.printf(c"0b0000001111111111111111111111111111111111111111111111111111111111111111: %llu\n",
\\ u64(0b0000001111111111111111111111111111111111111111111111111111111111111111));
\\
\\ _ = c.printf(c"\n");
\\
\\ _ = c.printf(c"0.0: %a\n",
\\ f64(0.0));
\\ _ = c.printf(c"0e0: %a\n",
\\ f64(0e0));
\\ _ = c.printf(c"0.0e0: %a\n",
\\ f64(0.0e0));
\\ _ = c.printf(c"000000000000000000000000000000000000000000000000000000000.0e0: %a\n",
\\ f64(000000000000000000000000000000000000000000000000000000000.0e0));
\\ _ = c.printf(c"0.000000000000000000000000000000000000000000000000000000000e0: %a\n",
\\ f64(0.000000000000000000000000000000000000000000000000000000000e0));
\\ _ = c.printf(c"0.0e000000000000000000000000000000000000000000000000000000000: %a\n",
\\ f64(0.0e000000000000000000000000000000000000000000000000000000000));
\\ _ = c.printf(c"1.0: %a\n",
\\ f64(1.0));
\\ _ = c.printf(c"10.0: %a\n",
\\ f64(10.0));
\\ _ = c.printf(c"10.5: %a\n",
\\ f64(10.5));
\\ _ = c.printf(c"10.5e5: %a\n",
\\ f64(10.5e5));
\\ _ = c.printf(c"10.5e+5: %a\n",
\\ f64(10.5e+5));
\\ _ = c.printf(c"50.0e-2: %a\n",
\\ f64(50.0e-2));
\\ _ = c.printf(c"50e-2: %a\n",
\\ f64(50e-2));
\\
\\ _ = c.printf(c"\n");
\\
\\ _ = c.printf(c"0x1.0: %a\n",
\\ f64(0x1.0));
\\ _ = c.printf(c"0x10.0: %a\n",
\\ f64(0x10.0));
\\ _ = c.printf(c"0x100.0: %a\n",
\\ f64(0x100.0));
\\ _ = c.printf(c"0x103.0: %a\n",
\\ f64(0x103.0));
\\ _ = c.printf(c"0x103.7: %a\n",
\\ f64(0x103.7));
\\ _ = c.printf(c"0x103.70: %a\n",
\\ f64(0x103.70));
\\ _ = c.printf(c"0x103.70p4: %a\n",
\\ f64(0x103.70p4));
\\ _ = c.printf(c"0x103.70p5: %a\n",
\\ f64(0x103.70p5));
\\ _ = c.printf(c"0x103.70p+5: %a\n",
\\ f64(0x103.70p+5));
\\ _ = c.printf(c"0x103.70p-5: %a\n",
\\ f64(0x103.70p-5));
\\
\\ _ = c.printf(c"\n");
\\
\\ _ = c.printf(c"0b10100.00010e0: %a\n",
\\ f64(0b10100.00010e0));
\\ _ = c.printf(c"0o10700.00010e0: %a\n",
\\ f64(0o10700.00010e0));
\\
\\ return 0;
\\}
,
\\0: 0
\\320402575052271: 320402575052271
\\0x01236789abcdef: 320402575052271
\\0xffffffffffffffff: 18446744073709551615
\\0x000000ffffffffffffffff: 18446744073709551615
\\0o1777777777777777777777: 18446744073709551615
\\0o0000001777777777777777777777: 18446744073709551615
\\0b1111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
\\0b0000001111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
\\
\\0.0: 0x0p+0
\\0e0: 0x0p+0
\\0.0e0: 0x0p+0
\\000000000000000000000000000000000000000000000000000000000.0e0: 0x0p+0
\\0.000000000000000000000000000000000000000000000000000000000e0: 0x0p+0
\\0.0e000000000000000000000000000000000000000000000000000000000: 0x0p+0
\\1.0: 0x1p+0
\\10.0: 0x1.4p+3
\\10.5: 0x1.5p+3
\\10.5e5: 0x1.0059p+20
\\10.5e+5: 0x1.0059p+20
\\50.0e-2: 0x1p-1
\\50e-2: 0x1p-1
\\
\\0x1.0: 0x1p+0
\\0x10.0: 0x1p+4
\\0x100.0: 0x1p+8
\\0x103.0: 0x1.03p+8
\\0x103.7: 0x1.037p+8
\\0x103.70: 0x1.037p+8
\\0x103.70p4: 0x1.037p+12
\\0x103.70p5: 0x1.037p+13
\\0x103.70p+5: 0x1.037p+13
\\0x103.70p-5: 0x1.037p+3
\\
\\0b10100.00010e0: 0x1.41p+4
\\0o10700.00010e0: 0x1.1c0001p+12
\\
);
cases.add("order-independent declarations",
\\const io = @import("std").io;
\\const z = io.stdin_fileno;
\\const x : @typeOf(y) = 1234;
\\const y : u16 = 5678;
\\pub fn main() -> %void {
\\ var x_local : i32 = print_ok(x);
\\}
\\fn print_ok(val: @typeOf(x)) -> @typeOf(foo) {
\\ %%io.stdout.printf("OK\n");
\\ return 0;
\\}
\\const foo : i32 = 0;
, "OK\n");
cases.addC("expose function pointer to C land",
\\const c = @cImport(@cInclude("stdlib.h"));
\\
\\export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int {
\\ const a_int = @ptrcast(&i32, a ?? unreachable);
\\ const b_int = @ptrcast(&i32, b ?? unreachable);
\\ if (*a_int < *b_int) {
\\ -1
\\ } else if (*a_int > *b_int) {
\\ 1
\\ } else {
\\ c_int(0)
\\ }
\\}
\\
\\export fn main() -> c_int {
\\ var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 };
\\
\\ c.qsort(@ptrcast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn);
\\
\\ for (array) |item, i| {
\\ if (item != i) {
\\ c.abort();
\\ }
\\ }
\\
\\ return 0;
\\}
, "");
cases.addC("casting between float and integer types",
\\const c = @cImport(@cInclude("stdio.h"));
\\export fn main(argc: c_int, argv: &&u8) -> c_int {
\\ const small: f32 = 3.25;
\\ const x: f64 = small;
\\ const y = i32(x);
\\ const z = f64(y);
\\ _ = c.printf(c"%.2f\n%d\n%.2f\n%.2f\n", x, y, z, f64(-0.4));
\\ return 0;
\\}
, "3.25\n3\n3.00\n-0.40\n");
cases.add("same named methods in incomplete struct",
\\const io = @import("std").io;
\\
\\const Foo = struct {
\\ field1: Bar,
\\
\\ fn method(a: &const Foo) -> bool { true }
\\};
\\
\\const Bar = struct {
\\ field2: i32,
\\
\\ fn method(b: &const Bar) -> bool { true }
\\};
\\
\\pub fn main() -> %void {
\\ const bar = Bar {.field2 = 13,};
\\ const foo = Foo {.field1 = bar,};
\\ if (!foo.method()) {
\\ %%io.stdout.printf("BAD\n");
\\ }
\\ if (!bar.method()) {
\\ %%io.stdout.printf("BAD\n");
\\ }
\\ %%io.stdout.printf("OK\n");
\\}
, "OK\n");
cases.add("defer with only fallthrough",
\\const io = @import("std").io;
\\pub fn main() -> %void {
\\ %%io.stdout.printf("before\n");
\\ defer %%io.stdout.printf("defer1\n");
\\ defer %%io.stdout.printf("defer2\n");
\\ defer %%io.stdout.printf("defer3\n");
\\ %%io.stdout.printf("after\n");
\\}
, "before\nafter\ndefer3\ndefer2\ndefer1\n");
cases.add("defer with return",
\\const io = @import("std").io;
\\const os = @import("std").os;
\\pub fn main() -> %void {
\\ %%io.stdout.printf("before\n");
\\ defer %%io.stdout.printf("defer1\n");
\\ defer %%io.stdout.printf("defer2\n");
\\ if (os.args.count() == 1) return;
\\ defer %%io.stdout.printf("defer3\n");
\\ %%io.stdout.printf("after\n");
\\}
, "before\ndefer2\ndefer1\n");
cases.add("%defer and it fails",
\\const io = @import("std").io;
\\pub fn main() -> %void {
\\ do_test() %% return;
\\}
\\fn do_test() -> %void {
\\ %%io.stdout.printf("before\n");
\\ defer %%io.stdout.printf("defer1\n");
\\ %defer %%io.stdout.printf("deferErr\n");
\\ %return its_gonna_fail();
\\ defer %%io.stdout.printf("defer3\n");
\\ %%io.stdout.printf("after\n");
\\}
\\error IToldYouItWouldFail;
\\fn its_gonna_fail() -> %void {
\\ return error.IToldYouItWouldFail;
\\}
, "before\ndeferErr\ndefer1\n");
cases.add("%defer and it passes",
\\const io = @import("std").io;
\\pub fn main() -> %void {
\\ do_test() %% return;
\\}
\\fn do_test() -> %void {
\\ %%io.stdout.printf("before\n");
\\ defer %%io.stdout.printf("defer1\n");
\\ %defer %%io.stdout.printf("deferErr\n");
\\ %return its_gonna_pass();
\\ defer %%io.stdout.printf("defer3\n");
\\ %%io.stdout.printf("after\n");
\\}
\\fn its_gonna_pass() -> %void { }
, "before\nafter\ndefer3\ndefer1\n");
cases.addCase({
var tc = cases.create("@embedFile",
\\const foo_txt = @embedFile("foo.txt");
\\const io = @import("std").io;
\\
\\pub fn main() -> %void {
\\ %%io.stdout.printf(foo_txt);
\\}
, "1234\nabcd\n");
tc.addSourceFile("foo.txt", "1234\nabcd\n");
tc
});
}

1577
test/compile_errors.zig Normal file

File diff suppressed because it is too large Load Diff

234
test/debug_safety.zig Normal file
View File

@ -0,0 +1,234 @@
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.CompareOutputContext) {
cases.addDebugSafety("calling panic",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\pub fn main() -> %void {
\\ @panic("oh no");
\\}
);
cases.addDebugSafety("out of bounds slice access",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\pub fn main() -> %void {
\\ const a = []i32{1, 2, 3, 4};
\\ baz(bar(a));
\\}
\\fn bar(a: []const i32) -> i32 {
\\ a[4]
\\}
\\fn baz(a: i32) { }
);
cases.addDebugSafety("integer addition overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = add(65530, 10);
\\ if (x == 0) return error.Whatever;
\\}
\\fn add(a: u16, b: u16) -> u16 {
\\ a + b
\\}
);
cases.addDebugSafety("integer subtraction overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = sub(10, 20);
\\ if (x == 0) return error.Whatever;
\\}
\\fn sub(a: u16, b: u16) -> u16 {
\\ a - b
\\}
);
cases.addDebugSafety("integer multiplication overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = mul(300, 6000);
\\ if (x == 0) return error.Whatever;
\\}
\\fn mul(a: u16, b: u16) -> u16 {
\\ a * b
\\}
);
cases.addDebugSafety("integer negation overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = neg(-32768);
\\ if (x == 32767) return error.Whatever;
\\}
\\fn neg(a: i16) -> i16 {
\\ -a
\\}
);
cases.addDebugSafety("signed integer division overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = div(-32768, -1);
\\ if (x == 32767) return error.Whatever;
\\}
\\fn div(a: i16, b: i16) -> i16 {
\\ a / b
\\}
);
cases.addDebugSafety("signed shift left overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = shl(-16385, 1);
\\ if (x == 0) return error.Whatever;
\\}
\\fn shl(a: i16, b: i16) -> i16 {
\\ a << b
\\}
);
cases.addDebugSafety("unsigned shift left overflow",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = shl(0b0010111111111111, 3);
\\ if (x == 0) return error.Whatever;
\\}
\\fn shl(a: u16, b: u16) -> u16 {
\\ a << b
\\}
);
cases.addDebugSafety("integer division by zero",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = div0(999, 0);
\\}
\\fn div0(a: i32, b: i32) -> i32 {
\\ a / b
\\}
);
cases.addDebugSafety("exact division failure",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = divExact(10, 3);
\\ if (x == 0) return error.Whatever;
\\}
\\fn divExact(a: i32, b: i32) -> i32 {
\\ @divExact(a, b)
\\}
);
cases.addDebugSafety("cast []u8 to bigger slice of wrong size",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = widenSlice([]u8{1, 2, 3, 4, 5});
\\ if (x.len == 0) return error.Whatever;
\\}
\\fn widenSlice(slice: []const u8) -> []const i32 {
\\ ([]const i32)(slice)
\\}
);
cases.addDebugSafety("value does not fit in shortening cast",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = shorten_cast(200);
\\ if (x == 0) return error.Whatever;
\\}
\\fn shorten_cast(x: i32) -> i8 {
\\ i8(x)
\\}
);
cases.addDebugSafety("signed integer not fitting in cast to unsigned integer",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ const x = unsigned_cast(-10);
\\ if (x == 0) return error.Whatever;
\\}
\\fn unsigned_cast(x: i32) -> u32 {
\\ u32(x)
\\}
);
cases.addDebugSafety("unwrap error",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\error Whatever;
\\pub fn main() -> %void {
\\ %%bar();
\\}
\\fn bar() -> %void {
\\ return error.Whatever;
\\}
);
cases.addDebugSafety("cast integer to error and no code matches",
\\pub fn panic(message: []const u8) -> noreturn {
\\ @breakpoint();
\\ while (true) {}
\\}
\\pub fn main() -> %void {
\\ _ = bar(9999);
\\}
\\fn bar(x: u32) -> error {
\\ return error(x);
\\}
);
}

243
test/parseh.zig Normal file
View File

@ -0,0 +1,243 @@
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.ParseHContext) {
cases.addAllowWarnings("simple data types",
\\#include <stdint.h>
\\int foo(char a, unsigned char b, signed char c);
\\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype
\\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d);
\\void baz(int8_t a, int16_t b, int32_t c, int64_t d);
,
\\pub extern fn foo(a: u8, b: u8, c: i8) -> c_int;
,
\\pub extern fn bar(a: u8, b: u16, c: u32, d: u64);
,
\\pub extern fn baz(a: i8, b: i16, c: i32, d: i64);
);
cases.add("noreturn attribute",
\\void foo(void) __attribute__((noreturn));
,
\\pub extern fn foo() -> noreturn;
);
cases.add("enums",
\\enum Foo {
\\ FooA,
\\ FooB,
\\ Foo1,
\\};
,
\\pub const enum_Foo = extern enum {
\\ A,
\\ B,
\\ @"1",
\\};
,
\\pub const FooA = 0;
,
\\pub const FooB = 1;
,
\\pub const Foo1 = 2;
,
\\pub const Foo = enum_Foo
);
cases.add("restrict -> noalias",
\\void foo(void *restrict bar, void *restrict);
,
\\pub extern fn foo(noalias bar: ?&c_void, noalias arg1: ?&c_void);
);
cases.add("simple struct",
\\struct Foo {
\\ int x;
\\ char *y;
\\};
,
\\const struct_Foo = extern struct {
\\ x: c_int,
\\ y: ?&u8,
\\};
,
\\pub const Foo = struct_Foo;
);
cases.add("qualified struct and enum",
\\struct Foo {
\\ int x;
\\ int y;
\\};
\\enum Bar {
\\ BarA,
\\ BarB,
\\};
\\void func(struct Foo *a, enum Bar **b);
,
\\pub const struct_Foo = extern struct {
\\ x: c_int,
\\ y: c_int,
\\};
,
\\pub const enum_Bar = extern enum {
\\ A,
\\ B,
\\};
,
\\pub const BarA = 0;
,
\\pub const BarB = 1;
,
\\pub extern fn func(a: ?&struct_Foo, b: ?&?&enum_Bar);
,
\\pub const Foo = struct_Foo;
,
\\pub const Bar = enum_Bar;
);
cases.add("constant size array",
\\void func(int array[20]);
,
\\pub extern fn func(array: ?&c_int);
);
cases.add("self referential struct with function pointer",
\\struct Foo {
\\ void (*derp)(struct Foo *foo);
\\};
,
\\pub const struct_Foo = extern struct {
\\ derp: ?extern fn(?&struct_Foo),
\\};
,
\\pub const Foo = struct_Foo;
);
cases.add("struct prototype used in func",
\\struct Foo;
\\struct Foo *some_func(struct Foo *foo, int x);
,
\\pub const struct_Foo = @OpaqueType();
,
\\pub extern fn some_func(foo: ?&struct_Foo, x: c_int) -> ?&struct_Foo;
,
\\pub const Foo = struct_Foo;
);
cases.add("#define a char literal",
\\#define A_CHAR 'a'
,
\\pub const A_CHAR = 97;
);
cases.add("#define an unsigned integer literal",
\\#define CHANNEL_COUNT 24
,
\\pub const CHANNEL_COUNT = 24;
);
cases.add("#define referencing another #define",
\\#define THING2 THING1
\\#define THING1 1234
,
\\pub const THING1 = 1234;
,
\\pub const THING2 = THING1;
);
cases.add("variables",
\\extern int extern_var;
\\static const int int_var = 13;
,
\\pub extern var extern_var: c_int;
,
\\pub const int_var: c_int = 13;
);
cases.add("circular struct definitions",
\\struct Bar;
\\
\\struct Foo {
\\ struct Bar *next;
\\};
\\
\\struct Bar {
\\ struct Foo *next;
\\};
,
\\pub const struct_Bar = extern struct {
\\ next: ?&struct_Foo,
\\};
,
\\pub const struct_Foo = extern struct {
\\ next: ?&struct_Bar,
\\};
);
cases.add("typedef void",
\\typedef void Foo;
\\Foo fun(Foo *a);
,
\\pub const Foo = c_void;
,
\\pub extern fn fun(a: ?&c_void);
);
cases.add("generate inline func for #define global extern fn",
\\extern void (*fn_ptr)(void);
\\#define foo fn_ptr
\\
\\extern char (*fn_ptr2)(int, float);
\\#define bar fn_ptr2
,
\\pub extern var fn_ptr: ?extern fn();
,
\\pub fn foo();
,
\\pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8;
,
\\pub fn bar(arg0: c_int, arg1: f32) -> u8;
);
cases.add("#define string",
\\#define foo "a string"
,
\\pub const foo: &const u8 = &(c str lit);
);
cases.add("__cdecl doesn't mess up function pointers",
\\void foo(void (__cdecl *fn_ptr)(void));
,
\\pub extern fn foo(fn_ptr: ?extern fn());
);
cases.add("comment after integer literal",
\\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */
,
\\pub const SDL_INIT_VIDEO = 32;
);
cases.add("zig keywords in C code",
\\struct comptime {
\\ int defer;
\\};
,
\\pub const struct_comptime = extern struct {
\\ @"defer": c_int,
\\};
,
\\pub const @"comptime" = struct_comptime;
);
cases.add("macro defines string literal with octal",
\\#define FOO "aoeu\023 derp"
\\#define FOO2 "aoeu\0234 derp"
\\#define FOO_CHAR '\077'
,
\\pub const FOO: &const u8 = &(c str lit);
,
\\pub const FOO2: &const u8 = &(c str lit);
,
\\pub const FOO_CHAR = 63;
);
}

File diff suppressed because it is too large Load Diff

868
test/tests.zig Normal file
View File

@ -0,0 +1,868 @@
const std = @import("std");
const debug = std.debug;
const build = std.build;
const os = std.os;
const StdIo = os.ChildProcess.StdIo;
const Term = os.ChildProcess.Term;
const Buffer0 = std.cstr.Buffer0;
const io = std.io;
const mem = std.mem;
const fmt = std.fmt;
const List = std.list.List;
const compare_output = @import("compare_output.zig");
const build_examples = @import("build_examples.zig");
const compile_errors = @import("compile_errors.zig");
const assemble_and_link = @import("assemble_and_link.zig");
const debug_safety = @import("debug_safety.zig");
const parseh = @import("parseh.zig");
error TestFailed;
pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(CompareOutputContext);
*cases = CompareOutputContext {
.b = b,
.step = b.step("test-compare-output", "Run the compare output tests"),
.test_index = 0,
.test_filter = test_filter,
};
compare_output.addCases(cases);
return cases.step;
}
pub fn addDebugSafetyTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(CompareOutputContext);
*cases = CompareOutputContext {
.b = b,
.step = b.step("test-debug-safety", "Run the debug safety tests"),
.test_index = 0,
.test_filter = test_filter,
};
debug_safety.addCases(cases);
return cases.step;
}
pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(CompileErrorContext);
*cases = CompileErrorContext {
.b = b,
.step = b.step("test-compile-errors", "Run the compile error tests"),
.test_index = 0,
.test_filter = test_filter,
};
compile_errors.addCases(cases);
return cases.step;
}
pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(BuildExamplesContext);
*cases = BuildExamplesContext {
.b = b,
.step = b.step("test-build-examples", "Build the examples"),
.test_index = 0,
.test_filter = test_filter,
};
build_examples.addCases(cases);
return cases.step;
}
pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(CompareOutputContext);
*cases = CompareOutputContext {
.b = b,
.step = b.step("test-asm-link", "Run the assemble and link tests"),
.test_index = 0,
.test_filter = test_filter,
};
assemble_and_link.addCases(cases);
return cases.step;
}
pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
const cases = %%b.allocator.create(ParseHContext);
*cases = ParseHContext {
.b = b,
.step = b.step("test-parseh", "Run the C header file parsing tests"),
.test_index = 0,
.test_filter = test_filter,
};
parseh.addCases(cases);
return cases.step;
}
pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8,
name:[] const u8, desc: []const u8) -> &build.Step
{
const step = b.step(b.fmt("test-{}", name), desc);
for ([]bool{false, true}) |release| {
for ([]bool{false, true}) |link_libc| {
const these_tests = b.addTest(root_src);
these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name,
if (release) "release" else "debug",
if (link_libc) "c" else "bare"));
these_tests.setFilter(test_filter);
these_tests.setRelease(release);
if (link_libc) {
these_tests.linkLibrary("c");
}
step.dependOn(&these_tests.step);
}
}
return step;
}
pub const CompareOutputContext = struct {
b: &build.Builder,
step: &build.Step,
test_index: usize,
test_filter: ?[]const u8,
const Special = enum {
None,
Asm,
DebugSafety,
};
const TestCase = struct {
name: []const u8,
sources: List(SourceFile),
expected_output: []const u8,
link_libc: bool,
special: Special,
const SourceFile = struct {
filename: []const u8,
source: []const u8,
};
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
%%self.sources.append(SourceFile {
.filename = filename,
.source = source,
});
}
};
const RunCompareOutputStep = struct {
step: build.Step,
context: &CompareOutputContext,
exe_path: []const u8,
name: []const u8,
expected_output: []const u8,
test_index: usize,
pub fn create(context: &CompareOutputContext, exe_path: []const u8,
name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep
{
const allocator = context.b.allocator;
const ptr = %%allocator.create(RunCompareOutputStep);
*ptr = RunCompareOutputStep {
.context = context,
.exe_path = exe_path,
.name = name,
.expected_output = expected_output,
.test_index = context.test_index,
.step = build.Step.init("RunCompareOutput", allocator, make),
};
context.test_index += 1;
return ptr;
}
fn make(step: &build.Step) -> %void {
const self = @fieldParentPtr(RunCompareOutputStep, "step", step);
const b = self.context.b;
const full_exe_path = b.pathFromRoot(self.exe_path);
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
{
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
};
const term = child.wait() %% |err| {
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
};
switch (term) {
Term.Clean => |code| {
if (code != 0) {
%%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code);
return error.TestFailed;
}
},
else => {
%%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path);
return error.TestFailed;
},
};
var stdout = %%Buffer0.initEmpty(b.allocator);
var stderr = %%Buffer0.initEmpty(b.allocator);
%%(??child.stdout).readAll(&stdout);
%%(??child.stderr).readAll(&stderr);
if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
%%io.stderr.printf(
\\
\\========= Expected this output: =========
\\{}
\\================================================
\\{}
\\
, self.expected_output, stdout.toSliceConst());
return error.TestFailed;
}
%%io.stderr.printf("OK\n");
}
};
const DebugSafetyRunStep = struct {
step: build.Step,
context: &CompareOutputContext,
exe_path: []const u8,
name: []const u8,
test_index: usize,
pub fn create(context: &CompareOutputContext, exe_path: []const u8,
name: []const u8) -> &DebugSafetyRunStep
{
const allocator = context.b.allocator;
const ptr = %%allocator.create(DebugSafetyRunStep);
*ptr = DebugSafetyRunStep {
.context = context,
.exe_path = exe_path,
.name = name,
.test_index = context.test_index,
.step = build.Step.init("DebugSafetyRun", allocator, make),
};
context.test_index += 1;
return ptr;
}
fn make(step: &build.Step) -> %void {
const self = @fieldParentPtr(DebugSafetyRunStep, "step", step);
const b = self.context.b;
const full_exe_path = b.pathFromRoot(self.exe_path);
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
{
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
};
const term = child.wait() %% |err| {
debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
};
const debug_trap_signal: i32 = 5;
switch (term) {
Term.Clean => |code| {
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
"but exited with return code {}\n", debug_trap_signal, code);
return error.TestFailed;
},
Term.Signal => |sig| {
if (sig != debug_trap_signal) {
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
"but instead signaled {}\n", debug_trap_signal, sig);
return error.TestFailed;
}
},
else => {
%%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++
" but exited in an unexpected way\n", debug_trap_signal);
return error.TestFailed;
},
}
%%io.stderr.printf("OK\n");
}
};
pub fn createExtra(self: &CompareOutputContext, name: []const u8, source: []const u8,
expected_output: []const u8, special: Special) -> TestCase
{
var tc = TestCase {
.name = name,
.sources = List(TestCase.SourceFile).init(self.b.allocator),
.expected_output = expected_output,
.link_libc = false,
.special = special,
};
const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
tc.addSourceFile(root_src_name, source);
return tc;
}
pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8,
expected_output: []const u8) -> TestCase
{
return createExtra(self, name, source, expected_output, Special.None);
}
pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
var tc = self.create(name, source, expected_output);
tc.link_libc = true;
self.addCase(tc);
}
pub fn add(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
const tc = self.create(name, source, expected_output);
self.addCase(tc);
}
pub fn addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
const tc = self.createExtra(name, source, expected_output, Special.Asm);
self.addCase(tc);
}
pub fn addDebugSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) {
const tc = self.createExtra(name, source, undefined, Special.DebugSafety);
self.addCase(tc);
}
pub fn addCase(self: &CompareOutputContext, case: &const TestCase) {
const b = self.b;
const root_src = %%os.path.join(b.allocator, "test_artifacts", case.sources.items[0].filename);
const exe_path = %%os.path.join(b.allocator, "test_artifacts", "test");
switch (case.special) {
Special.Asm => {
const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name);
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
return;
}
const obj = b.addAssemble("test", root_src);
obj.setOutputPath(obj_path);
for (case.sources.toSliceConst()) |src_file| {
const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
obj.step.dependOn(&write_src.step);
}
const exe = b.addLinkExecutable("test");
exe.step.dependOn(&obj.step);
exe.addObjectFile(obj_path);
exe.setOutputPath(exe_path);
const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
case.expected_output);
run_and_cmp_output.step.dependOn(&exe.step);
self.step.dependOn(&run_and_cmp_output.step);
},
Special.None => {
for ([]bool{false, true}) |release| {
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} {} ({})",
"compare-output", case.name, if (release) "release" else "debug");
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
continue;
}
const exe = b.addExecutable("test", root_src);
exe.setOutputPath(exe_path);
exe.setRelease(release);
if (case.link_libc) {
exe.linkLibrary("c");
}
for (case.sources.toSliceConst()) |src_file| {
const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
exe.step.dependOn(&write_src.step);
}
const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
case.expected_output);
run_and_cmp_output.step.dependOn(&exe.step);
self.step.dependOn(&run_and_cmp_output.step);
}
},
Special.DebugSafety => {
const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "debug-safety {}", case.name);
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
return;
}
const exe = b.addExecutable("test", root_src);
exe.setOutputPath(exe_path);
if (case.link_libc) {
exe.linkLibrary("c");
}
for (case.sources.toSliceConst()) |src_file| {
const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
exe.step.dependOn(&write_src.step);
}
const run_and_cmp_output = DebugSafetyRunStep.create(self, exe_path, annotated_case_name);
run_and_cmp_output.step.dependOn(&exe.step);
self.step.dependOn(&run_and_cmp_output.step);
},
}
}
};
pub const CompileErrorContext = struct {
b: &build.Builder,
step: &build.Step,
test_index: usize,
test_filter: ?[]const u8,
const TestCase = struct {
name: []const u8,
sources: List(SourceFile),
expected_errors: List([]const u8),
link_libc: bool,
is_exe: bool,
const SourceFile = struct {
filename: []const u8,
source: []const u8,
};
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
%%self.sources.append(SourceFile {
.filename = filename,
.source = source,
});
}
pub fn addExpectedError(self: &TestCase, text: []const u8) {
%%self.expected_errors.append(text);
}
};
const CompileCmpOutputStep = struct {
step: build.Step,
context: &CompileErrorContext,
name: []const u8,
test_index: usize,
case: &const TestCase,
release: bool,
pub fn create(context: &CompileErrorContext, name: []const u8,
case: &const TestCase, release: bool) -> &CompileCmpOutputStep
{
const allocator = context.b.allocator;
const ptr = %%allocator.create(CompileCmpOutputStep);
*ptr = CompileCmpOutputStep {
.step = build.Step.init("CompileCmpOutput", allocator, make),
.context = context,
.name = name,
.test_index = context.test_index,
.case = case,
.release = release,
};
context.test_index += 1;
return ptr;
}
fn make(step: &build.Step) -> %void {
const self = @fieldParentPtr(CompileCmpOutputStep, "step", step);
const b = self.context.b;
const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename);
const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o");
var zig_args = List([]const u8).init(b.allocator);
%%zig_args.append(if (self.case.is_exe) "build_exe" else "build_obj");
%%zig_args.append(b.pathFromRoot(root_src));
%%zig_args.append("--name");
%%zig_args.append("test");
%%zig_args.append("--output");
%%zig_args.append(b.pathFromRoot(obj_path));
if (self.release) {
%%zig_args.append("--release");
}
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
if (b.verbose) {
printInvocation(b.zig_exe, zig_args.toSliceConst());
}
var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map,
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
{
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
};
const term = child.wait() %% |err| {
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
};
switch (term) {
Term.Clean => |code| {
if (code == 0) {
%%io.stderr.printf("Compilation incorrectly succeeded\n");
return error.TestFailed;
}
},
else => {
%%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe);
return error.TestFailed;
},
};
var stdout_buf = %%Buffer0.initEmpty(b.allocator);
var stderr_buf = %%Buffer0.initEmpty(b.allocator);
%%(??child.stdout).readAll(&stdout_buf);
%%(??child.stderr).readAll(&stderr_buf);
const stdout = stdout_buf.toSliceConst();
const stderr = stderr_buf.toSliceConst();
if (stdout.len != 0) {
%%io.stderr.printf(
\\
\\Expected empty stdout, instead found:
\\================================================
\\{}
\\================================================
\\
, stdout);
return error.TestFailed;
}
for (self.case.expected_errors.toSliceConst()) |expected_error| {
if (mem.indexOf(u8, stderr, expected_error) == null) {
%%io.stderr.printf(
\\
\\========= Expected this compile error: =========
\\{}
\\================================================
\\{}
\\
, expected_error, stderr);
return error.TestFailed;
}
}
%%io.stderr.printf("OK\n");
}
};
fn printInvocation(exe_path: []const u8, args: []const []const u8) {
%%io.stderr.printf("{}", exe_path);
for (args) |arg| {
%%io.stderr.printf(" {}", arg);
}
%%io.stderr.printf("\n");
}
pub fn create(self: &CompileErrorContext, name: []const u8, source: []const u8,
expected_lines: ...) -> &TestCase
{
const tc = %%self.b.allocator.create(TestCase);
*tc = TestCase {
.name = name,
.sources = List(TestCase.SourceFile).init(self.b.allocator),
.expected_errors = List([]const u8).init(self.b.allocator),
.link_libc = false,
.is_exe = false,
};
tc.addSourceFile(".tmp_source.zig", source);
comptime var arg_i = 0;
inline while (arg_i < expected_lines.len; arg_i += 1) {
// TODO mem.dupe is because of issue #336
tc.addExpectedError(%%mem.dupe(self.b.allocator, u8, expected_lines[arg_i]));
}
return tc;
}
pub fn addC(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
var tc = self.create(name, source, expected_lines);
tc.link_libc = true;
self.addCase(tc);
}
pub fn addExe(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
var tc = self.create(name, source, expected_lines);
tc.is_exe = true;
self.addCase(tc);
}
pub fn add(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) {
const tc = self.create(name, source, expected_lines);
self.addCase(tc);
}
pub fn addCase(self: &CompileErrorContext, case: &const TestCase) {
const b = self.b;
for ([]bool{false, true}) |release| {
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "compile-error {} ({})",
case.name, if (release) "release" else "debug");
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
continue;
}
const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, release);
self.step.dependOn(&compile_and_cmp_errors.step);
for (case.sources.toSliceConst()) |src_file| {
const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
compile_and_cmp_errors.step.dependOn(&write_src.step);
}
}
}
};
pub const BuildExamplesContext = struct {
b: &build.Builder,
step: &build.Step,
test_index: usize,
test_filter: ?[]const u8,
pub fn addC(self: &BuildExamplesContext, root_src: []const u8) {
self.addAllArgs(root_src, true);
}
pub fn add(self: &BuildExamplesContext, root_src: []const u8) {
self.addAllArgs(root_src, false);
}
pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) {
const b = self.b;
for ([]bool{false, true}) |release| {
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})",
root_src, if (release) "release" else "debug");
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
continue;
}
const exe = b.addExecutable("test", root_src);
exe.setRelease(release);
if (link_libc) {
exe.linkLibrary("c");
}
const log_step = b.addLog("PASS {}\n", annotated_case_name);
log_step.step.dependOn(&exe.step);
self.step.dependOn(&log_step.step);
}
}
};
pub const ParseHContext = struct {
b: &build.Builder,
step: &build.Step,
test_index: usize,
test_filter: ?[]const u8,
const TestCase = struct {
name: []const u8,
sources: List(SourceFile),
expected_lines: List([]const u8),
allow_warnings: bool,
const SourceFile = struct {
filename: []const u8,
source: []const u8,
};
pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) {
%%self.sources.append(SourceFile {
.filename = filename,
.source = source,
});
}
pub fn addExpectedError(self: &TestCase, text: []const u8) {
%%self.expected_lines.append(text);
}
};
const ParseHCmpOutputStep = struct {
step: build.Step,
context: &ParseHContext,
name: []const u8,
test_index: usize,
case: &const TestCase,
pub fn create(context: &ParseHContext, name: []const u8, case: &const TestCase) -> &ParseHCmpOutputStep {
const allocator = context.b.allocator;
const ptr = %%allocator.create(ParseHCmpOutputStep);
*ptr = ParseHCmpOutputStep {
.step = build.Step.init("ParseHCmpOutput", allocator, make),
.context = context,
.name = name,
.test_index = context.test_index,
.case = case,
};
context.test_index += 1;
return ptr;
}
fn make(step: &build.Step) -> %void {
const self = @fieldParentPtr(ParseHCmpOutputStep, "step", step);
const b = self.context.b;
const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename);
var zig_args = List([]const u8).init(b.allocator);
%%zig_args.append("parseh");
%%zig_args.append(b.pathFromRoot(root_src));
%%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
if (b.verbose) {
printInvocation(b.zig_exe, zig_args.toSliceConst());
}
var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map,
StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
{
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
};
const term = child.wait() %% |err| {
debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err));
};
switch (term) {
Term.Clean => |code| {
if (code != 0) {
%%io.stderr.printf("Compilation failed with exit code {}\n", code);
return error.TestFailed;
}
},
Term.Signal => |code| {
%%io.stderr.printf("Compilation failed with signal {}\n", code);
return error.TestFailed;
},
else => {
%%io.stderr.printf("Compilation terminated unexpectedly\n");
return error.TestFailed;
},
};
var stdout_buf = %%Buffer0.initEmpty(b.allocator);
var stderr_buf = %%Buffer0.initEmpty(b.allocator);
%%(??child.stdout).readAll(&stdout_buf);
%%(??child.stderr).readAll(&stderr_buf);
const stdout = stdout_buf.toSliceConst();
const stderr = stderr_buf.toSliceConst();
if (stderr.len != 0 and !self.case.allow_warnings) {
%%io.stderr.printf(
\\====== parseh emitted warnings: ============
\\{}
\\============================================
\\
, stderr);
return error.TestFailed;
}
for (self.case.expected_lines.toSliceConst()) |expected_line| {
if (mem.indexOf(u8, stdout, expected_line) == null) {
%%io.stderr.printf(
\\
\\========= Expected this output: ================
\\{}
\\================================================
\\{}
\\
, expected_line, stdout);
return error.TestFailed;
}
}
%%io.stderr.printf("OK\n");
}
};
fn printInvocation(exe_path: []const u8, args: []const []const u8) {
%%io.stderr.printf("{}", exe_path);
for (args) |arg| {
%%io.stderr.printf(" {}", arg);
}
%%io.stderr.printf("\n");
}
pub fn create(self: &ParseHContext, allow_warnings: bool, name: []const u8,
source: []const u8, expected_lines: ...) -> &TestCase
{
const tc = %%self.b.allocator.create(TestCase);
*tc = TestCase {
.name = name,
.sources = List(TestCase.SourceFile).init(self.b.allocator),
.expected_lines = List([]const u8).init(self.b.allocator),
.allow_warnings = allow_warnings,
};
tc.addSourceFile("source.h", source);
comptime var arg_i = 0;
inline while (arg_i < expected_lines.len; arg_i += 1) {
// TODO mem.dupe is because of issue #336
tc.addExpectedError(%%mem.dupe(self.b.allocator, u8, expected_lines[arg_i]));
}
return tc;
}
pub fn add(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) {
const tc = self.create(false, name, source, expected_lines);
self.addCase(tc);
}
pub fn addAllowWarnings(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) {
const tc = self.create(true, name, source, expected_lines);
self.addCase(tc);
}
pub fn addCase(self: &ParseHContext, case: &const TestCase) {
const b = self.b;
const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "parseh {}", case.name);
if (const filter ?= self.test_filter) {
if (mem.indexOf(u8, annotated_case_name, filter) == null)
return;
}
const parseh_and_cmp = ParseHCmpOutputStep.create(self, annotated_case_name, case);
self.step.dependOn(&parseh_and_cmp.step);
for (case.sources.toSliceConst()) |src_file| {
const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename);
const write_src = b.addWriteFile(expanded_src_path, src_file.source);
parseh_and_cmp.step.dependOn(&write_src.step);
}
}
};