Skip to content
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

cargo or go build alike cross platform build support #1544

Closed
laoshaw opened this issue Jul 22, 2022 · 13 comments
Closed

cargo or go build alike cross platform build support #1544

laoshaw opened this issue Jul 22, 2022 · 13 comments

Comments

@laoshaw
Copy link

laoshaw commented Jul 22, 2022

if there is one thing needs to do better in c++, it's the cross-platform build, even better, a self-contained cross-platform build like GOOS for go build, so I can build on one and run on all platforms. Please make this a priority.

@chandlerc
Copy link
Contributor

While build systems, and especially as you say good cross-platform self-contained build systems, are a huge area of interest for me personally, I don't think this is really in-scope for the Carbon project.

At a very high level, a principle that we have talked about but not yet codified is the idea that Carbon really needs to work with existing C++ build systems rather than trying to provide its own, replace them, or convince everyone to converge on any one build system.

We should probably codify this somewhere though and make sure everyone is aware and aligned on that direction.

@laoshaw
Copy link
Author

laoshaw commented Jul 22, 2022

That's unfortunate, I actually feel a self-contained build system is what modern c++ needs the most and where go|rust really had some advantages ,i.e. the so-called modern build environment(cargo, go build). Changing some syntax etc alone is not an upgraded c++ to me, if you truly put developers first then toolchain shall be part of the priority. I honestly lost 95% of the interests about carbon now.

@davidzchen
Copy link

davidzchen commented Jul 22, 2022

I agree with @chandlerc that Carbon should aim to work with existing build systems. Creating a custom build system for Carbon is not only a very significant, non-trivial undertaking but also makes it a lot more difficult to integrate into large source trees containing code in other languages. C/C++ interop with other languages is a common practice. There are many use cases where C/C++ code is wrapped so that it can be called from other languages such as Python or even Java (via JNI) where the performance of native code is necessary.

Many existing build systems are already cross-platform. Bazel and CMake are both available across Linux, macOS, and Windows. I think that in terms of the Carbon developer experience, it is more important to make sure that setting up build files for Carbon code is easy to do with the existing build systems, especially given that one of Carbon's main goals is to seamlessly interop with, and thus integrate into, existing C++ codebases.

@mispp
Copy link

mispp commented Jul 22, 2022

From the official goals:

This includes not only a compiler and standard library, but also a broad range of other tools that enable developers to be more effective, efficient, or productive.

Doesn't a better build system contribute to this?

@davidzchen
Copy link

davidzchen commented Jul 22, 2022

With how many build systems there are out there, especially those used by C/C++ projects, I think that creating Yet Another Build System isn't really the right route.

One of Carbon's primary goals is interop, and I think creating a custom build system for Carbon works against that goal.

Personally, I have used many different build systems, and Bazel is the only one I have used where I did not feel like I was fighting with the build system all the time. I find that it is much easier to understand all the dependencies between different parts of the source tree with Bazel since all dependencies are explicitly specified. The fact that Bazel builds are fully reproducible and incremental also means you never have to do a 'make clean', which I always had to do with every other build system I've used. Given that it is also designed to be extensible to any language means that it is as close as a universal build system as it gets.

@laoshaw
Copy link
Author

laoshaw commented Jul 22, 2022

"With how many build systems there are out there, especially those used by C/C++ projects", this, is one of the biggest pain point for c++. We had way too many options, and none really did what cargo/go-build/zig can do already: self-contained cross-platform(some OS-layer in the library makes that possible).

Carbon will attract lots of followers if a true modern build tool becomes one of it goal, which will make creating cross-platform packages a solved problem as well.

Otherwise, it just kind feels like a half-baked Rust-for-c++ to me without Cargo, what's the point to use it then.

@nacaclanga
Copy link

