From 61e6b52870072a95c2ec2b3701323fc493e0e8eb Mon Sep 17 00:00:00 2001
From: Bob McWhirter <bmcwhirt@redhat.com>
Date: Tue, 17 Aug 2021 12:15:24 -0400
Subject: [PATCH] xtask runner for CI types of things and other utilities.

---
 xtask/.cargo/config |   6 ++
 xtask/Cargo.toml    |  14 +++
 xtask/src/main.rs   | 225 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 245 insertions(+)
 create mode 100644 xtask/.cargo/config
 create mode 100644 xtask/Cargo.toml
 create mode 100644 xtask/src/main.rs

diff --git a/xtask/.cargo/config b/xtask/.cargo/config
new file mode 100644
index 000000000..919ec05a4
--- /dev/null
+++ b/xtask/.cargo/config
@@ -0,0 +1,6 @@
+[alias]
+xtask    = "run --package xtask --"
+ci       = "run --package xtask -- ci"
+core     = "run --package xtask -- core"
+examples = "run --package xtask -- examples"
+fmt      = "run --package xtask -- fmt"
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
new file mode 100644
index 000000000..ffa06ea9e
--- /dev/null
+++ b/xtask/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+authors = [
+    "Ulf Lilleengen <lulf@redhat.com>",
+    "Bob McWhirter <bmcwhirt@redhat.com>"
+]
+edition = "2018"
+name = "xtask"
+version = "0.1.0"
+
+[dependencies]
+anyhow = "1.0"
+xshell = "0.1"
+yaml-rust = "0.4"
+walkdir = "2.3.2"
\ No newline at end of file
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
new file mode 100644
index 000000000..d8d5e07da
--- /dev/null
+++ b/xtask/src/main.rs
@@ -0,0 +1,225 @@
+#![allow(dead_code)]
+#![deny(unused_must_use)]
+
+use std::format;
+use std::{env, fs, path::PathBuf};
+
+use std::path::Path;
+use walkdir::WalkDir;
+use xshell::{cmd, Cmd};
+use yaml_rust::YamlLoader;
+
+extern crate yaml_rust;
+
+fn main() -> Result<(), anyhow::Error> {
+    let args = env::args().skip(1).collect::<Vec<_>>();
+    let args = args.iter().map(|s| &**s).collect::<Vec<_>>();
+
+    match &args[..] {
+        ["ci"] => task_ci()?,
+        ["core"] => task_check(Realm::Core)?,
+        ["metapac"] => task_metapac_gen()?,
+        ["examples"] => task_check(Realm::Examples)?,
+        ["fmt-check"] => task_cargo_fmt_check()?,
+        ["fmt"] => task_cargo_fmt()?,
+        _ => {
+            println!("");
+            println!("USAGE: cargo xtask [command]");
+            println!("");
+            println!("Commands:");
+            println!("  ci        :: Runs entire CI");
+            println!("  core      :: Builds the core");
+            println!("  metapac   :: Builds the metapac");
+            println!("  examples  :: Builds the examples");
+            println!("  fmt-check :: Checks rustfmt");
+            println!("  fmt       :: Performs rustfmt");
+            println!("");
+        }
+    }
+    Ok(())
+}
+
+fn task_ci() -> Result<(), anyhow::Error> {
+    task_check(Realm::Core)?;
+    task_check(Realm::Examples)?;
+    task_metapac_gen()?;
+    task_cargo_fmt_check()?;
+    Ok(())
+}
+
+#[derive(Copy, Clone)]
+enum Realm {
+    All,
+    Core,
+    Examples,
+}
+
+impl Realm {
+    fn accepts(&self, package: &str) -> bool {
+        match self {
+            Realm::All => true,
+            Realm::Core => !package.contains("examples"),
+            Realm::Examples => package.contains("examples"),
+        }
+    }
+}
+
+fn task_check(realm: Realm) -> Result<(), anyhow::Error> {
+    let _e = xshell::pushenv("CI", "true");
+
+    let matrix_yaml = root_dir()
+        .join(".github")
+        .join("workflows")
+        .join("rust.yml");
+
+    let matrix = YamlLoader::load_from_str(&*fs::read_to_string(matrix_yaml).unwrap()).unwrap();
+
+    let matrix = &matrix.get(0).unwrap()["jobs"]["ci"]["strategy"]["matrix"]["include"];
+
+    let entries = matrix.as_vec().unwrap();
+
+    for entry in entries {
+        let package = entry["package"].as_str().unwrap();
+        if !realm.accepts(package) {
+            continue;
+        }
+        let target = entry["target"].as_str().unwrap();
+        let features = entry["features"].as_str();
+        let package_dir = root_dir().join(entry["package"].as_str().unwrap());
+        let _p = xshell::pushd(package_dir)?;
+        banner(&*format!(
+            "Building {} [target={}] [features={}]",
+            package,
+            target,
+            features.unwrap_or("default-features")
+        ));
+
+        let root_cargo_dir = root_dir().join(".cargo");
+        fs::create_dir_all(root_cargo_dir.clone()).unwrap();
+        fs::write(
+            root_cargo_dir.join("config"),
+            "[target.\"cfg(all())\"]\nrustflags = [\"-D\", \"warnings\"]",
+        )
+        .unwrap();
+
+        let mut args = Vec::new();
+        args.push("check");
+        args.push("--target");
+        args.push(target);
+
+        if let Some(features) = features {
+            args.push("--features");
+            args.push(features);
+        }
+
+        let command = Cmd::new(PathBuf::from("cargo"));
+        let command = command.args(args);
+        let result = command.run();
+
+        fs::remove_file(root_cargo_dir.join("config")).unwrap();
+
+        result?;
+    }
+
+    Ok(())
+}
+
+fn task_metapac_gen() -> Result<(), anyhow::Error> {
+    banner("Building metapac");
+    let _p = xshell::pushd(root_dir().join("stm32-metapac-gen"));
+    cmd!("cargo run").run()?;
+    Ok(())
+}
+
+fn task_cargo_fmt() -> Result<(), anyhow::Error> {
+    for entry in WalkDir::new(root_dir())
+        .follow_links(false)
+        .into_iter()
+        .filter_map(|e| e.ok())
+    {
+        let f_name = entry.file_name().to_string_lossy();
+
+        if f_name.ends_with(".rs") {
+            if !is_primary_source(entry.path()) {
+                continue;
+            }
+            let mut args = Vec::new();
+            args.push("--skip-children");
+            args.push("--unstable-features");
+            args.push("--edition=2018");
+            args.push(&*entry.path().to_str().unwrap());
+            let command = Cmd::new("rustfmt");
+            command.args(args).run()?;
+        }
+    }
+
+    Ok(())
+}
+
+fn task_cargo_fmt_check() -> Result<(), anyhow::Error> {
+    let mut actual_result = Ok(());
+    for entry in WalkDir::new(root_dir())
+        .follow_links(false)
+        .into_iter()
+        .filter_map(|e| e.ok())
+    {
+        let f_name = entry.file_name().to_string_lossy();
+
+        if f_name.ends_with(".rs") {
+            if !is_primary_source(entry.path()) {
+                continue;
+            }
+            let mut args = Vec::new();
+            args.push("--check");
+            args.push("--skip-children");
+            args.push("--unstable-features");
+            args.push("--edition=2018");
+            args.push(&*entry.path().to_str().unwrap());
+            let command = Cmd::new("rustfmt");
+            if let Err(result) = command.args(args).run() {
+                actual_result = Err(result.into());
+            }
+        }
+    }
+
+    actual_result
+}
+
+fn root_dir() -> PathBuf {
+    let mut xtask_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+    xtask_dir.pop();
+    xtask_dir
+}
+
+fn examples_dir() -> PathBuf {
+    root_dir().join("examples")
+}
+
+fn is_primary_source(path: &Path) -> bool {
+    let mut current = path;
+
+    loop {
+        let current_file_name = current.file_name().unwrap().to_str().unwrap();
+        if current_file_name == "target"
+            || current_file_name == "stm32-metapac-gen"
+            || current_file_name == "stm32-data"
+        {
+            return false;
+        }
+
+        if let Some(path) = current.parent() {
+            current = path.into();
+            if current == root_dir() {
+                return true;
+            }
+        } else {
+            return false;
+        }
+    }
+}
+
+fn banner(text: &str) {
+    println!("------------------------------------------------------------------------------------------------------------------------------------------------");
+    println!("== {}", text);
+    println!("------------------------------------------------------------------------------------------------------------------------------------------------");
+}