How to update Rust¶
This guide details the process of creating a new versioned rustc Ubuntu package for a new upstream Rust release.
To see the process of backporting Rust, consult the How to backport Rust guide instead.
To see the process of fixing an existing Rust package, consult the How to patch Rust guide instead.
Attention
This is not a guide for updating your system’s Rust toolchain. This guide is intended only for Ubuntu toolchain package maintainers seeking to add new Rust versions to the Ubuntu Archive.
Background¶
The rustc source package, which provides binary packages for the Rust toolchain, is a versioned package. This means that a new source package is created for every Rust release (e.g., rustc-1.83 and rustc-1.84).
These packages are maintained largely in order to support building other Rust packages in the Ubuntu archive. Rust developers seeking to work on their own Rust programs typically use the rustup snap instead.
The default Rust toolchain version used to build Rust packages in the archive is denoted using the rust-defaults package.
High-Level Summary of the Update Process¶
A typical rustc update goes through the following steps:
The source code of the new Rust version is downloaded from upstream, overwriting the old upstream source code.
The existing package patches are refreshed so they apply properly onto the new Rust source code.
Unnecessary vendored dependencies are pruned.
The upstream Rust source is re-downloaded with the new list of files to yank out, overwriting the source code once again, but with the unnecessary files removed.
Substitution Terms¶
From now on, the documentation will contain certain terms within angle brackets which must be replaced with the actual value that applies to your situation.
As an example, let’s assume you are updating rustc-1.84 (upstream version 1.84.0) to rustc-1.85 (upstream version 1.85.1) for Noble Numbat:
<X.Y>: The short Rust version you’re updating to.Example:
1.85
<X.Y.Z>: The long Rust version you’re updating to.Example:
1.85.1
<X.Y_old>: The short Rust version you’re updating from.Example:
1.84
<X.Y.Z_old>: The long Rust version you’re updating from.Example:
1.84.0
<release>: The target Ubuntu release adjective.Example:
noble
<lpuser>: Your Launchpad username. This is also used to refer to your personal Launchpad Git repository’s remote name.<foundations>: Your local Git remote name for the FoundationsrustcGit repository.<lp_bug_number>: The number of the Launchpad bug associated with this upload.
Setting up the Repository Locally¶
This only needs to be done once when setting up a machine for Rust toolchain maintenance for the first time.
Project directory structure¶
Since the Debian build tools generate files in the parent directory of your package source directory, it’s recommended to keep things organized by placing the cloned repository inside of a fresh directory of its own.
Clone the repository inside an existing rustc directory so your file structure looks like the following:
rustc
├── rustc
│ ├── [...]
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── debian
│ └── [...]
├── rustc-<...>.orig.tar.xz
└── rustc-<...>.orig-vendor.tar.xz
Naturally, your higher-level rustc directory won’t have the .orig.tar.xz files yet, but they will be stored there once you start working on the package.
Cloning the Git repository¶
The main repository for all versioned Rust toolchain packages is the Foundations Launchpad Git repository. A branch exists for every single upstream release and backport and serves as a central place to store all Rust toolchain code, regardless of which versioned package a particular branch belongs to.
Clone the Foundations Git repository within your existing parent directory:
$ git clone git+ssh://<lpuser>@git.launchpad.net/~canonical-foundations/ubuntu/+source/rustc
Then, create your own personal Git repository on Launchpad:
$ git remote add <lpuser> git+ssh://<lpuser>@git.launchpad.net/~<lpuser>/ubuntu/+source/rustc
Generally, it’s recommended to use your personal Git repository as a remote backup throughout the process — the update procedure involves multiple rebases, so it’s best to wait pushing to the Foundations repository until you’re done.
The Rust Update¶
This section details the process of the actual Rust package update, which is repeated every time a new upstream Rust release comes out.
Creating a Bug Report¶
In order to publicly track the progress and status of the update, you must create a bug report on Launchpad. The type of bug report you create depends on whether or not this Rust version will be the default for the current devel release.
Bug report for the default release¶
If this Rust version is the target rust-defaults version for devel, then you should create a bug under rust-defaults. This is because you will eventually need to update rust-defaults to point to this new Rust version. As an example, consult this real-life rust-defaults bug report.
Bug report for non-default releases¶
If this Rust version is not the target default for devel, then the process is closer to adding a new package to the archive in general. You can create a general Ubuntu bug tagged with needs-packaging and Wishlist importance. A real-life bug report for rustc-1.86 can be found here. Notice that it is targeted to the appropriate series and tagged accordingly.
Setting Up¶
You’re now ready to start work on the next Rust version. To begin, you must create a new Git branch titled merge-<X.Y>. All new Rust branches for devel use this naming convention.
First, make sure you’re on the previous version’s branch:
$ git fetch --all
$ git checkout merge-<X.Y_old>
Create your new branch, then upload it to your personal Git repo:
$ git checkout -b merge-<X.Y>
$ git push <lpuser> merge-<X.Y>
Getting the New Upstream Rust Source¶
In this step, you get the source code of the new Rust version. The watch file, debian/watch, automates this process.
Updating the changelog and package name¶
Important
The changelog version string is complicated. It’s strongly recommended to consult the Rust version strings article before making this change to ensure you understand the version string.
First, update the changelog at debian/changelog, manually setting the version number:
$ dch -v <X.Y.Z>+dfsg-0ubuntu1
You must also update the versioned package name in the changelog, i.e., rustc-<X.Y_old> -> rustc-<X.Y>
Finally, within the changelog entry, you must declare the new Rust version you’re packaging, including the Launchpad bug number.
Your new changelog entry should look similar to this:
rustc-<X.Y> (<X.Y.Z>+dfsg-0ubuntu1) <release>; urgency=medium
* New upstream version <X.Y.Z> (LP: #<lp_bug_number>)
-- Jane Doe <example@example.com> Thu, 01 Jan 1970 00:00:00 -0000
Important
Make sure <lp_bug_number> matches the bug number you created earlier!
Temporarily including all vendored dependencies¶
Later on in the upgrade process, unwanted vendored dependencies are pruned from the upstream source. Right now, however, we don’t know which dependencies must be pruned, so we must temporarily include all vendored dependencies.
Temporarily modify debian/copyright, commenting out the line in Files-Excluded, which excludes the vendor/ directory from the orig tarball:
--- a/debian/copyright
+++ b/debian/copyright
@@ -10,7 +10,7 @@ Files-Excluded:
# Pre-generated docs
src/tools/rustfmt/docs
# Exclusions on the vendor/ dir should be in Files-Excluded-vendor
- vendor
+# vendor
# DOCX versions of TRPL book prepared for No Starch Press
src/doc/book/nostarch/docx
# Exclude submodules https://github.com/rust-lang/rust/tree/master/src/tools
Important
Remember, this change shouldn’t be committed to version control. It’s just a temporary measure for the next step.
This means that in the next step, all vendored dependencies will be included in the tarball.
Getting the new source and orig tarball with uscan¶
We can use uscan(1) (from the devscripts package) to generate the new orig tarball. This is the upstream source code for the Rust toolchain, filtered to exclude any files listed in Files-Excluded of the debian/copyright file. Files can be excluded for various reasons:
They are vendored dependencies (e.g.
src/gcc) where we prefer to instead use a system version.They are only relevant for executing on platforms unsupported by Ubuntu (e.g. Windows).
They apply to unstable Rust features that we do not yet support, e.g.
src/tools/enzyme.
The orig tarball needs to be rebuilt every time we make a change to the Files-Excluded or when working on a new upstream Rust version. If no such changes are made, the orig tarball can instead be downloaded from Launchpad; it is listed as a file ending in .orig.tar.xz under the for the package, either in the Ubuntu Archive or in the PPA to which it was uploaded (e.g. the “Rust Toolchain” Staging PPA).
To rebuild the orig tarball, run uscan while saving its log somewhere:
$ uscan --download-version <X.Y.Z> -v 2>&1 | tee <path_to_log_output>
The output warns you if any files you’ve excluded from debian/copyright aren’t actually in the original source. Consult the upstream Rust changes to see what happened to that file, and update debian/copyright accordingly, depending on whether it was removed, renamed, or refactored.
This process can take a while. It results in a file with an .orig.tar.xz suffix in your parent rustc directory. Rename it to match the part of your package version number before the first hyphen. For example, if your package name is rustc-1.89, and the package version is 1.89.0+dfsg-0ubuntu1, rename the orig tarball to rustc-1.89_1.89.0+dfsg.orig.tar.xz.
You are now free to restore debian/copyright to its original state, un-commenting the vendor line in Files-Excluded:
$ git restore debian/copyright
Since this particular tarball contains all vendored dependencies and will therefore be different from the final tarball, it should be renamed with an ~old suffix, like so:
$ mv ../rustc-<X.Y>_<X.Y.Z>+dfsg{1,~old}.orig.tar.xz
Updating the source code in your repository¶
uscan just downloads the new Rust source, yanks out the ignored files, and packs the orig tarball. Your actual Git repository hasn’t changed at all yet. To do that, use gbp to import the new Rust source code into your existing repository.
Note
At this point your actual source code moves from <X.Y.Z_old> to <X.Y.Z>.
We use the experimental branch to store the upstream releases. Normally, this would be where the Debian experimental branch is, but we can’t use that because our Rust package is not downstream from Debian. Make sure you reset this branch beforehand, branching it off from your current Git branch:
$ git branch -D experimental
$ git branch experimental
Now, we’re ready to invoke gbp:
$ gbp import-orig \
--no-symlink-orig \
--no-pristine-tar \
--upstream-branch=experimental \
--debian-branch=merge-<X.Y> \
../rustc-<X.Y>_<X.Y.Z>+dfsg~old.orig.tar.xz
Afterwards, you should now see two commits in your Git log stating that your upstream source has been updated.
To be able to return to this point just in case you make a mistake repacking the tarballs, create a branch here for safekeeping:
$ git branch import-old-<X.Y>
$ git push <lpuser> import-old-<X.Y>
Initial Patch Refresh¶
Now that the actual upstream source code has changed, many of the different patches in debian/patches won’t apply cleanly. In this step, you must fix these patches.
Getting the next patch to refresh¶
To identify the next patch that fails to apply, try to push all the patches:
$ quilt push -a
quilt will then stop applying patches right before the patch that fails to apply. You can then force-apply this patch, displaying the conflicts in a Git-style merge conflict format:
$ quilt push -f --merge
The response you get from this command will list all of the patch components which failed to apply.
There are several common reasons why a patch fails to apply. These reasons are listed below.
Surrounding code changed¶
The easiest situation is that the surrounding code was changed.
In this case, you must simply verify that the changes don’t impact the patch itself before re-applying it to this new context.
Patch implemented upstream¶
Sometimes, a patch has changes that were incorporated upstream.
In this case, you can drop the patch entirely by deleting the .patch file itself and removing it from debian/patches/series.
Vendored dependency changed¶
Whenever a vendored dependency gets updated, patches which apply to vendored dependencies won’t be able to find their target files because the file paths have changed.
The easiest solution in this case is to manually edit the target file paths within the .patch file, then try to reapply the patch and solve any remaining conflicts from there.
Targeted code refactored¶
The most difficult case to deal with is when the code impacted by a patch is refactored or replaced entirely.
In this case, you must consult the upstream changes to figure out what replaced it, then act accordingly, modifying or dropping the patch if warranted.
Remember, your goal here is to preserve the intent of the patch. If, for example, a patch disables certain tests which require an internet connection, and those tests get refactored completely, it’s your responsibility to track down the tests which require an internet connection and disable them accordingly.
Pruning unwanted dependencies¶
As mentioned above, we don’t want to include unnecessary dependencies, especially Windows-related crates like windows-sys. This pruning ensures adherence to free software principles, reduces the attack surface of the binary packages, and reduces the binary package size on the end user’s hard drive.
Since we included all vendored dependencies before getting the upstream source, our vendor directory will contain everything. We must now remove the dependencies on things we don’t need.
Generating the pruned vendor tarball component¶
In addition to the standard orig tarball containing all the upstream code, the Rust toolchain Ubuntu package also comes with an additional orig tarball component called vendor. This tarball component contains just the pruned vendored dependencies of the Rust toolchain.
cargo-vendor-filterer is used to generate a pruned vendor directory. It’s recommended to download the rustup snap, use rustup to download the toolchain matching your target version, then install cargo-vendor-filterer for that Rust installation:
# snap install rustup
$ rustup install <X.Y.Z>
$ cargo +<X.Y.Z> install cargo-vendor-filterer
Make sure that ~/.cargo/bin is in your $PATH, otherwise you won’t be able to run cargo-vendor-filterer.
Vendor tarball rule
After that, call the vendor-tarball rule in debian/rules. This uses cargo-vendor-filterer to generate a vendor directory that only contains the dependencies required by supported Ubuntu targets. It then repacks this directory into the vendor tarball component. Make sure you point it to your installed Rust toolchain via RUST_BOOTSTRAP_DIR:
$ RUST_BOOTSTRAP_DIR=~/.rustup/toolchains/<X.Y.Z>-x86_64-unknown-linux-gnu/bin/rustc \
debian/rules vendor-tarball
You should now see a new tarball in the parent directory: ../rustc-<X.Y>_<X.Y.Z>+dfsg.orig-vendor.tar.xz. In later steps, we use this to replace the existing vendor/ directory.
Removing Vendored C Libraries¶
Unlike C, Rust doesn’t have a stable ABI, meaning that dependencies (generally) must be statically linked to the binary. An excellent article by a Gentoo maintainer goes more in depth regarding the conflicts between Linux packaging and static dependencies.
This is relevant because while we must vendor Rust dependencies, we don’t have to vendor the C libraries included within some vendored crates. This can be seen in the Files-Excluded field of debian/copyright:
Files-Excluded:
...
# Embedded C libraries
vendor/blake3-*/c
vendor/curl-sys-*/curl
vendor/libdbus-sys-*/vendor
vendor/libgit2-sys-*/libgit2
...
Subdirectories within vendored crates are being pruned from the orig tarball. They are replaced by the system C libraries, thoughtfully provided in the Ubuntu archive. Considering the exclusion of vendor/libgit2-sys-*/libgit2 above, we can consult debian/control:
Build-Depends:
...
libgit2-dev (>= 1.9.0~~),
libgit2-dev (<< 1.10~~),
...
These two changes form the basis of removing vendored C dependencies.
Finding vendored C dependencies¶
Search for C source files within your newly-pruned vendor tarball:
$ tar -tJf ../rustc-<X.Y>_<X.Y.Z>+dfsg.orig-vendor.tar.xz | grep '\.c$'
Note
Don’t to search your unpacked source directory right now because it contains some things we just pruned in Pruning unwanted dependencies.
Individual C files are likely fine. You’re just looking for entire C libraries that have been bundled in with vendored crates.
Removing C dependencies from the vendored orig tarball¶
Naturally, the process of pruning a vendored C library varies from library to library. As an example, we will use a removal of the bundled oniguruma library from rustc-1.86, which caused some build failures when it wasn’t removed.
To do this, add the C library directory to Files-Excluded-vendor in debian/copyright:
--- a/debian/copyright
+++ b/debian/copyright
@@ -52,6 +52,7 @@ Files-Excluded-vendor:
vendor/libsqlite3-sys-*/sqlcipher
vendor/libz-sys-*/src/zlib*
vendor/lzma-sys*/xz-*
+ vendor/onig_sys*/oniguruma
# Embedded binary blobs
# vendor/jsonpath_lib-*/docs
vendor/mdbook-*/src/front-end/playground_editor
Caution
Remember, this new exclusion should be under Files-Excluded-vendor, not Files-Excluded! The primary tarball doesn’t contain anything in the vendor/ directory.
After that, return to the previous step and regenerate the vendored tarball component. The vendor-tarball rule reads your new debian/copyright and generates a new vendor tarball without the C library source code you just excluded.
Adding the system library as a build dependency¶
We can’t remove a C library needed by a vendored dependency without providing a proper equivalent of said library in its place. Instead, we can use the oniguruma Ubuntu package, libonig-dev. We do this by adding the package to Build-Depends in d/control AND d/control.in:
--- a/debian/control
+++ b/debian/control
@@ -37,6 +37,7 @@ Build-Depends:
libgit2-dev (<< 1.10~~),
libhttp-parser-dev,
libsqlite3-dev,
+ libonig-dev,
# test dependencies:
binutils (>= 2.26) <!nocheck> | binutils-2.26 <!nocheck>,
git <!nocheck>,
--- a/debian/control.in
+++ b/debian/control.in
@@ -37,6 +37,7 @@ Build-Depends:
libgit2-dev (<< 1.10~~),
libhttp-parser-dev,
libsqlite3-dev,
+ libonig-dev,
# test dependencies:
binutils (>= 2.26) <!nocheck> | binutils-2.26 <!nocheck>,
git <!nocheck>,
Making the vendored crate use the system library instead¶
In all likelihood, you’ll need to adjust the vendored crate so it knows to use the system library instead of the bundled one. This can vary greatly, but it usually involves patching the crate’s Cargo.toml or build.rs, so look in those places first.
In the case of onig_sys, we can simply patch it to use the system library by default:
--- a/vendor/onig_sys-69.8.1/build.rs
+++ b/vendor/onig_sys-69.8.1/build.rs
@@ -219,7 +219,7 @@
pub fn main() {
let link_type = link_type_override();
- let require_pkg_config = env_var_bool("RUSTONIG_SYSTEM_LIBONIG").unwrap_or(false);
+ let require_pkg_config = env_var_bool("RUSTONIG_SYSTEM_LIBONIG").unwrap_or(true);
if require_pkg_config || link_type == Some(LinkType::Dynamic) {
let mut conf = Config::new();
Updating the Source Tree Again¶
Now that you have a vendored tarball with all unwanted vendored crates and vendored C libraries removed, you are ready to update your source tree once more.
To recap, your parent directory should contain the following:
rustc-<X.Y>_<X.Y.Z>+dfsg~old.orig.tar.xz: The original upstream source code tarball with an unpruned vendor directory.rustc-<X.Y>_<X.Y.Z>+dfsg.orig-vendor.tar.xz: The pruned vendor directory.
Now update the orig tarball and unpacked source tree to match your pruned files.
Updating the orig tarball¶
Double-check your
debian/copyrightto ensure thatvendoris listed underFiles-Excluded(but NOTFiles-Excluded-vendor).Generate a new orig tarball without the
vendordirectory (because the vendor tarball component provides that directory):$ uscan --download-version <X.Y.Z> -v 2>&1 | tee <path_to_log_output>
This is the version you will be using for the final package upload. Therefore, rename it to the standard orig tarball format:
$ mv ../rustc-<X.Y>_<X.Y.Z>+dfsg{1,}.orig.tar.xz
Source tree update¶
To keep the Git tree clean, rebase all your changes on top of the newly-pruned orig tarballs.
Make a backup just to be safe:
$ git branch backup
Return to the previous Rust version and create a new branch to import the updated tarballs:
$ git checkout merge-<X.Y_old> $ git checkout -b import-new-<X.Y>
The version string in
debian/changelogmust match the names of the generated tarballs. Consultgit log merge-<X.Y>and cherry-pick the commit where you added the changelog entry for<X.Y>:$ git cherry-pick <commit_where_new_changelog_entry_was_added>
Recreate the
experimentalbranch:$ git branch -D experimental $ git branch experimental
Merge our newly-pruned tarballs onto the previous Rust version’s source cleanly. Note the added
componentargument pointinggbpto the vendor tarball component:$ gbp import-orig \ --no-symlink-orig \ --no-pristine-tar \ --upstream-branch=experimental \ --debian-branch=import-new-<X.Y> \ --component=vendor \ ../rustc-<X.Y>_<X.Y.Z>+dfsg.orig.tar.xzFinally, switch back to your actual branch and rebase:
$ git checkout merge-<X.Y> $ git rebase -i import-new-<X.Y>
When consulting the list of Git rebase commands, don’t forget to drop the commit in which you imported the old version of the tarball. Example for
rustc-1.91:drop 0759faf6707 New upstream version 1.91.1+dfsg~old pick 85d8e6af63d Refresh d-0000-ignore-removed-submodules.patch pick f31152ca1b9 Refresh d-0010-cargo-remove-vendored-c-crates.patch [...]
Consulting your
git log, you should see the two newgbpcommits immediately after the creation of the<X.Y>changelog entry.
Verifying your changes¶
To ensure that things were correctly pruned, look at the list of vendored Windows crates:
$ ls -1 vendor | grep 'windows'
You should see a list of windows-related crates. This may seem counter-intuitive — shouldn’t these crates have been pruned from the vendor/ directory?
In actuality, these crates have been pruned. cargo-vendor-filterer replaces pruned crates with stubs – empty crates with the minimum Cargo.toml files required to satisfy cargo whilst building the compiler.
To verify that unwanted crates have been pruned properly, spot-check a few of the Windows crates inside vendor/. They should have an empty lib.rs and have a structure like this:
$ tree vendor/windows_x86_64_msvc-0.53.0
vendor/windows_x86_64_msvc-0.53.0
├── Cargo.toml
└── src
└── lib.rs
2 directories, 2 files
Updating Versioned Package References in Control Files¶
Certain control files, such as debian/control and debian/source/lintian-overrides, list versioned package names. These files must be updated to match the new version numbers.
update-version, in debian/rules, updates all relevant control files automatically. In order to run it, it must be given an up-to-date Rust toolchain via RUST_BOOTSTRAP_DIR:
$ RUST_BOOTSTRAP_DIR=~/.rustup/toolchains/<X.Y.Z>-x86_64-unknown-linux-gnu/bin/rustc \
debian/rules update-version
After running the script, consult git diff and verify that, in debian/control, the two Build-Depends options for a bootstrapping compiler are rustc-<X.Y_old> and rustc-<X.Y>.
After checking that the changes are correct, commit these changes and continue.
After-Repack Patch Refreshes¶
Some of the patches will no longer apply now that more files have been removed. You must refresh all the patches so they once again apply cleanly onto the newly-pruned source.
In general, follow the same protocol as the initial patch refresh. The most common change is dropping patches of pruned vendored files.
Updating XS-Vendored-Sources-Rust¶
Inside of debian/control, there’s a special field called XS-Vendored-Sources-Rust which must be updated. It lists all the vendored crate dependencies, along with their version numbers, on a single line.
The dh-cargo package contains a script for automatically generating this line. Push all your patches, then run the script:
$ quilt push -a
$ CARGO_VENDOR_DIR=vendor/ /usr/share/cargo/bin/dh-cargo-vendored-sources
Replace the existing XS-Vendored-Sources-Rust field in debian/control with this new expected value.
Attention
Make sure there’s still an empty line after the end of the field! Mistakenly dropping the empty line results in a build failure right at the end of the test build.
This is another opportunity to verify that you pruned unwanted crates properly. You shouldn’t see any of the pruned Windows crates in the new XS-Vendored-Sources-Rust field.
Note
If you’re running a pre-versioned Rust Ubuntu release, there’s a chance the cargo installation required by dh-cargo is too old. In this case, don’t use dh-cargo—instead, manually download dh-cargo-vendored-sources (it’s a Perl script) and use it without deb-based installations of Rust, which ensures that the Snap version of rustup is used instead.
Updating vendored copyright overrides¶
debian/copyright contains copyright stanzas for all the vendored dependencies of rustc. However, the crate stubs are “red herrings” for the purposes of debian/copyright. They’re just empty crates; they don’t contain any copyrighted code.
To prevent packaging tools from complaining that the stubbed crates are missing copyright stanzas, run debian/add-vendored-copyright-overrides to automatically update debian/source/lintian-overrides:
$ debian/add-vendored-copyright-overrides
Updating debian/copyright¶
All the new vendor files must be added to debian/copyright. In the following steps, all the missing copyright stanzas will be generated.
Generate the Lintian report¶
Clean up previous build artifacts, build the source package using dpkg-buildpackage(1), then run Lintian, redirecting the output to somewhere convenient:
$ dpkg-buildpackage -S -I -i -nc -d -sa
$ lintian -i -I -E --pedantic | tee <lintian_results_path>
Clean up lintian-overrides¶
Some of the crate stubs are covered by debian/copyright stanzas because of glob matching. For example, if vendor/foo-1.0.2 was included and vendor/foo-1.0.3 was stubbed, then a debian/copyright stanza matching vendor/foo-* covers both. In this case, the autogenerated Lintian override for vendor/foo-1.0.3 triggers a Lintian warning for being mismatched.
Get a list of these unnecessary overrides:
$ grep 'mismatched-override file-without-copyright-information' <lintian_results_path>
All of these overrides can be removed from debian/source/lintian-overrides. After removing the overrides, clean up old build artifacts, rebuild the source package, and re-run Lintian, like you did in the updating-rust-lintian-command step.
Get missing copyright stanzas¶
Getting the missing copyright stanzas is tedious. The debian/lintian-to-copyright.sh script automates it.
Pipe the output from your call to Lintian to the script:
$ cat <lintian_results_path> | debian/lintian-to-copyright.sh
You may need to fill in some fields manually. To find the start date of a GitHub repo, use the GitHub API. For example:
$ curl -s https://api.github.com/repos/<owner>/<repo> | jq '.created_at'
Keep things clean by adding the new d/copyright stanzas alphabetically. It makes things a lot easier in the long run.
Local Build and Bugfixing¶
You’re now ready to try to build rustc using sbuild(1).
First, make sure that all previous build artifacts have been cleaned from your upper-level directory:
$ rm -vf ../*.{debian.tar.xz,dsc,buildinfo,changes,ppa.upload}
$ rm -vf debian/files
$ rm -rf .pc
Then, run the build! Depending on your computer, a full build tends to take about 1-3 hours.
$ sbuild -Ad <release>
Using another PPA to bootstrap¶
Not all rustc releases are necessarily in the archive. Perhaps you’re waiting on a previous version to be upload, or you’re creating a backport which isn’t needed by the subsequent Ubuntu release.
If this applies to you, you must add your PPA as an extra repository to your sbuild command:
$ sbuild -Ad <release> \
--extra-repository="deb [trusted=yes] http://ppa.launchpadcontent.net/<lpuser>/<ppa_name>/ubuntu/ <release> main"
Fixing bugs¶
If the build fails, it’s up to you to figure out why. This will require problem-solving skills and attention to detail.
First, try to find any upstream issues related to the problem on the Rust GitHub page. It’s quite common for non-packaging-related problems to be already known upstream, and you can often find a patch from there.
Searching for failing tests within the build log¶
sbuild saves the build logs to your computer. You can easily jump to the standard output of each failing test by searching for the following within the log:
stdout ----
Running individual tests¶
If the build fails, then sbuild will place you in an interactive shell for debugging. This is extremely useful, as you can change the source code and retry tests without rebuilding the whole thing.
For example, here’s how to re-run all the bootstrap tests:
$ debian/rules override_dh_auto_test-arch RUSTBUILD_TEST_FLAGS="src/bootstrap/"
Here’s how to re-run just the alias_and_path_for_library bootstrap test:
$ debian/rules override_dh_auto_test-arch RUSTBUILD_TEST_FLAGS="src/bootstrap/ --test-args alias_and_path_for_library"
Proper patch header format¶
In order to fix certain bugs, it’s likely you’ll need to create your own patch at some point. It’s important that this patch contains enough information for other people to understand what it’s doing and why it’s doing it.
First, ensure that Debian has not already created an equivalent patch. If so, you can simply use their patch directly. If you need to modify the patch in any way, make sure to add Origin: backport, <Debian VCS patch URL> to the patch header.
Otherwise, you must create your own patch. A template DEP-3 header can be generated using the following command:
$ quilt header -e --dep3 <path/to/patch>
For the most part, you can follow the Debian DEP-3 patch guidelines. However, there are a few extra things you must do:
Debian developers typically don’t use the
This patch header follows DEP-3 [...]line added byquilt. Delete this line.If this patch isn’t something needed to get the new Rust version to build, and you’re instead updating an existing source package, add a
Bug-Ubuntu:line linking to the Launchpad bug.
Common update build issues¶
Unresolved import¶
When building the compiler, you may see an error like the following:
error[E0432]: unresolved import `foo::Bar`
--> /<<PKGBUILDDIR>>/vendor/baz-1.0.4/src/quux.rs:16:5
|
16 | use foo::Bar;
| ^^^^^^^^ no `Bar` in the root
This most commonly happens when there are two different versions of the same vendored crate, and the newer version gets stubbed; e.g.:
vendor/foo-1.0.20(STUB)vendor/foo-1.0.19(NOT A STUB)
In this case, create (or modify) debian/patches/ubuntu/ubuntu-pin-dep-ver.patch, add the Cargo.toml of the offending file’s crate (in this case vendor/baz-1.0.4/Cargo.toml) to the patch, and pin the version to the older one:
--- a/vendor/baz-1.0.4/Cargo.toml
+++ b/vendor/baz-1.0.4/Cargo.toml
@@ -136,3 +136,3 @@
[dependencies.foo]
-version = "1.0.17"
+version = "= 1.0.19"
Lintian Checks¶
Lintian (lintian(1)) checks your source package for bugs and Debian policy violations.
Clean up previous build artifacts then build the source package:
$ dpkg-buildpackage -S -I -i -nc -d -sa
First, check the Lintian output with just the warnings and errors:
$ lintian -i --tag-display-limit 0 2>&1 | tee <path_to_log_file>
Addressing warnings and errors¶
You must address all of these in one way or another. They must either be fixed or added to debian/source/lintian-overrides{,.in}, with a few notable exceptions:
E: rustc-1.86 source: field-too-long Vendored-Sources-RustThis is simply the length of the field. While we would like to change this in the future in
dh-cargo, there’s nothing that can (or should) be done about this for now.
E: rustc-1.86 source: unknown-file-in-debian-source [debian/source/lintian-overrides.in]This is just the file used to generate the Lintian overrides for a given Rust version. It’s completely harmless to have in the source tree.
E: rustc-1.86 source: version-substvar-for-external-package Depends ${binary:Version} cargo-<X.Y> -> rustc [debian/control:*]This is just a fallback for a non-versioned
rustcpackage. While it’s unlikely to ever be used, it’s not a typo, so you don’t need to worry about it.
W: rustc-1.86 source: unknown-field Vendored-Sources-RustThis is a custom field, not a typo.
As for any other warnings or errors, you must figure out whether the lint should be ignored or remedied. Don’t be afraid to ask for help from more experienced package maintainers, or consult the existing Lintian overrides for precedence.
Extra lints¶
Now you can run Lintian with all the pedantic, experimental, and informational lints enabled. It isn’t typically necessary to fix most of the extra lints, but it’s a good idea to check everything and see if there are some ways to improve the package based on these lints.
$ lintian -i -I -E --pedantic
Important
Don’t forget to clean and rebuild the source package before re-running Lintian, otherwise your changes to the package won’t apply!
PPA Build¶
Once everything builds on your local machine and Lintian is satisfied, it’s time to test the package on all architectures by uploading it to a PPA.
Creating a new PPA¶
If this is your first PPA upload for this Rust version, you must create a new PPA using the ppa-dev-tools snap. The PPA name depends on whether you are updating Rust, backporting Rust, or patching Rust.
New versioned Rust package:
$ ppa create rustc-<X.Y>-merge
Rust backport:
$ ppa create rustc-<X.Y>-release
Rust patch:
$ ppa create rustc-<X.Y>-lp<lp_bug_number>
The command should return a URL leading to the PPA. You must go to that Launchpad URL and do two things:
“Change Details” -> Enable all “Processors” (Make sure RISC-V is enabled!)
“Edit PPA Dependencies” -> Set Ubuntu dependencies to “Proposed”
If you are using another PPA to bootstrap, then you must explicitly add this PPA as a dependency in the “Edit PPA Dependencies” menu.
PPA changelog entry¶
Next, add a temporary changelog entry, appending ~ppa<N> to your version number so the PPA version isn’t used in favour of the actual version in the archive:
Note
<N> is just the number of the upload. You may have to fix something and re-upload to this PPA, so you should use ~ppa1 for your first PPA upload, ~ppa2 for your second, etc.
$ dch -bv <X.Y.Z>+dfsg0ubuntu1-0ubuntu1\~ppa<N> \
--distribution "<release>" \
"PPA upload"
Uploading the source package¶
Make sure that your source directory is clean (especially debian/files), then build the source package:
$ dpkg-buildpackage -S -I -i -nc -d -sa
Finally, upload the newly-created source package.
New versioned Rust package:
$ dput ppa:<lpname>/rustc-<X.Y>-merge <path_to_source_changes>
Rust backport:
$ dput ppa:<lpname>/rustc-<X.Y>-<release> <path_to_source_changes>
Rust patch:
$ dput ppa:<lpname>/rustc-<X.Y>-lp<lp_bug_number> <path_to_source_changes>
The PPA will then build the Rust package for all architectures supported by Ubuntu. These builds will highlight any architecture-specific build failures.
Handling early PPA build failures¶
Sometimes, a PPA build on a specific architecture will fail in under 15 minutes with no build log provided. If this happens, there was a Launchpad issue, and you can simply retry the build without consequence.
If the build failed and there is a build log provided, then there was indeed a build failure which you must address.
autopkgtests¶
The comprehensive suite of tests included with the Rust toolchain source code is run every time the package is built. These tests, however, only check to see how the package itself functions — not how the package interacts with other packages in a real Ubuntu system.
autopkgtests fill this role. autopkgtests are tests for the installed package. For a package to migrate from the -proposed pocket to the -release pocket, all autopkgtests must pass.
Currently, the Rust toolchain package has two autopkgtests:
Use the installed Rust toolchain (i.e. this package) to build the current Rust compiler from scratch
Use
cargoto build, test, and run a “Hello, World!” binary crate with a1 + 1 = 2unit test and externalanyhowdependency
Naturally, the first test requires a considerable amount of time and resources. In fact, you’ll likely need to request more resources for the test runner.
Checking autopkgtest resource usage locally¶
We must experimentally verify that the default resources allocated to the autopkgtest test bed are insufficient. By default, autopkgtests are run using limited resources, but select packages can be added to the big_packages list to be granted more resources. This can be done locally using autopkgtest(1) and qemu-system-x86_64(1).
Creating local test beds¶
If this is your first time running Rust autopkgtests locally, you must create two local test beds.
There are multiple Openstack flavours used to run autopkgtests. Compare the default m1.small unit resources with the m1.large unit resources:
Unit |
RAM Size (MB) |
CPU Cores |
Disk Size (GB) |
|---|---|---|---|
|
4096 |
2 |
20 |
|
8192 |
4 |
100 |
Packages in the big_packages list use m1.large. Therefore, we’ll make one testbed modelled after m1.small and another modelled after m1.large and use them to determine whether or not we must add this Rust package to big_packages.
In a convenient place, e.g. ~/test_beds, create the default test bed using autopkgtest-buildvm-ubuntu-cloud(1) and rename it accordingly:
$ autopkgtest-buildvm-ubuntu-cloud -v -r <release>
$ mv autopkgtest-<release>-<arch>.img autopkgtest-<release>-<arch>-default.img
Then, in the same place, create a big_packages test bed:
$ autopkgtest-buildvm-ubuntu-cloud -s 100G --ram-size=8192 --cpus=4 -v -r <release>
$ mv autopkgtest-<release>-<arch>.img autopkgtest-<release>-<arch>-big.img
Verifying the necessity of big_packages¶
First, run the autopkgtests locally using the default test bed to check if the default Openstack flavour resources are sufficient for this build:
Note
The --log-file option is picky. It doesn’t do bash path expansions and the log file needs to exist already.
$ autopkgtest rustc-<X.Y> \
--apt-upgrade \
--shell-fail \
--add-apt-source=ppa:<lpuser>/rustc-<X.Y>-merge \
--log-file=<path/to/log/file> \
-- \
qemu \
--ram-size=4096 \
--cpus=2 \
<path/to/test/bed/autopkgtest-<series>-<arch>-default.img
If the autopkgtests pass, then you are ready to run the autopkgtests for real. No further action is necessary. Otherwise, consult the log for something like the following:
Did not run successfully: signal: 9 (SIGKILL)
rustc exited with signal: 9 (SIGKILL)
If you see this, then the default autopkgtest resources are insufficient. To verify that adding your package to big_packages will fix the problem, you must now try the autopkgtests again using the big testbed you set up earlier:
$ autopkgtest rustc-<X.Y> \
--apt-upgrade \
--shell-fail \
--add-apt-source=ppa:<lpuser>/rustc-<X.Y>-merge \
--log-file=<path/to/log/file> \
-- \
qemu \
--ram-size=8192 \
--cpus=4 \
<path/to/test/bed/autopkgtest-<series>-<arch>-big.img
If these autopkgtests pass, then you have successfully proven that the default autopkgtest runner will not have enough resources.
Getting more resources for the autopkgtests¶
We need to make sure that when the autopkgtests are run for real, they’re run on the larger test bed profile.
To do this, create a merge proposal in the autopkgtest-package-configs repo adding the new Rust version to the list of big_packages, which are granted the 100GB disk space, 8192MiB of memory, and 4 vCPUs we used earlier.
Launchpad may autofill the incorrect default branch. Make sure you double-check the target repository into which you’re merging — you want to merge into autopkgtest-package-configs, NOT autopkgtest-cloud.
The change itself is trivial:
--- a/big_packages
+++ b/big_packages
@@ -174,6 +174,7 @@ rsass
ruby-minitest
ruby-parallel
rustc
+rustc-<X.Y>
rust-ahash
rust-axum/ppc64el
rust-cargo-c/ppc64el
For an example on how to format this merge proposal, you can see a real-life proposal here.
Running the actual PPA autopkgtests¶
To run the autopkgtests for real, run the following command provided by the ppa-dev-tools snap to get links to all the autopkgtests:
$ ppa tests \
ppa:<lpuser>/rustc-<X.Y>-merge \
--release <release> \
--show-url
Click all of the links except i386 to trigger the autopkgtests for each target architecture.
Re-run the same ppa tests ... command to check the status of the autopkgtests themselves.
The infrastructure can be a little flaky at times. If you get a “BAD” response (instead of a “PASS” or “FAIL”), then you just need to retry it.
Uploading the Package¶
You’re nearly ready to request sponsorship. First, it’s your duty to make your sponsor’s job as easy as possible.
Obtaining the right info¶
It’s recommended to create a local draft for your upload comment, so you can make sure everything is correct beforehand.
You need both the Lintian and the autopkgtest results for your package.
Retrieve links to all your passing autopkgtest build logs provided by the autopkgtest command you used earlier.
Get the default Lintian output for the source package by cleaning up all build artifacts, using dpkg-buildpackage to build the source package, then simply running the default Lintian command, redirecting the output to a file for later use:
$ lintian > <path_to_saved_lintian_output_file>
Finally, you can push your merge-<X.Y> branch to the Foundations rustc Launchpad Git remote:
$ git push <foundations> merge-<X.Y>
Compile the following information:
A link to your successfully-built PPA packages
A link to your
merge-<X.Y>branch in the FoundationsrustcLaunchpad Git repositoryA list of any notable packaging changes (not upstream changes). These are changes you made to the package, such as the removal or addition of patches. Nothing routine (such as patch refreshes or vendored dependency pruning) should be added here.
The output of
lintianyou just gotThe links to the build logs of all PPA autopkgtests you just got
The i386 allowlist¶
We must ask an Archive Admin to add the new rustc-<X.Y> package to the i386 allowlist so it can be added to the new upload queue.
Usually, the easiest and fastest thing way of doing this is just messaging an Archive Admin directly, politely asking them to add rustc-<X.Y> to the i386 allowlist and providing them a link to the bug report.
Requesting a review¶
Once your comment is ready, subscribe ubuntu-sponsors to your bug to make it visible for sponsorship and upload.
After that, go to the bug report you originally opened and add a comment providing all the necessary info you compiled earlier.
An example of such a comment can be found at the rustc-1.87 bug report here.
Note
You will likely have difficulties finding a sponsor just by subscribing ubuntu-sponsors. You’re uploading a new package and it’s large and complicated. To get a timely sponsorship, it’s better to reach out to the Foundations or Toolchains teams and personally request sponsorship that way.
Toolchain Availability Page¶
After uploading a new Rust version, you must also update the Rust Toolchain Availability Page with the version you have added.