[Build] Convert build system to raw cmake and remove premake layer

This commit is contained in:
Herman S.
2026-03-04 15:28:01 +09:00
parent f1bfb9416d
commit 71c5702ee8
106 changed files with 1829 additions and 3249 deletions

View File

@@ -413,7 +413,7 @@ def generate_version_h():
commit_short = ":("
# header
contents_new = f"""// Autogenerated by `xb premake`.
contents_new = f"""// Autogenerated by xenia-build.py.
#ifndef GENERATED_VERSION_H_
#define GENERATED_VERSION_H_
#define XE_BUILD_BRANCH "{branch_name}"
@@ -626,58 +626,31 @@ def get_clang_format_binary():
sys.exit(1)
def get_premake_target_os(target_os_override=None):
"""Gets the target --os to pass to premake, either for the current platform
or for the user-specified cross-compilation target.
def run_cmake_configure(build_type="Release", cc=None):
"""Runs cmake configure on the project.
Args:
target_os_override: override specified by the user for cross-compilation,
or None to target the host platform.
build_type: Build configuration (Debug, Release, Checked).
cc: C compiler to use (e.g. 'clang', 'gcc').
Returns:
Target --os to pass to premake. If a return value of this function valid
for the current configuration is passed to it again, the same value will
be returned.
"""
if sys.platform == "darwin":
target_os = "macosx"
elif sys.platform == "win32":
target_os = "windows"
elif host_linux_platform_is_android:
target_os = "android"
else:
target_os = "linux"
if target_os_override and target_os_override != target_os:
if target_os_override == "android":
target_os = target_os_override
else:
print_error(
"cross-compilation is only supported for Android target")
sys.exit(1)
return target_os
def run_premake(target_os, action, cc=None):
"""Runs premake on the main project with the given format.
Args:
target_os: target --os to pass to premake.
action: action to perform.
Return code from cmake.
"""
args = [
sys.executable,
os.path.join("tools", "build", "premake.py"),
"--file=premake5.lua",
f"--os={target_os}",
"--test-suite-mode=combined",
"--verbose",
action,
"cmake",
"-S", ".",
"-B", "build",
"-G", "Ninja Multi-Config",
]
if not cc:
cc = get_cc(cc=cc)
if cc:
args.insert(4, f"--cc={cc}")
if sys.platform != "win32":
if not cc:
cc = get_cc(cc=cc)
c_compiler = cc or os.environ.get("CC", "clang")
cxx_compiler = (cc + "++") if cc else os.environ.get("CXX", "clang++")
args += [
f"-DCMAKE_C_COMPILER={c_compiler}",
f"-DCMAKE_CXX_COMPILER={cxx_compiler}",
]
ret = subprocess.call(args)
@@ -687,29 +660,6 @@ def run_premake(target_os, action, cc=None):
return ret
def run_platform_premake(target_os_override=None, cc=None, devenv=None):
"""Runs all gyp configurations.
"""
target_os = get_premake_target_os(target_os_override)
if not devenv:
if target_os == "macosx":
devenv = "xcode4"
elif target_os == "windows":
vs_version = os.getenv("VSVERSION", VSVERSION_MINIMUM)
# VS 2026 preview reports as vs18, map to vs2022 for premake
# as it doesn't yet have a vs2026 target
if vs_version == "18":
vs_version = "2022"
devenv = f"vs{vs_version}"
elif target_os == "android":
devenv = "androidndk"
else:
devenv = "cmake"
if not cc:
cc = get_cc(cc=cc)
return run_premake(target_os=target_os, action=devenv, cc=cc)
def get_build_bin_path(args):
"""Returns the path of the bin/ path with build results based on the
configuration specified in the parsed arguments.
@@ -720,13 +670,10 @@ def get_build_bin_path(args):
Returns:
A full path for the bin folder.
"""
if sys.platform == "darwin":
platform = "macosx"
elif sys.platform == "win32":
platform = "windows"
else:
platform = "linux"
return os.path.join(self_path, "build", "bin", platform.capitalize(), args["config"].capitalize())
config = args["config"].title()
platform = "Windows" if sys.platform == "win32" else "Linux"
# Multi-config: build/bin/<Platform>/<Config>
return os.path.join(self_path, "build", "bin", platform, config)
def create_clion_workspace():
@@ -743,7 +690,7 @@ def create_clion_workspace():
with open(os.path.join(".idea", "misc.xml"), "w") as f:
f.write("""<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$/build">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
</project>
@@ -845,9 +792,6 @@ class SetupCommand(Command):
name="setup",
help_short="Setup the build environment.",
*args, **kwargs)
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
print("Setting up the build environment...\n")
@@ -859,8 +803,8 @@ class SetupCommand(Command):
else:
print_warning("Git not available or not a repository. Dependencies may be missing.")
print("\n- running premake...")
ret = run_platform_premake(target_os_override=args["target_os"])
print("\n- running cmake configure...")
ret = run_cmake_configure()
print_status(ResultStatus.SUCCESS if not ret else ResultStatus.FAILURE)
return ret
@@ -878,9 +822,6 @@ class PullCommand(Command):
self.parser.add_argument(
"--merge", action="store_true",
help=f"Merges on {default_branch} instead of rebasing.")
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
print("Pulling...\n")
@@ -910,36 +851,29 @@ class PullCommand(Command):
git_submodule_update()
print("")
print("- running premake...")
if run_platform_premake(target_os_override=args["target_os"]) == 0:
print("- running cmake configure...")
if run_cmake_configure() == 0:
print_status(ResultStatus.SUCCESS)
return 0
class PremakeCommand(Command):
"""'premake' command.
"""'premake' command (now runs cmake configure).
"""
def __init__(self, subparsers, *args, **kwargs):
super(PremakeCommand, self).__init__(
subparsers,
name="premake",
help_short="Runs premake to update all projects.",
help_short="Runs cmake configure to update all projects.",
*args, **kwargs)
self.parser.add_argument(
"--cc", choices=["clang", "gcc", "msc"], default=None, help="Compiler toolchain passed to premake")
self.parser.add_argument(
"--devenv", default=None, help="Development environment")
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
"--cc", choices=["clang", "gcc", "msc"], default=None, help="Compiler toolchain")
def execute(self, args, pass_args, cwd):
# Update premake. If no binary found, it will be built from source.
print("Running premake...\n")
ret = run_platform_premake(target_os_override=args["target_os"],
cc=args["cc"], devenv=args["devenv"])
print("Running cmake configure...\n")
ret = run_cmake_configure(cc=args["cc"])
print_status(ResultStatus.SUCCESS if not ret else ResultStatus.FAILURE)
return ret
@@ -954,7 +888,7 @@ class BaseBuildCommand(Command):
subparsers,
*args, **kwargs)
self.parser.add_argument(
"--cc", choices=["clang", "gcc", "msc"], default=None, help="Compiler toolchain passed to premake")
"--cc", choices=["clang", "gcc", "msc"], default=None, help="Compiler toolchain")
self.parser.add_argument(
"--config", choices=["checked", "debug", "release"], default="debug",
type=str.lower, help="Chooses the build configuration.")
@@ -966,69 +900,32 @@ class BaseBuildCommand(Command):
help="Forces a full rebuild.")
self.parser.add_argument(
"--no_premake", action="store_true",
help="Skips running premake before building.")
help="Skips running cmake configure before building.")
def execute(self, args, pass_args, cwd):
config = args["config"].title()
if not args["no_premake"]:
print("- running premake...")
run_platform_premake(cc=args["cc"])
print("- running cmake configure...")
run_cmake_configure(build_type=config, cc=args["cc"])
print("")
print("- building (%s):%s..." % (
"all" if not len(args["target"]) else ", ".join(args["target"]),
args["config"]))
if sys.platform == "win32":
if not vs_version:
print_error("Visual Studio is not installed.")
result = 1
else:
targets = None
if args["target"]:
targets = "/t:" + ";".join(
target + (":Rebuild" if args["force"] else "")
for target in args["target"])
else:
targets = "/t:Rebuild" if args["force"] else None
result = subprocess.call([
"msbuild",
"build/xenia.sln",
"/nologo",
"/m",
"/v:m",
f"/p:Configuration={args['config']}",
] + ([targets] if targets else []) + pass_args)
elif sys.platform == "darwin":
schemes = args["target"] or ["xenia-app"]
nested_args = [["-scheme", scheme] for scheme in schemes]
scheme_args = [arg for pair in nested_args for arg in pair]
result = subprocess.call([
"xcodebuild",
"-workspace",
"build/xenia.xcworkspace",
"-configuration",
args["config"]
] + scheme_args + pass_args, env=dict(os.environ))
else:
result = subprocess.call([
"cmake",
"-Sbuild",
f"-Bbuild/build_{args['config']}",
f"-DCMAKE_BUILD_TYPE={args['config'].title()}",
f"-DCMAKE_C_COMPILER={os.environ.get('CC', 'clang')}",
f"-DCMAKE_CXX_COMPILER={os.environ.get('CXX', 'clang++')}",
"-GNinja"
] + pass_args, env=dict(os.environ))
print("")
if result != 0:
print_error("cmake failed with one or more errors.")
return result
result = subprocess.call([
"ninja",
f"-Cbuild/build_{args['config']}",
] + pass_args, env=dict(os.environ))
if result != 0:
print_error("ninja failed with one or more errors.")
build_args = [
"cmake",
"--build", "build",
"--config", config,
]
if args["target"]:
for target in args["target"]:
build_args += ["--target", target]
if args["force"]:
build_args += ["--clean-first"]
result = subprocess.call(build_args + pass_args)
if result != 0:
print_error("Build failed with one or more errors.")
return result
@@ -1653,14 +1550,13 @@ class CleanCommand(Command):
name="clean",
help_short="Removes intermediate files and build outputs.",
*args, **kwargs)
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
print("Cleaning build artifacts...\n"
"- premake clean...")
run_premake(get_premake_target_os(args["target_os"]), "clean")
print("Cleaning build artifacts...")
# Clean all build directories
if os.path.isdir("build"):
print("- cleaning build...")
subprocess.call(["cmake", "--build", "build", "--target", "clean"])
# Also clean generated files
clean_generated_files()
@@ -1698,9 +1594,6 @@ class NukeCommand(Command):
name="nuke",
help_short="Removes all build/ output.",
*args, **kwargs)
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
print("Cleaning build artifacts...\n"
@@ -1719,8 +1612,8 @@ class NukeCommand(Command):
default_branch,
])
print("\n- running premake...")
run_platform_premake(target_os_override=args["target_os"])
print("\n- running cmake configure...")
run_cmake_configure()
print_status(ResultStatus.SUCCESS)
return 0
@@ -1963,15 +1856,11 @@ class TidyCommand(Command):
self.parser.add_argument(
"--fix", action="store_true",
help="Applies suggested fixes, where possible.")
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
# Run premake to generate our compile_commands.json file for clang to use.
# TODO(benvanik): only do linux? whatever clang-tidy is ok with.
run_premake(get_premake_target_os(args["target_os"]),
"export-compile-commands")
# Run cmake configure to generate compile_commands.json for clang-tidy.
# Use Ninja generator which produces compile_commands.json by default.
run_cmake_configure()
if sys.platform == "darwin":
platform_name = "darwin"
@@ -2030,9 +1919,6 @@ class StubCommand(Command):
self.parser.add_argument(
"--class", default=None,
help="Generate a class pair (.cc/.h) at the provided location in the source tree")
self.parser.add_argument(
"--target_os", default=None,
help="Target OS passed to premake, for cross-compilation")
def execute(self, args, pass_args, cwd):
root = os.path.dirname(os.path.realpath(__file__))
@@ -2064,7 +1950,8 @@ class StubCommand(Command):
print_error("Please specify a file/class to generate")
return 1
run_platform_premake(target_os_override=args["target_os"])
# Reconfigure to pick up the new source file.
run_cmake_configure()
return 0
class DevenvCommand(Command):
@@ -2079,23 +1966,15 @@ class DevenvCommand(Command):
*args, **kwargs)
def execute(self, args, pass_args, cwd):
devenv = None
show_reload_prompt = False
if sys.platform == "win32":
if not vs_version:
print_error("Visual Studio is not installed.");
return 1
print("Launching Visual Studio...")
elif sys.platform == "darwin":
print("Launching Xcode...")
devenv = "xcode4"
elif has_bin("clion") or has_bin("clion.sh"):
print("Launching CLion...")
show_reload_prompt = create_clion_workspace()
devenv = "cmake"
else:
print("Launching CodeLite...")
devenv = "codelite"
print("IDE not detected. CMakeLists.txt is in the project root.")
print("\n- generating shaders...")
shader_result = build_shaders()
@@ -2103,37 +1982,30 @@ class DevenvCommand(Command):
print_error("Shader generation failed")
return shader_result
print("\n- running premake...")
run_platform_premake(devenv=devenv)
print("\n- running cmake configure...")
run_cmake_configure()
print("\n- launching devenv...")
if show_reload_prompt:
print_box("Please run \"File ⇒ ↺ Reload CMake Project\" from inside the IDE!")
if sys.platform == "win32":
shell_call([
"devenv",
"build\\xenia.sln",
])
elif sys.platform == "darwin":
shell_call([
"xed",
"build/xenia.xcworkspace",
# Generate a VS .sln for IDE use (normal builds still use Ninja)
vs_build_dir = os.path.join("build", "vs")
subprocess.call([
"cmake",
"-S", ".",
"-B", vs_build_dir,
"-G", "Visual Studio 17 2022",
"-A", "x64",
])
sln_path = os.path.join(vs_build_dir, "xenia.sln")
print(f"Opening {sln_path} in Visual Studio...")
shell_call(["devenv", sln_path])
elif has_bin("clion"):
shell_call([
"clion",
".",
])
shell_call(["clion", "."])
elif has_bin("clion.sh"):
shell_call([
"clion.sh",
".",
])
shell_call(["clion.sh", "."])
else:
shell_call([
"codelite",
"build/xenia.workspace",
])
print("No supported IDE found. Open the project root in your IDE.")
print("CMakeLists.txt and CMakePresets.json are in the project root.")
print("")
return 0