Hey thanks for your response. Apologies if I am a bit verbose with the following, just want to make sure I provide enough context to make sense!
My current test structure is like this:
workspace/
- Cargo.toml
- /crate1
- /crate2
- "" (same as crate 1 struct)
The root Cargo.toml looks like
[workspace]
members = [
crate1
crate2
]
And the Cargo.toml's for each crate:
[package]
name = "safe_crate_1"
version = "0.1.1"
edition = "2021"
[lib]
name = "safe"
path = "src/lib.rs"
[[bin]]
name = "safe"
path = "src/main.rs"
Here, the package's version is what I am using to control versioning. So in this case, when the pipeline runs my python script reads the Cargo.toml and increments the version to 0.1.2. I do this by using cargo package. Here is a small glimpse of context.
new_version = ensure_and_bump_version(member_manifest)
cmd = f"cargo package --manifest-path {member_manifest}"
print(f"Running packaging command: {cmd}")
result = c.run(cmd, warn=True)
if result.exited != 0:
print(f"cargo package failed for {member_manifest} with exit code {result.exited}")
exit_code = result.exited
else:
print(f"cargo package succeeded for {member_manifest} with version {new_version}")
packaged_manifests.append(member_manifest)
def collect_crate_files() -> list[str]:
package_dir = os.path.join(manifest_dir, "target", "package")
if not os.path.exists(package_dir):
print(f"Package directory {package_dir} does not exist")
return []
return glob.glob(os.path.join(package_dir, "*.crate"))
# Determine whether this manifest is a workspace root
with c.cd(manifest_dir):
metadata_cmd = f"cargo metadata --format-version 1 --no-deps --manifest-path {manifest_path}"
print(f"Reading workspace metadata: {metadata_cmd}")
metadata_result = c.run(metadata_cmd, warn=True, hide=True)
then my versioning is done with toml
def ensure_and_bump_version(manifest_path: str, default_version: str = "0.1.0") -> str:
"""
Read version from Cargo.toml.
If missing or empty, set to default_version.
Else bump patch version.
Update Cargo.toml with new version.
Return new version string.
"""
with open(manifest_path, "r") as f:
cargo_data = toml.load(f)
if "package" not in cargo_data:
raise RuntimeError(f"No [package] section in {manifest_path}")
old_version = cargo_data["package"].get("version", "").strip()
if not old_version:
new_version = default_version
print(f"No version found in {manifest_path}, setting to default {default_version}")
else:
v = Version(old_version)
new_version = f"{v.major}.{v.minor}.{v.micro + 1}"
print(f"Bumping version in {manifest_path}: {old_version} -> {new_version}")
cargo_data["package"]["version"] = new_version
with open(manifest_path, "w") as f:
toml.dump(cargo_data, f)
return new_version
In this situation, since the source of truth is the Cargo.toml for the version/determining if we are bumping, will it always define itself in this way when a user is trying to publish crates? Can the version always be sourced because in order to publish the Cargo.toml defines the [package]?
Currently I am not updating the version in the Cargo.toml when a new crate is published because I wanted to see if there alternatives to avoid doing a whole cycle of clone, write, push within the pipeline itself in order to keep the end package/crate and associated Cargo.toml in sync. I am a little confused by the central repository phrasing. What I think about is chicken before the egg scenario, where we are merging into main, we get a new version for the crate we package and publish to our registry but the Cargo.toml remains a version behind.
Hopefully that makes more sense. Gitlab Components are super powerful and helpful, but versioning has been a struggle in other areas, for example we also build the docker image in the same pipeline that downstream jobs use and versioning with that has been a nightmare but there has not been a writing back to the versions source of truth.