Skip to content

Conversation

@cottsay
Copy link
Member

@cottsay cottsay commented Oct 13, 2025

This largely mirrors the approach taken by major Linux distributions like Ubuntu and Fedora. We can curate the crates in a way that should allow us to use them in downstream package builds within this colcon workspace or downstream workspaces.

This change doesn't yet add the code necessary to instruct cargo to use our curated registry.


Example of an Ubuntu package:

Details
$ apt-file list librust-serde-dev
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/.cargo-checksum.json
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/.cargo_vcs_info.json
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/Cargo.toml
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/LICENSE-APACHE
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/LICENSE-MIT
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/README.md
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/build.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/crates-io.md
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/format.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/ignored_any.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/impls.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/mod.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/seed.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/size_hint.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/de/value.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/integer128.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/lib.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/macros.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/private/de.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/private/doc.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/private/mod.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/private/ser.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/ser/fmt.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/ser/impls.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/ser/impossible.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/ser/mod.rs
librust-serde-dev: /usr/share/cargo/registry/serde-1.0.195/src/std_error.rs
librust-serde-dev: /usr/share/doc/librust-serde-dev/changelog.Debian.gz
librust-serde-dev: /usr/share/doc/librust-serde-dev/copyright

Example of a Fedora package:

Details
$ dnf repoquery --list rust-serde-devel
Updating and loading repositories:
Repositories loaded.
/usr/share/cargo/registry/serde-1.0.210
/usr/share/cargo/registry/serde-1.0.210/.cargo-checksum.json
/usr/share/cargo/registry/serde-1.0.210/Cargo.toml
/usr/share/cargo/registry/serde-1.0.210/LICENSE-APACHE
/usr/share/cargo/registry/serde-1.0.210/LICENSE-MIT
/usr/share/cargo/registry/serde-1.0.210/README.md
/usr/share/cargo/registry/serde-1.0.210/build.rs
/usr/share/cargo/registry/serde-1.0.210/crates-io.md
/usr/share/cargo/registry/serde-1.0.210/src
/usr/share/cargo/registry/serde-1.0.210/src/de
/usr/share/cargo/registry/serde-1.0.210/src/de/ignored_any.rs
/usr/share/cargo/registry/serde-1.0.210/src/de/impls.rs
/usr/share/cargo/registry/serde-1.0.210/src/de/mod.rs
/usr/share/cargo/registry/serde-1.0.210/src/de/seed.rs
/usr/share/cargo/registry/serde-1.0.210/src/de/size_hint.rs
/usr/share/cargo/registry/serde-1.0.210/src/de/value.rs
/usr/share/cargo/registry/serde-1.0.210/src/format.rs
/usr/share/cargo/registry/serde-1.0.210/src/integer128.rs
/usr/share/cargo/registry/serde-1.0.210/src/lib.rs
/usr/share/cargo/registry/serde-1.0.210/src/macros.rs
/usr/share/cargo/registry/serde-1.0.210/src/private
/usr/share/cargo/registry/serde-1.0.210/src/private/de.rs
/usr/share/cargo/registry/serde-1.0.210/src/private/doc.rs
/usr/share/cargo/registry/serde-1.0.210/src/private/mod.rs
/usr/share/cargo/registry/serde-1.0.210/src/private/ser.rs
/usr/share/cargo/registry/serde-1.0.210/src/ser
/usr/share/cargo/registry/serde-1.0.210/src/ser/fmt.rs
/usr/share/cargo/registry/serde-1.0.210/src/ser/impls.rs
/usr/share/cargo/registry/serde-1.0.210/src/ser/impossible.rs
/usr/share/cargo/registry/serde-1.0.210/src/ser/mod.rs
/usr/share/cargo/registry/serde-1.0.210/src/std_error.rs
/usr/share/cargo/registry/serde-1.0.225
/usr/share/cargo/registry/serde-1.0.225/.cargo-checksum.json
/usr/share/cargo/registry/serde-1.0.225/Cargo.toml
/usr/share/cargo/registry/serde-1.0.225/LICENSE-APACHE
/usr/share/cargo/registry/serde-1.0.225/LICENSE-MIT
/usr/share/cargo/registry/serde-1.0.225/README.md
/usr/share/cargo/registry/serde-1.0.225/build.rs
/usr/share/cargo/registry/serde-1.0.225/crates-io.md
/usr/share/cargo/registry/serde-1.0.225/src
/usr/share/cargo/registry/serde-1.0.225/src/integer128.rs
/usr/share/cargo/registry/serde-1.0.225/src/lib.rs
/usr/share/cargo/registry/serde-1.0.225/src/private
/usr/share/cargo/registry/serde-1.0.225/src/private/de.rs
/usr/share/cargo/registry/serde-1.0.225/src/private/mod.rs
/usr/share/cargo/registry/serde-1.0.225/src/private/ser.rs

