How to patch Rust

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

Background

Unfortunately, since rustc is a versioned source package, we are unable to use the more modern git-ubuntu (git-ubuntu(1)) workflow. Whenever you must fix a bug in an already-released Rust source package, you must follow the legacy debdiff(1) workflow instead.

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.

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 Foundations rustc Git 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

Naturally, your higher-level rustc directory won’t have any .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 Patching Process

You may make your changes to the package the same way you would with any other package. 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 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 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.

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

    • This 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 rustc package. 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-Rust

    • This 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:

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

  2. “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:

Note

You can get the source-changes-file script here.

New versioned Rust package:

$ dput ppa:<lpname>/rustc-<X.Y>-merge $(source-changes-file)

Rust backport:

$ dput ppa:<lpname>/rustc-<X.Y>-<release> $(source-changes-file)

Rust patch:

$ dput ppa:<lpname>/rustc-<X.Y>-lp<lp_bug_number> $(source-changes-file)

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” reponse (instead of a “PASS” or “FAIL”), then you just need to retry it.

Creating a Reviewable Diff

Once you’ve verified that your updated package builds properly in a PPA, passes all autopkgtests, and meets Lintian standards, then you’re ready to create a reviewable diff using debdiff(1).

See also

To get more info on the legacy debdiff process in general, consult the Submitting the fix section.

Essentially, since the Git history of rustc-<X.Y> was wiped when it was uploaded as a new package, we need to manually generate a diff between the uploaded version of rustc-<X.Y> and your updated version of rustc that doesn’t rely on Git. To do this, we’ll need .dscs for both package versions.

Build the source package for both the new and old versions:

$ dpkg-buildpackage -S -I -i -nc -d -sa

After that, use debdiff to generate a diff between the two .dscs. Redirect the output to an easily-accessible place:

$ debdiff <old_dsc> <new_dsc> > 1-<new_full_version_number>.debdiff

debdiff patch naming convention

Important

An understanding of Rust-specific version string conventions is necessary for this portion. Read the Rust version strings article before continuing.

Let’s break down an example debdiff patch name: 1-1.86.0+dfsg0ubuntu2-0ubuntu1.debdiff

  • 1- means that this is the first revision of this patch.

  • 1.86.0+dfsg0ubuntu2-0ubuntu1 is the full version number of your updated version.

    • 0ubuntu2 means that the orig tarball has been regenerated after the initial upload. You don’t have to increment this number unless you’ve changed the orig tarball.

    • 0ubuntu1 has been reset, no matter what the previous version number is. This is because the orig tarball was regenerated. You only have to increment this portion of the version number when the orig tarball was the same.

  • The .debdiff suffix is simply a hint that this is a patch. Launchpad will complain (but still allow you to upload the patch) if this is not here.

Here’s another example: 2-1.81.0+dfsg0ubuntu1-0ubuntu3.debdiff

  • 2-: It’s the second revision of this patch, meaning that the sponsor had some feedback and another patch had to be generated.

  • 0ubuntu1: The orig tarball has been unchanged since the initial upload.

  • 0ubuntu3: This package has already been updated once before since its initial upload.

Sharing your changes for review

Instead of opening a merge proposal, you must share your patch directly underneath the bug report.

First, subscribe ubuntu-sponsors to the bug.

Then, click “Add attachment or patch” underneath the bug report description and add your debdiff as an attachment, ticking the box labelled “This attachment contains a solution (patch) for this bug”. As for the comment field itself, all the regular sponsorship request standards apply — include links to the passing autopkgtests, the PPA build, the Lintian results, and the updated Git branch itself.

If you had to regenerate the orig tarball, you must also include the tarball as an attachment to the bug report.