-
-
Notifications
You must be signed in to change notification settings - Fork 324
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
Ninja generation tool - experimental version #3642
Conversation
5ab790f
to
2588627
Compare
@dmoody256 - yup. not news. Ninja's raw speed is hard to beat. |
@bdbaddog yes, and that's why I'm excited SCons can leverage that speed! Ninja is meant to be fast and generated, and SCons has great syntax (python!), most extendable and broad feature support, now with this Ninja feature, it can crank through big builds as fast as possible. |
40eb81a
to
8803ceb
Compare
Moving to 4.1.0 Project. Unlikely this will be releasable in the very near term. Likely we'll push a beta with whatever of this is ready shortly after 4.0.0 |
ok sounds good, I wrote some basic tests covering building basic C libs/applications. I still need to write some tests for the Ninja specific methods added to environment, and finishing documentation. Overall the tool probably still needs some updates/tests regarding using other tools in conjunction with the ninja tool activated (example: other builders like Java, Fortran, etc..) since it reworks alot of SCons internals. That might not need to be handled on the PR, maybe just have a warning printed and documenting that this new feature is in a experimental phase. |
SCons/Tool/ninja.py
Outdated
# this check for us. | ||
"SharedFlagChecker": ninja_noop, | ||
# The install builder is implemented as a function action. | ||
"installFunc": _install_action_function, |
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.
This is the motivation for #3573. By implementing our own handler for installFunc
here, it means that customization of what Install means at the SCons layer doesn't get reflected in the generated Ninja file. It would be good to see that fixed.
SCons/Tool/ninja.py
Outdated
def _install_action_function(_env, node): | ||
"""Install files using the install or copy commands""" | ||
return { | ||
"outputs": get_outputs(node), | ||
"rule": "INSTALL", | ||
"inputs": [get_path(src_file(s)) for s in node.sources], | ||
"implicit": get_dependencies(node), | ||
} | ||
|
||
def _mkdir_action_function(env, node): | ||
return { | ||
"outputs": get_outputs(node), | ||
"rule": "CMD", | ||
# implicit explicitly omitted, we translate these so they can be | ||
# used by anything that depends on these but commonly this is | ||
# hit with a node that will depend on all of the fake | ||
# srcnode's that SCons will never give us a rule for leading | ||
# to an invalid ninja file. | ||
"variables": { | ||
# On Windows mkdir "-p" is always on | ||
"cmd": "{mkdir}".format( | ||
mkdir="mkdir $out & exit 0" if env["PLATFORM"] == "win32" else "mkdir -p $out", | ||
), | ||
}, | ||
} | ||
|
||
def _copy_action_function(env, node): | ||
return { | ||
"outputs": get_outputs(node), | ||
"inputs": [get_path(src_file(s)) for s in node.sources], | ||
"rule": "CMD", | ||
# implicit explicitly omitted, we translate these so they can be | ||
# used by anything that depends on these but commonly this is | ||
# hit with a node that will depend on all of the fake | ||
# srcnode's that SCons will never give us a rule for leading | ||
# to an invalid ninja file. | ||
"variables": { | ||
# On Windows mkdir "-p" is always on | ||
"cmd": "$COPY $in $out", | ||
}, | ||
} | ||
|
||
|
||
def _lib_symlink_action_function(_env, node): | ||
"""Create shared object symlinks if any need to be created""" | ||
symlinks = getattr(getattr(node, "attributes", None), "shliblinks", None) | ||
|
||
if not symlinks or symlinks is None: | ||
return None | ||
|
||
outputs = [link.get_dir().rel_path(linktgt) for link, linktgt in symlinks] | ||
inputs = [link.get_path() for link, _ in symlinks] | ||
|
||
return { | ||
"outputs": outputs, | ||
"inputs": inputs, | ||
"rule": "SYMLINK", | ||
"implicit": get_dependencies(node), | ||
} |
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.
The more of these that can be later moved into command generators, the better, because then Ninja doesn't need to special case them and changes at the SCons layer will be auto-reflected in the Ninja layer.
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.
If alot of the SCons function actions would be converted to command actions, I wonder the level of negative impact to performance for normal SCons builds if they need to spawn new shells more often? Would anyone or anything else prefer the command actions over function actions besides the ninja tool?
If its significant impact, maybe function actions could have command action variants? However, I don't like the idea of maintaining the same feature in two different implementations.
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.
@dmoody256 - I did that with Install/InstallAs to check perf. And the intrinsic python logic was the same or slightly faster. (Surprisingly) even on linux.. Even for parallel builds.
Realistically, there will be almost no maintenance. What's going to change with logic to copy a file? As long as the action is well defined, a note in the sources specifying that the logic in Ninja generator has to be kept in sync if the functionality changes should suffice.
Or.. An equivalent command line action could be annotated to the python action which the ninja (or other) generator can use. At least that way the logic is all in one place
(Think python decorator).
SCons/Tool/ninja.py
Outdated
"implicit": dependencies, | ||
} | ||
|
||
raise Exception("Unhandled list action with rule: " + results[0]["rule"]) |
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.
This is currently one of the major weaknesses of the tool. It is unclear though how to address it.
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.
Why does the handle_list_action function only check the rule of the first result for its list of results? It calls the action_to_ninja_build function for each item which may return a different ninja rule for each item in results.
I am understanding this function to
- take in a list action and convert each action in the list to a ninja build dictionary
- don't actually use the series of dictionary in those results, but generate a new single ninja build dictionary to represent all of them
So if we only check the first item we, are making some assumptions that the rest are of that same type?
Could instead the several ninja build dictionaries be made to depend on each other so they execute in the same order? Maybe some of the current handling are optimization cases where its better to have a single build item, but those could still be handled separately, and instead of just raising exception at the end here we generate all the ninja build items and make them depend on each other?
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.
Could instead the several ninja build dictionaries be made to depend on each other so they execute in the same order?
This is how it used to work and it was too hard to get a ninja file that both functioned properly (had correct DAG) and that Ninja would happily parse. In the interest of shipping I changed this to the simpler solution here which doesn't break on the Mongo code base (which was our smaller subset of caring at the time).
Also a trick is that some things are ListActions
that don't need to be, for instance Linking on Windows is a ListAction
where the second Action is a FunctionAction
which we had no need or analogy for. (here are the relevant functions)
tl;dr It can be done that way but it was more fiddly than I had time for when originally writing this tool.
SCons/Tool/ninja.py
Outdated
# Used to determine if a build generates a source file. Ninja | ||
# requires that all generated sources are added as order_only | ||
# dependencies to any builds that *might* use them. | ||
env["NINJA_GENERATED_SOURCE_SUFFIXES"] = [".h", ".hpp"] |
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.
Another place where the generated sources stuff needs to be abstracted out and dealt with.
SCons/Tool/ninja.py
Outdated
# dependencies to any builds that *might* use them. | ||
env["NINJA_GENERATED_SOURCE_SUFFIXES"] = [".h", ".hpp"] | ||
|
||
if env["PLATFORM"] != "win32" and env.get("RANLIBCOM"): |
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.
This too belongs in a tool's customization of its interaction with the Ninja tool, and not in the Ninja tool itself.
SCons/Tool/ninja.py
Outdated
try: | ||
emitter = builder.emitter | ||
if emitter is not None: | ||
builder.emitter = SCons.Builder.ListEmitter( |
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.
Using an emitter here seems not quite right. Should this instead be a scanner?
SCons/Tool/ninja.py
Outdated
# In the future we may be able to use this to actually cache the build.ninja | ||
# file once we have the upstream support for referencing SConscripts as File | ||
# nodes. | ||
def ninja_execute(self): |
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.
I think this could re-use more of SCons:
+
+ base_execute = SCons.Taskmaster.Task.execute
def ninja_execute(self):
global NINJA_STATE
target = self.targets[0]
target_name = str(target)
- if target_name != ninja_file_name and "conftest" not in target_name:
- NINJA_STATE.add_build(target)
- else:
- target.build()
+ if target_name == ninja_file_name or "conftest" in target_name:
+ base_execute(self)
+ else:
+ NINJA_STATE.add_build(target)
SCons/Tool/ninja.py
Outdated
# date-ness. | ||
SCons.Script.Main.BuildTask.needs_execute = lambda x: True | ||
|
||
# We will eventually need to overwrite TempFileMunge to make it |
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.
This looks like an area that needs investigation regarding what we can do at the SCons layer to address it.
This is a great start. Thanks @dmoody256 for putting it together and adding tests. There are a ton of things we need to investigate, improve, or re-design to make this a truly useful and general purpose tool. I've left a ton of comments trying to highlight these places and provide some context. |
Another thing to consider long term is whether the model and approach here could be extended to generate for other backends. Could we emit real msbuild or vcxproj this way? |
SCons can already generate the MSVC projects, I'm not sure if by "real" your implying some discrepancy with the SCons generated MSVC projects. I think generalizing an approach to generate different builds is a good path to take up. It would flush out issues in SCons by generalizing the build graph to mesh with other build systems and maybe give insights into techniques other build systems have leveraged. |
SCons/Tool/ninja.py
Outdated
# the DAG which is required so that we walk every target, and therefore add | ||
# it to the global NINJA_STATE, before we try to write the ninja file. | ||
def ninja_file_depends_on_all(target, source, env): | ||
if not any("conftest" in str(t) for t in target): |
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.
Forgot to mention: this is where #3626 becomes important.
I think the SCons generated MSVC projects just re-invoke SCons though? |
ah yes, thats correct, SCons still issues the build commands. |
SCons/Tool/ninja.py
Outdated
# TODO: this is hacking into scons, preferable if there were a less intrusive way | ||
# We will subvert the normal builder execute to make sure all the ninja file is dependent | ||
# on all targets generated from any builders | ||
SCons_Builder_BuilderBase__execute = SCons.Builder.BuilderBase._execute |
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.
Why do it this way rather than just writing a scanner?
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.
I am looking into the scanner method you suggested currently. I already had this implemented in my local and had been testing with it, so pushed it up before investigating the scanner route a bit more, but there are few draw backs with going through the env's list of builders.
One problem with just using the list of builders in the current environment, is it only captures the builders at the time the ninja tool was generated, so in a given SCons build, if you add builders later, they don't get emitters/scanners updated to make everything depend on the ninja file. Also the ninja tool affects SCons not just in the environment it was generated with, it affects all environments, so another reason going through the passed in environment's builder list is not reliable.
Secondly, there are other builders that will never be in the env's builder list, such as the Alias builder or the Command builder, so alternate solutions need to be done for those. There may be other builders too that I am not aware of that are never added to the builder list.
This solution, every builder will use this execute function, and this happens after the scons scripts have been read and executed so you can add builders after the ninja tool is initialized.
Ideally the instead of forcing this change directly into the SCons internal class from the tool, SCons has some new function that allows some callback to be setup, or lets you set all emitters/scanners for all builders.
c786703
to
bbec480
Compare
@dmoody256 - FYI, please be advised of the following fix that landed in the MongoDB version of this: mongodb/mongo@18cbf0d#diff-e5450d4ac791a19fac9a32fac3a1b19c Another one incoming that looks like this: diff --git a/site_scons/site_tools/ninja_next.py b/site_scons/site_tools/ninja_next.py
|
@acmorrow thanks! I would like to add some tests for these cases to capture this functionality. For the sorted issue, from my understanding it caused the generated ninja file to put the outputs in the wrong order to some build command that was expecting dwo to come after the .o? I would like to mock it up with some script that expects args in a certain order, but want to make sure that was the issue. |
@dmoody256 - No, not exactly. It was more that having the |
Also @dmoody256, this commit did land in the MongoDB version: mongodb/mongo@a7541c6 |
… vs using Glob()'s
…c55859f325ce23daa90b3c1c55bc76cb
ceba1ff
to
a9bf0d4
Compare
…eration # Conflicts: # SCons/Tool/ninja/Methods.py # SCons/Tool/ninja/NinjaState.py # SCons/Tool/ninja/Utils.py # SCons/Tool/ninja/__init__.py # doc/generated/functions.gen
@@ -39,7 +39,7 @@ | |||
|
|||
diskcheck_all = SCons.Node.FS.diskcheck_types() | |||
|
|||
experimental_features = {'warp_speed', 'transporter'} | |||
experimental_features = {'warp_speed', 'transporter', 'ninja'} |
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.
are you intending to leave the two "dummy" names in now there's a real one?
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.
Yes. Easter eggs are fun?
SCons/Tool/ninja/ninja.xml
Outdated
<tool name="ninja"> | ||
<summary> | ||
<para> | ||
Sets up &b-link-Ninja; builder which generates a ninja build file, and then optionally runs ninja. |
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.
when referring to the ninja command it might be worth annotating it... unfortunately <application>
doesn't render with our current settings, so probably <command>ninja</command>
or add it to doc/scons.mod
and use &ninja;
.
SCons/Tool/ninja/ninja.xml
Outdated
<para> | ||
&b-Ninja; is a special builder which | ||
adds a target to create a ninja build file. | ||
The builder does not require any source files to be specified, |
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.
nit: trailing comma - intend period?
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.
addressed in next push.
<summary> | ||
<para> | ||
The list of source file suffixes which are generated by SCons build steps. | ||
All source files which match these suffixes will be added to the _generated_sources alias in the output |
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.
since this is prelim we can fixup these later - annotation of filenames and the alias name
Summary
This tool attempts to generically take a SCons build and generate a ninja file which is representative of the build and can be built by Ninja.
Ninja is a very fast build system, which is meant to have its build files generated by other systems. Ninja is especially good at iterative rebuilds and quick traversing a build graph to determine what needs to be rebuilt.
This PR is to introduce the ninja tool based off the ninja tool from mongodb's site tools.
This PR scope is BETA functionality of the ninja tool. It will not work well with all other tools and is primarily aimed at cross platform (win32/linux/osx) C/C++ builds. The goal of this PR is to get the tool to a point that it can be integrated in beta form.
Feature/Test List
Known Issues
Contributor Checklist:
src/CHANGES.txt
(and read theREADME.txt
in that directory)