@cottsay cottsay self-assigned this Oct 13, 2025
@cottsay cottsay added the enhancement New feature or request label Oct 13, 2025
@codecov-commenter
Copy link

codecov-commenter commented Oct 13, 2025

Codecov Report

❌ Patch coverage is 71.87500% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.41%. Comparing base (518a4f9) to head (cffbe77).

Files with missing lines Patch % Lines
colcon_cargo/task/cargo/build.py 71.87% 3 Missing and 6 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #67      +/-   ##
==========================================
- Coverage   72.46%   72.41%   -0.05%     
==========================================
  Files           9        9              
  Lines         345      377      +32     
  Branches       60       68       +8     
==========================================
+ Hits          250      273      +23     
- Misses         61       64       +3     
- Partials       34       40       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@maspe36 maspe36 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't yet add the code necessary to instruct cargo to use our curated registry.

I am extremely interested in this part. Do you have an example of we expect users to have in their .cargo/config.toml or what they should have in their Cargo.toml to reference these deps?

As I understand it, you cannot mix local registry dependencies, with crates.io dependencies, without either
A. having an index for the local registry
B. Mirroring all of the crates used from crates.io locally and assuming all dependencies come from this local registry

@cottsay
Copy link
Member Author

cottsay commented Oct 13, 2025

This change doesn't yet add the code necessary to instruct cargo to use our curated registry.

I am extremely interested in this part.

Really, this is the biggest "opportunity" for colcon-cargo to deliver a user story that is (thus far) under-developed in existing tooling. I say that not as a judgement, it's just not a story that appears to be important to the Cargo developers.

I'll call out a few "features" of how colcon's install space is shaped:

  1. They generally follow the FHS and layout conventions of the target platform. Python packages, CMake modules and configs, data files, executables, etc., are placed where a platform's user would expect if they were to set --install-base /usr.
  2. Given that --symlink-install wasn't used, the resulting install space from a colcon build is generally a portable directory structure that contains the necessary files for:
    • Executing the intended functionality of the built packages
    • Building downstream packages using the built packages in the form they had when that upstream workspace was built
  3. Files in an install space are (re-)placed, but are never modified. Notably, this means that any "databases" that are created are built up from multiple files at specific locations. For example, adding content to a sqlite database on a per-package basis would violate this feature.

In reference to Cargo behaviors, I draw these conclusions:

  1. Our target platforms (namely Debian-based and Fedora-based, but others too) are using Directory Sources to organize the crates that have been curated by the platform maintainers, so following their conventions would mean doing the same.
  2. We must include the crate in the install space in some form for downstream builds to use, and cannot simply reference package sources between dependencies as they sit in colcon's workspace sources. There are really only two options to do this: *.crate files and extracted directories (as is presented here). The cargo docs discuss consuming these using either "Local Registry Sources" or "Directory Sources" (respectively). The former creates a sort of hybrid file index. If we enforce that a given colcon workspace may not carry more than one version of a package with a given name I believe this index meets the requirements outlined in feature (3), but I don't see any benefit at our scale, and the compression cycle for creating and using the *.crate file may impact performance.

