2023-05-26 15:34:20 +08:00
|
|
|
## Contributing to `topgrade`
|
|
|
|
|
|
2024-05-06 13:24:57 +01:00
|
|
|
Thank you for your interest in contributing to `topgrade`!
|
2023-07-14 18:11:59 +02:00
|
|
|
We welcome and encourage contributions of all kinds, such as:
|
2023-05-26 15:34:20 +08:00
|
|
|
|
|
|
|
|
1. Issue reports or feature requests
|
|
|
|
|
2. Documentation improvements
|
|
|
|
|
3. Code (PR or PR Review)
|
|
|
|
|
|
2025-11-08 19:42:04 +01:00
|
|
|
### LLM/AI guidelines
|
|
|
|
|
|
|
|
|
|
You may use LLMs (AI tools) for:
|
|
|
|
|
|
2025-11-09 09:13:27 +01:00
|
|
|
* Inspiration, problem solving, help with Rust, translation, etc.
|
2025-11-08 19:42:04 +01:00
|
|
|
* Generating small and self-contained snippets of code (e.g., shell scripts or utility functions)
|
|
|
|
|
|
|
|
|
|
Do **not** use LLMs to:
|
|
|
|
|
|
|
|
|
|
* Generate ("vibe code") entire pull requests
|
|
|
|
|
* Write or generate issue or pull request descriptions
|
|
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
### General guidelines
|
2023-07-14 18:11:59 +02:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
**Please use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR title**.
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
We use [pre-commit](https://github.com/pre-commit/pre-commit). It runs in CI, but you can optionally install the hook
|
|
|
|
|
locally with `pre-commit install`. If you don't want to use pre-commit, make sure the following pass before submitting
|
|
|
|
|
your PR:
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
$ cargo fmt
|
|
|
|
|
$ cargo clippy
|
|
|
|
|
$ cargo test
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Adding a new step
|
|
|
|
|
|
|
|
|
|
In `topgrade`'s terms, a package manager (or something else that can be upgraded) is called a step.
|
|
|
|
|
To add a new step to `topgrade`:
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2024-05-06 13:24:57 +01:00
|
|
|
1. Add a new variant to
|
2025-07-16 10:16:27 +01:00
|
|
|
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/main/src/step.rs)
|
2023-05-26 15:34:20 +08:00
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
pub enum Step {
|
2025-04-09 04:03:32 +02:00
|
|
|
// Existing steps
|
2023-05-26 15:34:20 +08:00
|
|
|
// ...
|
|
|
|
|
|
|
|
|
|
// Your new step here!
|
2025-04-09 04:03:32 +02:00
|
|
|
// Make sure it stays sorted alphabetically because that looks great :)
|
2023-05-26 15:34:20 +08:00
|
|
|
Xxx,
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. Implement the update function
|
|
|
|
|
|
|
|
|
|
You need to find the appropriate location where this update function goes, it should be
|
2025-07-16 10:16:27 +01:00
|
|
|
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/main/src/steps),
|
2025-11-02 16:40:11 +01:00
|
|
|
the file names are self-explanatory, for example, steps related to `zsh` are
|
|
|
|
|
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs), and steps that run on
|
|
|
|
|
Linux only are placed in [`steps/linux.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/linux.rs).
|
2023-05-26 15:34:20 +08:00
|
|
|
|
|
|
|
|
Then you implement the update function, and put it in the file where it belongs.
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
pub fn run_xxx(ctx: &ExecutionContext) -> Result<()> {
|
|
|
|
|
// Check if this step is installed, if not, then this update will be skipped.
|
|
|
|
|
let xxx = require("xxx")?;
|
|
|
|
|
|
|
|
|
|
// Print the separator
|
|
|
|
|
print_separator("xxx");
|
|
|
|
|
|
|
|
|
|
// Invoke the new step to get things updated!
|
2025-06-24 15:20:29 +02:00
|
|
|
ctx.execute(xxx)
|
2023-05-26 15:34:20 +08:00
|
|
|
.arg(/* args required by this step */)
|
|
|
|
|
.status_checked()
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
Such an update function would be conventionally named `run_xxx()`, where `xxx`
|
|
|
|
|
is the name of the new step, and it should take an argument of type
|
|
|
|
|
`&ExecutionContext`.
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
The update function should usually do 3 things:
|
2025-07-16 10:16:27 +01:00
|
|
|
1. Check if the step is installed
|
2025-11-02 16:40:11 +01:00
|
|
|
2. Output the separator
|
|
|
|
|
3. Execute commands
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
This is sufficient for most tools, but you may need some extra stuff
|
|
|
|
|
for complicated steps.
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-07-16 10:16:27 +01:00
|
|
|
3. Add a match arm to `Step::run()`
|
2023-05-26 15:34:20 +08:00
|
|
|
|
|
|
|
|
```rust
|
2025-07-16 10:16:27 +01:00
|
|
|
Xxx => runner.execute(*self, "xxx", || ItsModule::run_xxx(ctx))?
|
2023-05-26 15:34:20 +08:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
2025-11-02 16:40:11 +01:00
|
|
|
to separate the steps. For example, for steps that are Linux-only, it goes
|
2023-05-26 15:34:20 +08:00
|
|
|
like this:
|
|
|
|
|
|
2025-04-09 04:03:32 +02:00
|
|
|
```rust
|
2023-05-26 15:34:20 +08:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
{
|
|
|
|
|
// Xxx is Linux-only
|
|
|
|
|
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2025-07-16 10:16:27 +01:00
|
|
|
4. Finally, add the step to `default_steps()` in `step.rs`
|
|
|
|
|
```rust
|
|
|
|
|
steps.push(Xxx)
|
|
|
|
|
```
|
2025-11-02 16:40:11 +01:00
|
|
|
Keep the conditional compilation the same as in the above step 3.
|
2025-07-16 10:16:27 +01:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
Congrats, you just added a new step :)
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
### Modification to the configuration entries
|
2023-05-30 18:03:22 +08:00
|
|
|
|
2024-05-06 13:24:57 +01:00
|
|
|
If your PR has the configuration options
|
2025-07-16 10:16:27 +01:00
|
|
|
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/config.rs))
|
2023-05-30 18:03:22 +08:00
|
|
|
modified:
|
|
|
|
|
|
|
|
|
|
1. Adding new options
|
|
|
|
|
2. Changing the existing options
|
|
|
|
|
|
|
|
|
|
Be sure to apply your changes to
|
2025-07-16 10:16:27 +01:00
|
|
|
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/main/config.example.toml),
|
2023-05-30 18:03:22 +08:00
|
|
|
and have some basic documentations guiding user how to use these options.
|
|
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
### Breaking changes
|
2023-12-03 09:52:35 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev].
|
|
|
|
|
It should be written in Markdown and wrapped at 80, for example:
|
2023-12-03 09:52:35 +08:00
|
|
|
|
|
|
|
|
```md
|
|
|
|
|
1. The configuration location has been updated to x.
|
|
|
|
|
|
|
|
|
|
2. The step x has been removed.
|
|
|
|
|
|
|
|
|
|
3. ...
|
|
|
|
|
```
|
|
|
|
|
|
2024-01-23 11:50:02 +08:00
|
|
|
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
|
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
### I18n
|
2024-10-22 08:46:59 +08:00
|
|
|
|
2025-04-09 04:03:32 +02:00
|
|
|
If your PR introduces user-facing messages, we need to ensure they are translated.
|
|
|
|
|
Please add the translations to [`locales/app.yml`][app_yml]. For simple messages
|
|
|
|
|
without arguments (e.g., "hello world"), we can simply translate them according
|
2025-11-02 16:40:11 +01:00
|
|
|
(Tip: LLMs are good at translation). If a message contains
|
2024-10-22 08:46:59 +08:00
|
|
|
arguments, e.g., "hello <NAME>", please follow this convention:
|
|
|
|
|
|
|
|
|
|
```yml
|
2025-07-16 10:16:27 +01:00
|
|
|
"hello {name}": # key
|
2024-10-22 08:46:59 +08:00
|
|
|
en: "hello %{name}" # translation
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Arguments in the key should be in format `{argument_name}`, and they will have
|
2025-08-12 00:15:21 +08:00
|
|
|
a preceding `%` when used in translations.
|
2024-10-22 08:46:59 +08:00
|
|
|
|
|
|
|
|
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
|
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
### Locales
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
Some steps respect locale, which means their output can be in language other
|
|
|
|
|
than English. In those cases, we cannot rely on the output of a command.
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
For example, one may want to check if a tool works by doing this:
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
```rust
|
|
|
|
|
let output = Command::new("xxx").arg("--help").output().unwrap();
|
|
|
|
|
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
if stdout.contains("help") {
|
|
|
|
|
// xxx works
|
|
|
|
|
}
|
|
|
|
|
```
|
2023-05-26 15:34:20 +08:00
|
|
|
|
2025-11-02 16:40:11 +01:00
|
|
|
If `xxx` respects locale, then the above code should work on English system,
|
|
|
|
|
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
|
|
|
|
translated to `"帮助"`, and the above code won't work.
|