Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the xtask package, implement automation for building examples #1157

Merged
merged 3 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
}
Loading