There appears to be no official Cargo mechanism for declaring that a package may be acquired from more than one source, so we can't say "use the current workspace, then fall back to my parent workspace(s), then fall back to crates.io". Something will need to make the resolution decision before Cargo is run, and instruct Cargo which registry it should use for the dependency. Developing that pre-resolution mechanism is where my attention lies, and the Cargo instrument through which it communicates to the build invocation would be to use selective patching.

I'd love to have a more in-depth discussion about developing that mechanism further, but to plant the seed, I'm currently hung up on the fact that a single cargo package build can (and some do today) utilize more than one version of a particular package in the dependency tree, and replacing each occurrence of that package with one sourced from a workspace (current or parent) my not satisfy the build. This is in conflict with a colcon user's expectation that if an upstream package is in their workspace(s), it will be used in the build of a downstream package.

@cottsay cottsay force-pushed the cottsay/install-libs branch 2 times, most recently from 14da959 to 66f76c7 Compare October 13, 2025 22:40
@maspe36
Copy link

maspe36 commented Oct 25, 2025

This ended up taking longer to write than I expected, so I did my best to structure it clearly. Hopefully it’s not too much of a wall of text! 😁

Cargo Directory Sources

Details

Our target platforms (...) are using Directory Sources to organize the crates that have been curated by the platform maintainers...

TThat’s true — though there’s an important caveat worth calling out, there is no way to mix curated crates and those from crates-io. Take a look at the Rust page for Debian. They even call this out

To use only the local (Debian) version of crates...

That is to say, your .cargo/config.toml needs this content

[source]
[source.debian-packages]
directory = "/usr/share/cargo/registry"

[source.crates-io]
replace-with = "debian-packages"

Do note, that they are replacing crates-io with the debian-packages source. This means you cannot mix crates from crates-io with crates from the local debian-packages source directory.

All cargo sources require an index (associated with a registry) of some form. Debian could have a .cargo/config.toml like this

[source.debian-packages]
directory = "/usr/share/cargo/registry"

But then you'd have no way from a <crate>/Cargo.toml to actually selectively pull from that directory source.

[dependencies]
# This does _NOT_ work
some-crate = { version = "1.0", source = "debian-packages" }
error: no matching package named `some-crate` found
location searched: crates.io index

Interestingly, you can have crates in these directory sources that are not actually uploaded on crates.io.

# Assume a `.cargo/config.toml` similar to what Debian's Rust page lays out

[dependencies]
# This is on crates.io and vendored locally
rand_core = "0.9.3" 

# This is _not_ on crates.io, but is vendored locally, and does work
my_autogen_msg_crate = "*"

Directory sources are what I'm referring to in B from my initial comment

B. Mirroring all of the crates used from crates.io locally and assuming all dependencies come from this local registry

Unfortunately, that means we can’t use crates directly from crates-io with directory sources as our approach, we have to mirror them locally.


Local Registry Sources

Details

There are really only two options to do this: *.crate files... Local Registry Sources

This option does allow you to mix crates-io crates with local crates. However, it comes with even more overhead than just creating the *.crate files. We would have to maintain a git repository(!!) in our install space, in addition to blowing away caches created in user space (~/.cargo/registry/, mainly anytime source code changes without a version bump. Namely, any edit at all to any .msg or interface related file with code gen).

But, assuming we actually do all of that leg work, we could have user Cargo.toml's that look like this

[dependencies]
rand_core = "0.9.3"
my_autogen_msg_crate = { version="*", registry="A" }
my_crate_from_overlay = { version="*", registry="B" }

Naming these registries would also be a huge challenge...

This approach is what I was referring to in A in my original comment

A. having an index for the local registry


Cargo Feature Gap

