浏览代码

Merge pull request #40 from guttatus/xtask

workflow(prototyper): Use cargo-xtask instead of cargo-make
guttatus 3 月之前
父节点
当前提交
085540a878

+ 4 - 0
.cargo/config.toml

@@ -0,0 +1,4 @@
+[alias]
+xtask = "run --package xtask --release --"
+prototyper = "xtask prototyper"
+test-kernel = "xtask test"

+ 199 - 0
Cargo.lock

@@ -8,6 +8,55 @@ version = "0.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a01ba40421eca6c4f1afcedd8465fba6d9e5ef8e0e13060d0141e4cded4ab4a"
 
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.4.0"
@@ -26,6 +75,52 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "clap"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
 [[package]]
 name = "critical-section"
 version = "1.2.0"
@@ -50,6 +145,18 @@ version = "0.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7fbe69badc2e0dc98ad2787648fa140b5772d24b49e9a6b180a67e1348f7544c"
 
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
 [[package]]
 name = "lock_api"
 version = "0.4.12"
@@ -276,6 +383,12 @@ dependencies = [
  "lock_api",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
 [[package]]
 name = "syn"
 version = "2.0.89"
@@ -308,6 +421,12 @@ version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
 
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
 [[package]]
 name = "vcell"
 version = "0.1.3"
@@ -322,3 +441,83 @@ checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
 dependencies = [
  "vcell",
 ]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "clap",
+]

+ 2 - 1
Cargo.toml

@@ -3,7 +3,8 @@ resolver = "2"
 members = [
   "prototyper",
   "test-kernel",
-  "supervisor"
+  "supervisor", 
+  "xtask"
 ]
 
 [workspace.package]

+ 0 - 68
Makefile.toml

@@ -1,68 +0,0 @@
-[config]
-default_to_workspace = false
-
-[tasks.clean]
-command = "cargo"
-args = ["clean"]
-
-[tasks.prototyper-nemu-build]
-command = "cargo"
-args = ["build", "-prustsbi-prototyper", "--release", "--target", "riscv64imac-unknown-none-elf", "-Zbuild-std=core", "--features=nemu,payload"]
-env = {"RUSTFLAGS"="-C relocation-model=pie -C link-arg=-pie" }
-
-[tasks.prototyper-nemu]
-command = "rust-objcopy"
-args = [
-        "--binary-architecture=riscv64",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper",
-        "--output-target=binary",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin",
-]
-dependencies = ["prototyper-nemu-build"]
-
-[tasks.prototyper-build]
-command = "cargo"
-args = [
-        "build",
-        "-prustsbi-prototyper",
-        "--release",
-        "--target",
-        "riscv64imac-unknown-none-elf",
-        "-Zbuild-std=core",
-]
-env = { "RUSTFLAGS" = "-C relocation-model=pie -C link-arg=-pie" }
-
-[tasks.prototyper]
-command = "rust-objcopy"
-args = [
-        "--binary-architecture=riscv64",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper",
-        "--output-target=binary",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-prototyper.bin",
-]
-dependencies = ["prototyper-build"]
-
-[tasks.test-kernel-build]
-command = "cargo"
-args = ["build", "-prustsbi-test-kernel", "--release"]
-env = { "RUSTFLAGS"= { unset = true }}
-
-[tasks.test-kernel]
-command = "rust-objcopy"
-args = [
-        "--binary-architecture=riscv64",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel",
-        "--output-target=binary",
-        "target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.bin",
-]
-dependencies = ["test-kernel-build"]
-
-[tasks.test-kernel-itb]
-script = '''
-cp test-kernel/scripts/rustsbi-test-kernel.its target/riscv64imac-unknown-none-elf/release
-cd target/riscv64imac-unknown-none-elf/release/
-mkimage -f rustsbi-test-kernel.its  rustsbi-test-kernel.itb
-rm rustsbi-test-kernel.its
-cd ../../../
-'''
-dependencies = ["prototyper", "test-kernel"]

+ 0 - 8
README.md

@@ -4,14 +4,6 @@ RustSBI Prototyper is a developing RISC-V Secure Bootloader solution. It can be
 
 ## Setting Up the Development Environment
 
