Merge pull request #915 from zig-lang/self-hosted-cli

Revise self-hosted command line interface
master
Andrew Kelley 2018-04-13 11:16:06 -04:00 committed by GitHub
commit 30c5f3c441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1344 additions and 736 deletions

284
src-self-hosted/arg.zig Normal file
View File

@ -0,0 +1,284 @@
const std = @import("std");
const debug = std.debug;
const mem = std.mem;
const Allocator = mem.Allocator;
const ArrayList = std.ArrayList;
const HashMap = std.HashMap;
fn trimStart(slice: []const u8, ch: u8) []const u8 {
var i: usize = 0;
for (slice) |b| {
if (b != '-') break;
i += 1;
}
return slice[i..];
}
fn argInAllowedSet(maybe_set: ?[]const []const u8, arg: []const u8) bool {
if (maybe_set) |set| {
for (set) |possible| {
if (mem.eql(u8, arg, possible)) {
return true;
}
}
return false;
} else {
return true;
}
}
// Modifies the current argument index during iteration
fn readFlagArguments(allocator: &Allocator, args: []const []const u8, required: usize,
allowed_set: ?[]const []const u8, index: &usize) !FlagArg {
switch (required) {
0 => return FlagArg { .None = undefined }, // TODO: Required to force non-tag but value?
1 => {
if (*index + 1 >= args.len) {
return error.MissingFlagArguments;
}
*index += 1;
const arg = args[*index];
if (!argInAllowedSet(allowed_set, arg)) {
return error.ArgumentNotInAllowedSet;
}
return FlagArg { .Single = arg };
},
else => |needed| {
var extra = ArrayList([]const u8).init(allocator);
errdefer extra.deinit();
var j: usize = 0;
while (j < needed) : (j += 1) {
if (*index + 1 >= args.len) {
return error.MissingFlagArguments;
}
*index += 1;
const arg = args[*index];
if (!argInAllowedSet(allowed_set, arg)) {
return error.ArgumentNotInAllowedSet;
}
try extra.append(arg);
}
return FlagArg { .Many = extra };
},
}
}
const HashMapFlags = HashMap([]const u8, FlagArg, std.hash.Fnv1a_32.hash, mem.eql_slice_u8);
// A store for querying found flags and positional arguments.
pub const Args = struct {
flags: HashMapFlags,
positionals: ArrayList([]const u8),
pub fn parse(allocator: &Allocator, comptime spec: []const Flag, args: []const []const u8) !Args {
var parsed = Args {
.flags = HashMapFlags.init(allocator),
.positionals = ArrayList([]const u8).init(allocator),
};
var i: usize = 0;
next: while (i < args.len) : (i += 1) {
const arg = args[i];
if (arg.len != 0 and arg[0] == '-') {
// TODO: hashmap, although the linear scan is okay for small argument sets as is
for (spec) |flag| {
if (mem.eql(u8, arg, flag.name)) {
const flag_name_trimmed = trimStart(flag.name, '-');
const flag_args = readFlagArguments(allocator, args, flag.required, flag.allowed_set, &i) catch |err| {
switch (err) {
error.ArgumentNotInAllowedSet => {
std.debug.warn("argument '{}' is invalid for flag '{}'\n", args[i], arg);
std.debug.warn("allowed options are ");
for (??flag.allowed_set) |possible| {
std.debug.warn("'{}' ", possible);
}
std.debug.warn("\n");
},
error.MissingFlagArguments => {
std.debug.warn("missing argument for flag: {}\n", arg);
},
else => {},
}
return err;
};
if (flag.mergable) {
var prev =
if (parsed.flags.get(flag_name_trimmed)) |entry|
entry.value.Many
else
ArrayList([]const u8).init(allocator);
// MergeN creation disallows 0 length flag entry (doesn't make sense)
switch (flag_args) {
FlagArg.None => unreachable,
FlagArg.Single => |inner| try prev.append(inner),
FlagArg.Many => |inner| try prev.appendSlice(inner.toSliceConst()),
}
_ = try parsed.flags.put(flag_name_trimmed, FlagArg { .Many = prev });
} else {
_ = try parsed.flags.put(flag_name_trimmed, flag_args);
}
continue :next;
}
}
// TODO: Better errors with context, global error state and return is sufficient.
std.debug.warn("could not match flag: {}\n", arg);
return error.UnknownFlag;
} else {
try parsed.positionals.append(arg);
}
}
return parsed;
}
pub fn deinit(self: &Args) void {
self.flags.deinit();
self.positionals.deinit();
}
// e.g. --help
pub fn present(self: &Args, name: []const u8) bool {
return self.flags.contains(name);
}
// e.g. --name value
pub fn single(self: &Args, name: []const u8) ?[]const u8 {
if (self.flags.get(name)) |entry| {
switch (entry.value) {
FlagArg.Single => |inner| { return inner; },
else => @panic("attempted to retrieve flag with wrong type"),
}
} else {
return null;
}
}
// e.g. --names value1 value2 value3
pub fn many(self: &Args, name: []const u8) ?[]const []const u8 {
if (self.flags.get(name)) |entry| {
switch (entry.value) {
FlagArg.Many => |inner| { return inner.toSliceConst(); },
else => @panic("attempted to retrieve flag with wrong type"),
}
} else {
return null;
}
}
};
// Arguments for a flag. e.g. arg1, arg2 in `--command arg1 arg2`.
const FlagArg = union(enum) {
None,
Single: []const u8,
Many: ArrayList([]const u8),
};
// Specification for how a flag should be parsed.
pub const Flag = struct {
name: []const u8,
required: usize,
mergable: bool,
allowed_set: ?[]const []const u8,
pub fn Bool(comptime name: []const u8) Flag {
return ArgN(name, 0);
}
pub fn Arg1(comptime name: []const u8) Flag {
return ArgN(name, 1);
}
pub fn ArgN(comptime name: []const u8, comptime n: usize) Flag {
return Flag {
.name = name,
.required = n,
.mergable = false,
.allowed_set = null,
};
}
pub fn ArgMergeN(comptime name: []const u8, comptime n: usize) Flag {
if (n == 0) {
@compileError("n must be greater than 0");
}
return Flag {
.name = name,
.required = n,
.mergable = true,
.allowed_set = null,
};
}
pub fn Option(comptime name: []const u8, comptime set: []const []const u8) Flag {
return Flag {
.name = name,
.required = 1,
.mergable = false,
.allowed_set = set,
};
}
};
test "parse arguments" {
const spec1 = comptime []const Flag {
Flag.Bool("--help"),
Flag.Bool("--init"),
Flag.Arg1("--build-file"),
Flag.Option("--color", []const []const u8 { "on", "off", "auto" }),
Flag.ArgN("--pkg-begin", 2),
Flag.ArgMergeN("--object", 1),
Flag.ArgN("--library", 1),
};
const cliargs = []const []const u8 {
"build",
"--help",
"pos1",
"--build-file", "build.zig",
"--object", "obj1",
"--object", "obj2",
"--library", "lib1",
"--library", "lib2",
"--color", "on",
"pos2",
};
var args = try Args.parse(std.debug.global_allocator, spec1, cliargs);
debug.assert(args.present("help"));
debug.assert(!args.present("help2"));
debug.assert(!args.present("init"));
debug.assert(mem.eql(u8, ??args.single("build-file"), "build.zig"));
debug.assert(mem.eql(u8, ??args.single("color"), "on"));
const objects = ??args.many("object");
debug.assert(mem.eql(u8, objects[0], "obj1"));
debug.assert(mem.eql(u8, objects[1], "obj2"));
debug.assert(mem.eql(u8, ??args.single("library"), "lib2"));
const pos = args.positionals.toSliceConst();
debug.assert(mem.eql(u8, pos[0], "build"));
debug.assert(mem.eql(u8, pos[1], "pos1"));
debug.assert(mem.eql(u8, pos[2], "pos2"));
}

View File

@ -0,0 +1,57 @@
// Introspection and determination of system libraries needed by zig.
const std = @import("std");
const mem = std.mem;
const os = std.os;
const warn = std.debug.warn;
/// Caller must free result
pub fn testZigInstallPrefix(allocator: &mem.Allocator, test_path: []const u8) ![]u8 {
const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig");
errdefer allocator.free(test_zig_dir);
const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
defer allocator.free(test_index_file);
var file = try os.File.openRead(allocator, test_index_file);
file.close();
return test_zig_dir;
}
/// Caller must free result
pub fn findZigLibDir(allocator: &mem.Allocator) ![]u8 {
const self_exe_path = try os.selfExeDirPath(allocator);
defer allocator.free(self_exe_path);
var cur_path: []const u8 = self_exe_path;
while (true) {
const test_dir = os.path.dirname(cur_path);
if (mem.eql(u8, test_dir, cur_path)) {
break;
}
return testZigInstallPrefix(allocator, test_dir) catch |err| {
cur_path = test_dir;
continue;
};
}
return error.FileNotFound;
}
pub fn resolveZigLibDir(allocator: &mem.Allocator) ![]u8 {
return findZigLibDir(allocator) catch |err| {
warn(
\\Unable to find zig lib directory: {}.
\\Reinstall Zig or use --zig-install-prefix.
\\
,
@errorName(err)
);
return error.ZigLibDirNotFound;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -109,6 +109,29 @@ pub const Module = struct {
LlvmIr,
};
pub const CliPkg = struct {
name: []const u8,
path: []const u8,
children: ArrayList(&CliPkg),
parent: ?&CliPkg,
pub fn init(allocator: &mem.Allocator, name: []const u8, path: []const u8, parent: ?&CliPkg) !&CliPkg {
var pkg = try allocator.create(CliPkg);
pkg.name = name;
pkg.path = path;
pkg.children = ArrayList(&CliPkg).init(allocator);
pkg.parent = parent;
return pkg;
}
pub fn deinit(self: &CliPkg) void {
for (self.children.toSliceConst()) |child| {
child.deinit();
}
self.children.deinit();
}
};
pub fn create(allocator: &mem.Allocator, name: []const u8, root_src_path: ?[]const u8, target: &const Target,
kind: Kind, build_mode: builtin.Mode, zig_lib_dir: []const u8, cache_dir: []const u8) !&Module
{

View File

@ -54,7 +54,6 @@ static int usage(const char *arg0) {
" --verbose-ir turn on compiler debug output for Zig IR\n"
" --verbose-llvm-ir turn on compiler debug output for LLVM IR\n"
" --verbose-cimport turn on compiler debug output for C imports\n"
" --zig-install-prefix [path] override directory where zig thinks it is installed\n"
" -dirafter [dir] same as -isystem but do it last\n"
" -isystem [dir] add additional search path for other .h files\n"
" -mllvm [arg] additional arguments to forward to LLVM's option processing\n"
@ -200,24 +199,15 @@ static int find_zig_lib_dir(Buf *out_path) {
return ErrorFileNotFound;
}
static Buf *resolve_zig_lib_dir(const char *zig_install_prefix_arg) {
static Buf *resolve_zig_lib_dir(void) {
int err;
Buf *result = buf_alloc();
if (zig_install_prefix_arg == nullptr) {
if ((err = find_zig_lib_dir(result))) {
fprintf(stderr, "Unable to find zig lib directory. Reinstall Zig or use --zig-install-prefix.\n");
fprintf(stderr, "Unable to find zig lib directory\n");
exit(EXIT_FAILURE);
}
return result;
}
Buf *zig_lib_dir_buf = buf_create_from_str(zig_install_prefix_arg);
if (test_zig_install_prefix(zig_lib_dir_buf, result)) {
return result;
}
fprintf(stderr, "No Zig installation found at prefix: %s\n", zig_install_prefix_arg);
exit(EXIT_FAILURE);
}
enum Cmd {
CmdInvalid,
@ -300,7 +290,6 @@ int main(int argc, char **argv) {
const char *libc_include_dir = nullptr;
const char *msvc_lib_dir = nullptr;
const char *kernel32_lib_dir = nullptr;
const char *zig_install_prefix = nullptr;
const char *dynamic_linker = nullptr;
ZigList<const char *> clang_argv = {0};
ZigList<const char *> llvm_argv = {0};
@ -360,17 +349,12 @@ int main(int argc, char **argv) {
} else if (i + 1 < argc && strcmp(argv[i], "--cache-dir") == 0) {
cache_dir = argv[i + 1];
i += 1;
} else if (i + 1 < argc && strcmp(argv[i], "--zig-install-prefix") == 0) {
args.append(argv[i]);
i += 1;
zig_install_prefix = argv[i];
args.append(zig_install_prefix);
} else {
args.append(argv[i]);
}
}
Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix);
Buf *zig_lib_dir_buf = resolve_zig_lib_dir();
Buf *zig_std_dir = buf_alloc();
os_path_join(zig_lib_dir_buf, buf_create_from_str("std"), zig_std_dir);
@ -591,8 +575,6 @@ int main(int argc, char **argv) {
msvc_lib_dir = argv[i];
} else if (strcmp(arg, "--kernel32-lib-dir") == 0) {
kernel32_lib_dir = argv[i];
} else if (strcmp(arg, "--zig-install-prefix") == 0) {
zig_install_prefix = argv[i];
} else if (strcmp(arg, "--dynamic-linker") == 0) {
dynamic_linker = argv[i];
} else if (strcmp(arg, "-isystem") == 0) {
@ -804,7 +786,7 @@ int main(int argc, char **argv) {
full_cache_dir);
}
Buf *zig_lib_dir_buf = resolve_zig_lib_dir(zig_install_prefix);
Buf *zig_lib_dir_buf = resolve_zig_lib_dir();
CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, zig_lib_dir_buf);
codegen_set_out_name(g, buf_out_name);

View File

@ -28,6 +28,7 @@ pub extern "c" fn unlink(path: &const u8) c_int;
pub extern "c" fn getcwd(buf: &u8, size: usize) ?&u8;
pub extern "c" fn waitpid(pid: c_int, stat_loc: &c_int, options: c_int) c_int;
pub extern "c" fn fork() c_int;
pub extern "c" fn access(path: &const u8, mode: c_uint) c_int;
pub extern "c" fn pipe(fds: &c_int) c_int;
pub extern "c" fn mkdir(path: &const u8, mode: c_uint) c_int;
pub extern "c" fn symlink(existing: &const u8, new: &const u8) c_int;

View File

@ -41,6 +41,11 @@ pub const SA_64REGSET = 0x0200; /// signal handler with SA_SIGINFO args with 64
pub const O_LARGEFILE = 0x0000;
pub const O_PATH = 0x0000;
pub const F_OK = 0;
pub const X_OK = 1;
pub const W_OK = 2;
pub const R_OK = 4;
pub const O_RDONLY = 0x0000; /// open for reading only
pub const O_WRONLY = 0x0001; /// open for writing only
pub const O_RDWR = 0x0002; /// open for reading and writing
@ -209,6 +214,10 @@ pub fn fork() usize {
return errnoWrap(c.fork());
}
pub fn access(path: &const u8, mode: u32) usize {
return errnoWrap(c.access(path, mode));
}
pub fn pipe(fds: &[2]i32) usize {
comptime assert(i32.bit_count == c_int.bit_count);
return errnoWrap(c.pipe(@ptrCast(&c_int, fds)));

View File

@ -85,6 +85,47 @@ pub const File = struct {
};
}
pub fn access(allocator: &mem.Allocator, path: []const u8, file_mode: os.FileMode) !bool {
const path_with_null = try std.cstr.addNullByte(allocator, path);
defer allocator.free(path_with_null);
if (is_posix) {
// mode is ignored and is always F_OK for now
const result = posix.access(path_with_null.ptr, posix.F_OK);
const err = posix.getErrno(result);
if (err > 0) {
return switch (err) {
posix.EACCES => error.PermissionDenied,
posix.EROFS => error.PermissionDenied,
posix.ELOOP => error.PermissionDenied,
posix.ETXTBSY => error.PermissionDenied,
posix.ENOTDIR => error.NotFound,
posix.ENOENT => error.NotFound,
posix.ENAMETOOLONG => error.NameTooLong,
posix.EINVAL => error.BadMode,
posix.EFAULT => error.BadPathName,
posix.EIO => error.Io,
posix.ENOMEM => error.SystemResources,
else => os.unexpectedErrorPosix(err),
};
}
return true;
} else if (is_windows) {
if (os.windows.PathFileExists(path_with_null.ptr) == os.windows.TRUE) {
return true;
}
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.FILE_NOT_FOUND => error.NotFound,
windows.ERROR.ACCESS_DENIED => error.PermissionDenied,
else => os.unexpectedErrorWindows(err),
};
} else {
@compileError("TODO implement access for this OS");
}
}
/// Upon success, the stream is in an uninitialized state. To continue using it,
/// you must use the open() function.
@ -245,7 +286,9 @@ pub const File = struct {
};
}
return stat.mode;
// TODO: we should be able to cast u16 to ModeError!u32, making this
// explicit cast not necessary
return os.FileMode(stat.mode);
} else if (is_windows) {
return {};
} else {

View File

@ -38,6 +38,11 @@ pub const MAP_STACK = 0x20000;
pub const MAP_HUGETLB = 0x40000;
pub const MAP_FILE = 0;
pub const F_OK = 0;
pub const X_OK = 1;
pub const W_OK = 2;
pub const R_OK = 4;
pub const WNOHANG = 1;
pub const WUNTRACED = 2;
pub const WSTOPPED = 2;
@ -705,6 +710,10 @@ pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) usize {
return syscall4(SYS_pread, usize(fd), @ptrToInt(buf), count, offset);
}
pub fn access(path: &const u8, mode: u32) usize {
return syscall2(SYS_access, @ptrToInt(path), mode);
}
pub fn pipe(fd: &[2]i32) usize {
return pipe2(fd, 0);
}

View File

@ -23,3 +23,20 @@ test "makePath, put some files in it, deleteTree" {
assert(err == error.PathNotFound);
}
}
test "access file" {
if (builtin.os == builtin.Os.windows) {
return;
}
try os.makePath(a, "os_test_tmp");
if (os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) |ok| {
unreachable;
} else |err| {
assert(err == error.NotFound);
}
try io.writeFile(a, "os_test_tmp/file.txt", "");
assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true);
try os.deleteTree(a, "os_test_tmp");
}

View File

@ -78,6 +78,8 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem
pub extern "kernel32" stdcallcc fn MoveFileExA(lpExistingFileName: LPCSTR, lpNewFileName: LPCSTR,
dwFlags: DWORD) BOOL;
pub extern "kernel32" stdcallcc fn PathFileExists(pszPath: ?LPCTSTR) BOOL;
pub extern "kernel32" stdcallcc fn ReadFile(in_hFile: HANDLE, out_lpBuffer: &c_void,
in_nNumberOfBytesToRead: DWORD, out_lpNumberOfBytesRead: &DWORD,
in_out_lpOverlapped: ?&OVERLAPPED) BOOL;