In my opinion, Cargo is indeed missing a feature here. I have asked on the rust internals forum and didn't get much traction. The cargo team claims that

Cargo makes a fundamental assumption that registry and git dependencies are immutable

But from my perspective, they already have mutable registries built-in (look no further than patches), just not in an ergonomic way. I have been on and off drafting an RFC to close this gap in Cargo.

On the Topic of Patching

Developing that pre-resolution mechanism is where my attention lies, and the Cargo instrument through which it communicates to the build invocation would be to use selective patching.

We are already using patching today. We don't need either directory sources or local registry sources if patching is the means to the end. The downside to patching is that you need to patch in each workspace, and you have no way to know if you'll use a package from a workspace you've sourced. That generates a flood of warnings which you cannot disable (as of this moment).

I'm sure there is some way to make our patching logic smarter such that this is less of a pain, but then that's code we need to maintain, and behavior we need to explain to colcon-cargo users until the end of time. Speaking from experience with rclrs's current message generation pipeline, this is a huge pain to teach and debug issues.

warning: Patch `action_msgs v2.4.2 (/ws/install/action_msgs/share/action_msgs/rust)` was not used in the crate graph.
Patch `builtin_interfaces v2.4.2 (/ws/install/builtin_interfaces/share/builtin_interfaces/rust)` was not used in the crate graph.
Patch `example_interfaces v0.14.1 (/ws/install/example_interfaces/share/example_interfaces/rust)` was not used in the crate graph.
Patch `rcl_interfaces v2.4.2 (/ws/install/rcl_interfaces/share/rcl_interfaces/rust)` was not used in the crate graph.
Patch `rclrs_example_msgs v0.5.0 (/ws/install/rclrs_example_msgs/share/rclrs_example_msgs/rust)` was not used in the crate graph.
Patch `rosgraph_msgs v2.4.2 (/ws/install/rosgraph_msgs/share/rosgraph_msgs/rust)` was not used in the crate graph.
Patch `service_msgs v2.4.2 (/ws/install/service_msgs/share/service_msgs/rust)` was not used in the crate graph.
Patch `std_msgs v5.8.2 (/ws/install/std_msgs/share/std_msgs/rust)` was not used in the crate graph.
Patch `test_msgs v2.4.2 (/ws/install/test_msgs/share/test_msgs/rust)` was not used in the crate graph.
Patch `unique_identifier_msgs v2.8.1 (/ws/install/unique_identifier_msgs/share/unique_identifier_msgs/rust)` was not used in the crate graph.
Check that the patched package version and available features are compatible
with the dependency requirements. If the patch has a different version from
what is locked in the Cargo.lock file, run `cargo update` to use the new
version. This may also occur with an optional dependency that is not enabled.

Single Source of Truth for a Crate

hung up on the fact that a single cargo package build can (and some do today) utilize more than one version of a particular package in the dependency tree, and replacing each occurrence of that package with one sourced from a workspace (current or parent) may not satisfy the build. This is in conflict with a colcon user's expectation that if an upstream package is in their workspace(s), it will be used in the build of a downstream package.

If I'm understanding you correctly here, this appears to be a fundamental difference in philosophy between colcon and cargo. Because Rust leans so heavily into full source builds and static linking, its completely fine to use multiple versions of a crate in a single build [1]. I suspect this is extremely common and even if a crate author is aware and trying not to, their dependencies probably exhibit this behavior.

I'm not sure we'll ever be able to have complete alignment on this.

Conclusion

I don't think either directory sources or local registry sources are robust solutions to the problems we're facing, without adding significant maintenance cost.

I think effort is best spent trying to improve Cargo and in the meantime, leveraging both -sys like crates for things like builtin_interfaces and wrapping cmake interface packages in a build.rs for local interface packages. I've talked about this a few times in the monthly meetings, but have been delayed on writing a proof of concept for the local interface packages.

