Skip to content

Commit

Permalink
Add the xtask package, implement automation for building examples (#1157
Browse files Browse the repository at this point in the history
)

* Create the `xtask` package, add command for building examples

* Do not perform changelog check for `xtask` package changes

* Fix unrelated `rustfmt` error
  • Loading branch information
jessebraham authored Feb 12, 2024
1 parent c92d69c commit ebdd59b
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"
5 changes: 4 additions & 1 deletion .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
name: Change log check
name: Changelog check

on:
pull_request:
# We will not track changes for the `xtask` package.
paths-ignore:
- "/xtask/"
# Run on labeled/unlabeled in addition to defaults to detect
# adding/removing skip-changelog labels.
types: [opened, reopened, labeled, unlabeled, synchronize]
Expand Down
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[workspace]
members = ["xtask"]
exclude = [
"esp-hal",
"esp-hal-procmacros",
"esp-hal-smartled",
"esp-lp-hal",
"esp-riscv-rt",
"esp32-hal",
"esp32c2-hal",
"esp32c3-hal",
"esp32c6-hal",
"esp32h2-hal",
"esp32p4-hal",
"esp32s2-hal",
"esp32s3-hal",
]
2 changes: 1 addition & 1 deletion esp-hal/src/rom/crc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
//!
//! ```
//! // CRC-32/MPEG-2
//! const CRC_INITIAL = 0xffffffff; // "init" or "xorin" of all ones
//! const CRC_INITIAL: _ = 0xffffffff; // "init" or "xorin" of all ones
//! let mut crc = crc32_be(!CRC_INITIAL, &data0); // start
//! crc = crc32_be(crc, &data1);
//! crc = !crc32_be(crc, &data2); // finish
Expand Down
12 changes: 12 additions & 0 deletions xtask/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "xtask"
version = "0.0.0"
edition = "2021"
publish = false

[dependencies]
anyhow = "1.0.79"
clap = { version = "4.5.0", features = ["derive"] }
env_logger = "0.11.1"
log = "0.4.20"
strum = { version = "0.26.1", features = ["derive"] }
16 changes: 16 additions & 0 deletions xtask/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# xtask

Automation using [cargo-xtask](https://github.com/matklad/cargo-xtask).

## Usage

```text
Usage: xtask <COMMAND>
Commands:
build-examples Build all examples for the specified chip
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
```
181 changes: 181 additions & 0 deletions xtask/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::{
collections::VecDeque,
fs,
path::{Path, PathBuf},
process::{Command, Stdio},
};

use anyhow::{bail, Result};
use clap::ValueEnum;
use strum::{Display, EnumIter, IntoEnumIterator};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, ValueEnum)]
#[strum(serialize_all = "kebab-case")]
pub enum Chip {
Esp32,
Esp32c2,
Esp32c3,
Esp32c6,
Esp32h2,
Esp32p4,
Esp32s2,
Esp32s3,
}

impl Chip {
pub fn target(&self) -> &str {
use Chip::*;

match self {
Esp32 => "xtensa-esp32-none-elf",
Esp32c2 | Esp32c3 => "riscv32imc-unknown-none-elf",
Esp32c6 | Esp32h2 => "riscv32imac-unknown-none-elf",
Esp32p4 => "riscv32imafc-unknown-none-elf",
Esp32s2 => "xtensa-esp32s2-none-elf",
Esp32s3 => "xtensa-esp32s3-none-elf",
}
}

pub fn toolchain(&self) -> &str {
use Chip::*;

match self {
Esp32 | Esp32s2 | Esp32s3 => "xtensa",
_ => "nightly",
}
}
}

#[derive(Debug)]
pub struct Metadata {
path: PathBuf,
chips: Vec<Chip>,
features: Vec<String>,
}

impl Metadata {
pub fn new(path: PathBuf, chips: Vec<Chip>, features: Vec<String>) -> Self {
let chips = if chips.is_empty() {
Chip::iter().collect()
} else {
chips
};

Self {
path,
chips,
features,
}
}

/// Absolute path to the example.
pub fn path(&self) -> &Path {
&self.path
}

/// A list of all features required for building a given examples.
pub fn features(&self) -> &[String] {
&self.features
}

/// If the specified chip is in the list of chips, then it is supported.
pub fn supports_chip(&self, chip: Chip) -> bool {
self.chips.contains(&chip)
}
}

pub fn load_examples(workspace: &Path) -> Result<Vec<Metadata>> {
let bin_path = workspace
.join("examples")
.join("src")
.join("bin")
.canonicalize()?;

let mut examples = Vec::new();

for entry in fs::read_dir(bin_path)? {
let path = entry?.path();
let text = fs::read_to_string(&path)?;

let mut chips = Vec::new();
let mut features = Vec::new();

// We will indicate metadata lines using the `//%` prefix:
let lines = text
.lines()
.filter(|line| line.starts_with("//%"))
.collect::<Vec<_>>();

for line in lines {
let mut split = line
.trim_start_matches("//%")
.trim()
.split_ascii_whitespace()
.map(|s| s.to_string())
.collect::<VecDeque<_>>();

if split.len() < 2 {
bail!(
"Expected at least two elements (key, value), found {}",
split.len()
);
}

// The trailing ':' on metadata keys is optional :)
let key = split.pop_front().unwrap();
let key = key.trim_end_matches(':');

if key == "CHIPS" {
chips = split
.iter()
.map(|s| Chip::from_str(s, false).unwrap())
.collect::<Vec<_>>();
} else if key == "FEATURES" {
features = split.into();
} else {
log::warn!("Unregognized metadata key '{key}', ignoring");
}
}

let meta = Metadata::new(path, chips, features);
examples.push(meta);
}

Ok(examples)
}

pub fn build_example(workspace: &Path, chip: Chip, example: &Metadata) -> Result<()> {
let path = example.path();
let features = example.features();

log::info!("Building example '{}' for '{}'", path.display(), chip);
if !features.is_empty() {
log::info!(" Features: {}", features.join(","));
}

let bin_name = example
.path()
.file_name()
.unwrap()
.to_string_lossy()
.replace(".rs", "");

let args = &[
&format!("+{}", chip.toolchain()),
"build",
"--release",
&format!("--target={}", chip.target()),
&format!("--features={},{}", chip, features.join(",")),
&format!("--bin={bin_name}"),
];
log::debug!("{args:#?}");

Command::new("cargo")
.args(args)
.current_dir(workspace.join("examples"))
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.output()?;

Ok(())
}
50 changes: 50 additions & 0 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::path::{Path, PathBuf};

use anyhow::Result;
use clap::{Args, Parser};
use xtask::Chip;

// ----------------------------------------------------------------------------
// Command-line Interface

#[derive(Debug, Parser)]
enum Cli {
/// Build all examples for the specified chip.
BuildExamples(BuildExamplesArgs),
}

#[derive(Debug, Args)]
struct BuildExamplesArgs {
/// Which chip to build the examples for.
#[clap(value_enum)]
chip: Chip,
}

// ----------------------------------------------------------------------------
// Application

fn main() -> Result<()> {
env_logger::Builder::new()
.filter_module("xtask", log::LevelFilter::Info)
.init();

let workspace = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace = workspace.parent().unwrap().canonicalize()?;

match Cli::parse() {
Cli::BuildExamples(args) => build_examples(&workspace, args),
}
}

// ----------------------------------------------------------------------------
// Subcommands

fn build_examples(workspace: &Path, args: BuildExamplesArgs) -> Result<()> {
// Load all examples and parse their metadata. Filter down the examples to only
// those for which our chip is supported, and then attempt to build each
// remaining example, with the required features enabled:
xtask::load_examples(workspace)?
.iter()
.filter(|example| example.supports_chip(args.chip))
.try_for_each(|example| xtask::build_example(workspace, args.chip, example))
}

0 comments on commit ebdd59b

Please sign in to comment.