Rust crate source bundler


#1

Hello

After getting tired of fighting dependencies manually, I've converted my rust bots to use cargo. That, in turn, required a bundler script to join multiple source files into the one that is sync'd with cg. I ended up cooking a build.rs that did that inside cargo and worked very well. I have now evolved it into a crate at the following repository:

This is an alternative to this script, though this project has no intention of performing any optimization, focusing only on convenience.


#2

I'm not clear on how to use this script? It doesn't seem to be bundling files for me. But I'm also not clear on whether I need a crate, or how to setup my project.


#3

So, cargo has the concept of a "build script" (https://doc.rust-lang.org/cargo/reference/build-scripts.html) where you can plug-in arbitrary pieces of code.

To use this, you add the following to your Cargo.toml file:

[package]
(...)
build = "build.rs"

[build-dependencies]
rustsourcebundler = { git = "https://github.com/lpenz/rust-sourcebundler" }

And create a build.rs very much like the following (changing the file names, crate name):

/*! Bundle mybin.rs and the crate libraries into singlefile.rs */

use std::path::Path;
extern crate rustsourcebundler;
use rustsourcebundler::Bundler;

fn main() {
    let mut bundler: Bundler = Bundler::new(Path::new("src/bin/csbk.rs"),
                                            Path::new("src/bin/singlefile.rs"));
    bundler.crate_name("<crate name>");
    bundler.run();
}

In fact, I'd say that this is more like a library that helps you use cargo's build infrastructure to create the single file than a standalone script.

Advantages I found using cargo:
- cargo test
- working with smaller files (though this has a price in this setup)
- emacs flycheck static analysis depends on it
- it manages the dependencies (the few that you can have anyway...)

Let me know if it's not working. (maybe a more concrete example could help too)


#4

I'm not clear on what csbk.rs should be replaced with. I assumed my main.rs which has the entry point.

I'm also not sure about <crate name> -- is this an input or output name? I created a lib.rs with some mod omd_name in it, but that didn't appear to be enough. Do I need a separate directory structure for my crate and main files?


#5

I'm getting a singlefile.rs out, it doesn't include my modules. I have this structure:

  • cargo.toml
  • bundle.rs
  • src/main.rs
  • src/main/lib.rs
  • src/main/point.rs

lib.rs contains mod point. Does main.ts require something as well?

Sorry if these are basic Rust questions and not about your script itself.


#6

I think I have it working now. I can post what I did a bit later.


#7

Good!

I've added an example subdirectory to the github repository. You can use that as a starting point also.

What the code does is also somewhat simplistic: it replaces the extern crate <crate> in the binary with the contents of lib.rs, while also replacing the pub mod ... lines with the contents of the corresponding rs file, recursively. It works on most cases, AFAICT, by taking advantage of some things that cargo itself is already opinionated about (lib.rs, etc.)


#8

An example is nice. This work is well appreciated!

I hit a problem in the stream yesterday, and we needed to do a workaround. I had a src/bin/main.rs like this:

extern crate botters;
use botters::entry;

fn main() {
	entry::entry()
}

This was causing duplicate mod errors in the emitted singlefile.rs.

407 | pub mod entry {
    | ------------- previous definition of the module `entry` here
...
663 | use entry;
    |     ^^^^^ `entry` reimported here

As a workaround I built a second file:

extern crate botters;
use entry::*;

fn main() {
	entry::entry()
}

This second one isn't actually a valid Rust program, as entry::* doesn't exist -- but it works in the single file mode because they've been packed together.

I'm guessing this has to do with the form of the import, just use botters::entry, the whole thing, not specific names like use botters::entry::foo. Maybe you need to detect that case and just drop the line entirely?


#9

I took a better look at this and opened https://github.com/lpenz/rust-sourcebundler/issues/1

The issue is a bit more involved, because the bundler has to track the embedded modules to know when the whole line can be dropped. The use is valid if you are importing a function that lives inside an embedded module, for instance. I'll try fixing that over the weekend.

Thanks for pointing it out, and for using rust-sourcebundler!


#10

Thank you for taking a look at this.


#11

I wish you had notified me about this!

One of the primary to-dos was always to convert my script into rust/get it supported under cargo! The main hold-up was that I had had issues working with string manipulations in Rust for puzzles, so I was hesitant.

Also, CG has finally started to tackle the issue of release mode compilation, and is now compiling rust in release mode on submission! So the optimisations are no longer neccessary.

Pretty printing and/or minification may still be on the table. But that's a much simpler task in any case.

When I'm done with my assessments (in a few days) I'll take a look at this and see if there are any features in my script that I could port onto yours, likely via fork+PR. (E.g. local (personal) crate support/addressing your issue).

Also is there any particular reason you implemented this for writing a dedicated build.rs instead of specifying the input and output paths purely by configuration in cargo.toml? (I don't know for sure, but my understanding is that there is a way to specify the paths as configurations of your crate within the toml)