Ultimately, I think the less development effort required of colcon itself, the better — it keeps us aligned with Cargo’s ecosystem and lets existing tools continue to “just work.”

Slightly tangential question — wouldn’t having colcon manage either a directory source or a local registry source potentially go against some of the items that are explicitly out of scope for colcon itself?

From the design doc (link)

Install dependencies of the packages which should be processed by colcon.

It seems like trying to vendor or curate crates from crates.io could potentially fall into that category, no?


  1. If you're curious, here is some information on Rust's symbol mangling which supports multiple crates with the same name in a single build
    https://doc.rust-lang.org/rustc/symbol-mangling/v0.html#path-crate-root

@juleskers
Copy link

juleskers commented Nov 10, 2025

Cargo makes a fundamental assumption that registry and git dependencies are immutable

But from my perspective, they already have mutable registries built-in (look no further than patches)

A very quick drive-by comment, I'm afraid you might be reading too much into this.
The fundamental assumption is that uploaded versions stay the same forever (meaning, build caches are safe to do, builds are somewhat reproducible, we won't have an NPM-left-pad, etc.)
So contents are immutable, not where they come from.

Patching a source, and rewriting stuff in that source (e.g. git history rewrites, mutating files) will lead to weirdness if you don't religiously clean your build caches.

Yes, you can use patches to sneakily build with a not-yet-upstreamed bugfix included (but buyer beware).
No, you shouldn't use patches to point at mutable data, unless you plan to do only full, non-incremental, cold-cache (i.e. sloooow) builds.

This largely mirrors the approach taken by major Linux distributions
like Ubuntu and Fedora. We can curate the crates in a way that should
allow us to use them in downstream package builds within this colcon
workspace or downstream workspaces.

This change doesn't yet add the code necessary to instruct cargo to use
our curated registry.
@luca-della-vedova
Copy link
Member

While testing this I noticed that the general approach we have been taking, both here and in colcon-ros-cargo of "just" copying the crate itself into the install folder doesn't play well with Cargo workspace dependencies.
Specifically, let's say you have the following structure in your colcon_ws/src directory:

> rust_ws
--- Cargo.toml
--- crate
-------- Cargo.toml

It's allowed to specify a dependency on the root level Cargo.toml, which is for the cargo workspace:

serde = "1"

And it the workspace members can just refer to it:

serde = { workspace = true }

What Cargo will then do when building the crate is look for the workspace Cargo.toml and use it to define the dependency. However, if we only copy the crate's Cargo.toml the workspace Cargo.toml won't be found, hence we will get errors such as:

error: failed to load source for dependency `crate`                                             

Caused by:
  Unable to update [...]/install/crate/share/cargo/registry/crate-0.0.1

Caused by:
  failed to parse manifest at `[...]/install/crate/share/cargo/registry/crate-0.0.1/Cargo.toml`

Caused by:
  error inheriting `serde` from workspace root manifest's `workspace.dependencies.serde`

This is not a super simple fix, I suppose a way to do it could be to introduce one additional intermediate folder with a custom rust workspace Cargo.toml that creates a workspace with only a single crate. Or in alternative manually resolving the workspace dependencies for each workspace member and setting them to the workspace dependency itself then copying these resolved Cargo.toml files to the install folder, or just be explicit in the fact that we will not tackle this issue just yet and create a ticket to keep track / discuss further.

@Blast545
Copy link

@maspe36 Thank you for your detailed answer in #67 (comment), it really helped me understanding the big picture.

For Cargo directory sources, I think there's a way to mix directory sources with crates.io, PTAL to the CI in: Blast545/test_cargo_pulling_deps#1

I had to use a "bundler_tool" to trick cargo into exporting the local files of the local dependency in a format that can be consumed by the main application (this part probably has to be combined somehow with pallet patcher). But otherwise, after that's process is completed there's only a small addition needed to the main user .cargo/config.toml file:

[registries]
my-curated-ros-registry = { index = "https://non-existent-intranet:8080/git/index" }

[source.my-curated-ros-registry]
registry = "https://non-existent-intranet:8080/git/index"
replace-with = "local-mirror"

[source.local-mirror]
directory = "deps"

@maspe36
Copy link

maspe36 commented Dec 13, 2025

Hey @Blast545, thanks for spending time on this and bringing some fresh air to what feels like a stale problem :)