The main differences between C++ build systems and these other build systems boils down to two points:
a) Standardized. A single build system is heavily pushed by the compiler builders up to the point that it becomes part of the compiler itself (like in zig) or the compiler distribution (like with cargo). It is not the quality of the build system, but this standardization, that makes that build system so successful. Support tools like cargo doc or cargo deb, rely on the fact that the build config is so standardized.
b) Focus: cargo, zig build, cabal, etc. focus on one single thing: Determine dependencies provided in a single format, download them from a reprository, build the dependencies, build the end artifact (system lib or executable). They require all dependencies to be available in source and to be statically linkable. Anything extra is done via, something like "build.rs" or extra tools. For example cargo is relatively poor, when it comes building and linking C/C++ code or systems libraries in a controlled and standardized fashion.

A Carbon build system cannot completely reproduce this, due to the huge focus on C++ interopt. But we can make a list of requirements:
a) The build system should be able to automatically load dependencies from a repository like cargo an handle external / system libraries.
b) Everybody should be able to set up their own repro but there should be one offical hosted by the
b) The build system should be simple, preferably using a purely declarative config (like a cargo.toml)
c) The build system should have some support for compiling C++ code.
d) The build system should have simple support for building and linking C++ dependencies that use common C++ build systems, like make/configure&make, cmake or meson in a default kind of fashion. (In the same manner as meson is supporting cmake sub projects).
e) carbon itself should consider using the build system
f) MOST IMPORTNANT: The carbon project blesses and distributes the choosen build system to prevent alternatives from establishing.

Maybe one does not need to invent a new build system but rather just bless ONE simple C++ build system maybe in an adapted fork.

@eli-schwartz
Copy link

Speaking as a maintainer of a general-purpose build system (in this case, Meson), I'd like to interject with a couple thoughts on build systems.

...

What is the plan for supporting project components that aren't written in any programming language at all, but still need to be built?

Data files, man pages (asciidoc, help2man, xslt transformations, etc), etc.

What about installing static resources to GNU directory variable locations? Default config files, icon themes, mimetypes, manpages again?

And of course as mentioned above, many projects need to build code in multiple languages.

All these things cry out for, if not a general purpose build system like meson/cmake, then at least the ability to comfortably invoke one build system from another. This is something that both rust and go completely failed at.

Rust doesn't have a general purpose build system, and AFAIR this goes hand in hand with being totally unable to install anything anywhere (you cannot even install binaries to /usr/bin in a Linux distro packaging recipe, let alone a cdylib). What you can do is write a build.rs which then open-codes your own build system to run subprocesses that perform tasks, and then dump their output in a mysterious hashed directory deep within cargo's private data. You then get to do unguided pattern matching or find . -name foo.1 to get at the resources you want, and hope that there aren't different versions left behind from another build. And build.rs cannot install anything anyway, only build it, so anyone wanting to use this information needs to manually hack it up by first running cargo, then running a lot of mkdir/cp. Not unlike installing the binary itself.

Go is in a bit of the same situation, except that it only builds binaries and from what I've seen, people usually then write the simplest homemade build system they can -- Makefiles that call go build and anything else needed, then make install. Surprisingly, this then turns out to be a better experience than the state of the art in rust.

go cannot tell Make (or ninja) when it's necessary to reinvoke go build. You cannot get instant no-op builds because e.g. in Meson terms, it must be defined as build_always_stale: true and then re-run the language-specific tool to recompile if necessary or do nothing. The tool is free to take as long as it wants figuring out whether any work needs to be done, including recalculating the world. I've heard cargo has depfile support, but I've yet to see a Meson project running cargo build which uses this, which I suspect is because of a combination of: not reliably handling rerunning build.rs, not emitting depfile paths compatible with a parent build system, not outputting the binary to the place the parent build system expects to see it anyway. Most people do seem to invoke cargo via a cargo-wrapper.sh that copies files around after building it, and sets build_always_stale: true.

Supposedly cargo build plans were supposed to describe the build rules sufficiently that e.g. Meson could then merge cargo's build rules into a build.ninja file and track build edges itself, figure out when rebuilds are necessary, and generally drive rustc as part of a larger superproject. Cargo build plans were apparently broken on arrival, no one really uses them, no one cares about them, they are deprecated, and will probably be deleted.

