gutter_runner/builder/helpers.odin
2025-05-23 17:42:10 +04:00

183 lines
3.8 KiB
Odin

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"
import "core:thread"
Process_Error :: enum {
OK,
Invalid_Exit_Code,
Crash,
}
Run_Error :: union #shared_nil {
Process_Error,
os.Error,
}
run_cmd_internal :: proc(
cmd: []string,
cwd: string,
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 := os.pipe() or_return
defer os.close(r)
desc := os.Process_Desc {
command = cmd,
working_dir = cwd,
stderr = os.stderr,
stdout = w,
}
Read_Context :: struct {
r: ^os.File,
result: ^string,
}
read_ctx := Read_Context {
r = r,
result = &result,
}
thread_ctx := context
thread_ctx.user_ptr = &read_ctx
read_thread := thread.create_and_start(
proc() {
ctx := cast(^Read_Context)context.user_ptr
builder: strings.Builder
string_writer := strings.to_writer(&builder)
defer io.destroy(string_writer)
mw: io.Multi_Writer
w := io.multi_writer_init(&mw, string_writer, os.to_writer(os.stderr))
tee: io.Tee_Reader
reader := io.tee_reader_init(&tee, os.to_reader(ctx.r), w)
buf: [1024]u8
err: io.Error
for {
n: int
n, err = io.read(reader, buf[:], nil)
if err == .EOF {
break
}
handle_error(err)
}
// NOTE: mem leak, but who cares
ctx.result^ = strings.clone(strings.to_string(builder))
},
thread_ctx,
)
process := os.process_start(desc) or_return
os.close(w)
state := os.process_wait(process) or_return
thread.join(read_thread)
if !state.success {
return "", .Crash
}
if state.exit_code != 0 {
return "", .Invalid_Exit_Code
}
return result, nil
}
run_cmd :: proc(
cmd: []string,
cwd: string,
expr := #caller_expression,
loc := #caller_location,
) -> string {
result, err := run_cmd_internal(cmd, cwd, 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)
}
}
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)
handle_error(os.mkdir_all(path), "", 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))
}