Skip to content

Latest commit

 

History

History
120 lines (88 loc) · 3.66 KB

CONTRIBUTING.md

File metadata and controls

120 lines (88 loc) · 3.66 KB

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)

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

    pub enum Step {
        // Existed steps
        // ...
    
        // Your new step here!
        // You may want it to be sorted alphabetically becauses 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, the file names are self-explanatory, for example, steps related to zsh are placed in steps/zsh.rs.

    Then you implement the update function, and put it in the file where it belongs.

    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.run_type()
           .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 (e.g., you need to cleanup after update).

    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. Finally, invoke that update function in main.rs

    runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;

    We use conditional compilation to separate the steps, for example, for steps that are Linux-only, it goes like this:

    #[cfg(target_os = "linux")]
    {
        // Xxx is Linux-only
        runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
    }
    

    Congrats, you just added a new step:)

Before you submit your PR

Make sure your patch passes the following tests on your host:

$ cargo build
$ cargo fmt
$ cargo clippy
$ cargo test

Don't worry about other platforms, we have most of them covered in our CI.

Some tips

  1. Locale

    Some step respects locale, which means their output can be in language other then English, we should not do check on it.

    For example, one may want to check if a tool works by doing this:

    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.