Cache busting of course doesn't work well (especially when the language package manager stores tons of files in arbitrary locations in $HOME instead of the designated build directory).

Every programming language wants its own build system, then reinvents the mistakes of the past, only concerns itself with the need to build single-file executables, and fails to implement basic functionality like installing it after you build it. It would be good if carbon could avoid that.

This doesn't have to mean that carbon totally eschews having its own native build system, but whatever solution is chosen, it should at least:

  • support standard approaches to installation
  • be able to integrate with other build systems (people programming in carbon should not be forced to exclusively use the carbon build system, unless carbon's build system intends to be a viable alternative to meson/cmake for general-purpose builds, rather than be carbon-specific)
  • it would be nice if there was an obvious way to invoke the compiler without the build system, as this allows greater flexibility in architecting integrations with other build systems

@nacaclanga
Copy link

Supposedly cargo build plans were supposed to describe the build rules sufficiently that e.g. Meson could then merge cargo's build rules into a build.ninja file and track build edges itself, figure out when rebuilds are necessary, and generally drive rustc as part of a larger superproject. Cargo build plans were apparently broken on arrival, no one really uses them, no one cares about them, they are deprecated, and will probably be deleted.

The issue here is one common problem encountered, when compiling a language that doesn't use header files to share information between dependencies (which will also be true for Carbon): Compilation of upstream compilation units is blocked on metadata generated from compiling downstream ones. These metadata are usually generated halfway through the compilation process. There are 3 solutions here:
a) Fully compleate the compilation of downstream compilation units before starting upstream ones. This works, but it often creates bottelnecks that result in a low usage of multicore capabilities.
b) In a first step, only create metadata and then abort compilation. This is much faster (per unit) them full compilation. In a secound step, interdependences are resolved and actual compilation can be performed fully parallelized. However initial compilation phases have to be done twice, which makes compilation less effective. An imporved variant might consist of saving the entire compiler state and restoring it in the secound step.
c) Create metadata but continue compilation. In contrast to a) the compiler signals to the build system, when metadata compilation is created, so the build system can start compilation of upstream compilation units before the compilation of downstream ones is fully compleated. This is the most performant solution, but requirers interaction with the build system.

As far as I know, ninja is currently only capable of modeling strategy a) and b), but not c). Strategy c) is used by cargo, as initial phases of the compilation are often relativly expensive, e.g. due to macro expansion. This might also be a reason, that alternative build systems have a hard time here, as they would immediatly feel a perfomance penilty.

@davidzchen
Copy link

davidzchen commented Jul 25, 2022

I think Bazel (which builds the Carbon codebase) could be a good contender for this since it fulfills nearly all of the use cases that @eli-schwartz mentioned in his comment.

Bazel is as close to a universal, general-purpose build system as it gets. It is not only used to build Google's entire O(billion) LoC codebase, but also has a vast ecosystem and external adoption:

I think Bazel would also resolve some of the concerns that @nacaclanga described. The way Bazel solves this is by requiring all dependencies to be explicitly listed in deps (and data for static files) in BUILD file targets. The build graph is constructed and executed from this without requiring additional analysis of source files. While this requires a bit more toil, I think that configuration-over-convention has a lot of merit and benefits:

  • The dependency graph becomes much easier to understand and build breakages become easier to debug.
  • The build system itself is simpler and more generic without the need for language-specific conventions.
  • Integration between building source code and other build and package steps work better, as well as code generation (e.g. protobuf and parser generators) and different language bindings.

Though, there are some challenges of using Bazel for:

  • Deploying/installing artifacts outside of the source tree
  • An easy to use external dependency system
  • Interfacing well with other build systems

Installing Files

