Browse Source

SCons: Add emitter to declutter build objects

Thaddeus Crews 6 months ago
parent
commit
10ed66f28c
7 changed files with 50 additions and 11 deletions
  1. 16 0
      SConstruct
  2. 26 4
      methods.py
  3. 1 1
      modules/SCsub
  4. 2 4
      platform/android/SCsub
  5. 1 1
      platform/web/detect.py
  6. 3 0
      platform/windows/SCsub
  7. 1 1
      platform/windows/detect.py

+ 16 - 0
SConstruct

@@ -14,6 +14,7 @@ from importlib.util import module_from_spec, spec_from_file_location
 from types import ModuleType
 from types import ModuleType
 
 
 from SCons import __version__ as scons_raw_version
 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
 # 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
 # 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(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_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("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
 # Thirdparty libraries
 opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True))
 opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True))
@@ -1052,6 +1060,14 @@ if env["ninja"]:
 if env["threads"]:
 if env["threads"]:
     env.Append(CPPDEFINES=["THREADS_ENABLED"])
     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.
 # Build subdirs, the build order is dependent on link order.
 Export("env")
 Export("env")
 
 

+ 26 - 4
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
 from misc.utility.color import print_error, print_info, print_warning
 
 
 # Get the "Godot" folder name ahead of time
 # 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
 compiler_version_cache = None
 
 
@@ -83,6 +82,29 @@ def add_source_files(self, sources, files, allow_gen=False):
     return True
     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):
 def disable_warnings(self):
     # 'self' is the environment
     # 'self' is the environment
     if self.msvc and not using_clang(self):
     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():
 def get_git_info():
-    os.chdir(base_folder_path)
+    os.chdir(base_folder)
 
 
     # Parse Git hash if we're in a Git repo.
     # Parse Git hash if we're in a Git repo.
     git_hash = ""
     git_hash = ""
@@ -775,7 +797,7 @@ def show_progress(env):
     if env["ninja"]:
     if env["ninja"]:
         return
         return
 
 
-    NODE_COUNT_FILENAME = f"{base_folder_path}.scons_node_count"
+    NODE_COUNT_FILENAME = base_folder / ".scons_node_count"
 
 
     class ShowProgress:
     class ShowProgress:
         def __init__(self):
         def __init__(self):

+ 1 - 1
modules/SCsub

@@ -101,7 +101,7 @@ for name, path in env.module_list.items():
 if env["tests"]:
 if env["tests"]:
 
 
     def modules_tests_builder(target, source, env):
     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:
         with methods.generated_wrapper(str(target[0])) as file:
             for header in headers:
             for header in headers:
                 file.write(f'#include "{header}"\n')
                 file.write(f'#include "{header}"\n')

+ 2 - 4
platform/android/SCsub

@@ -44,7 +44,7 @@ env_thirdparty.disable_warnings()
 thirdparty_obj = env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc")
 thirdparty_obj = env_thirdparty.SharedObject("#thirdparty/misc/ifaddrs-android.cc")
 android_objects.append(thirdparty_obj)
 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.
 # Needed to force rebuilding the platform files when the thirdparty code is updated.
 env.Depends(lib, thirdparty_obj)
 env.Depends(lib, thirdparty_obj)
@@ -78,9 +78,7 @@ if lib_arch_dir != "":
         lib_tools_dir = ""
         lib_tools_dir = ""
 
 
     out_dir = "#platform/android/java/lib/libs/" + lib_tools_dir + lib_type_dir + "/" + lib_arch_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 = (
     stl_lib_path = (
         str(env["ANDROID_NDK_ROOT"]) + "/sources/cxx-stl/llvm-libc++/libs/" + lib_arch_dir + "/libc++_shared.so"
         str(env["ANDROID_NDK_ROOT"]) + "/sources/cxx-stl/llvm-libc++/libs/" + lib_arch_dir + "/libc++_shared.so"

+ 1 - 1
platform/web/detect.py

@@ -98,7 +98,7 @@ def library_emitter(target, source, env):
 
 
 
 
 def configure(env: "SConsEnvironment"):
 def configure(env: "SConsEnvironment"):
-    env.Append(LIBEMITTER=library_emitter)
+    env.Append(LIBEMITTER=[library_emitter])
 
 
     # Validate arch.
     # Validate arch.
     supported_arches = ["wasm32"]
     supported_arches = ["wasm32"]

+ 3 - 0
platform/windows/SCsub

@@ -8,6 +8,8 @@ from pathlib import Path
 
 
 import platform_windows_builders
 import platform_windows_builders
 
 
+from methods import redirect_emitter
+
 sources = []
 sources = []
 
 
 common_win = [
 common_win = [
@@ -49,6 +51,7 @@ def arrange_program_clean(prog):
         Clean(prog, extra_files_to_clean)
         Clean(prog, extra_files_to_clean)
 
 
 
 
+env["BUILDERS"]["RES"].emitter = redirect_emitter
 res_file = "godot_res.rc"
 res_file = "godot_res.rc"
 res_target = "godot_res" + env["OBJSUFFIX"]
 res_target = "godot_res" + env["OBJSUFFIX"]
 res_obj = env.RES(res_target, res_file)
 res_obj = env.RES(res_target, res_file)

+ 1 - 1
platform/windows/detect.py

@@ -278,7 +278,7 @@ def configure_msvc(env: "SConsEnvironment"):
         from tempfile import mkstemp
         from tempfile import mkstemp
 
 
         # Ensure we have a location to write captured output to, in case of false positives.
         # 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"):
         with open(capture_path, "wt", encoding="utf-8"):
             pass
             pass