-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Support for pre-built dependencies #1139
Comments
This is a Rust problem even more than a Cargo problem. You can't guarantee that a pre-built Rust library will work unless it's built with the exact same SHA of the compiler. |
Yes unfortunately this would require changes to The specific restriction I'm referring to is that you're basically limited to only working with binaries generated by the exact revision of the compiler you're using, as well as the exact same set of dependencies. |
Is there something I can read/follow that explains the issues relating to why this requirement is so strict? Is this expected to change over time? Regardless, assume I can meet the requirement of providing a set of prebuilt libraries with the exact same SHA. Is this a reasonable feature? Even something like letting the build script emit |
/cc @aturon: I had a chat with Steve on IRC about this and he suggested getting your input. One big reason I want to avoid building dependencies over and over is that our build system rebuilds consumers - in my example, a change to I'm happy to contribute the change required to implement this if the team feels it is a worthwhile feature. I imagine there are plenty of companies out their with their own in-house build systems, so something like this could be an adoption blocker. |
I actually meant @alexcrichton not @aturon :) |
Unfortunately no :(. We don't have a ton of documentation in this area, just a bunch of cargo-culted knowledge. In general though this is largely because of two primary reasons (that I can think of):
Certainly! We probably won't invest too much energy into it before 1.0, but I'd love to see progress in this area!
I suppose it depends on how much cargo integration you want. In your example you gave in the second comment, the manifest probably says that In principle allowing
I agree this would definitely be bad! I'd like to hone in on what's going on here first though. My first question would be: Does Cargo suffice? If you're using I suppose my other questions would be based on that answer, so I'll hold off for that :) |
Thanks for the detailed answer Alex!
Imagine To integrate with this build system, one only needs to implement the simple contract: provide something that can be executed that will produce build artifacts. This is just a one-line shell script that turns around and calls Along comes project (We now have the dependency declared in two places. We can either live with the duplication, or adjust our build script to copy them from one into the other.) However, we've just hit the first real problem: Hopefully, at this point, I've answered the latter question: the intention is to use cargo to build libraries such as
The question then, is what happens when And this brings us to the second problem. As I mentioned in the issue overview, I can work around this by using a cargo build script that adds a But then we hit the problem of ABI compatibility, for which it sounds like there is no solution yet. This means I'll need to find a way of (effectively) adding the rustc SHA to the tuple that identifies an artifact (similar to disambiguating 32/64 bit builds). Or just going with the aforementioned option of building all dependencies for each consumer. A question you might also ask is: "would hosting a crates.io mirror help with this?". It doesn't. Not because it doesn't "work", but because it only meets some of the requirements (such as private code, not having direct dependencies on external sources for security reasons). One huge benefit we get out of a single extensible build system is that adding a dependency on a Rust package is no different to adding a dependency on a C, Ruby, Java, Python or Haskell package - they're just named and versioned artifacts. A big use case for me is going to be enabled by that: for example, authoring Rubygems in Rust to speed up performance-critical code paths. I hope this detail makes my initial question more clear: cargo does things I'd otherwise have to implement myself, but it also does things I'd like to skip. Specifically, I'd like to be able to use something like FWIW, I'm probably going to go with:
|
Alex points out that http://doc.crates.io/build-script.html#overriding-build-scripts could be extended to support overriding rust crates. We could then generate |
Alright, after reading that over (thanks for taking the time to write it up!) it sounds like what we discussed on IRC is the best way to move forward with this. Specifically I'd be thinking of something like: # .cargo/config
[target.$triple.rust.foo]
libs = ["path/to/libfoo.rlib", "path/to/libfoo.so"]
dep_dirs = [ ... ] Note that the current overrides ( One problem I can forsee, however, is that you mentioned about not wanting to share the source code between projects. Cargo would still need the source code, however, to read data such as the Does that sound like what would work for you? |
bump? |
Please support this, it's frustrating that it doesn't work yet. |
Nominated for discussion at the Cargo team meeting. |
Was progress made on this at the meeting? |
I created DHL as a workaround for this issue. |
I don't know about "pre-built" dependencies, but it'd be nice to be able to build a variety of leaf crates without building the dependency crates more than once, if the leaf crates request the same features from the dependencies. |
Bump? |
Probably not: still nominated.. |
I'm still a bit in the dark about what happened here. Was anything discussed at the team meeting? |
TL;DR Would just like to add another +1 for support for pre-built binaries. It would be great to get a follow up on what was discussed at the meeting. Motivation Story Last night we ran a workshop on the nannou creative coding framework. Seeing as nannou supports audio, graphics, lasers, etc along with quite a high-level API in a cross-platform manner, it has a lot of dependencies. It took between 5 minutes and 25 minutes (depending on the user's machine) for users just to build nannou and all of its dependencies for the first time in order for us to begin working through the examples together. Ideally in the future we would write a build script that attempted to first fetch pre-built dependencies before falling back to building from the src. It seems like the feature described within this issue would help to simplify this. |
What about some global cache on disk for both the downloaded source code the built binaries, so that if the compiler and crate version match it can avoid a rebuild? Similar to yarn. |
This is also what Nix wants to do. There won't be a compiler mismatch for packages built with Nix, because Nix will use the compiler as a build input to the package. |
Bump. Does anybody know what's happening here? |
I would be interested to know what's happening here. Our company has some products in Rust and we've been working on a new build system. If you use Docker to do your builds, it's actually a good way to get around this problem, because you can do a |
Hey, do you have any feedback on this? I reckon pre-build binaries not only benefit long term builds but also short-term ones. In my case, our serverless application is getting bigger, therefore our build times are increasing linearly. Pre-build dependencies would definitely reduce this build time, even if occasionally we have to re-build these libraries from the code. I understand the argument that, due to Rust's unstable ABI, the generated lib might not be compatible anymore. But we could work around that by pinning a Rust version in the CI/CD - just as others have mentioned. It should be possible for cargo, given this deterministic build scenario, to generate pre-built binary (rlibs or dyn-libs) from crates and use them as valid dependencies for our binaries. |
FWIW, such a feature would be highly desirable for distributions based on functional package managers such as GNU Guix, where the whole dependency chain is controlled. In other words, the ABI compatibility problem of rustc is not really a problem for Guix. |
Up, need support for this to speed up compilation specially for small computing devices |
Be a good citizen in making it easy to integrate Rust code into bigger projects and implement this please. |
How would this help with bigger projects? |
In projects where you want to control all of the dependencies in a uniform detailed manner having a build system for some of them that demands to download packages itself is quite bothersome. Basically for all of the same reasons the Nix users above have given. |
In that case you should probably not use cargo, but instead have whichever build system builds those dependencies for you, build all rust crates using rustc. Mixing two build systems at the same time for your dependencies is bound to give trouble. Even having two instances of cargo may result in some dependencies being built twice, which either had unintended effects or fails depending on if the two cargo instances used different |
These are all hacks on top of Cargo being both build system and package manager. I don't use Nix, I am trying to build packages with Spack which is similar. I don't see why I should have to write build scripts for other people's packages. At most some light patching or flags are needed to be changed to accomodate a meta-build system. Not rewrite the whole build script. As a non-Rust developer just looking to use Rust packages I don't know how to recreate the actual build logic that Cargo performs. Is it as easy as just running |
You don't? For nix crate2nix takes a cargo project as input and produces a nix build script for you. You don't need to write a build script yourself. If anything using pre-built dependencies would require more effort on your end as you need to perform most of the actions of cargo to build your dependencies and then edit the
Not really. You can add |
A manual rewrite, crate2nix rewrites it what is the difference? The point being that Cargo doesn't provide the mechanism needed and so a translation layer is necessary. If for instance it operated (or had the option to operate) more like a traditional build system then no translation would be necessary at all. You would just flip a flag and Cargo would not fetch and build all the dependencies.
I think you do see my point now though, I want to build everything myself, and Cargo to not do as much. Yes this means I have to repackage everything in my system, but the end goal is a single tree of dependencies across all language ecosystems without recompilations. Its a tradeoff of control vs convenience that currently Cargo doesn't support.
I did look through these recommendations and I run into this build script which seems like quite a tight coupling of rustc to Cargo. I will see if I can run the build scripts without Cargo. My current idea though is to patch the Cargo.toml to use local dependencies as you mentioned above. I tried to look at the nix solutions but unfortunately its quite obtuse to someone that doesn't know that DSL and terminology. |
There are couple of scenarios that I can think of: LayeringIn a large project it can be useful to divide the code into various layers for both architectural cleanliness, but also build efficiency. Frequently developers working in the middle or leaf layers will have a huge amount of code in the root layers that they will never touch - being able to use prebuilt libraries from CI instead of having to rebuild those layers is a huge productivity win. Even if you don't have the infrastructure to leverage prebuilt libraries from CI, being able to do a single full build and then manually rebuild only small portions of your code is helpful. Having layered builds can also help CI by allowing the build to be distributed: once the root layers are built, those libraries can be distributed to multiple other build machines to build the wider graph of middle and leaf layers. Note that, in this scenario, Cargo would not check if the rlibs were out-of-date (the original source files might not even be available), but rustc would be responsible for checking if the rlibs are compatible with the current build and fail otherwise. Distributing pre-built librariesOne can think of this like the layering scenario above, but where the layers may cross organizations/projects/companies - namely that pre-built libraries are available (through a package manager, installed in a known directory, etc.) and that no source is available at all. For example:
CargoNote neither of the scenarios that I mentioned above require the use of an additional build system beyond Cargo: there may be some scripts required to grab the prebuilt binaries from somewhere, but one can also imaging just having multiple cargo.toml files in a repo that are unaware of each other, but that know that specific rlibs should be in a specific directory. Even if another build system is involved, because Cargo handles things like build scripts, downloading/building dependencies, and setting up command line arguments to rustc, the idea of avoiding cargo and directly invoking rustc is a non-starter. |
Then what would cargo do if not fetching and building? It already supports separating the fetch and build steps.
A lot IMHO. A manual rewrite is a lot of work. crate2nix or equivalent can be integrated directly in your build system so that it is done transparently for you. One way or another you have to get a build script to be executed by your build system. The crate2nix approach means you don't have to manually write it, but can simply point the build system to a |
You don't need layers for that, right? You could consider every crate a single task to be scheduled across the build farm.
If you are doing that with the intent to keep source private, just be aware that the crate metadata lists every private function and type and a whole lot of other information that should theoretically make it possible to reconstruct something that looks a lot like the original source code. (modulo regular comments. doc comments do end up in the crate metadata)
Have you considered using sccache for that? It supports caching build artifacts in the cloud or on other machines. Using it is a matter of setting the |
A thought occurred to me while thinking of a particular use-case for this that you'll run into difficulty with transitive dependencies that are shared between a pre-compiled rlib and the crate being built on top of it. Consider a crate Which is all to say: I think the lockfile needs to be embedded in/passed alongside an |
I am having a problem that may relate to this thread. I am building my crate B ( Is there any way to make crateB only link with I am kind of stuck on this problem and was hoping to get any suggestions. Thanks! Update: My particular problem was tackled by
Relevance to this topic: |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
There is a Pre-RFC for a subset of this: https://internals.rust-lang.org/t/pre-rfc-sandboxed-deterministic-reproducible-efficient-wasm-compilation-of-proc-macros/19359. That is blocked on some work from #5720. There is also #5931. That could be extended with a plugin system to allow something like sccache for distributed caching. |
i'm looking into this considering the possibility of packaging rust crates in a gentoo overlay as precompiled rlibs. the strict compiler version is not that big of an issue on gentoo because subslotting on rustc can ensure that every lib gets recompiled when the system-provided rustc updates (even tho it's kind of a subpar solution, works) for now i'll try installing rust crates system-wide in source format (as one would with python packages for example), but cargo supporting pre-built libs would reduce build times for rust packages, and is overall a better solution, imo. |
That has the issue that crates can have different cargo features enabled depending on who uses it. Each set of enabled cargo features results in a different rlib. Just enabling all cargo features is not really an option as it would likely pull in a lot of deps that are likely never used and some crates have mutually exclusive cargo features. What you could try though is something like picking a couple of fixed sets of cargo features for each crate, building it with sccache as RUSTC_WRAPPER and then shipping the sccache cache entries and telling sccache to use those shipped cache entries. This way sccache will simply rebuild if the cargo features don't match and you don't need any cargo modifications. |
gentoo has useflags as a core functionality, and useflags are basically features (build time options that the user can set and ebuilds can require). so i can represent features as useflags and an ebuild script can actually pick what should be present, by basically doing |
How does that work with different programs that need mutually exclusive features of a crate? Or what about a program that needs a crate with the |
as far as i know, it conflicts, but
if there's enough binary programs for this to be quite common, i'd have to poke and see. probably drop the idea of pre-compiling rlibs, sticking to installing only the source, since i can't see a better solution yet (still a improvement over the current way gentoo packages rust apps imo, but sad). precompiling libs for each package is the same as just installing the source, but with extra steps, so that's eh. it's either get packages to share precompiled rlibs, or install the sources and let packages build them |
it sounds analagous to re-inventing the wheel with respect to flatpaks, which are built ontop of versioned dependancies. such that multiple versions are installed, several duplicate clones of same libraries. and ostree to point to some git-esq btree structure or whatever it is. i am not saying re-invent the wheel, but maybe there are already similar solutions out there that could be considered, evaluated, adapted to then fit the rust / cargo infrastructure? |
One problem I've had with pre-compiled package is "pre-compiled for what?"
#5931 takes the opposite approach. Instead of trying to have a fixed set of the above that is pre-compiled, it is reactive and caches it on-demand. Of course, plugins could then be extended to upload common sets. In thinking of this, I thought of a good analogy for a way forward for pre-compiled. I will say that #5931 will likely involve a lot less design work and would be a faster path to something. We have the build-std project to make
To put this in C++ terms, a crate lib would be like a headers only library while this new concept would be like creating a proper SO/DLL. It would be designed for it and the types / systems would be unique. |
Currently you can add dependencies using
path
orgit
. Cargo assumes this is a location to source code, which it will then proceed to build.My use-case stems from integrating Cargo into a private build and dependency management system. I need to be able to tell Cargo to only worry about building the current package. That is, I will tell it where the other already-built libraries are.
Consider two projects:
a
(lib) andb
(bin) such thatb
depends ona
:A clean build will output something like:
Importantly:
Would it make sense to expose a
extern
option (independencies.a
) for low level customization?This can be worked around by using a build script along the lines of:
But it is not ideal to have to do this with every project.
The text was updated successfully, but these errors were encountered: