-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
[Feature Request] Lazy variable/dependency evaluation #8178
Comments
First of all, I do understand your issue (and I also ran into this problem myself when I accidentally changed the order of my subdirs) and I get that this is an annoyance, however, implementing lazy evaluation would have massive implications for the meson project:
And finally, implementing this correctly would be a huge effort, since it would require some major changes to the interpreter to allow correct general lazy evaluation or some hacky and error-prone mess after the interpreter ran to fix all the references... |
Since I never read the meson source code, I cannot say how easy or hard the implementation will be.
Libraries can still be immutable if they are created late enough. My proposal: Add a function called e.g. "order_and_eval":
Notice how Graphlib can do topological sorts, which is exactly what What I wrote is basically what Maybe there could also be some syntax sugar for
Maybe something like
If the AST parsing to find all undefined variable references, is too complicated one could also write
The following should abort:
|
Ok, this proposal could work, however, there are some problems.
Yes, this is way too complicated, since we would have to do a special case in the parser (not even the interpreter) for one object + method call combination... Secondly, I don't know the official 100% correct stance regarding What could work is a new object type for defining dependency graphs for user-defined objects (maybe even as a new experimental module). Essentially something like this could work: dag = import('graph')
// Syntax:
// dag.insert('<provides>', depends: ['<optional>', '<list>', '<of>', '<deps>'], data: <opaque object>}
dag.insert('mylibA', depends: ['mylibB', 'mylibC', 'mylibE'], data: {'src': ['fileA.cpp']})
dag.insert('mylibB', depends: ['mylibD'], data: {'src': ['fileB.cpp']})
dag.insert('mylibC', depends: ['mylibD', 'mylibE'], data: {'src': ['fileC.cpp']})
dag.insert('mylibD', depends: ['mylibE'], data: {'src': ['fileD.cpp']})
dag.insert('mylibE', data: {'src': ['fileE.cpp']})
dag.insert('mylibX', depends: ['mylibY'], data: {'src': ['fileX.cpp']})
dag.insert('mylibY', depends: ['mylibZ'], data: {'src': ['fileY.cpp']})
dag.insert('mylibZ', depends: ['mylibX'], data: {'src': ['fileZ.cpp']}) // Generates a cycle error
foreach i : dag.as_list()
// Your code that just adds the libs
// Guaranteed to have all dependencies resolved
endforeach Also, thanks for telling me about |
I think the following syntax would make more sense:
|
The problem with My idea was a general dependency graph data structure where you can define some This approach has the advantage that you can maintain any kind of dependency tree and the impact for the meson codebase is minimal since no changes to the interpreter are required. |
Ok. I'm a bit disappointed, but if everything else would be too much of a writing and maintenance burden, then that's ok. |
Do you want to implement this Feature Request or should I do it? |
Patches are always welcome and I currently have limited time to work on meson. However, here are some (hopefully helpful) pointers for getting started: First of all, this feature should be in a new module, since
These are only recommendations and by no means hard requirements:
In meson code this would mean: dag_mod = import('graph') # dag_mod is stateles
dag = dag_mod.gen_dag() # generate the object that does all the actual work
dag.insert(...) # do the actual work Some important points from our contributing docs:
We are also in the (admittedly slow) process of type annotating the meson source code, so new code should also be fully type annotated and the new module should be added to our Feel free to ask if you have any questions. |
One of the core design points of Meson was that things are actually objects and not strings to things that might get defined later. Make, CMake, Bazel and possibly others do that and it always leads to incredibly convoluted and unmaintainable messes. For example you might look at the build definitions of Google's Abseil C++ libraries and try to work out how the dependencies go. Meson does not do that by design. It requires you to be very clear, direct and upfront about your build structure. It proceeds through source directories in specific and unsurprising ways. Enter one dir. Process it. Never return to it. Proceed to the next one. Never return to it. And so on. This means that people writing their build definitions can rely on this behaviour. It reduces the mental burden of working out what is happening when bugs occur. We are very, very, very unlikely to add functionality to add lazy evaluation like this. That is not to say we could not add other primitives to improve things, but a general lazy evaluation thing is exceedingly unlikely to be accepted. |
@jpakkane jus to be clear, do you then have any objections to a new general |
Yes I have. For mostly the same reasons. I am strongly of the opinion that if your build definition is so bizarrely complicated that it requires a DAG dependency solver, then the bettersolution is to make your build setup simpler rather than adding this functionality to the build system. |
Good plan, but to quote a prussian general: "no plan survives contact with the enemy", or in our case "no architecture design survives contact with legacy code". I'm currently working on a project with roughly 10000 *.cpp files, 10000 *.hpp files that are built into 130 shared libraries and 260 binaries. It currently uses gnu make + 2500 lines of custom bash scripts.
Every solution to this problem is a bad one, but the question is what the least bad solution is.
Do you seriously think that a build is bizarrely complicated, just because of the way files are organized into directories?
I don't want to bash on meson, but I just want to note that ninja, make and nearly every other build system has a DAG dependency resolver.
In my case it takes an impractical amount of time to do it and it would break the old build system. Do you seriously think that in a project with 900 k lines of code, "break the old build system and start using another one" is that easy? If both build systems work on the same files, then you can have gradual adoption which is a lot easier. Of the 5 solutions I suggested above, I think you can only seriously suggest 3., 4. or 5. |
I agree that it would be better to restructure the build system, but not everyone will be willing to do this (especially if you are porting a project to meson). It is already possible to build a DAG solver in meson (see the first comment), hence I would argue that meson already has the base functionality to do this. The new module basically moves the hacky Also, using the DAG solver is not trivial, since you have to come up with a system to pass all the kwargs and sources to the correct build type function. This would be far too much overkill for most projects where then manually resolving the dependencies is easier. So my point is (basically the same argument why we have modules in the first place and do not allow custom functions in meson): We can't prevent people from doing (from our perspective) stupid things if they can already do some hacky loop magic in meson that is duplicated many times (like CMake functions) and will break horribly if you touch it. We can however do it once correctly in meson itself and add a big warning label that you are 99% of the time better of just doing this manually but if you really can't be bothered and would rather copy-paste some hacky meson code, then we have an official module for it. So could you live with this functionality as a module + some warning labels in the docs? |
Thank you mesinda for your reasonable comment. I very much agree with you.
Also note that if its a module, it is written in python, but if its in meson.build it is written in the meson dsl. Python is much better suited for writing a DAG solver with meaningful error messages than the meson dsl.
Yes, this might be tricky, especially, if your problem does not look like this:
but more like this:
Having a |
If you are already doing this, then putting the DAG solver in that script is a good option. That way you can use all the cool new stuff in Python 3.9 (which we can't for many years to come). You can customize it to your exact needs and can keep it up to date as the code changes (if needed) until such time that you do the final changeover. Note that if Meson had this sort of a dependency solver, you'd still need to write a converter script like that. But its output would need to be Meson's potentially quite different format for specifying the DAG (it would need to be general, rather than tailored for your use case). It would also make debugging harder, because if problems occur you can't really tell whether they are in your converter script or Meson's DAG module (which is entirely possible, new code like this tends to have bugs and edge cases). |
I thought about this: The big advantage of the Doing what you suggest leaves me with two options.
I'm going to do what you suggest (therefore closing this issue now) (mainly because I know that you can be stubborn ), but I still think that writing this 'graph' library would be a good idea because:
I recently watched your talks and said something like Nonetheless, thank you for your great build system and your comments and suggestions in this thread. If I'm finished with porting my project to meson, I will write a blog post about it and then we will see whether your decision has lead to good or bad code. |
I would actually expect a PR to be accepted based on whether it seems like the proper design paradigm to do it. (@jpakkane's argument condenses to "this is bad design". Your argument condenses to "but my project would use it".) But let's go with your logic for the moment. Can you provide some supporting facts to "there are common cases where this is useful"? Because to be perfectly honest, I don't really understand the nuance of your use case. But I would have expected the only actual problem here to be "the root meson.build needs to I'm not entirely sure what churn you expect, either, unless you regularly |
Yes! Come to the dark side!
My Argument condenses to "some projects would use it". It is a general purpose DAG, not a special purpose DAG only suitable for my project. Because meson does not support user-defined modules, my only choices are submitting this PR or forking meson.
root What if dirX contains the library A and C and dirY contains the library B.
We don't do that. But it would be a nice bonus if you could git mv directories around more easily.
In case anyone want to understand my use case better: |
Uhhhhh. So the core problem here is the current organizational layout of the project in question has a library in dirX which depends on a library in dirY, which depends in turn on another library back in dirX? Multiplied at scale? This sounds... unfortunate. I'd really recommend as a long-term goal, no matter which build system is in use, to implement a more ordered approach to things. I'm not sure what the short-term solution should be... It would still be possible to define such libraries in the root meson.build, but only after |
It is code written by physicists. Trust me, if you have never read science code, you don't know what bad code is. If you want see a messy build, look at sagemath. Featuring:
Honestly, I have not checked if there are some parts of my project where this structure occurs. But our buildsystem, and many other buildsystems support it and I thought that its not that unordered and that it might be useful, because its easier to write stuff in any order than to write it in the correct order, which makes porting stuff from other build systems easier. I will report back in one or two days if we actually need it. (Maybe I should have done that before opening this issue and having a big mouth.) Actually, you made me think of another argument.
The question is if this is more or less ugly than the DAG solution. |
(I am honestly not trying to stir up old, long-dormant debates or make any sort of point here at all. It's just that, at the end of this whole saga we — meaning, anyone reading this for posterity — were left with a bit of a cliffhanger, and I'm genuinely curious to hear how it ends.) So, @Volker-Weissmann |
Sorry for the delay, sickness + overtime delayed me. |
I finally found time to write it: I hope it's understandable. Writing is not my strength. Feel free to suggest changes. |
Does it work if you use |
I can't quite follow you. How does |
It doesn't, link_whole is not the default for non-unix linkers. There's an unrelated feature that meson uses regardless of link_with vs. link_whole. That feature is, grouping all libraries together with This doesn't really have anything to do with referencing meson.build variables before they are defined, or alternatively adding to a target's |
Ok. Then why is jussi writing this in this thread? Am I expected to answer/test anything? |
Thinking about this a bit more there is a "Meson native" way of doing this already that is mostly a question of renaming the directories, but it is slightly involved and requires you to use subprojects. First you move all "target directories" under If you have "global" headers that everybody needs, keep them in the top level and do (approximately):
Then in each subproject you do this:
And now you are mostly done. Whenever you need a library with the name
If you need to expose headers too, then you'd use |
Thank you for trying to solve my problems. Unfortunately I am trying to create meson.build files that work without moving/changing any of the existing .C files. In Hindsight, setting this as a requirement is probably a bad idea and I should (maybe) never started working on it. |
Indeed, if rearranging the tree was an option, you could just move library C to dirY to make the ABC ordering possible. Or move library A to dirY to make CBA possible. (Edit: Or move them all into the same directory, if all else fails and the code is total spaghetti.) The subprojects trick is "neat", but it's overkill as a solution given that the exact same requirements it imposes also make less-invasive solutions possible. |
Although... will Meson follow symlinks, when reading a source tree to construct a build system? Will it follow directory symlinks? If you could create a |
Hello,
lets say your project includes building a lot of libraries, and the dependency graph of these libraries is given by this acyclic graph. You might solve this by writing
This works, but there is one major problem: It breaks once you change the order of those lines. If all of these lines are in the same meson.build file, then putting them into the correct order is easy. But if these lines are scattered across multiple meson.build files in nested subdirectories, then it might no longer be possible (Imagine if libEvar and libCvar are in a subdirectory of libDvar's directory).
One solution would be to write this:
This basically does what I want/need, but it feels like very bad style. Meson should have some functionality to build these acyclic graphs itself.
This lazy function would also solve similar problems that arise if you try to use an executable to generate sources, before building that executable.
I thought of using subprojects instead of subdirs, but that would result in sandbox violations if some libraries share source files with other libraries.
The text was updated successfully, but these errors were encountered: