add new and init to toolchain
This commit is contained in:
@@ -14,6 +14,7 @@ import "core:strings"
|
||||
Cli_Options :: struct {
|
||||
input_path: string,
|
||||
output_path: string,
|
||||
project_root: string,
|
||||
link_arguments: []linker.Argument,
|
||||
c_options: cimport.Options,
|
||||
target: target.Target,
|
||||
@@ -71,6 +72,11 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
return {}, false
|
||||
}
|
||||
options.target = selected
|
||||
case "--root":
|
||||
if len(options.project_root) > 0 {
|
||||
return {}, false
|
||||
}
|
||||
options.project_root = value
|
||||
case:
|
||||
return {}, false
|
||||
}
|
||||
@@ -87,13 +93,19 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
|
||||
|
||||
print_usage :: proc() {
|
||||
fmt.eprintln(
|
||||
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
"usage: brolang <package-directory> -o <executable> [--root <project-root>] [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang translate-c|--translate-c <header.h> [--target aarch64-macos] [--c-include-path <dir> | --c-define <name[=value]>]...",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang build [root] (reads root/build.bro; root defaults to the current directory)",
|
||||
" brolang build [root] (reads root/build.bro; writes root/build/name)",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang new <project-path>",
|
||||
)
|
||||
fmt.eprintln(
|
||||
" brolang init",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,6 +117,230 @@ is_build_command :: proc(arg: string) -> bool {
|
||||
return arg == "build"
|
||||
}
|
||||
|
||||
is_new_command :: proc(arg: string) -> bool {
|
||||
return arg == "new"
|
||||
}
|
||||
|
||||
is_init_command :: proc(arg: string) -> bool {
|
||||
return arg == "init"
|
||||
}
|
||||
|
||||
template_root_valid :: proc(root: string) -> bool {
|
||||
std_build, std_error := filepath.join({root, "std", "build", "build.bro"})
|
||||
if std_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(std_build)
|
||||
ffi_stdio, ffi_error := filepath.join({root, "ffi", "c", "stdio.bro"})
|
||||
if ffi_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(ffi_stdio)
|
||||
return os.exists(std_build) && os.exists(ffi_stdio)
|
||||
}
|
||||
|
||||
find_template_root :: proc(allocator := context.allocator) -> (string, bool) {
|
||||
if exe, exe_error := os2.get_executable_path(allocator); exe_error == nil {
|
||||
defer delete(exe, allocator)
|
||||
exe_dir := filepath.dir(exe, allocator)
|
||||
defer delete(exe_dir, allocator)
|
||||
if template_root_valid(exe_dir) {
|
||||
return strings.clone(exe_dir, allocator), true
|
||||
}
|
||||
parent := filepath.dir(exe_dir, allocator)
|
||||
defer delete(parent, allocator)
|
||||
if template_root_valid(parent) {
|
||||
return strings.clone(parent, allocator), true
|
||||
}
|
||||
}
|
||||
cwd := os.get_current_directory(allocator)
|
||||
defer delete(cwd, allocator)
|
||||
if template_root_valid(cwd) {
|
||||
return strings.clone(cwd, allocator), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
ensure_directory :: proc(path: string) -> bool {
|
||||
if os.exists(path) {
|
||||
if os.is_dir(path) {
|
||||
return true
|
||||
}
|
||||
fmt.eprintfln("path exists and is not a directory: %s", path)
|
||||
return false
|
||||
}
|
||||
if err := os2.make_directory_all(path); err != nil {
|
||||
fmt.eprintfln("failed to create directory '%s': %v", path, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
ensure_child_directory :: proc(root, name: string) -> bool {
|
||||
path, err := filepath.join({root, name})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(path)
|
||||
return ensure_directory(path)
|
||||
}
|
||||
|
||||
write_file_if_missing :: proc(path, text: string) -> bool {
|
||||
if os.exists(path) {
|
||||
if os.is_dir(path) {
|
||||
fmt.eprintfln("path exists and is not a file: %s", path)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !os.write_entire_file(path, transmute([]byte)text) {
|
||||
fmt.eprintfln("failed to write file '%s'", path)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
write_escaped_brolang_string :: proc(builder: ^strings.Builder, value: string) {
|
||||
for b in transmute([]byte)value {
|
||||
if b == '\\' || b == '"' {
|
||||
strings.write_byte(builder, '\\')
|
||||
}
|
||||
strings.write_byte(builder, b)
|
||||
}
|
||||
}
|
||||
|
||||
default_build_bro :: proc(project_name: string, allocator := context.allocator) -> string {
|
||||
name := project_name
|
||||
if len(name) == 0 || name == "." || name == "/" {
|
||||
name = "app"
|
||||
}
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
strings.write_string(&builder, "b :: import \"@std/build\"\n\nconfig :: b.BuildConfig{\n\tname = \"")
|
||||
write_escaped_brolang_string(&builder, name)
|
||||
strings.write_string(&builder, "\",\n\tsource = \"source\",\n\tlibraries = &[],\n\tlib_paths = &[],\n\tincludes = &[],\n\tdefines = &[],\n\tlinks = &[],\n}\n")
|
||||
return strings.clone(strings.to_string(builder), allocator)
|
||||
}
|
||||
|
||||
ensure_default_sources :: proc(root: string) -> bool {
|
||||
source_dir, source_error := filepath.join({root, "source"})
|
||||
if source_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(source_dir)
|
||||
if !ensure_directory(source_dir) {
|
||||
return false
|
||||
}
|
||||
main_path, main_error := filepath.join({source_dir, "main.bro"})
|
||||
if main_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(main_path)
|
||||
return write_file_if_missing(main_path, "main func() i32 {\n\treturn 0\n}\n")
|
||||
}
|
||||
|
||||
copy_tree_if_missing :: proc(root, template_root, name: string) -> bool {
|
||||
dst, dst_error := filepath.join({root, name})
|
||||
if dst_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(dst)
|
||||
if os.exists(dst) {
|
||||
if os.is_dir(dst) {
|
||||
return true
|
||||
}
|
||||
fmt.eprintfln("path exists and is not a directory: %s", dst)
|
||||
return false
|
||||
}
|
||||
src, src_error := filepath.join({template_root, name})
|
||||
if src_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(src)
|
||||
if err := os2.copy_directory_all(dst, src); err != nil {
|
||||
fmt.eprintfln("failed to copy '%s' to '%s': %v", src, dst, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
init_project :: proc(root: string) -> bool {
|
||||
if !ensure_directory(root) {
|
||||
return false
|
||||
}
|
||||
if !ensure_default_sources(root) || !ensure_child_directory(root, "vendor") {
|
||||
return false
|
||||
}
|
||||
|
||||
needs_template := true
|
||||
std_path, std_error := filepath.join({root, "std"})
|
||||
ffi_path, ffi_error := filepath.join({root, "ffi"})
|
||||
if std_error == nil && ffi_error == nil {
|
||||
needs_template = !os.exists(std_path) || !os.exists(ffi_path)
|
||||
}
|
||||
if std_error == nil {
|
||||
delete(std_path)
|
||||
}
|
||||
if ffi_error == nil {
|
||||
delete(ffi_path)
|
||||
}
|
||||
|
||||
template_root := ""
|
||||
if needs_template {
|
||||
ok: bool
|
||||
template_root, ok = find_template_root()
|
||||
if !ok {
|
||||
fmt.eprintln("could not find bundled std/ffi sources")
|
||||
return false
|
||||
}
|
||||
defer delete(template_root)
|
||||
if !copy_tree_if_missing(root, template_root, "std") ||
|
||||
!copy_tree_if_missing(root, template_root, "ffi") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
build_path, build_error := filepath.join({root, "build.bro"})
|
||||
if build_error != nil {
|
||||
return false
|
||||
}
|
||||
defer delete(build_path)
|
||||
project_name := filepath.base(root)
|
||||
build_text := default_build_bro(project_name)
|
||||
defer delete(build_text)
|
||||
return write_file_if_missing(build_path, build_text)
|
||||
}
|
||||
|
||||
new_project :: proc(path: string) -> bool {
|
||||
if os.exists(path) {
|
||||
fmt.eprintfln("project path already exists: %s", path)
|
||||
return false
|
||||
}
|
||||
template_root, ok := find_template_root()
|
||||
if !ok {
|
||||
fmt.eprintln("could not find bundled std/ffi sources")
|
||||
return false
|
||||
}
|
||||
defer delete(template_root)
|
||||
if !ensure_directory(path) {
|
||||
return false
|
||||
}
|
||||
if !init_project(path) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
run_init_project :: proc() -> int {
|
||||
cwd := os.get_current_directory()
|
||||
defer delete(cwd)
|
||||
return 0 if init_project(cwd) else 2
|
||||
}
|
||||
|
||||
run_new_project :: proc(path: string) -> int {
|
||||
return 0 if new_project(path) else 2
|
||||
}
|
||||
|
||||
zig_lib_dir_from_env_output :: proc(text: string, allocator := context.allocator) -> (string, bool) {
|
||||
rest := text
|
||||
prefix := ".lib_dir = \""
|
||||
@@ -241,9 +477,27 @@ run_translate_c :: proc(args: []string) -> int {
|
||||
|
||||
main :: proc() {
|
||||
if len(os2.args) >= 2 && is_build_command(os2.args[1]) {
|
||||
root := os2.args[2] if len(os2.args) >= 3 else "."
|
||||
if len(os2.args) > 3 {
|
||||
print_usage()
|
||||
os2.exit(2)
|
||||
}
|
||||
root := os2.args[2] if len(os2.args) == 3 else ""
|
||||
os2.exit(compiler.run_build(root))
|
||||
}
|
||||
if len(os2.args) >= 2 && is_new_command(os2.args[1]) {
|
||||
if len(os2.args) != 3 {
|
||||
print_usage()
|
||||
os2.exit(2)
|
||||
}
|
||||
os2.exit(run_new_project(os2.args[2]))
|
||||
}
|
||||
if len(os2.args) >= 2 && is_init_command(os2.args[1]) {
|
||||
if len(os2.args) != 2 {
|
||||
print_usage()
|
||||
os2.exit(2)
|
||||
}
|
||||
os2.exit(run_init_project())
|
||||
}
|
||||
if len(os2.args) >= 2 && is_translate_c_command(os2.args[1]) {
|
||||
os2.exit(run_translate_c(os2.args))
|
||||
}
|
||||
@@ -255,7 +509,14 @@ main :: proc() {
|
||||
defer delete(options.link_arguments)
|
||||
defer delete(options.c_options.include_paths)
|
||||
defer delete(options.c_options.defines)
|
||||
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options)
|
||||
status := compiler.compile_package(
|
||||
options.input_path,
|
||||
options.output_path,
|
||||
options.link_arguments,
|
||||
options.target,
|
||||
options.c_options,
|
||||
options.project_root,
|
||||
)
|
||||
if status != 0 {
|
||||
os2.exit(status)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user