`package.rs` script to reduce build dependencies of published crates

It's quite common for crates to use some sort of compiler in their build script in order to include the resulting file with one of the include macros (include!, include_str! or include_bytes!). My concern is that using such a tool in the build.rs script often adds many build dependencies, for instance:

  • depending on lalrpop adds 56 build dependencies
  • depending on deno adds 958 build dependencies

For my latest project I want to include_str! a .js file that was built from TypeScript with deno bundle but I certainly don't want a cargo install to require nearly a thousand dependencies nor do I want to require a system or user-wide Deno installation. Instead I simply want to include the already built .js file in the source (.crate) tarball (without needing to commit the .js file since I generally want to keep build artifacts out of version control).

While Cargo supports build scripts (a build.rs file will be executed on cargo build), Cargo does not appear to support "package scripts". I think it would make sense for Cargo to recognize a package.rs script and execute it on cargo package or cargo publish to make it easy to avoid such unnecessary build dependencies.

What do you think of this idea? (As usual I am looking for feedback here first before starting an RFC.)

The thing about build.rs is it runs as part of the normal cargo build workflow.

Are you suggesting the same for package.rs, and if not, how do you deal with the absence of generated files which are only available after publication?

1 Like

Ah yes ... thanks that's a good question! I guess it would make sense that cargo build executes package.rs by default (before build.rs if it also exists).

We previously discussed the idea of a publish.rs on zulip though package.rs is a bit more accurate of a name.

In the mean time, I use tests and snapshots testing for this

2 Likes

Thanks, nice to see that this has been discussed previously!

In the mean time, I use tests and snapshots testing for this

Yeah that makes sense ... only that snapshot tests require the snapshot to be committed which is often undesirable for larger build artifacts. (For instance committing a bundled .js file would make rebasing much more cumbersome.)

1 Like

I don't think package.rs should modify files within the manifest directory. It could get an OUT_DIR similar to build.rs (but a different temporary directory to what build.rs gets) in which it can write generated files. That then gets put into the archive along with the manifest directory and provided to the source to reference in include! paths like build.rs.

2 Likes

I agree ... this would also allow the artifacts to be cleaned with cargo clean.

This would make switching from a registry based dependency to a path or git based dependency potentially more disruptive for crates using a package script. That's definitely not the end of the world, but it might need some consideration in the design.

1 Like

I think any design for this would have to handle dependencies from any source, and run the package.rs script locally if using a path-based or git-based dependency.

1 Like

And I guess that yet another thing that would need to be addressed is that we would need a new kind of dependencies ... build dependencies are for build scripts ... "package dependencies" could be for package scripts. Though I don't really like the name "package dependencies" since I think it could be easily confused with regular dependencies ... since any dependency is a dependency on a package.

Perhaps it would be best to rename cargo package to cargo bundle, so then we could have bundle.rs scripts and "bundle dependencies" (which I think would be clearly less confusing than "package dependencies"). A hurdle with this approach would be that there already exists a 3rd party cargo-bundle command, so that 3rd party command would have to change its name.

Further thinking on this I actually think cargo pack would be a better name (and there is precedence for that with npm-pack). So in that case we'd have pack.rs and pack-dependencies in Cargo.toml.

I don't think we'd need a separate kind of dependencies. We can just use dev-dependencies, tag them with package = true or similar, and then cargo package can delete those dependencies from the manifest when generating the package for upload.

Right that would also work. However I think having them in a [pack-dependencies] section would be a bit more intuitive since it would be consistent with how we specify dependencies for build.rs scripts.

I think ideally we would have the following consistency:

Command Script filename Script filename config Dependency section
cargo build build.rs package.build [build-dependencies]
cargo pack pack.rs package.pack [pack-dependencies]

This makes conceptualizing the two different kinds of scripts very easy. I think it would be a bit awkward if pack dependencies would have to be specified under dev-dependencies. The Cargo reference currently states:

Dev-dependencies are not used when compiling a package for building, but are used for compiling tests, examples, and benchmarks.

Pack dependencies are however used when compiling a package for building when running cargo run or cargo build locally in a repository or when depending on a package via a git or path dependency. Rather than adding such a complicated foonote, I think it would make sense to simply have them in a separate section. (Which of course could still be removed by cargo pack).

It would have the advantage that any other tool parsing a cargo manifest and looking for dependencies would automatically pick them up, whereas a separate section would get ignored.

A more intuitive Manifest format would benefit Rust developers in perpetuity, while the updating of the majority of existing 3rd-party tools to recognize the new section could probably be achieved within a year. So I think it makes sense to prioritize the former.

1 Like