Replies: 25 comments 32 replies
-
The proposal is exciting and I vote for config as code simply because of the potentially better IDE smartness, as for the format of the manifest anything goes, but yaml and JSON aren't that great in my opinion. TOML seems like a great option as it's super readable, even Apple's new language Pkl looks decent but that is probably overreaching. An important question I have is for the future package manager, as it defines how we interact with the tool. I'll give you two examples, Cargo and Swift Package Manager. The reason cargo is so good for dependencies is that you can simply do Honestly, it's important to think this through now and decide if a Cargo approach is more suitable, or something different, such as Zig and Rust have nailed it with build.zig and build.rs respectively (although build.rs is more uncommon as you often don't need it). So I'd put Go and Cargo as the examples to follow simply because they are what I'm familiar with. I'd avoid anything like CMake and/or Gradle as they're oftentimes viewed as pain points. I prefer things to be batteries included, and a build/project.mojo file seems like the right choice. |
Beta Was this translation helpful? Give feedback.
-
Do you agree with the motivations and guiding principles in the proposal?Yes, agree, especially the support interfacing with other build tools. It's critical for the adoption of Mojo. Which project manifest formats and build tools do you love, and why?I dislike:
I like much more:
Should we adopt the build server protocol?I don't have enough knowledge to answer. Should we define the project manifest as an executable program (such as project.mojo)?I believe this is one of the most important questions here. Before taking a decision, may I suggest that we gather the pros/cons of this so that the community can make a good decision. Very few people have interacted with multiple build systems. Even fewer have interacted enough to answer the question of having an executable program as the main build system file or not. I'll put here some pros and cons I can see: Pros of an executable program:
Pros of a json/yaml/toml... file:
Something interesting that we saw is the python community slowly moving away from setup.py and progressively migrating to pyproject.toml. Some projects still use both. It would be very interesting to know why the move was done. Something interesting too is that rust uses Cargo.toml as the main configuration manifest, and users are so far very happy with it. It may be because there are escape hatches like @modocache Since you are a "build systems and language tooling nerd" 😆 I would appreciate to have more takes on the pros/cons of the manifest as an executable program. It would help the community choose better. I'm myself not sure what is preferable here. Do you have any other thoughts you'd like to contribute?If we go for a non-executable program as manifest, let's stay as close as possible to the pyproject.toml, without binding ourselves completely to it, obviously. |
Beta Was this translation helpful? Give feedback.
-
I want to present a specific counterpoint to allowing fully programmatic (and therefore undecidable) dependencies, regardless of how other parts of setup are declared. Dependency resolution is NP-hard, and there's lots of prior art on package managers attempting to tractably deal with this problem:
For any sufficiently large project, in order to update a dependency safely with turing-complete dependency definitions, you need to do the following:
in practice this often takes many hours for large projects, and updating even one dependency will result in conflicts where no solution to the dependency resolution exists, and dependencies will need to be "pinned", "overridden", or other packages will need to also be manually updated until the problem has a solution. I think there's a lot of good discussion to be had later/elsewhere about how to help create valid solutions to version conflicts. However, fully static dependency configuration allows the package repository to store pre-computed dependency version bounds for each version of each package, which allows maintainers to avoid having to walk transitive dependencies at each step, as well as having to pull and execute package versions at all, and the algorithm simplifies to:
I suspect the concerns about config-as-code are more about clarify and simplicity of configuration than actually wanting Turing-complete configurations. For instance if I have a dependency that exists for platforms X and Y but not Z, I want to be able to clearly state that in my config rather than listing 100 slightly different dependencies 3 times, but dependencies shouldn't need to depend on the output of (for example) a network call or random number generator. For that purpose I think a configuration language such as JSonnet might be a better fit than full-scriptability, or a design where the |
Beta Was this translation helpful? Give feedback.
-
I think TOML works nicely for the manifest. I have tried Zig's build manifest, it had a lot going on and was confusing to get started with. I think build manifests should be declarative, like Rust has with Cargo.toml, or Node's package.json. It should just describe what dependencies are there and some other meta information, the build system should handle the builds the best way it sees fit. This is also the way Bazel is designed to work. It creates a nice separation of concern, the build system can change internal algorithms without breaking the manifest; in fact, you can replace the build system entirely with something else without touching the manifest. |
Beta Was this translation helpful? Give feedback.
-
How about taking both? I assume that |
Beta Was this translation helpful? Give feedback.
-
Cargo stands out as a prime exemplar of an all-encompassing solution. Its familiarity also makes it an excellent 'starting point'. While 'build.zig' and 'Package.swift' are functional, integration with CLI poses challenges. Similar to setuptools, I find them uncomfortable to deal with. I frequently use Poetry and appreciate the progress being made in Rye. In this scenario, advocating for a structure akin to Cargo seems prudent to me. It offers fair tradeoffs that can particularly benefit mojo projects. While there are limitations with static files like TOML, using a CLI is much easier with them, as mentioned in the comments. Dynamic and programmable solutions are only relevant for heterogeneous and complex build workflows and there are already many solutions in this area. |
Beta Was this translation helpful? Give feedback.
-
Another feature I'd like to see is being able to easily turn my Mojo project into an extension for Python, and integrate it with Python's package ecosystem. Though this seems really hard with the Python packaging story being a complete mess. To illustrate why, I will name all the Python packaging tools I know of: pip, venv, virtualenv, pipenv, poetry, hatch, conda, pixi, uv You shouldn't need an entire website to explain how to manage a project. Edit: I don't know whether to laugh or cry on reading this:
|
Beta Was this translation helpful? Give feedback.
-
I've really enjoyed Julia's split (Julia)Project.toml and (Julia)Manifest.toml: The Project.toml describes the immediate dependencies and compatibilities. The Manifest.toml describes all transitive dependencies allowing for reproducible environments. |
Beta Was this translation helpful? Give feedback.
-
Just a heads up for 2 projects: https://github.com/cps-org/cps and https://github.com/apple/pkl They want to achieve interoperability between different build systems. They are using an own meta language to describe packages. Probably worth to have a look at them. |
Beta Was this translation helpful? Give feedback.
-
This is great news, open source Mojo based build tool is ❤️🔥 . Few opinions:
Few ideas for the build tool:
|
Beta Was this translation helpful? Give feedback.
-
Just a quick note: from a security perspective, anything that is in a declarative format (e.g. |
Beta Was this translation helpful? Give feedback.
-
It's worth keeping in mind that Mojo's goals of solving the "two language problem" and "meeting users where they are" means that it will have to cover certain use cases that other build systems may not have to consider. Off the top of my head, Mojo plans to be used in:
With that context, we can consider some of the tradeoffs that come with a given packaging decision. Notebooks & Single File Scripts (In Python) In a notebook, one might install dependencies using a cell like:
since there is no way to handle dependencies from within Python itself. Meanwhile in single file scripts, users are basically out of luck. There is PEP 723 now, which will at least include some metadata for single file scripting use cases, but part of that decision comes from the fact that Python does not ship its build system. The question of how to use that metadata is left up to individual tools in the ecosystem. Neither example even touches build system isolation, so either a user is setting up a virtual environment or wrapping their script in some something like a Docker container. Since Mojo will ship with a batteries included build system, it should be able to offer something more ergonomic here. That could look something like Julia's from buildsystem import Build
Build("""
[dependencies]
max = { git = "...", version = "..." }
[python-dependencies]
requests = { source = "pypy", version = "2.31.0" }
""") and with an executable based solution it might look like: from buildsystem import Build, Package, PyPackage
Build(
deps=[
Package(name="max", git="...", version="..."),
PyPackage(name="requests", source="pypy", version="2.31.0")
]
) One weakness of the configuration language approach here is that we give up editor support (highlighting and completion) when embedded in a script like this. One could make the argument that a script shouldn't have a long list of dependencies, but that's where the value trade-off comes in. More traditional projects with FFI Mojo plans to have first class FFI with C and C++. To have that support, the build system will definitely need a way to feed information about those projects (such as linker paths and arguments). It would also be nice if the build system had the option to specify the source for those dependencies, otherwise we're right back to writing a build script and wrapping it in a Docker container. Rust tries to handle the FFI case with the When doing serious FFI work, usually a build script will be more than just listing a series of dependencies. Users may want to search for system libraries, check hardware options, etc. Specifying this control flow is at least difficult in a configuration language. I would also be curious to see what potential manifests look like for this with a configuration language based approach, since I imagine it would be slightly trickier. It could look something like:
This is another area where the executable based approach would shine, since the various package types would be able to be members of a This use case also complicates adding packages. On the flip side, if Mojo is not the primary language, reading a configuration language to ingest Mojo code will probably be easier. This weakness with the executable approach could be semi-alleviated via some sort of build hook system, but it is there nonetheless. Task Runners Another place that is at least interesting to explore is how the build system will possibly make use of task runners. Take code generation as an example (such as generating Vulkan bindings from an With an executable based build configuration, this could look something like: from buildsystem import Build, Task
from .proj import another_task
fn generate_structs() raises:
...
Build(
# ...
tasks=[
Task[generate_structs](name="build:types"),
Task[another_task](name="run:another_task")
]
) Some closing thoughts The above are just several ideas about some of the use cases a build script will be necessary, and are not specific enough to cover the wide variety of user needs. There are tradeoffs that can make some of the above use cases easier, at the disadvantage of others. The Modular team and the community will have to make decisions about which use cases take priority, and what tradeoffs are acceptable. |
Beta Was this translation helpful? Give feedback.
-
It would be quite neat if you could collaborate with https://prefix.dev/ as a default package manager. |
Beta Was this translation helpful? Give feedback.
-
Since notebooks came up, https://github.com/fonsp/Pluto.jl 's integration of Julia's Project.toml and Manifest.toml into the notebook file itself is game changer. The notebook file is itself just plain Julia source code, making it easy to commit the code into source control. Graphs and other artifacts are included in exports only. Also, it would be great to have a reactive notebook like Pluto.jl and IPyFlow. |
Beta Was this translation helpful? Give feedback.
-
Following @bethebunny opinion, the dependency resolution problem with Turing-complete manifests indeed seems like a burden we might want to avoid for as long as we can. I'm also a bit worried about the future complexities we'll face when we interact with the python package ecosystem, if we ever work on that, striving for simplicity might be key to our success. Aside from that, I see 3 alternatives for the manifest
It seems to me like a nice progression would be 1 -> 3 -> 2, or even 1-> 2. The biggest reason is that starting with 1 leaves a foundation of an ecosystem of manifests that are very simple and easy to deal with, keeping simple things simple. Then, 3 or 2 could be implemented as a backdoor for the more complex cases that will eventually happen. |
Beta Was this translation helpful? Give feedback.
-
Absolutely!
Rust Cargo is a good reference.
I think this would be great.
I support some of the comments above that a TOML based (or even StrictYAML 🏃🏻♂️). In most of the cases in my experience, declarative is enough and also declarative formats encourage a standardized approach to builds. Mojo build files would result in projects implementing their own approaches and each project ends up being slightly different. An added benefit is that we can build simple tools to parse TOML files, while if the build is in Mojo language, we need more complex tools to process it. It increases barrier to entry for simple tools within for example an enterprise setup. There were only a very few cases in my decades experience where I needed to really debug deeply build systems. So I strongly would support declarative builds, with may be an escape hatch for "special" cases. Existence of such escape hatches indicates to a newcomer to the project that there is something special going on. My experience with Gradle which uses a Turing complete language also has not been that great - I need to worry if there is something "special" happening behind the file. Declarative files come with some restrictions that is really helpful. Let's optimize for 80% cases - which I strongly believe are simple builds, and therefore benefits from TOML/StrictYAML (YAML has benefit of broader schema support) based build files. |
Beta Was this translation helpful? Give feedback.
-
There are a lot of really really great threads going on in here. There are a lot of requirements that bubble up from the various avenues in which Mojo gets used, and it's important to keep those in mind in these discussions. For myself, outside of TOML vs Executable vs X vs Y, I'm more interested in what we actually want to achieve:
If I were to see that from the beginning, it'd involve using a Mojo API to define things (and have a structured form to communicate with tools, actually distribute things, etc.). I could also see a world mentioned above where we have a TOML-ish thing for the basics and a Mojo API for the escape hatch "I need to do something fun" path. If we were to walk that path, it would be good to have a TOML->Mojo generation from the beginning to be used for debugging/etc. (going from declarative to imperative can help spill out a lot of details in way that is easier to grok) |
Beta Was this translation helpful? Give feedback.
-
It would be great if there is one-to-one correspondence between terms used in TOML and structs used within the build system. That is: -- build.toml --
translates to (assuming dictionary literal, top level expressions, etc): -- build.mojo --
There were some ideas in threads above that Mojo files within |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Here's my two cents:
There definitely needs to be a way to build mojo projects, and a way to specify how to build, package and distribute the build artifact(s). However, as can be seen from the number of replies there's a lot more that needs to be addressed in additional proposals (which I'll cover in the last question)
I've got some experience with cargo, poetry, maven, gradle, leiningen, and yarn, and I think cargo is the bar to aim for (with poetry in second). Why?
There are a few shortcomings though that I will discuss in the last point.
Reading the docs, while it seems like a worthwhile endeavor, since the standard is still evolving, you might wind up doing a lot of refactoring.
While toml is a nice specification format for the manifest, I do also really like the concept of the manifest being written in a full turing complete language. Examples of this are clojure for leiningen or kotlin DSL for gradle. Since mojo will (eventually) be dynamic but with strong typing, I would strongly consider having the manifest itself be plain old mojo code. If mojo ever gets a macro system, it should be possible to build a DSL for the specification too. A long time ago, I used waf as a build system for C++ projects (I think CMake was still in its infancy), which basically was a python script that was executed to perform the building and linking of the project. waf, descended from scons, was orders of magnitude better than Makefiles, and honestly, I kind of preferred it to CMake.
As seen from many comments, there's many aspects of a build system that will need a lot more proposals to flesh out.
|
Beta Was this translation helpful? Give feedback.
-
I'm very excited to see this coming together and what it represents, not just for mojo, but my hope for what it could also mean for the broader ecosystem that mojo could interact with. For simplicity, I'm assuming that the key concern with the immediate design is about defining, building, and possibly executing targets (in Blaze/Bazel/Buck terminology, BuildTarget in BSP terminology). High-level feature requirements
Let me elaborate each a little. 1) Community and InteropThis is probably the most important one in my mind. There's lots of different way's I'd want (or need) mojo to interoperate with the existing library and tooling ecosystem. Future desired use cases
Notable existing tools and workflows
2) Modular designI think clear calling out the separation between the different layers of the build system will be really important to meet the ecosystem where it is. To me, this is less about if you should support BSP and more about defining good abstractions that would allow some level of interop. e.g. one of the great ideas in Bazel/Buck is the concept of a target but you can't easily interoperate with these targets without going all in to one of those systems. LL-build is probably a little too low-level. BSP might be a good starting point but probably still needs to evolve e.g. BuildTargetSources and BuildTargetDeps is probably not quite the right concept. You don't want to end up with an explosion of concepts here e.g. build-time (linking .o) time vs run-time dependencies (dll) but you often find yourself still needing some form of compile-time dependency module signature or require full source code (e.g. .switinterface/switmodule, rust HIR modules, Gradle and Maven have a concept of a "provided jar" expected to be on the jvm classpath at runtime). More than a build server protocol, I'm probably yearning for something like a build-tool-protocol (BTP). e.g. given a definition of a target, the BTP probably defines something like (requires_build, build, ...) It's probably a good idea to also follow the dual-use CLI/Library approach. LLVM, Rustc/Cargo and golang are all good examples of the type of community tooling ecosystem that can emerge with you allow programatic access. 3) IntuitiveThere's 2 aspects to this, consuming vs extending. When consuming, give me a standard set of verbs for project lifecycle. I love that I can go to a rust-cargo project or golang project and know what to do. Once you're over the hurdle of learning Bazel/Buck, the regularity of targets either being libs or executables that can be built or run is refreshing. There's only a few build system verbs I need to learn. The "nouns" are all the targets in the current project that I can easily query. When extending, don't make me learn every nuance of your internal implementation philosophy. Gradle and Maven are probably at the top of the list of bad offenders here. Basic things shouldn't require any extensions (e.g. if there's a crisp definition of a target, simple extensions like building the artifacts for a static doc site could be as simple as defining a few command line invocations). More complex extensions should be possible. Implementation ConcernsConfig vs CodeA few (maybe contrarian) thoughts here.
Workspace friendlyI know orchestrating workspaces of multiple projects and targets isn't the concern of this design proposal. The only thing I'd like to call out is that whatever is designed here shouldn't preclude the ability to support Blaze/Bazel/Buck, Yarn/NPM, Cargo/Rust style workspaces or Maven-Bom / Gradle multi-module distributions. Note, I'm not suggesting being prescriptive about mono-repo vs multi-repo cloned and stitched into a workspace vs submodules, merely that wherever the design of this layer of the system lands, it shouldn't prevent later developing a workspace style workflow tool. |
Beta Was this translation helpful? Give feedback.
-
Hey all -- first of all let me just say how amazing it is that all of you helped contribute to this discussion. I think of my work on Mojo as a great opportunity, but also a deep responsibility -- to get this stuff right, and to build something that the world needs to have built. All of your input is a really important part of that, so I want to thank you all for taking time out of your day to contribute to the discussion. I'm still reading through many of your comments, but the primary question I wanted to answer here was "do community members agree that a Mojo-specific project manifest be defined, and a Mojo-specific build tool be developed?" Reading your comments, it seems the answer is a resounding "yes!" I don't believe a single person has explicitly proposed otherwise (for example, that Mojo should adopt Bazel as its standard project manifest format -- if you do feel this way, let us know!). Let's keep this discussion going until Monday, at which point I'll close this out. More specific proposals will follow, with feedback from this discussion incorporated into them 🚀 |
Beta Was this translation helpful? Give feedback.
-
Do you agree with the motivations and guiding principles in the proposal?Absolutely. Which project manifest formats and build tools do you love, and why?Like many others, Cargo and TOML are the standouts to me. I also like the Go build tool, but not the go.mod manifest. Should we adopt the build server protocol?I'm primarily a Scala developer. BSP was created for, and almost exclusively used by, the Scala community. It solved a particular problem that I think Mojo can and will avoid: many competing build tools. The biggest thing BSP gave Scala was an LSP that worked for all major build tools. I don't see any value in a Mojo BSP if we have a single build tool. Should we define the project manifest as an executable program (such as project.mojo)?I do not like this idea. It enables complexity in the build that is probably not necessary. If it really becomes a pain point, build scripts like build.rs could always be added later. Keeping the manifest in something like TOML will push the community to standardize on a general layout and make it easier to hop in to new codebases. It would also be easier for tools to parse. Do you have any other thoughts you'd like to contribute?It may be worthwhile to connect with the folks at Astral about their vision for uv and how much it aligns with yours for Mojo. They released with a pip-compatible API for adoption, but plan for it to become a "Cargo for Python". Of course there are several other Python package managers competing, but since their Ruff tool has been widely adopted, this one may end up standing out. If uv does deliver a Cargo-like experience, and becomes popular, we may be able to ensure an easy migration to Mojo. |
Beta Was this translation helpful? Give feedback.
-
Should we define the project manifest as an executable program (such as project.mojo)? Yes - A reduced version of Mojo. Thinking:
|
Beta Was this translation helpful? Give feedback.
-
Once again, thank you all for the great discussion! I'll close this discussion out for now -- there will be much more to share here soon, including additional proposals and discussions. Thanks! |
Beta Was this translation helpful? Give feedback.
-
Hi all, please check out this proposal for a Mojo project manifest and build tool.
As mentioned on the proposal itself, we're looking to hear from the Mojo community:
project.mojo
)?We are build systems and language tooling nerds, and would love to hear from you! Please comment here to join the discussion -- thanks!
Beta Was this translation helpful? Give feedback.
All reactions