package builder import "base:intrinsics" import "core:bufio" import "core:fmt" import "core:io" import "core:log" import os "core:os/os2" import "core:path/filepath" import "core:strings" Process_Error :: enum { OK, Invalid_Exit_Code, Crash, } Run_Error :: union #shared_nil { Process_Error, os.Error, } @(private = "file") run_cmd_internal :: proc( cmd: []string, cwd: string, capture: bool, loc := #caller_location, ) -> ( result: string, err: Run_Error, ) { if len(cwd) > 0 { log.infof( "running [%s]: %s", cwd, strings.join(cmd, " ", context.temp_allocator), location = loc, ) } else { log.infof("running: %s", strings.join(cmd, " ", context.temp_allocator), location = loc) } r, w := handle_error2(os.pipe()) defer handle_error(os.close(r)) desc := os.Process_Desc { command = cmd, working_dir = cwd, stderr = os.stderr, stdout = w, } process: os.Process { defer handle_error(os.close(w)) process = handle_error1(os.process_start(desc)) } state: os.Process_State b := strings.builder_make(context.temp_allocator) defer strings.builder_destroy(&b) for { has_data, pipe_err := os.pipe_has_data(r) if pipe_err != .Broken_Pipe { handle_error(pipe_err) } n: int buf: [1024]u8 if has_data { n = handle_error1(os.read(r, buf[:])) if capture { strings.write_bytes(&b, buf[0:n]) } handle_error1(os.write(os.stderr, buf[0:n])) } if state.exited { break } wait_for_kill := false if pipe_err == .Broken_Pipe { _ = os.process_kill(process) wait_for_kill = true } wait_err: os.Error state, wait_err = os.process_wait(process, wait_for_kill ? os.TIMEOUT_INFINITE : 0) if wait_err != .Timeout { handle_error(wait_err) } } result = strings.to_string(b) if !state.success { return "", .Crash } if state.exit_code != 0 { return "", .Invalid_Exit_Code } return result, nil } run_cmd :: proc( cmd: []string, cwd: string, capture := true, expr := #caller_expression, loc := #caller_location, ) -> string { result, err := run_cmd_internal(cmd, cwd, capture, loc) handle_error(err, "", expr, loc) return result } handle_error :: proc( err: $E, msg: string = "", expr := #caller_expression, loc := #caller_location, ) where intrinsics.type_is_enum(E) || intrinsics.type_is_union(E) { if err != nil { log.panicf("%v %s error: %v", expr, msg, err, location = loc) } } handle_error1 :: proc( val: $V, err: $E, msg: string = "", expr := #caller_expression, loc := #caller_location, ) -> V { handle_error(err, msg, expr, loc) return val } handle_error2 :: proc( val1: $V1, val2: $V2, err: $E, msg: string = "", expr := #caller_expression, loc := #caller_location, ) -> ( V1, V2, ) { handle_error(err, msg, expr, loc) return val1, val2 } remove_file :: proc(path: string, expr := #caller_expression, loc := #caller_location) { log.infof("remove(%s)", path, location = loc) err := os.remove(path) if err != .Not_Exist { handle_error(err, fmt.tprintf("failed to remove %s", path), expr = expr, loc = loc) } } remove_all :: proc(path: string, expr := #caller_expression, loc := #caller_location) { log.infof("remove_all(%s)", path, location = loc) err := os.remove_all(path) if err != .Not_Exist { handle_error(err, fmt.tprintf("failed to remove %s", path), expr = expr, loc = loc) } } copy_file :: proc(src, dst: string, expr := #caller_expression, loc := #caller_location) { log.infof("cp %s %s", src, dst) handle_error(os.copy_file(dst, src), "", expr, loc) } rename :: proc(src, dst: string, expr := #caller_expression, loc := #caller_location) { log.infof("rename %s -> %s", src, dst) handle_error(os.rename(src, dst), "", expr, loc) } mkdir_all :: proc(path: string, expr := #caller_expression, loc := #caller_location) { log.infof("mkdir -p %s", path) err := os.mkdir_all(path) if err != .Exist { handle_error(err, "", expr, loc) } } absolute_path :: proc( path: string, allocator := context.temp_allocator, expr := #caller_expression, loc := #caller_location, ) -> string { abs_path, err := os.get_absolute_path(path, allocator) handle_error(err, "", expr, loc) return abs_path } set_env :: proc(env, val: string) { log.infof("set_env(%s, %s)", env, val) handle_error(os.set_env(env, val)) } process_exists :: proc(name: string) -> bool { pids := handle_error1(os.process_list(context.temp_allocator)) for pid in pids { pinfo, err := os.process_info_by_pid(pid, {.Executable_Path}, context.temp_allocator) if err == nil { if filepath.base(pinfo.executable_path) == name { return true } } } return false }