-### Install Cargo Make
-
-Cargo Make is a Rust task runner and build tool, which is essential for development.
-
-```bash
-cargo install cargo-make
-```
-
 ### Optional Tools
 
 The following tools are not mandatory but can be useful for enhancing your development experience.

+ 1 - 1
docs/booting-fedora-in-qemu-using-uboot-and-rustsbi.md

@@ -51,7 +51,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 1 - 1
docs/booting-freebsd-in-qemu-using-uboot-and-rustsbi.md

@@ -93,7 +93,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 1 - 1
docs/booting-linux-kernel-in-qemu-using-uboot-and-rustsbi.md

@@ -280,7 +280,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 1 - 1
docs/booting-openEuler-23.09-in-qemu-using-uboot-and-rustsbi.md

@@ -50,7 +50,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 1 - 1
docs/booting-openwrt-in-qemu-using-uboot-and-rustsbi.md

@@ -56,7 +56,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 1 - 1
docs/booting-polyos-in-qemu-using-uboot-and-rustsbi.md

@@ -20,7 +20,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ### Clone & Compile U-Boot

+ 2 - 1
docs/booting-test-kernel-in-qemu-using-uboot-and-rustsbi.md

@@ -103,7 +103,8 @@ $ cd prototyper
 编译RustSBI Prototyper和Test Kernel
 
 ``` shell
-$ cargo make test-kernel-itb
+$ cargo prototyper
+$ cargo test-kernel --pack
 ```
 
 本小节将使用二进制文件 `./target/riscv64imac-unknown-none-elf/release/rustsbi-test-kernel.itb`。

+ 1 - 1
docs/booting-ubuntu-24.04.1-in-qemu-using-uboot-and-rustsbi.md

@@ -53,7 +53,7 @@ $ cd prototyper
 编译RustSBI  Prototyper
 
 ``` shell
-$ cargo make prototyper
+$ cargo prototyper
 ```
 
 ## 编译U-Boot SPL

+ 2 - 2
prototyper/src/platform/payload.rs

@@ -24,7 +24,7 @@ pub fn get_boot_info(_nonstandard_a2: usize) -> BootInfo {
 #[link_section = ".fw_fdt"]
 pub unsafe extern "C" fn raw_fdt() {
     asm!(
-        concat!(".incbin \"", env!("PROTOTYPER_FDT"), "\""),
+        concat!(".incbin \"", env!("PROTOTYPER_FDT_PATH"), "\""),
         options(noreturn)
     );
 }
@@ -33,7 +33,7 @@ pub unsafe extern "C" fn raw_fdt() {
 #[link_section = ".payload"]
 pub unsafe extern "C" fn payload_image() {
     asm!(
-        concat!(".incbin \"", env!("PROTOTYPER_IMAGE"), "\""),
+        concat!(".incbin \"", env!("PROTOTYPER_PAYLOAD_PATH"), "\""),
         options(noreturn)
     );
 }

+ 9 - 0
xtask/Cargo.toml

@@ -0,0 +1,9 @@
+[package]
+name = "xtask"
+version = "0.1.0"
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+
+[dependencies]
+clap = { version = "4.5.4", features = ["derive", "env", "suggestions"] }

+ 40 - 0
xtask/src/main.rs

@@ -0,0 +1,40 @@
+use std::process::ExitCode;
+use clap::{Parser, Subcommand};
+
+#[macro_use]
+mod utils;
+mod prototyper;
+mod test;
+
+
+use crate::prototyper::PrototyperArg;
+use crate::test::TestArg;
+
+#[derive(Parser)]
+#[clap(
+    name = "xtask",
+    about = "A task runner for building, running and testing Prototyper",
+    long_about = None,
+)]
+struct Cli {
+    #[clap(subcommand)]
+    cmd: Cmd,
+}
+
+#[derive(Subcommand)]
+enum Cmd {
+    Prototyper(PrototyperArg),
+    Test(TestArg),
+}
+
+fn main() -> ExitCode {
+    if let Some(code) = match Cli::parse().cmd {
+        Cmd::Prototyper(ref arg) => prototyper::run(arg),
+        Cmd::Test(ref arg) => test::run(arg),
+    } {
+        if code.success() {
+            return ExitCode::SUCCESS;
+        }
+    }
+    ExitCode::FAILURE
+}

+ 68 - 0
xtask/src/prototyper.rs

@@ -0,0 +1,68 @@
+use std::{
+    env,
+    process::{Command, ExitStatus},
+};
+
+use clap::Args;
+
+use crate::utils::cargo;
+use crate::utils::CmdOptional;
+
+#[derive(Debug, Args, Clone)]
+pub struct PrototyperArg {
+    #[clap(long, short = 'f')]
+    pub features: Vec<String>,
+
+    #[clap(long, env = "PROTOTYPER_FDT_PATH")]
+    pub fdt: Option<String>,
+
+    #[clap(long, env = "PROTOTYPER_PAYLOAD_PATH")]
+    pub payload: Option<String>,
+}
+
+#[must_use]
+pub fn run(arg: &PrototyperArg) -> Option<ExitStatus> {
+    let arch = "riscv64imac-unknown-none-elf";
+    let fdt = arg.fdt.clone();
+    let payload = arg.payload.clone();
+
+    cargo::Cargo::new("build")
+        .package("rustsbi-prototyper")
+        .target(arch)
+        .unstable("build-std", ["core"])
+        .env("RUSTFLAGS", "-C relocation-model=pie -C link-arg=-pie")
+        .features(&arg.features)
+        .optional(arg.fdt.is_some(), |cargo| {
+            export_env!("PROTOTYPER_FDT_PATH" ?= fdt.unwrap());
+            cargo.features(["fdt".to_string()])
+        })
+        .optional(payload.is_some(), |cargo| {
+            export_env!("PROTOTYPER_PAYLOAD_PATH" ?= payload.unwrap());
+            cargo.features(["payload".to_string()])
+        })
+        .release()
+        .status()
+        .ok()?;
+
+    Command::new("rust-objcopy")
+        .args(["-O", "binary"])
+        .arg("--binary-architecture=riscv64")
+        .arg(
+            env::current_dir()
+                .unwrap()
+                .join("target")
+                .join(arch.to_string())
+                .join("release")
+                .join("rustsbi-prototyper"),
+        )
+        .arg(
+            env::current_dir()
+                .unwrap()
+                .join("target")
+                .join(arch.to_string())
+                .join("release")
+                .join("rustsbi-prototyper.bin"),
+        )
+        .status()
+        .ok()
+}

+ 72 - 0
xtask/src/test.rs

@@ -0,0 +1,72 @@
+use std::{
+    env, fs,
+    process::{Command, ExitStatus},
+};
+
+use clap::Args;
+
+use crate::utils::cargo;
+
+#[derive(Debug, Args, Clone)]
+pub struct TestArg {
+    /// Package Prototyper and Test-Kernel
+    #[clap(long)]
+    pub pack: bool,
+}
+
+#[must_use]
+pub fn run(arg: &TestArg) -> Option<ExitStatus> {
+    let arch = "riscv64imac-unknown-none-elf";
+    let current_dir = env::current_dir();
+    let target_dir = current_dir
+        .as_ref()
+        .unwrap()
+        .join("target")
+        .join(arch.to_string())
+        .join("release");
+
+    cargo::Cargo::new("build")
+        .package("rustsbi-test-kernel")
+        .target(arch)
+        .release()
+        .status()
+        .ok()?;
+
+    let exit_status = Command::new("rust-objcopy")
+        .args(["-O", "binary"])
+        .arg("--binary-architecture=riscv64")
+        .arg(target_dir.join("rustsbi-test-kernel"))
+        .arg(target_dir.join("rustsbi-test-kernel.bin"))
+        .status()
+        .ok()?;
+
+    if arg.pack {
+        match fs::exists(target_dir.join("rustsbi-prototyper.bin")) {
+            Ok(true) => {}
+            Ok(false) => {
+                panic!(" Couldn't open \"rustsbi-prototyper.bin\": No such file or directory. Please compile Prototyper first");
+            }
+            Err(_) => {
+                panic!("Can't check existence of file rustsbi-prototyper.bin, please compile Prototyper first");
+            }
+        }
+        fs::copy(
+            current_dir
+                .as_ref()
+                .unwrap()
+                .join("test-kernel")
+                .join("scripts")
+                .join("rustsbi-test-kernel.its"),
+            target_dir.join("rustsbi-test-kernel.its"),
+        )
+        .ok()?;
+        env::set_current_dir(&target_dir).ok()?;
+        Command::new("mkimage")
+            .args(["-f", "rustsbi-test-kernel.its"])
+            .arg("rustsbi-test-kernel.itb")
+            .status()
+            .ok()?;
+        fs::remove_file(env::current_dir().unwrap().join("rustsbi-test-kernel.its")).ok()?;
+    }
+    Some(exit_status)
+}

+ 105 - 0
xtask/src/utils/cargo.rs

@@ -0,0 +1,105 @@
+use std::{
+    ffi::OsStr,
+    ops::{Deref, DerefMut},
+    path::Path,
+    process::Command,
+};
+
+use super::CmdOptional;
+
+pub struct Cargo {
+    cmd: Command,
+}
+
+#[allow(unused)]
+impl Cargo {
+    pub fn new(action: &str) -> Self {
+        let mut cmd = Command::new(env!("CARGO"));
+        cmd.arg(action);
+        Self { cmd }
+    }
+
+    pub fn package<S: AsRef<OsStr>>(&mut self, package: S) -> &mut Self {
+        self.args(["--package", package.as_ref().to_str().unwrap()]);
+        self
+    }
+
+    pub fn work_dir<S: AsRef<Path>>(&mut self, dir: S) -> &mut Self {
+        self.current_dir(dir);
+        self
+    }
+
+    pub fn release(&mut self) -> &mut Self {
+        self.arg("--release");
+        self
+    }
+
+    pub fn target<S: AsRef<OsStr>>(&mut self, target: S) -> &mut Self {
+        self.args(["--target", target.as_ref().to_str().unwrap()]);
+        self
+    }
+
+    pub fn features<I, S>(&mut self, features: I) -> &mut Self
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        self.args([
+            "--features",
+            features
+                .into_iter()
+                .map(|f| f.as_ref().to_str().unwrap().to_string())
+                .collect::<Vec<_>>()
+                .join(",")
+                .as_ref(),
+        ]);
+        self
+    }
+
+    pub fn no_default_features(&mut self) -> &mut Self {
+        self.arg("--no-default-features");
+        self
+    }
+
+    pub fn unstable<I, S>(&mut self, key: S, values: I) -> &mut Self
+    where
+        I: IntoIterator<Item = S>,
+        S: AsRef<OsStr>,
+    {
+        self.arg(format!(
+            "-Z{}={}",
+            key.as_ref().to_str().unwrap(),
+            values
+                .into_iter()
+                .map(|f| f.as_ref().to_str().unwrap().to_string())
+                .collect::<Vec<_>>()
+                .join(",")
+        ));
+        self
+    }
+
+    pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
+    where
+        K: AsRef<OsStr>,
+        V: AsRef<OsStr>,
+    {
+        self.cmd.env(key, value);
+        self
+    }
+}
+
+impl CmdOptional for Cargo {}
+
+impl Deref for Cargo {
+    type Target = Command;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cmd
+    }
+}
+
+impl DerefMut for Cargo {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cmd
+    }
+}

+ 13 - 0
xtask/src/utils/envs.rs

@@ -0,0 +1,13 @@
+macro_rules! export_env {
+    ($env:literal ?= $val:expr) => {
+        if std::env::vars_os().all(|(k, _)| k != $env) {
+            std::env::set_var($env, $val);
+        }
+    };
+    ($env0:literal ?= $val0:expr, $($env:literal ?= $val:expr,)+) => {
+        export_env!($env0 ?= $val0);
+        $(
+            export_env!($env ?= $val);
+        )+
+    };
+}

+ 13 - 0
xtask/src/utils/mod.rs

@@ -0,0 +1,13 @@
+pub mod cargo; 
+
+#[macro_use]
+pub mod envs;
+
+pub trait CmdOptional {
+    fn optional(&mut self, pred: bool, f: impl FnOnce(&mut Self) -> &mut Self) -> &mut Self {
+        if pred {
+            f(self);
+        }
+        self
+    }
+}