176 lines
5.3 KiB
Markdown
176 lines
5.3 KiB
Markdown
## Contributing to `topgrade`
|
|
|
|
Thank you for your interest in contributing to `topgrade`!
|
|
We welcome and encourage contributions of all kinds, such as:
|
|
|
|
1. Issue reports or feature requests
|
|
2. Documentation improvements
|
|
3. Code (PR or PR Review)
|
|
|
|
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
|
for commit messages.
|
|
|
|
## Adding a new `step`
|
|
|
|
In `topgrade`'s term, package manager is called `step`.
|
|
To add a new `step` to `topgrade`:
|
|
|
|
1. Add a new variant to
|
|
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/main/src/step.rs)
|
|
|
|
```rust
|
|
pub enum Step {
|
|
// Existing steps
|
|
// ...
|
|
|
|
// Your new step here!
|
|
// Make sure it stays sorted alphabetically because that looks great :)
|
|
Xxx,
|
|
}
|
|
```
|
|
|
|
2. Implement the update function
|
|
|
|
You need to find the appropriate location where this update function goes, it should be
|
|
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/main/src/steps),
|
|
the file names are self-explanatory, for example, `step`s related to `zsh` are
|
|
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs).
|
|
|
|
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!
|
|
ctx.execute(xxx)
|
|
.arg(/* args required by this step */)
|
|
.status_checked()
|
|
}
|
|
```
|
|
|
|
Such a update function would be conventionally named `run_xxx()`, where `xxx`
|
|
is the name of the new step, and it should take a argument of type
|
|
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
|
|
needed (You can find some examples where extra arguments are needed
|
|
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
|
|
|
|
Update function would usually do 3 things:
|
|
1. Check if the step is installed
|
|
2. Output the Separator
|
|
3. Invoke the step
|
|
|
|
Still, this is sufficient for most tools, but you may need some extra stuff
|
|
with complicated `step`.
|
|
|
|
3. Add a match arm to `Step::run()`
|
|
|
|
```rust
|
|
Xxx => runner.execute(*self, "xxx", || ItsModule::run_xxx(ctx))?
|
|
```
|
|
|
|
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
|
to separate the steps, for example, for steps that are Linux-only, it goes
|
|
like this:
|
|
|
|
```rust
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
// Xxx is Linux-only
|
|
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
|
}
|
|
```
|
|
|
|
4. Finally, add the step to `default_steps()` in `step.rs`
|
|
```rust
|
|
steps.push(Xxx)
|
|
```
|
|
Try to keep the conditional compilation the same as in the above step 3.
|
|
|
|
Congrats, you just added a new `step` :)
|
|
|
|
## Modification to the configuration entries
|
|
|
|
If your PR has the configuration options
|
|
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/config.rs))
|
|
modified:
|
|
|
|
1. Adding new options
|
|
2. Changing the existing options
|
|
|
|
Be sure to apply your changes to
|
|
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/main/config.example.toml),
|
|
and have some basic documentations guiding user how to use these options.
|
|
|
|
## Breaking changes
|
|
|
|
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:
|
|
|
|
```md
|
|
1. The configuration location has been updated to x.
|
|
|
|
2. The step x has been removed.
|
|
|
|
3. ...
|
|
```
|
|
|
|
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
|
|
|
## Before you submit your PR
|
|
|
|
Make sure your patch passes the following tests on your host:
|
|
|
|
```shell
|
|
$ cargo build
|
|
$ cargo fmt
|
|
$ cargo clippy
|
|
$ cargo test
|
|
```
|
|
|
|
Don't worry about other platforms, we have most of them covered in our CI.
|
|
|
|
## I18n
|
|
|
|
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
|
|
(Tip: ChatGPT or similar LLMs is good at translation). If a message contains
|
|
arguments, e.g., "hello <NAME>", please follow this convention:
|
|
|
|
```yml
|
|
"hello {name}": # key
|
|
en: "hello %{name}" # translation
|
|
```
|
|
|
|
Arguments in the key should be in format `{argument_name}`, and they will have
|
|
a preceeding `%` when used in translations.
|
|
|
|
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
|
|
|
## Some tips
|
|
|
|
1. Locale
|
|
|
|
Some `step` respects locale, which means their output can be in language other
|
|
than English, we should not do check on it.
|
|
|
|
For example, one may want to check if a tool works by doing this:
|
|
|
|
```rust
|
|
let output = Command::new("xxx").arg("--help").output().unwrap();
|
|
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
|
|
|
if stdout.contains("help") {
|
|
// xxx works
|
|
}
|
|
```
|
|
|
|
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.
|