I believe the big discovery in your approach is that the registry index can essentially be completely empty, which is indeed interesting. Here are my thoughts after spending a few hours familiarizing myself with this approach.

We do not need the bundler_tool because we probably shouldn't use cargo vendor. There are two major obstacles I am aware of with cargo vendor

  1. We inadvertently create entries in the $CARGO_HOME cache.

We could potentially skirt around this by manually specifying this env var while running our cargo commands (or having this be set when the colcon workspace is sourced). But this will also require repopulating this cache with not just our generated interface packages, but any dependency, meaning this folder can be large. My local ~/.cargo cache is ~860M.

  1. Our generated rust crates need to be git repositories

This isn't as simple as running git init in the root of the crate, but also committing changes to the repo. Anytime the source code changes, we'd need to commit, and re-run cargo vendor, otherwise our changes to the source in build/ (or wherever) wouldn't be picked up.

These obstacles can be overcome, but I question what this additional complexity buys us.


Your example can be further simplified to not depend on cargo vendor. I have a structure like this

.
├── build
│   └── msg_pkg
│       ├── .cargo-checksum.json
│       ├── Cargo.toml
│       └── src
│           └── lib.rs
├── .cargo
│   └── config.toml
└── src
    └── foo_node
        ├── Cargo.toml
        └── src
            └── main.rs

And my .cargo/config.toml looks like this

[registries]
my-curated-ros-registry = { index = "https://empty" }

[source.my-curated-ros-registry]
registry = "https://empty"
replace-with = "local-mirror"

[source.local-mirror]
directory = "build/"

foo_node then depends on msg_pkg like this, so far so good.

[dependencies]
msg_pkg = { registry = "my-curated-ros-registry", version = "0.1.0"}

msg_pkg simply defines the example add() function created by default when you run cargo new --lib

// build/msg_pkg/src/lib.rs

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

And finally, foo_node calls this add() function in its main()

// src/foo_node/src/main.rs

use msg_pkg;

fn main() {
    println!("2 + 2 = {}", msg_pkg::add(2, 2));
}

If we build and run, you'll see everything works as expected

$ cargo run
     Locking 1 package to latest Rust 1.91.1 compatible version
   Compiling msg_pkg v0.1.0 (registry `my-curated-ros-registry`)
   Compiling foo_node v0.1.0 (/path/to/foo_node)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
     Running `target/debug/foo_node`
2 + 2 = 4

But, here is where the trouble starts. What if msg_pkg changes? As a test, lets just change the add function to actually subtract the two numbers.

// build/msg_pkg/src/lib.rs

pub fn add(left: u64, right: u64) -> u64 {
    // LGTM
    left - right
}

And rebuild

$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/foo_node`
2 + 2 = 4

No changes were picked up even though we are pointed directly at the source code of msg_pkg and there is no caching shenanigans with $CARGO_HOME... Whats going on?

The critical piece here is this pesky .cargo-checksum.json file. This is a file that cargo itself expects for directory sources (You can read more about this in the Cargo source code itself).

In my example, this is the contents.

{"files":{},"package":null}

The hashes of the files are used by cargo to determine if a rebuild is necessary. Even if we go to the trouble of trying to manage these hashes ourself, cargo will actually just fail in this case as this is not a supported workflow for directory sources.
https://github.com/rust-lang/cargo/blob/master/src/cargo/sources/directory.rs#L239

So the only way for us to actually see our changes is by clearing out the build folder of foo_node

$ rm -rf target/
$ cargo run
   Compiling msg_pkg v0.1.0 (registry `my-curated-ros-registry`)
   Compiling foo_node v0.1.0 (/path/to/foo_node)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/foo_node`
