From 10ed66f28c0408bf8ea920d6e178c0afc768f3c4 Mon Sep 17 00:00:00 2001 From: Thaddeus Crews Date: Mon, 13 Jan 2025 11:13:17 -0600 Subject: [PATCH] SCons: Add emitter to declutter build objects --- SConstruct | 16 ++++++++++++++++ methods.py | 30 ++++++++++++++++++++++++++---- modules/SCsub | 2 +- platform/android/SCsub | 6 ++---- platform/web/detect.py | 2 +- platform/windows/SCsub | 3 +++ platform/windows/detect.py | 2 +- 7 files changed, 50 insertions(+), 11 deletions(-) diff --git a/SConstruct b/SConstruct index 87bf5f2f8b1..cfe472467b8 100644 --- a/SConstruct +++ b/SConstruct @@ -14,6 +14,7 @@ from importlib.util import module_from_spec, spec_from_file_location from types import ModuleType from SCons import __version__ as scons_raw_version +from SCons.Builder import ListEmitter # Explicitly resolve the helper modules, this is done to avoid clash with # modules of the same name that might be randomly added (e.g. someone adding @@ -236,6 +237,13 @@ opts.Add(BoolVariable("engine_update_check", "Enable engine update checks in the opts.Add(BoolVariable("steamapi", "Enable minimal SteamAPI integration for usage time tracking (editor only)", False)) opts.Add("cache_path", "Path to a directory where SCons cache files will be stored. No value disables the cache.", "") opts.Add("cache_limit", "Max size (in GiB) for the SCons cache. 0 means no limit.", "0") +opts.Add( + BoolVariable( + "redirect_build_objects", + "Enable redirecting built objects/libraries to `bin/obj/` to declutter the repository.", + True, + ) +) # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) @@ -1052,6 +1060,14 @@ if env["ninja"]: if env["threads"]: env.Append(CPPDEFINES=["THREADS_ENABLED"]) +# Ensure build objects are put in their own folder if `redirect_build_objects` is enabled. +env.Prepend(LIBEMITTER=[methods.redirect_emitter]) +env.Prepend(SHLIBEMITTER=[methods.redirect_emitter]) +for key in (emitters := env.StaticObject.builder.emitter): + emitters[key] = ListEmitter([methods.redirect_emitter] + env.Flatten(emitters[key])) +for key in (emitters := env.SharedObject.builder.emitter): + emitters[key] = ListEmitter([methods.redirect_emitter] + env.Flatten(emitters[key])) + # Build subdirs, the build order is dependent on link order. Export("env") diff --git a/methods.py b/methods.py index cdb75f2ab0f..29afe58949e 100644 --- a/methods.py +++ b/methods.py @@ -16,8 +16,7 @@ from typing import Generator, List, Optional, Union, cast from misc.utility.color import print_error, print_info, print_warning # Get the "Godot" folder name ahead of time -base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/" -base_folder_only = os.path.basename(os.path.normpath(base_folder_path)) +base_folder = Path(__file__).resolve().parent compiler_version_cache = None @@ -83,6 +82,29 @@ def add_source_files(self, sources, files, allow_gen=False): return True +def redirect_emitter(target, source, env): + """ + Emitter to automatically redirect object/library build files to the `bin/obj` directory, + retaining subfolder structure. External build files will attempt to retain subfolder + structure relative to their environment's parent directory, sorted under `bin/obj/external`. + If `redirect_build_objects` is `False`, or an external build file isn't relative to the + passed environment, this emitter does nothing. + """ + if not env["redirect_build_objects"]: + return target, source + + redirected_targets = [] + for item in target: + if base_folder in (path := Path(item.get_abspath()).resolve()).parents: + item = env.File(f"#bin/obj/{path.relative_to(base_folder)}") + elif (alt_base := Path(env.Dir(".").get_abspath()).resolve().parent) in path.parents: + item = env.File(f"#bin/obj/external/{path.relative_to(alt_base)}") + else: + print_warning(f'Failed to redirect "{path}"') + redirected_targets.append(item) + return redirected_targets, source + + def disable_warnings(self): # 'self' is the environment if self.msvc and not using_clang(self): @@ -150,7 +172,7 @@ def get_version_info(module_version_string="", silent=False): def get_git_info(): - os.chdir(base_folder_path) + os.chdir(base_folder) # Parse Git hash if we're in a Git repo. git_hash = "" @@ -775,7 +797,7 @@ def show_progress(env): if env["ninja"]: return - NODE_COUNT_FILENAME = f"{base_folder_path}.scons_node_count" + NODE_COUNT_FILENAME = base_folder / ".scons_node_count" class ShowProgress: def __init__(self): diff --git a/modules/SCsub b/modules/SCsub index 485dfe4abdc..c2400fbed8f 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -101,7 +101,7 @@ for name, path in env.module_list.items(): if env["tests"]: def modules_tests_builder(target, source, env): - headers = sorted([os.path.relpath(src.path, methods.base_folder_path).replace("\\", "/") for src in source]) + headers = sorted([os.path.relpath(src.path, methods.base_folder).replace("\\", "/") for src in source]) with methods.generated_wrapper(str(target[0])) as file: for header in headers: file.write(f'#include "{header}"\n') diff --git a/platform/android/SCsub b/platform/android/SCsub index 2260a84a760..8c2064635c6 100644 --- a/platform/android/SCsub +++ b/platform/android/SCsub @@ -44,7 +44,7 @@ env_thirdparty.disable_warnings() thirdparty_obj = env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc") android_objects.append(thirdparty_obj) -lib = env_android.add_shared_library("#bin/libgodot", [android_objects], SHLIBSUFFIX=env["SHLIBSUFFIX"]) +lib = env_android.add_shared_library("libgodot", android_objects) # Needed to force rebuilding the platform files when the thirdparty code is updated. env.Depends(lib, thirdparty_obj) @@ -78,9 +78,7 @@ if lib_arch_dir != "": lib_tools_dir = "" out_dir = "#platform/android/java/lib/libs/" + lib_tools_dir + lib_type_dir + "/" + lib_arch_dir - env_android.Command( - out_dir + "/libgodot_android.so", "#bin/libgodot" + env["SHLIBSUFFIX"], Move("$TARGET", "$SOURCE") - ) + env_android.Command(out_dir + "/libgodot_android.so", lib, Move("$TARGET", "$SOURCE")) stl_lib_path = ( str(env["ANDROID_NDK_ROOT"]) + "/sources/cxx-stl/llvm-libc++/libs/" + lib_arch_dir + "/libc++_shared.so" diff --git a/platform/web/detect.py b/platform/web/detect.py index 0bedfd7b12e..2a8011ec5b7 100644 --- a/platform/web/detect.py +++ b/platform/web/detect.py @@ -98,7 +98,7 @@ def library_emitter(target, source, env): def configure(env: "SConsEnvironment"): - env.Append(LIBEMITTER=library_emitter) + env.Append(LIBEMITTER=[library_emitter]) # Validate arch. supported_arches = ["wasm32"] diff --git a/platform/windows/SCsub b/platform/windows/SCsub index 1ddefb9c331..9ae70628a39 100644 --- a/platform/windows/SCsub +++ b/platform/windows/SCsub @@ -8,6 +8,8 @@ from pathlib import Path import platform_windows_builders +from methods import redirect_emitter + sources = [] common_win = [ @@ -49,6 +51,7 @@ def arrange_program_clean(prog): Clean(prog, extra_files_to_clean) +env["BUILDERS"]["RES"].emitter = redirect_emitter res_file = "godot_res.rc" res_target = "godot_res" + env["OBJSUFFIX"] res_obj = env.RES(res_target, res_file) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index b008a897261..31a8d07fea8 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -278,7 +278,7 @@ def configure_msvc(env: "SConsEnvironment"): from tempfile import mkstemp # Ensure we have a location to write captured output to, in case of false positives. - capture_path = methods.base_folder_path + "platform/windows/msvc_capture.log" + capture_path = methods.base_folder / "platform" / "windows" / "msvc_capture.log" with open(capture_path, "wt", encoding="utf-8"): pass