package builder import "core:flags" import "core:fmt" import "core:io" import "core:log" import os "core:os/os2" import "core:path/filepath" import "core:slice" import "core:strings" import "core:thread" Build_Variant :: enum { Hot_Reload, Desktop, Web, } Options :: struct { variant: Build_Variant `usage:"Variant of the build"`, optimize: bool `args:"name=opt",usage:"Enable compiler optimizations"`, debug: bool `usage:"Enable debug symbols"`, force: bool `usage:"When enabled dependencies will be cleaned and rebuilt"`, tracy: bool `usage:"Enable tracy profiler"`, run: bool, } temp_concat :: proc(strs: ..[]string) -> []string { result := slice.concatenate(strs, context.temp_allocator) return result } temp_path_join :: proc(paths: ..string) -> string { result, _ := filepath.join(paths, context.temp_allocator) return result } when ODIN_OS == .Windows { EXE_EXT :: ".exe" DLL_EXT :: ".dll" LIB_PREFIX :: "" EMSCRIPTEN_SHELL_EXT :: ".bat" } else when ODIN_OS == .Linux { EXE_EXT :: ".bin" DLL_EXT :: ".so" LIB_PREFIX :: "lib" EMSCRIPTEN_SHELL_EXT :: "" } else when ODIN_OS == .Darwin { EXE_EXT :: ".bin" DLL_EXT :: ".dylib" LIB_PREFIX :: "lib" EMSCRIPTEN_SHELL_EXT :: "" } else { EXE_EXT :: "" DLL_EXT :: "" LIB_PREFIX :: "" EMSCRIPTEN_SHELL_EXT :: "" } // Returns a list of shared dependencies that have to be copied to out dir build_deps :: proc(opts: Options) -> []string { log.infof("build_deps") force := opts.force shared := opts.variant == .Hot_Reload out_libs := make([dynamic]string, 0, 3, context.temp_allocator) // Raylib { cwd := "./libs/raylib" out_dir := shared ? "zig-out-shared" : "zig-out-static" remove_all(fmt.tprintf("./libs/raylib/%s", out_dir)) if force { remove_all("./libs/raylib/.zig-cache") } target := opts.variant == .Web ? "wasm32-emscripten" : "native" run_cmd( { "zig", "build", "-p", out_dir, fmt.tprintf("-Dshared=%v", shared), fmt.tprintf("-Dtarget=%s", target), }, cwd, ) if shared { append( &out_libs, temp_path_join( "./libs/raylib/zig-out-shared/lib", LIB_PREFIX + "raylib" + DLL_EXT, ), ) } } // Physfs { cwd := "./libs/physfs" is_built := os.is_dir("./libs/physfs/build") if force { remove_all("./libs/physfs/build") } prepare_cmd := []string { "cmake", "-B", "build", fmt.tprintf("-DPHYSFS_BUILD_SHARED=%v", shared), "-DCMAKE_BUILD_TYPE=MinSizeRel", "./physfs", } build_cmd := []string{"cmake", "--build", "build", "--config", "MinSizeRel"} if !is_built || force { if opts.variant == .Web { run_cmd(temp_concat({"emcmake" + EMSCRIPTEN_SHELL_EXT}, prepare_cmd), cwd) run_cmd(temp_concat({"emmake" + EMSCRIPTEN_SHELL_EXT}, build_cmd), cwd) } else { run_cmd(prepare_cmd, cwd) run_cmd(build_cmd, cwd) } } if shared { append(&out_libs, "./libs/physfs/build/libphysfs.so.1") } } // Tracy if opts.tracy { assert(opts.variant != .Web) cwd := "./libs/tracy" when ODIN_OS == .Windows { TRACY_NAME_SHARED :: "tracy.dll" TRACY_NAME_STATIC :: "tracy.lib" TRACY_FLAGS :: []string{"-lws2_32", "-ldbghelp"} } else when ODIN_OS == .Linux { TRACY_NAME_SHARED :: "tracy.so" TRACY_NAME_STATIC :: "tracy.a" TRACY_FLAGS :: []string{} } else when ODIN_OS == .Darwin { TRACY_NAME_SHARED :: "tracy.dynlib" TRACY_NAME_STATIC :: "tracy.a" TRACY_FLAGS :: []string{} } file_path := temp_path_join("./libs/tracy", shared ? TRACY_NAME_SHARED : TRACY_NAME_STATIC) is_built := os.is_file(file_path) if is_built && force { remove_file(file_path) } if !is_built || force { run_cmd( temp_concat( { "zig", "c++", "-std=c++11", "-DTRACY_ENABLE", "-O2", "vendor/tracy/public/TracyClient.cpp", }, TRACY_FLAGS, shared ? {"-shared", "-o", TRACY_NAME_SHARED} : {"-c", "-o", "tracy.o"}, ), cwd, ) if !shared { run_cmd({"zig", "ar", "rc", TRACY_NAME_STATIC, "tracy.o"}, cwd) } } if shared { append(&out_libs, "./libs/tracy/tracy" + DLL_EXT) } } return out_libs[:] } COMMON_FLAGS :: []string { "-collection:libs=./libs", "-collection:common=./common", "-collection:game=./game", "-strict-style", "-vet", } setup_emsdk_env :: proc() { when ODIN_OS == .Windows { PATH_ENV_DELIMITER :: ";" } else { PATH_ENV_DELIMITER :: ":" } PATHS :: [2]string{"./libs/emsdk/upstream/bin", "./libs/emsdk/upstream/emscripten"} paths: [len(PATHS)]string for path, i in PATHS { paths[i] = absolute_path(path) } set_env( "PATH", strings.join( {paths[0], paths[1], os.get_env("PATH", context.temp_allocator)}, PATH_ENV_DELIMITER, context.temp_allocator, ), ) set_env("EMSDK", absolute_path("./libs/emsdk")) } setup_bin_dir :: proc(opts: Options, path: string, files_to_copy: []string) { mkdir_all(path) for file in files_to_copy { dst := temp_path_join(path, filepath.base(file)) if !os.is_file(dst) || opts.force { copy_file(file, dst) } } } main :: proc() { context.logger = log.create_console_logger() opts := Options { tracy = true, debug = true, optimize = false, run = true, } flags.parse_or_exit(&opts, os.args, .Unix, context.temp_allocator) if opts.variant == .Web { log.warnf("tracy is not supported on Web") opts.tracy = false setup_emsdk_env() } tracy_flag: []string = opts.tracy ? {"-define:TRACY_ENABLE=true"} : {} debug_flag: []string = opts.debug ? {"-debug"} : {} optimize_flag: []string = opts.optimize ? {"-o:speed"} : {} shared_dep_paths := build_deps(opts) switch opts.variant { case .Hot_Reload: setup_bin_dir(opts, "./bin/hotreload", shared_dep_paths) is_running := process_exists("game" + EXE_EXT) if !is_running { run_cmd( temp_concat( []string { "odin", "build", "main_hot_reload", "-out:./bin/hotreload/game" + EXE_EXT, }, debug_flag, tracy_flag, COMMON_FLAGS, ), "", ) } build_game_cmd := run_cmd( temp_concat( []string { "odin", "build", "game", "-define:RAYLIB_SHARED=true", "-define:PHYSFS_SHARED=true", "-define:DEV=true", "-build-mode:dll", "-out:./bin/hotreload/game_tmp" + DLL_EXT, }, tracy_flag, debug_flag, optimize_flag, COMMON_FLAGS, ), "", ) rename("./bin/hotreload/game_tmp" + DLL_EXT, "./bin/hotreload/game" + DLL_EXT) if !is_running && opts.run { run_cmd({"./bin/hotreload/game" + EXE_EXT}, "") } case .Desktop: setup_bin_dir(opts, "./bin/desktop", shared_dep_paths) cmd := slice.concatenate( [][]string { []string{"odin", "build", "main_release", "-out:./bin/desktop/game" + EXE_EXT}, tracy_flag, debug_flag, optimize_flag, COMMON_FLAGS, }, context.temp_allocator, ) run_cmd(cmd, "") case .Web: remove_all("./bin/web") mkdir_all("./bin/web") odin_root := run_cmd({"odin", "root"}, "") cmd := slice.concatenate( [][]string { []string { "odin", "build", "main_web", "-target:js_wasm32", "-build-mode:obj", "-out:bin/web/game", }, COMMON_FLAGS, debug_flag, optimize_flag, }, ) run_cmd(cmd, "") wasm_debug_flag := opts.debug ? []string{"-g"} : []string{} run_cmd( temp_concat( { "emcc" + EMSCRIPTEN_SHELL_EXT, "-o", "bin/web/index.html", "bin/web/game.wasm.o", "libs/raylib/zig-out-static/lib/libraylib.a", "libs/physfs/build/libphysfs.a", "-sUSE_GLFW=3", "-sWASM_BIGINT", "-sWARN_ON_UNDEFINED_SYMBOLS=0", "-sALLOW_MEMORY_GROWTH", "-sASSERTIONS=2", "--shell-file", "main_web/index_template.html", "--preload-file", "assets", }, wasm_debug_flag, ), "", ) remove_file("./bin/web/game.wasm.o") copy_file( filepath.join( {odin_root, "core", "sys", "wasm", "js", "odin.js"}, context.temp_allocator, ), "./bin/web/odin.js", ) } }