2 + 2 = 0

I believe this is what @juleskers was referring to with this comment

Patching a source, and rewriting stuff in that source (e.g. git history rewrites, mutating files) will lead to weirdness if you don't religiously clean your build caches.


Again, I come to the conclusion that without changes to Cargo itself, we should be looking into "pure" build.rs solutions with crates coming from crates.io.

I recently put up a PR to re-export all of our generated rust crate symbols in rclrs ros2-rust/ros2_rust#556. We'd still need to build with colcon once, namely to populate the $AMENT_PREFIX_PATH so the build script can find these generated crates.

PTAL and let me know what you think :)

@juleskers
Copy link

Considering that a lot of this is figuring out the non-public implementation behaviour of cargo, have you seen the recent writeup by the cargo team?

There is some big work going in in revamping the caching, detection of staleness thereof, and increased value for incremental compilation.
Specifically they are planning on revamping (and eventually stabilising) the build dir layout to accommodate this.
I am unsure how much this affects colcon, or if it may open new strategies.

"inside rust" Writeup:
https://blog.rust-lang.org/inside-rust/2025/11/24/this-development-cycle-in-cargo-1.92/#build-dir-layout

@Blast545
Copy link

Your example can be further simplified to not depend on cargo vendor. I have a structure like this

Yeah, totally. I mainly used cargo vendor to generate the .cargo-checksum.json "properly". Otherwise I would have moved the source files directly as your example. I was concerned just using the source code wouldn't work, for cases like the one that Luca found here: #68. But we can dig into it later if we decide moving forward with this approach.

So the only way for us to actually see our changes is by clearing out the build folder of foo_node

I mean, yes, I agree it's an inconvenience, but how often do developers have to change code related to a lib dependency from their code?

I would be expecting that users packages from the "curated registry" will be making changes to their projects and not directly to the dependencies. If they have to change their dependencies they can push the changes upstream or patch it locally. We can add documentation appropriate for those scenarios.

but I question what this additional complexity buys us.

If we don't need to maintain an additional index for the packages, users of the curated registry can install their "core ROS 2 Rust" with vcs via

vcs import --input https://raw.githubusercontent.com/ros2/ros2/ros2_rust_rolling/ros2.repos src

And the only burden of internal maintenance would be thinking what kind of requirements we want out of the curated registry and adding CI to it. We can request maintainers of the packages in this list to keep their checksum.json files per commit, so we don't use cargo vendor if it's not needed.

If we had to create a formal registry index, host it somewhere and maintain it, that would have thing more complicated on our end.

@Blast545
Copy link

I'm trying to assemble a more complete workflow to highlight how I see this could work in the future. Scott also told me in a meeting that he thinks this might not solve the problems related to colcon itself, so we'll dig into that too.

@luca-della-vedova
Copy link
Member

luca-della-vedova commented Dec 17, 2025

So the only way for us to actually see our changes is by clearing out the build folder of foo_node

I mean, yes, I agree it's an inconvenience, but how often do developers have to change code related to a lib dependency from their code?

I would be expecting that users packages from the "curated registry" will be making changes to their projects and not directly to the dependencies. If they have to change their dependencies they can push the changes upstream or patch it locally. We can add documentation appropriate for those scenarios.

I would argue that this is actually the main case where colcon could offer a useful workflow.
Given that dependencies are always available on crates.io and are automatically downloaded by cargo build, I feel the reason a developer would explicitly clone a dependency in their colcon workspace and build it there would be exactly because they want to push some changes to it and see how that affects their application. They would do that with the assumption that, like other colcon workflows, if a dependency is in their workspace it would take priority over other providers of the same dependency (system, crates.io).
After all, if they didn't want to change their library dependency there is no reason they should pull it in their workspace in the first place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

7 participants