| # Contributing to `bindgen` | |
| Hi! We'd love to have your contributions! If you want help or mentorship, reach | |
| out to us in a GitHub issue, or stop by | |
| [#rust on chat.mozilla.org](https://chat.mozilla.org/#/room/#rust:mozilla.org) | |
| and introduce yourself. | |
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | |
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | |
| - [Code of Conduct](#code-of-conduct) | |
| - [Filing an Issue](#filing-an-issue) | |
| - [Looking to Start Contributing to `bindgen`?](#looking-to-start-contributing-to-bindgen) | |
| - [Prerequisites](#prerequisites) | |
| - [`rustfmt` / `cargo fmt`](#rustfmt--cargo-fmt) | |
| - [Building](#building) | |
| - [Testing](#testing) | |
| - [Overview](#overview) | |
| - [Testing Bindings Generation](#testing-bindings-generation) | |
| - [Testing Generated Bindings](#testing-generated-bindings) | |
| - [Testing a Single Header's Bindings Generation and Compiling its Bindings](#testing-a-single-headers-bindings-generation-and-compiling-its-bindings) | |
| - [Authoring New Tests](#authoring-new-tests) | |
| - [Test Expectations and `libclang` Versions](#test-expectations-and-libclang-versions) | |
| - [Integration Tests](#integration-tests) | |
| - [Fuzzing `bindgen` with `csmith`](#fuzzing-bindgen-with-csmith) | |
| - [Property tests for `bindgen` with `quickchecking`](#property-tests-for-bindgen-with-quickchecking) | |
| - [Code Overview](#code-overview) | |
| - [Implementing new options using `syn`](#implementing-new-options-using-syn) | |
| - [Pull Requests and Code Reviews](#pull-requests-and-code-reviews) | |
| - [Generating Graphviz Dot Files](#generating-graphviz-dot-files) | |
| - [Debug Logging](#debug-logging) | |
| - [Using `creduce` to Minimize Test Cases](#using-creduce-to-minimize-test-cases) | |
| - [Getting `creduce`](#getting-creduce) | |
| - [Isolating Your Test Case](#isolating-your-test-case) | |
| - [Writing a Predicate Script](#writing-a-predicate-script) | |
| - [Cutting a new bindgen release](#cutting-a-new-bindgen-release) | |
| - [Updating the changelog](#updating-the-changelog) | |
| - [Merge to `main`](#merge-to-main) | |
| - [Tag and publish](#tag-and-publish) | |
| - [Create a new release on Github](#create-a-new-release-on-github) | |
| - [What to do if a Github release fails](#what-to-do-if-a-github-release-fails) | |
| - [Create a new crates.io release](#create-a-new-cratesio-release) | |
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | |
| ## Code of Conduct | |
| We abide by the [Rust Code of Conduct][coc] and ask that you do as well. | |
| [coc]: https://www.rust-lang.org/policies/code-of-conduct | |
| ## Filing an Issue | |
| Think you've found a bug? File an issue! To help us understand and reproduce the | |
| issue, provide us with: | |
| - A (preferably reduced) C/C++ header file that reproduces the issue | |
| - The `bindgen` flags used to reproduce the issue with the header file | |
| - The expected `bindgen` output | |
| - The actual `bindgen` output | |
| - The [debugging logs](#debug-logging) generated when running `bindgen` on this testcase | |
| ## Looking to Start Contributing to `bindgen`? | |
| - [Issues labeled "easy"](https://github.com/rust-lang/rust-bindgen/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy) | |
| - [Issues labeled "less easy"](https://github.com/rust-lang/rust-bindgen/issues?q=is%3Aopen+is%3Aissue+label%3AE-less-easy) | |
| - [Issues labeled "help wanted"](https://github.com/rust-lang/rust-bindgen/labels/help%20wanted) | |
| - Still can't find something to work on? [Drop a comment here](https://github.com/rust-lang/rust-bindgen/issues/747) | |
| ## Prerequisites | |
| ### `rustfmt` / `cargo fmt` | |
| We use `nightly` channel for `rustfmt`, | |
| so please set the appropriate setting in your editor/IDE for that. | |
| For rust-analyzer, you can set `rustfmt.extraArgs = ['+nightly']`. | |
| To check via command line, you can run `cargo +nightly fmt --check`. | |
| ## Building | |
| To build the `bindgen` library and the `bindgen` executable: | |
| ```sh | |
| cargo build | |
| ``` | |
| If you installed multiple versions of llvm, it may not be able to locate the | |
| latest version of `libclang`. In that case, you may want to either uninstall other | |
| versions of llvm, or specify the path of the desired `libclang` explicitly: | |
| ```sh | |
| export LIBCLANG_PATH=path/to/clang-16.0/lib | |
| ``` | |
| ## Testing | |
| ### Overview | |
| Input C/C++ test headers reside in the `bindgen-tests/tests/headers` directory. Expected | |
| output Rust bindings live in `bindgen-tests/tests/expectations/tests`. For example, | |
| `bindgen-tests/tests/headers/my_header.h`'s expected generated Rust bindings would be | |
| `bindgen-tests/tests/expectations/tests/my_header.rs`. | |
| There are also some integration tests in the `./bindgen-integration` crate, which uses `bindgen` to | |
| generate bindings to some C++ code, and then uses the bindings, asserting that | |
| values are what we expect them to be, both on the Rust and C++ side. | |
| The generated and expected bindings are formatted with [`prettyplease`] before they are | |
| compared. It is a default (but optional) dependency of `bindgen`, | |
| so be sure to keep that in mind | |
| (if you built `bindgen` with the `--no-default-features` option of Cargo). | |
| Note also that `rustfmt` formatting is disabled for the `bindgen-tests/tests/expectations/` | |
| directory tree, which helps avoid failing ui tests. | |
| Note: running `cargo test` from the root directory of `bindgen`'s repository does not | |
| automatically test the generated bindings or run the integration tests. | |
| These steps must be performed manually when needed. | |
| ### Testing Bindings Generation | |
| To regenerate bindings from the corpus of test headers in `bindgen-tests/tests/headers` and | |
| compare them against the expected bindings in `bindgen-tests/tests/expectations/tests`, run: | |
| ```sh | |
| cargo test | |
| ``` | |
| As long as you aren't making any changes to `bindgen`'s output, running this | |
| should be sufficient to test your local modifications. | |
| You may set the `BINDGEN_OVERWRITE_EXPECTED` environment variable to overwrite | |
| the expected bindings with `bindgen`'s current output: | |
| ```sh | |
| BINDGEN_OVERWRITE_EXPECTED=1 cargo test | |
| ``` | |
| If you set the `BINDGEN_TESTS_DIFFTOOL` environment variable, `cargo test` will | |
| execute `BINDGEN_TESTS_DIFFTOOL /path/of/expected/output /path/of/actual/output` | |
| when the expected output differs from the actual output. You can use this to | |
| hand check differences by setting it to e.g. "meld" (assuming you have meld | |
| installed). | |
| If you're not changing command line arguments, you may want to set | |
| `BINDGEN_DISABLE_ROUNDTRIP_TEST` to avoid a lot of tests for round-tripping of | |
| those. | |
| ### Testing Generated Bindings | |
| If your local changes are introducing expected modifications in the | |
| `bindgen-tests/tests/expectations/tests/*` bindings files, then you should test that the | |
| generated bindings files still compile, and that their struct layout tests still | |
| pass. Also, run the integration tests (see below). | |
| You can do this with these commands: | |
| ```sh | |
| cd bindgen-tests/tests/expectations | |
| cargo test | |
| ``` | |
| ### Testing a Single Header's Bindings Generation and Compiling its Bindings | |
| Note: You will need to install [graphviz](https://graphviz.org/) since that | |
| is a dependency for running `test-one.sh`. | |
| Sometimes it's useful to work with one test header from start (generating | |
| bindings for it) to finish (compiling the bindings and running their layout | |
| tests). This can be done with the `bindgen-tests/tests/test-one.sh` script. It supports fuzzy | |
| searching for test headers. For example, to test | |
| `tests/headers/what_is_going_on.hpp`, execute this command: | |
| ```sh | |
| ./bindgen-tests/tests/test-one.sh going | |
| ``` | |
| Note that `test-one.sh` does not recompile `bindgen`, so if you change the code, | |
| you'll need to rebuild it before running the script again. | |
| ### Authoring New Tests | |
| To add a new test header to the suite, simply put it in the `bindgen-tests/tests/headers` | |
| directory. Next, run `bindgen` to generate the initial expected output Rust | |
| bindings. Put those in `bindgen-tests/tests/expectations/tests`. | |
| If your new test requires certain flags to be passed to `bindgen`, you can | |
| specify them at the top of the test header, with a comment like this: | |
| `new_test_header.hpp`: | |
| ```c | |
| // bindgen-flags: --enable-cxx-namespaces -- -std=c++14 | |
| ``` | |
| Then verify the new Rust bindings compile and pass their layout tests: | |
| ```sh | |
| cd bindgen-tests/tests/expectations | |
| cargo test new_test_header | |
| ``` | |
| ### Test Expectations and `libclang` Versions | |
| If a test generates different bindings across different `libclang` versions (for | |
| example, because we take advantage of better/newer APIs when possible), then you | |
| can add multiple test expectations, one for each supported `libclang` | |
| version. Instead of having a single `bindgen-tests/tests/expectations/tests/my_test.rs` file, | |
| add each of: | |
| - `bindgen-tests/tests/expectations/tests/libclang-20/my_test.rs` | |
| - `bindgen-tests/tests/expectations/tests/libclang-16/my_test.rs` | |
| If you need to update the test expectations for a test file that generates | |
| different bindings for different `libclang` versions, you *don't* need to have | |
| many versions of `libclang` installed locally. Just make a work-in-progress pull | |
| request, and then when CI fails, it will log a diff of the | |
| expectations. Use the diff to patch the appropriate expectation file locally and | |
| then update your pull request. | |
| Usually, `bindgen`'s test runner can infer which version of `libclang` you | |
| have. If for some reason it can't, you can force a specific `libclang` version | |
| to check the bindings against with a cargo feature: | |
| ```sh | |
| cargo test --features __testing_only_libclang_$VERSION | |
| ``` | |
| depending on which version of `libclang` you have installed. | |
| ### Integration Tests | |
| The `./bindgen-integration` crate uses `bindgen` to | |
| generate bindings to some C++ code, and then uses the bindings, asserting that | |
| values are what we expect them to be, both on the Rust and C++ side. | |
| To run the integration tests, issue the following: | |
| ```sh | |
| cd bindgen-integration | |
| cargo test | |
| ``` | |
| ### Fuzzing `bindgen` with `csmith` | |
| We <3 finding hidden bugs and the people who help us find them! One way to help | |
| uncover hidden bugs is by running `csmith` to generate random headers to test | |
| `bindgen` against. | |
| See [./csmith-fuzzing/README.md](./csmith-fuzzing/README.md) for details. | |
| ### Property tests for `bindgen` with `quickchecking` | |
| The `tests/quickchecking` crate generates property tests for `bindgen`. | |
| From the crate's directory you can run the tests with `cargo run`. For details | |
| on additional configuration including how to preserve / inspect the generated | |
| property tests, see | |
| [`./tests/quickchecking/README.md`](./tests/quickchecking/README.md). | |
| ## Code Overview | |
| `bindgen` takes C and C++ header files as input and generates corresponding Rust | |
| `#[repr(C)]` type definitions and `extern` foreign function declarations. | |
| First, we use `libclang` to parse the input headers. See `src/clang.rs` for our | |
| Rust-y wrappers over the raw C `libclang` API that the `clang-sys` crate | |
| exposes. We walk over `libclang`'s AST and construct our own internal | |
| representation (IR). The `ir` module and submodules (`src/ir/*`) contain the IR | |
| type definitions and `libclang` AST into IR parsing code. | |
| The umbrella IR type is the `Item`. It contains various nested `enum`s that let | |
| us drill down and get more specific about the kind of construct that we're | |
| looking at. Here is a summary of the IR types and their relationships: | |
| - `Item` contains: | |
| - An `ItemId` to uniquely identify it. | |
| - An `ItemKind`, which is one of: | |
| - A `Module`, which is originally a C++ namespace and becomes a Rust | |
| module. It contains the set of `ItemId`s of `Item`s that are defined | |
| within it. | |
| - A `Type`, which contains: | |
| - A `Layout`, describing the type's size and alignment. | |
| - A `TypeKind`, which is one of: | |
| - Some integer type. | |
| - Some float type. | |
| - A `Pointer` to another type. | |
| - A function pointer type, with `ItemId`s of its parameter types | |
| and return type. | |
| - An `Alias` to another type (`typedef` or `using X = ...`). | |
| - A fixed size `Array` of `n` elements of another type. | |
| - A `Comp` compound type, which is either a `struct`, `class`, | |
| or `union`. This is potentially a template definition. | |
| - A `TemplateInstantiation` referencing some template definition | |
| and a set of template argument types. | |
| - Etc... | |
| - A `Function`, which contains: | |
| - An ABI | |
| - A mangled name | |
| - a `FunctionKind`, which describes whether this function is a plain | |
| function, method, static method, constructor, destructor, etc. | |
| - The `ItemId` of its function pointer type. | |
| - A `Var` representing a static variable or `#define` constant, which | |
| contains: | |
| - Its type's `ItemId` | |
| - Optionally, a mangled name | |
| - Optionally, a value | |
| - An optional `clang::SourceLocation` that holds the first source code | |
| location where the `Item` was encountered. | |
| The IR forms a graph of interconnected and inter-referencing types and | |
| functions. The `ir::traversal` module provides IR graph traversal | |
| infrastructure: edge kind definitions (base member vs field type vs function | |
| parameter, etc...), the `Trace` trait to enumerate an IR thing's outgoing edges, | |
| various traversal types. | |
| After constructing the IR, we run a series of analyses on it. These analyses do | |
| everything from allocate logical bitfields into physical units, compute for | |
| which types we can `#[derive(Debug)]`, to determining which implicit template | |
| parameters a given type uses. The analyses are defined in | |
| `src/ir/analysis/*`. They are implemented as fixed-point algorithms, using the | |
| `ir::analysis::MonotoneFramework` trait. | |
| The final phase is generating Rust source text from the analyzed IR, and it is | |
| defined in `src/codegen/*`. We use the `quote` crate, which provides the `quote!{ ... }` | |
| macro for quasi-quoting Rust forms. Some options that affect the | |
| generated Rust code are implemented using the [`syn`](https://docs.rs/syn) crate. | |
| ### Implementing new options using `syn` | |
| If a new option can be implemented using the `syn` crate it should be added to | |
| the `codegen::postprocessing` module by following these steps: | |
| - Introduce a new field to `BindgenOptions` for the option. | |
| - Write a free function inside `codegen::postprocessing` implementing the | |
| option. This function with the same name of the `BindgenOptions` field. | |
| - Add a new value to the `codegen::postprocessing::PASSES` for the option using | |
| the `pass!` macro. | |
| ## Pull Requests and Code Reviews | |
| Ensure that each commit stands alone, and passes tests. This enables better `git | |
| bisect`ing when needed. If your commits do not stand on their own, then rebase | |
| them on top of the latest main and squash them into a single commit. | |
| All pull requests undergo code review before merging. To request review, comment | |
| `r? @github_username_of_reviewer`. They we will respond with `r+` to approve the | |
| pull request, or may leave feedback and request changes to the pull request. Any | |
| changes should be squashed into the original commit. | |
| Unsure who to ask for review? Ask any of: | |
| - `@emilio` | |
| - `@pvdrz` | |
| More resources: | |
| - [Servo's GitHub Workflow](https://github.com/servo/servo/wiki/Github-workflow) | |
| - [Beginner's Guide to Rebasing and Squashing](https://github.com/servo/servo/wiki/Beginner's-guide-to-rebasing-and-squashing) | |
| ## Generating Graphviz Dot Files | |
| We can generate [Graphviz](http://graphviz.org/pdf/dotguide.pdf) dot files from | |
| our internal representation of a C/C++ input header, and then you can create a | |
| PNG or PDF from it with Graphviz's `dot` program. This is very useful when | |
| debugging bindgen! | |
| First, make sure you have Graphviz and `dot` installed: | |
| ```sh | |
| brew install graphviz # OS X | |
| sudo dnf install graphviz # Fedora | |
| # Etc... | |
| ``` | |
| Then, use the `--emit-ir-graphviz` flag to generate a `dot` file from our IR: | |
| ```sh | |
| cargo run -- example.hpp --emit-ir-graphviz output.dot | |
| ``` | |
| Finally, convert the `dot` file to an image: | |
| ```sh | |
| dot -Tpng output.dot -o output.png | |
| ``` | |
| The final result will look something like this: | |
| [](./example-graphviz-ir.png) | |
| ## Debug Logging | |
| To help debug what `bindgen` is doing, you can define the environment variable | |
| `RUST_LOG=bindgen` to get a bunch of debugging log spew. | |
| ```sh | |
| RUST_LOG=bindgen ./target/debug/bindgen [flags...] ~/path/to/some/header.h | |
| ``` | |
| This logging can also be used when debugging failing tests: | |
| ```sh | |
| RUST_LOG=bindgen cargo test | |
| ``` | |
| ## Using `creduce` to Minimize Test Cases | |
| If you find a test case that triggers an unexpected panic in `bindgen`, causes | |
| `bindgen` to emit bindings that won't compile, define structs with the wrong | |
| size/alignment, or results in any other kind of incorrectness, then using | |
| `creduce` can help reduce the test case to a minimal one that still exhibits | |
| that same bad behavior. | |
| ***Reduced test cases are SUPER helpful when filing bug reports!*** | |
| ### Getting `creduce` | |
| Often, you can install `creduce` from your OS's package manager: | |
| ```sh | |
| sudo apt install creduce | |
| brew install creduce | |
| # Etc... | |
| ``` | |
| Otherwise, follow [these instructions](https://github.com/csmith-project/creduce/blob/master/INSTALL.md) for building and/or installing `creduce`. | |
| Running `creduce` requires two things: | |
| 1. Your isolated test case, and | |
| 2. A script to act as a predicate script describing whether the behavior you're | |
| trying to isolate occurred. | |
| With those two things in hand, running `creduce` looks like this: | |
| ```sh | |
| creduce ./predicate.sh ./isolated-test-case.h | |
| ``` | |
| ### Isolating Your Test Case | |
| If you're using `bindgen` as a command line tool, pass | |
| `--dump-preprocessed-input` flag. | |
| If you're using `bindgen` as a Rust library, invoke the | |
| `bindgen::Builder::dump_preprocessed_input` method where you call | |
| `bindgen::Builder::generate`. | |
| Afterwards, there should be a `__bindgen.i` or `__bindgen.ii` file containing | |
| the combined and preprocessed input headers, which is usable as an isolated, | |
| standalone test case. | |
| ### Writing a Predicate Script | |
| Writing a `predicate.sh` script for a `bindgen` test case is straightforward. We | |
| already have a general purpose predicate script that you can use, you just have | |
| to wrap and configure it. | |
| ```bash | |
| #!/usr/bin/env bash | |
| # Exit the script with a nonzero exit code if: | |
| # * any individual command finishes with a nonzero exit code, or | |
| # * we access any undefined variable. | |
| set -eu | |
| # Invoke the general purpose predicate script that comes in the | |
| # `bindgen` repository. | |
| # | |
| # You'll need to replace `--whatever-flags` with things that are specific to the | |
| # incorrectness you're trying to pin down. See below for details. | |
| path/to/rust-bindgen/csmith-fuzzing/predicate.py \ | |
| --whatever-flags \ | |
| ./isolated-test-case.h | |
| ``` | |
| When hunting down a particular panic emanating from inside `bindgen`, you can | |
| invoke `predicate.py` like this: | |
| ```bash | |
| path/to/rust-bindgen/csmith-fuzzing/predicate.py \ | |
| --expect-bindgen-fail \ | |
| --bindgen-grep "thread main panicked at '<insert panic message here>'" \ | |
| ./isolated-test-case.h | |
| ``` | |
| Alternatively, when hunting down a bad `#[derive(Eq)]` that is causing `rustc` | |
| to fail to compile `bindgen`'s emitted bindings, you can invoke `predicate.py` | |
| like this: | |
| ```bash | |
| # the rustc-grep argument expects a regex, thus escape where necessary | |
| path/to/rust-bindgen/csmith-fuzzing/predicate.py \ | |
| --bindings-grep NameOfTheStructThatIsErroneouslyDerivingEq \ | |
| --expect-compile-fail \ | |
| --rustc-grep 'error\[E0277\]: the trait bound `f64: std::cmp::Eq` is not satisfied' \ | |
| ./isolated-test-case.h | |
| ``` | |
| Or, when minimizing a failing layout test in the compiled bindings, you can | |
| invoke `predicate.py` like this: | |
| ```bash | |
| path/to/rust-bindgen/csmith-fuzzing/predicate.py \ | |
| --bindings-grep MyStruct \ | |
| --expect-layout-tests-fail \ | |
| --layout-tests-grep "thread 'bindgen_test_layout_MyStruct' panicked" \ | |
| ./isolated-test-case.h | |
| ``` | |
| For details on all the flags that you can pass to `predicate.py`, run: | |
| ```sh | |
| path/to/rust-bindgen/csmith-fuzzing/predicate.py --help | |
| ``` | |
| And you can always write your own, arbitrary predicate script if you prefer. | |
| (Although, maybe we should add extra functionality to `predicate.py` -- file an | |
| issue if you think so!) | |
| `creduce` is *really* helpful and can cut hundreds of thousands of lines of test | |
| case down to 5 lines. | |
| Happy bug hunting and test case reducing! | |
| [More information on using `creduce`.](https://embed.cs.utah.edu/creduce/using/) | |
| ## Cutting a new bindgen release | |
| To cut a release, the following needs to happen: | |
| ### Updating the changelog | |
| Update the CHANGELOG.md file with the changes from the last release. Something | |
| like the following is a useful way to check what has landed: | |
| ```sh | |
| git log --oneline v0.62.0..HEAD | |
| ``` | |
| Also worth checking the [next-release | |
| tag](https://github.com/rust-lang/rust-bindgen/pulls?q=is%3Apr+label%3Anext-release). | |
| It is very important that you do not rename the `Unreleased` section of the | |
| changelog as this will be done automatically using `cargo release` on a further | |
| step. | |
| ### Merge to `main` | |
| For regular releases, the changes above should end up in `main` before | |
| publishing. For dot-releases of an old version (e.g., cherry-picking an | |
| important fix) you can skip this. | |
| ### Tag and publish | |
| Once you're in `main`. Remember to install `doctoc` by running: | |
| ```sh | |
| npm ci | |
| ``` | |
| And then run: | |
| ```sh | |
| cargo release [patch|minor] --no-publish --execute | |
| ``` | |
| This does the following: | |
| - Bump the version. | |
| - Turn the `Unreleased` section of the changelog into the section for the version being released. | |
| - Update the table of contents of the changelog using `doctoc`. | |
| - Tag (`git tag`) the HEAD commit | |
| - Push (`git push`) to GitHub | |
| The `patch` and `minor` refer to semver concepts: | |
| - `patch` would bump **v0.68.1** to **v0.68.2** | |
| - `minor` would bump **v0.68.2** to **v0.69.0** | |
| > NOTE: | |
| > We use the `--no-publish` so that the crates are only published after the release is complete. | |
| > This is automatic, provided the release CI job is successful. | |
| ### Create a new release on Github | |
| The release is automated with the help of `.github/workflows/release.yml`, | |
| and will only be created... | |
| - when a Git tag is pushed | |
| - when all tests succeed | |
| While the tests are still running, | |
| a draft GitHub release will be created, | |
| to avoid notifying watchers of the repo should a CI step fail. | |
| If everything succeeds, | |
| tarballs containing bindgen cli executables for Linux and MacOS | |
| (both for x86 and Arm) will be created. | |
| See `[workspace.metadata.dist]` section in `Cargo.toml` for the configuration. | |
| To update the release configuration, | |
| when a new `cargo-dist` is available: | |
| ```sh | |
| cargo dist init # from "cargo install cargo-dist" | |
| ``` | |
| ### What to do if a Github release fails | |
| If the release process fails after you run `cargo release`, you can manually | |
| delete the tag and release from Github. Also remember to delete the tag locally | |
| by running `git tag -d`. Once all the extra changes are in the `main` branch, | |
| you can trigger a release by creating a new tag using `git tag` and push it | |
| using `git push --tag`. | |
| ### Create a new crates.io release | |
| Go to [the Publish | |
| workflow](https://github.com/rust-lang/rust-bindgen/actions/workflows/publish.yml) | |
| and run a new workflow using the "Run Workflow" button. | |
| Remember that crates.io releases cannot be deleted! | |
| [`prettyplease`]: https://github.com/dtolnay/prettyplease |