This is the simplest of the problems to solve. Bazel does not have built-in support for is making any modifications to the source tree or outside of it, but this is by design due to its design goal of being completely hermetic and reproducible. However, it is not difficult to implement this functionality using custom verbs for installing files onto the system. To make this easy, we could implement a build rule that would generate the necessary scripts (perhaps a macro that wraps a genrule and an sh_binary) that would handle installing artifacts on the system:

carbon_library(
    name = "bar",
    srcs = ["bar.cb"],
    deps = [
        ":base",
        ":util",
        …
    ],
)

carbon_binary(
    name = "foo",
    srcs = ["foo.cb"],
    deps = [
        ":bar",
        …
    ],
)

carbon_install(
    name = "install",
    default_prefix = "/usr/local",
    binaries = [":foo"],
    libraries = [":bar"],
)

Then, running bazel run :install would be equivalent to running make install on a make-based build system. We could implement a similar rule for packaging Carbon artifacts and uploading them to Carbon's package management repository if such a system is implemented.

External Dependencies

Bazel has support for pulling in external dependencies, though it has been somewhat similar to the current state of external dependency support of CMake. One main challenge with external dependencies is that many external dependencies use other build systems. This results in some pain points, such as projects needing to write their own BUILD file for the external repository (for example, see Tensorflow’s third_party directory) and manually listing all transitive dependencies.

Though, people have written a number of external repository rules for interfacing with different package and artifact management systems, such as for Maven artifacts (see the Maven category on Awesome Bazel) and rules for Cargo Crates in the Bazel Rust rules. Recent developments such as bzlmod, the new external dependency management system for Bazel, could potentially make this easier.

If Carbon standardizes on Bazel from the beginning, then I think implementing external dependency rules for Carbon packages, including support for transitive dependencies, could become a lot simpler than the status quo of C++ projects and be much easier to use.

Integration With Other Build Systems

This is touched on above. Integration with other build systems is currently one of the pain points, especially with bespoke language-specific build systems such as those for Go and Rust. The Go rules contain a significant amount of complexity and workarounds due to go build’s conventions. The Rust rules contain rules that aim to interface well with Cargo and Cargo’s build.rs, but similarly, there is a significant amount of workaround needed.

In terms of getting Carbon projects built with Bazel to work with repositories using other build systems such as CMake and Meson, one potential solution is to create tooling that would analyze the Bazel build graph for a Carbon project and generate build files for these other build systems if migrating these other projects to Bazel is infeasible.

Though, this would require a wider discussion and would also involve members of the Bazel team, but this should certainly be made much better than the status quo.


Even if Bazel does not end up being Carbon's standard, official build system, these problems will still nevertheless need to be solved since there will invariably be many Carbon projects that use Bazel. If Carbon ends up with its own build system, I certainly agree that it should interface well with other build systems, including Bazel, both for building source code and managing external dependencies.

@helmesjo
Copy link

helmesjo commented Jul 31, 2022

Just gonna throw build2 into the mix. It should cover everything required & more, in a clean & structured way. Inspired by cargo among others.

@chichid
Copy link

chichid commented Sep 9, 2022

I'd tolerate rust learning curve complexity or go's garbage collector over carbon just because of the cross platform build system to be honest. It's a shame that this feature is not high priority in carbon.

@chandlerc
Copy link
Contributor

FWIW, I don't think there is really an issue to address here at this time.

Our roadmap clearly lays out priorities and while we'd like to dig into build system & ecosystem problems eventually, we need to focus on the critical experimental work on Carbon first.

Closing this as not-planned to avoid more distractions here.

That said, just as a brief note:

I'd tolerate rust learning curve complexity or go's garbage collector over carbon just because of the cross platform build system to be honest. It's a shame that this feature is not high priority in carbon.

I think once Carbon gets beyond the super early experimental stages, this may well become a much higher priority. =] I think the real point here is that Carbon is in a much earlier project stage than Rust or Go, both of which are mature and ready for business. =]

@chandlerc chandlerc closed this as not planned Won't fix, can't repro, duplicate, stale Sep 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants