How to patch Rust

This guide details the process of fixing an existing versioned rustc Ubuntu package.

Attention

This guide assumes that you already have a basic understanding of maintaining Ubuntu packages in general. It only covers the things that make Rust package patching unique.

Background

rustc VCS policy is unique because it is a versioned source package. This means that every new upstream Rust version corresponds to a package with a new, unique name in the Ubuntu Archive.

Therefore, there are two repositories for every rustc version uploaded to the archive:

  1. The Foundations Rust repository: this repository is used for adding new Rust versions to the archive. It tracks the complete history of all rustc packages in Ubuntu, but once a versioned package is uploaded to the archive, updates to that package must be synced back to this repository manually.

  2. The package’s unique git-ubuntu (git-ubuntu(1)) repository (example: rustc-1.93): this is the repository that matches the actual state of the package in the Ubuntu archive. Whenever an update to the package is uploaded, this repository gets updated automatically.

In short, patching an existing rustc release entails modifying and uploading the release’s git-ubuntu repository, then manually copying over the changes to the Foundations Rust repository after your changes are uploaded to the archive.

Substitution terms

From now on, the documentation contains 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 patching rustc-1.94 (upstream version 1.94.1) for 26.10 Stonking Stingray:

<X.Y>

The short Rust version you’re working on.

  • Example: 1.94

<X.Y.Z>

The long Rust version you’re on.

  • Example: 1.94.1

<release>

The target Ubuntu release adjective.

  • Example: stonking

<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 Foundations Rust 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.


Setting up the git-ubuntu repository

Just like the Foundations Rust repository, set up a parent directory, then clone the versioned package repository:

$ mkdir rustc-<X.Y>
$ cd rustc-<X.Y>
$ git-ubuntu clone rustc-<X.Y>
$ cd rustc-<X.Y>

Inspect debian/changelog and make sure the most recent changelog entry version matches the version in the archive.

After that, get the current orig tarball from the archive, so you are able to build the package locally:

$ git-ubuntu export-orig

Create your own new branch referencing the Launchpad bug you’re fixing:

$ git checkout -b lp<lp_bug_number>

Attention

Any patches to rustc must fix at least one Launchpad bug. If your patch doesn’t have a Launchpad bug yet, create one and reference its version number in your changelog.

Patching process

You must make all changes to the package within the git-ubuntu repository, not the Foundations Rust repository.

You may make your changes to the package the same way you would with any other package: How to make changes to a package.

Don’t forget to update the changelog! See the version string format guide for help on selecting the proper version string.

The changelog must also reference the bug you are fixing.

After that, you are ready to test the build locally.

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 uploaded, 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"

A common case is including the Staging PPA for bootstrapping:

$ sbuild -Ad <release> \
    --extra-repository="deb [trusted=yes] http://ppa.launchpad.net/rust-toolchain/staging/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, you’ll likely 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 by quilt. 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.

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. This step is optional if doing a backport, but it is still recommended when doing a backport for the first time, or if there is reason to expect that the backport may need extensive troubleshooting.

Creating a new PPA

If this is your first PPA upload for this Rust version, you can create a new PPA using the ppa-dev-tools snap (it is also possible to use the Launchpad web UI). It is recommended to use the following naming convention for the PPA depending 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:

  1. “Change Details” -> Enable all “Processors” (Make sure RISC-V is enabled!)

  2. “Edit PPA Dependencies” -> Set Ubuntu dependencies to “Security” if doing a backport, or “Proposed” otherwise.

If you are using another PPA to bootstrap, then you must explicitly add this PPA as a dependency in the “Edit PPA Dependencies” menu.

Note

When a new Rust toolchain is uploaded to the Archive for the devel series, it initially goes into the proposed pocket. By configuring your PPA to depend on “Proposed”, the set of available packages in the PPA aligns with what will be available in the Archive. This avoids a situation where the PPA build could succeed but then the Archive build fails due to missing dependencies, or vice versa.

On the other hand, when backporting a Rust toolchain to a stable release, it will be uploaded to the security pocket. In this case, configuring your PPA to depend on “Security” aligns with the dependencies that will be available in the Archive. For this same reason, the Staging PPA is configured to depend on “Security” since it is used for backports.

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>+dfsg-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

You must also verify that none of your changes have interfered with autopkgtests in any way.

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 patched package

Once you are satisfied with your changes, upload the package.

First, push your changes to your personal branch:

$ git push <lpuser>

Then, go to your personal repository list at https://code.launchpad.net/~<lpuser>/+git. Select your repo, select your lp<lp_bug_number> branch, then select “Propose for merging”. Submit your merge proposal there.

Updating the Foundations Rust repository

After your changes have been uploaded to the archive, the Foundations Rust repository must be manually updated to reflect the package’s actual state in the archive.

You must add your changes to the rich Git history of the Foundations repo. First, go to your git-ubuntu repo, switch to the ubuntu/devel branch, and ensure it’s synced with the archive:

$ git checkout ubuntu/devel
$ git pull

Then, create a series of patches for each commit you added since the last version published in the archive:

$ git format-patch pkg/import/<version_string_before_your_changes>..

This command generates a series of numbered patches, which you can then apply to the Foundations Rust repository.

Go to the Foundations Rust repository working tree you set up earlier and switch to the appropriate merge branch:

$ git checkout merge-<X.Y>

Then, apply each patch to the branch in numerical order:

$ git am <path_to_git_ubuntu_repo>/rustc-<X.Y>/rustc-<X.Y>/0001-<patch_name>.patch
$ git am <path_to_git_ubuntu_repo>/rustc-<X.Y>/rustc-<X.Y>/0002-<patch_name>.patch
[...]

Once every patch has been applied, update both your personal remote and the general Foundations remote:

$ git push <lpuser>
$ git push <foundations>

Finally, delete the patches you generated from your git-ubuntu repo:

$ rm <path_to_git_ubuntu_repo>/rustc-<X.Y>/rustc-<X.Y>/00*