-
-
Notifications
You must be signed in to change notification settings - Fork 21.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SCons: Enable the experimental Ninja backend and minimize timestamp changes to generated code #89452
Conversation
Ooops sorry I did some mistakes while cleaning up the generation method and messed some things up, one sec... |
72db703
to
c8bc666
Compare
There, should be fixed :D Edit: nope, not yet :/ Edit 2: ding ding ding! I had to add an handler for |
0dd31db
to
d4501a2
Compare
The good news though is that Ninja uses all CPU threads by default (unlike |
@Calinou yeah on my machine the default is 10, which is a bit more of my actual threads but still greater than 1, so that's something :D |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally, it works as expected.
I much prefer Ninja's single-line output as well (with subsequent compilation lines replacing previous lines), so this is also an upgrade from an usability perspective.
I agree it makes sense to push users to run Ninja directly to avoid the cost of spawning a Python interpreter and SCons every time you want to compile Godot. CMake has the luxury of having a fast cmake --build
command that redirects to ninja
in a few milliseconds since it's a C++ program, but we don't 🙂
A small issue I noticed is that if you run SCons and Ninja once, then run SCons with no changes and run ninja again, Ninja will check 100+ files instead of exiting early. I'm not sure if this can be fixed though.
scons
ninja # Initial build.
ninja # Null build (very fast).
scons
ninja # This one takes longer than the null build,
# though not as long as the initial build.
Benchmark
OS: Fedora 39 (GCC 13.2.1, ccache 4.8.2, mold 2.4.0)
CPU: Intel Core i9-13900K (performance
governor)
SSD: Solidigm P44 Pro 2 TB
SCons command line:
scons -j32 platform=linuxbsd linker=mold optimize=none fast_unsafe=yes progress=no debug_symbols=yes builtin_embree=no builtin_enet=no builtin_freetype=no builtin_graphite=no builtin_harfbuzz=no builtin_libogg=no builtin_libpng=no builtin_libtheora=no builtin_libvorbis=no builtin_libwebp=no builtin_mbedtls=no builtin_miniupnpc=no builtin_pcre2=no builtin_zlib=no builtin_zstd=no scu_build=all
All tests done with a warm CCache to isolate buildsystem performance.
Null build
SCons:
Time (mean ± σ): 1.841 s ± 0.016 s [User: 1.662 s, System: 0.168 s]
Range (min … max): 1.820 s … 1.871 s 10 runs
Ninja:
Time (mean ± σ): 104.2 ms ± 3.8 ms [User: 79.8 ms, System: 23.3 ms]
Range (min … max): 98.4 ms … 110.3 ms 27 runs
main.cpp change (sd '//' '// ' main/main.cpp
1):
SCons:
Time (mean ± σ): 4.864 s ± 0.035 s [User: 4.834 s, System: 0.616 s]
Range (min … max): 4.808 s … 4.924 s 10 runs
Ninja:
Time (mean ± σ): 3.174 s ± 0.031 s [User: 2.648 s, System: 0.237 s]
Range (min … max): 3.090 s … 3.199 s 10 runs
editor_node.h change (sd '//' '// ' editor/editor_node.h
1)
SCons:
Time (mean ± σ): 3.660 s ± 0.035 s [User: 12.658 s, System: 2.558 s]
Range (min … max): 3.618 s … 3.737 s 10 runs
Ninja:
Time (mean ± σ): 2.547 s ± 0.044 s [User: 14.285 s, System: 3.283 s]
Range (min … max): 2.504 s … 2.651 s 10 runs
Footnotes
@Calinou thanks a lot for testing!
Mh, that's interesting. I've run
It doesn't look like the
Yeah I don't think we can fix this easily if at all, although one has to call scons relatively rarely, as it's just there for setting the compilation options, see below for some more findings. BTW I finally found out why in the world I couldn't get it to build with mold: apparently I don't think that the SCons folks can do much either; TIWAGOS, but I think that this is by design as ninja has to invoke SCons to (re)generate any "dynamic" source file (e.g. anything Also, it's an explicit non-goal to have configuration handling in ninja1. They look to be extremely minimalist and opinionated, which is what gives them their speed and determinism. So, I think that we might have to live with that and communicate to the users that this workflow is a bit different from what we're used to. It might not be that bad though, as after all one has to call SCons only once per configuration change (and any SConstruct/SCsub change is taken in account by ninja too) I think that we can get this to an usable state as-is if we become aware of its quirks and limitations. The current speed boost sounds already very appetizing IMO. Eventually, we could discuss this further with the SCons folks; I suppose that they would appreciate the feedback and perhaps we could help get a bunch of improvements upstream too, such as with ninja detection Hopefully this shines some further light on this backend, as there's unfortunately not much documentation, despite this being surprisingly rock solid for being experimental (we're a huge SCons project and it built first try, requiring only a few tweaks to have perfect incremental rebuilds, that's impressive :D ) Footnotes
|
b5dea26
to
5c3e245
Compare
So folks I did some more testing and realized I had a 2 year old version of SCons... The latest, 4.6.0 is faster, better, harder and stronger: In other words, this is even more stable than I reported before :) |
Can you do a small rebase against master? Thanks! |
Previously, all of the code generation routines would just needlessly write the same files over and over, even when not needed. This became a problem with the advent of the experimental ninja backend for SCons, which can be trivially enabled with a few lines of code and relies on timestamp changes, making it thus impractical.
5c3e245
to
76618e6
Compare
@fire rebased! I also added a version check like with CompileDB, used the native setting for disabling the ninja autorun and renamed the new flaky file whitelist to Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
With this option turned on, if properly set up, SCons generates a `build.ninja` file and quits. To actually build the engine, the user can then call `ninja` with whatever options they might prefer (not everything is yet transferred properly to this new generated file). Ideally, the scons file should never be called again, as ninja automatically detects any SCons build script change and invokes the required commands to regenerate itself. This approach speeds up incremental builds considerably, as it limits SCons to code generation and uses ninja's extremely fast timestamp-based file change detector.
76618e6
to
55558fb
Compare
On windows 11,
Found some warnings and a failure on Windows 11 with python 3.12.
|
To be clear this isn't a blocker and we can work on it later as long as one platform works. |
Can you check that there is no breakage if you don't use |
This comment was marked as outdated.
This comment was marked as outdated.
Let me try harder to see if I can get a build on any platform. Edited: Build failure on windows using msvc. https://gist.github.com/fire/69086e702fc02bca20c515a50b518cdc I wasn't able to install python package |
I have to do some errands so I won't be able test currently. |
I've tested this PR on Windows 11 23H2 with MSVC 2022, but I get a build failure when calling
It works when I start x64 Native Tools Command Prompt for VS 2022 in the start menu, so the automatic MSVC detection isn't working anymore. Is this something we could contribute to Ninja upstream? It helps a lot with MSVC usability, especially for people less familiar with compiling C++ code on Windows. Afterwards, there's a build failure early on:
This is the code in question:
The generated paths in
Maybe it's an issue with how quotes are escaped? |
@Calinou FTR, on my linux machine the same line is escaped properly:
I suppose that some platform code is incorrectly swapping I don't think that this is blocking, as @fire said. This looks like an upstream issue to me. Might be worth an issue ticket. |
Been testing locally on my Windows machine, and two things stood out:
Diffdiff --git a/.gitignore b/.gitignore
index 46dcf84b43..3946cc96e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ platform/windows/godot_res.res
# Ninja build files
build.ninja
.ninja
+run_ninja_env.bat
# Generated by Godot binary
.import/
diff --git a/core/crypto/SCsub b/core/crypto/SCsub
index ac79e10d19..093cb5a2f4 100644
--- a/core/crypto/SCsub
+++ b/core/crypto/SCsub
@@ -21,9 +21,7 @@ if is_builtin or not has_module:
# to make a "light" build with only the necessary mbedtls files.
if not has_module:
# Minimal mbedTLS config file
- env_crypto.Append(
- CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')]
- )
+ env_crypto.Append(CPPDEFINES=[("MBEDTLS_CONFIG_FILE", "<thirdparty/mbedtls/include/godot_core_mbedtls_config.h>")])
# Build minimal mbedTLS library (MD5/SHA/Base64/AES).
env_thirdparty = env_crypto.Clone()
env_thirdparty.disable_warnings()
@@ -47,7 +45,7 @@ if not has_module:
elif is_builtin:
# Module mbedTLS config file
env_crypto.Append(
- CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')]
+ CPPDEFINES=[("MBEDTLS_CONFIG_FILE", "<thirdparty/mbedtls/include/godot_module_mbedtls_config.h>")]
)
# Needed to force rebuilding the core files when the configuration file is updated.
thirdparty_obj = ["#thirdparty/mbedtls/include/godot_module_mbedtls_config.h"]
diff --git a/modules/mbedtls/SCsub b/modules/mbedtls/SCsub
index 4b8f65d8ff..dc6b214552 100644
--- a/modules/mbedtls/SCsub
+++ b/modules/mbedtls/SCsub
@@ -101,7 +101,7 @@ if env["builtin_mbedtls"]:
env_mbed_tls.Prepend(CPPPATH=["#thirdparty/mbedtls/include/"])
env_mbed_tls.Append(
- CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')]
+ CPPDEFINES=[("MBEDTLS_CONFIG_FILE", "<thirdparty/mbedtls/include/godot_module_mbedtls_config.h>")]
)
env_thirdparty = env_mbed_tls.Clone()
Footnotes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite the above hiccups, this is still a rock-solid starting point for getting ninja officially integrated! That, and the added checks/conditionals on generated files is lovely QOL.
Thanks! |
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
PR godotengine#89452 made assumptions on comparing paths as strings which doesn't work when composing them as POSIX paths (`/`) but processing them on NT (`\`, `\\`).
This PR implements everything needed to have initial support for the experimental ninja build backend for SCons documented here: https://scons.org/doc/production/HTML/scons-user/ch27s02.html
Unfortunately, just enabling the backend (which was trivial) was not enough, as ninja is timestamp-based and the code generators kept rewriting (and thus retouching) everything for no good reason, tripping it up.
This PR is thus a patchset of 2 related but atomic commits:
methods.py
calledwrite_file_if_needed
, which compares the file's contents to the target string before writing to it. The SCU generators had to get a special treatment, where they keep track of every file considered and use that as a reference for what to not delete when globbing everything (thus only deleting any "stale" file).To use it, you'll need to have ninja installed and a python module named
ninja
, which offers nothing more than theninja_syntax.py
logic bundled by ninja.To get it, either install the
ninja
python package like the docs recommend:or just put the two files needed in your local
site-packages
directory.Here's a zip which makes SCons use the system version of ninja by bundling the upstream
ninja_syntax.py
and a stub__init__.py
that does absolutely nothing (it just needs to be there to be recognized by SCons): ninja.zip(yeah, I know, this is a bit unnecessary, but that's how it works. We can improve things upstream if this approach becomes more popular)
After that, just add the
ninja=yes
option when invokingscons
. SCons will generate abuild.ninja
and quit. After that, just callninja
with whatever flags you might need and enjoy :DAlso, ideally you won't have to call
scons
never again, unless you want to change some build flag, as ninja will detect any (timestamp-based) change to any SCons build script and invoke everything needed to regenerate itself.While experimenting sometimes it would trip itself in an infinite loop, probably due to some backwards-and-forwards that changed
SConstruct
but not its actual contents. In that case, start a clean build by deletingbuild.ninja
and the.ninja
folder and try again.Be aware that not everything is transferred properly for some reason, such as
whether to buildthe number of jobs (you'll have to manually specifycompile_commands.json
(it does so by default), the linker choice (it usesld.bfd
on my machine for some reason) and-j
while invoking ninja or it will use whatever's the default, which on my machine is 10).Edit: more precisely,custom.py
is not working completely for some reason. Passing manually the linker or other things will work, with the exception of compile_db and the jobs as it looks like they might be treated specially by SCons. I think that this workflow does not really allow it in the first place by design, so I would not rely on it.Edit2: I had an old version of scons (4.3.0). The latest at the time of writing, 4.6.0, imports
custom.py
fine. It also skips buildingcompile_commands.json
correctly now.That said, since this is still marked as experimental, I would not be surprised if some of those were some upstream limitations, so I would consider this good enough for now and open to testing.