diff --git a/esp-config/README.md b/esp-config/README.md index 0d7f43b2f9b..37cdd324003 100644 --- a/esp-config/README.md +++ b/esp-config/README.md @@ -52,8 +52,8 @@ This crate is guaranteed to compile when using the latest stable Rust version at Licensed under either of: -- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT) +- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](../LICENSE-MIT) or ) at your option. diff --git a/resources/index.html.jinja b/resources/index.html.jinja index 0a7d5506b6b..8d31897ad52 100644 --- a/resources/index.html.jinja +++ b/resources/index.html.jinja @@ -93,16 +93,15 @@
- {%- for (package, metadata) in packages|items %} -

{{ package }}

+

{{ metadata[0].package }}

{%- for meta in metadata %}
- + {{ meta.chip_pretty }} @@ -110,7 +109,6 @@ {{ meta.version }}
{%- endfor %} - {%- endfor %}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 0667dfece2e..9b1eb8e5262 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -54,6 +54,33 @@ pub enum Package { XtensaLxRt, } +impl Package { + /// Does the package have chip-specific cargo features? + pub fn has_chip_features(&self) -> bool { + use Package::*; + + matches!( + self, + EspBacktrace + | EspHal + | EspHalEmbassy + | EspIeee802154 + | EspLpHal + | EspPrintln + | EspStorage + | EspWifi + | XtensaLxRt + ) + } + + /// Do the package's chip-specific cargo features affect the public API? + pub fn chip_features_matter(&self) -> bool { + use Package::*; + + matches!(self, EspHal | EspLpHal | EspWifi) + } +} + #[derive(Debug, Clone)] pub struct Metadata { example_path: PathBuf, @@ -126,56 +153,82 @@ pub enum Version { Patch, } -/// Build the documentation for the specified package and device. -pub fn build_documentation(workspace: &Path, package: Package, chip: Chip) -> Result { +/// Build the documentation for the specified package and, optionally, a +/// specific chip. +pub fn build_documentation( + workspace: &Path, + package: Package, + chip: Option, +) -> Result { let package_name = package.to_string(); let package_path = windows_safe_path(&workspace.join(&package_name)); - log::info!("Building '{package_name}' documentation targeting '{chip}'"); - - // Determine the appropriate build target for the given package and chip: - let target = target_triple(package, &chip)?; - - // We need `nightly` for building the docs, unfortunately: - let toolchain = if chip.is_xtensa() { "esp" } else { "nightly" }; + if let Some(chip) = chip { + log::info!("Building '{package_name}' documentation targeting '{chip}'"); + } else { + log::info!("Building '{package_name}' documentation"); + } - let mut features = vec![chip.to_string()]; + // We require some nightly features to build the documentation: + let toolchain = if chip.is_some_and(|chip| chip.is_xtensa()) { + "esp" + } else { + "nightly" + }; - let chip = Config::for_chip(&chip); + // Determine the appropriate build target for the given package and chip, + // if we're able to: + let target = if let Some(ref chip) = chip { + Some(target_triple(package, chip)?) + } else { + None + }; - features.extend(apply_feature_rules(&package, chip)); + let mut features = vec![]; + if let Some(chip) = chip { + features.push(chip.to_string()); + features.extend(apply_feature_rules(&package, Config::for_chip(&chip))); + } // Build up an array of command-line arguments to pass to `cargo`: - let builder = CargoArgsBuilder::default() + let mut builder = CargoArgsBuilder::default() .toolchain(toolchain) .subcommand("doc") - .target(target) .features(&features) - .arg("-Zbuild-std=alloc,core") .arg("-Zrustdoc-map") .arg("--lib") .arg("--no-deps"); + if let Some(target) = target { + builder = builder.target(target); + } + + // Special case: `esp-metadata` requires `std`, and we get some really confusing + // errors if we try to pass `-Zbuild-std=core`: + if package != Package::EspMetadata { + builder = builder.arg("-Zbuild-std=alloc,core"); + } + let args = builder.build(); log::debug!("{args:#?}"); + let mut envs = vec![("RUSTDOCFLAGS", "--cfg docsrs --cfg not_really_docsrs")]; + // Special case: `esp-storage` requires the optimization level to be 2 or 3: + if package == Package::EspStorage { + envs.push(("CARGO_PROFILE_DEBUG_OPT_LEVEL", "3")); + } + // Execute `cargo doc` from the package root: - cargo::run_with_env( - &args, - &package_path, - [("RUSTDOCFLAGS", "--cfg docsrs --cfg not_really_docsrs")], - false, - )?; + cargo::run_with_env(&args, &package_path, envs, false)?; - let docs_path = windows_safe_path( - &workspace - .join(package.to_string()) - .join("target") - .join(target) - .join("doc"), - ); + // Build up the path at which the built documentation can be found: + let mut docs_path = workspace.join(package.to_string()).join("target"); + if let Some(target) = target { + docs_path = docs_path.join(target); + } + docs_path = docs_path.join("doc"); - Ok(docs_path) + Ok(windows_safe_path(&docs_path)) } fn apply_feature_rules(package: &Package, config: &Config) -> Vec { @@ -183,6 +236,8 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec { let mut features = vec![]; match package { + Package::EspBacktrace => features.push("defmt".to_owned()), + Package::EspConfig => features.push("build".to_owned()), Package::EspHal => { features.push("unstable".to_owned()); features.push("ci".to_owned()); @@ -215,6 +270,7 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec { } _ => {} } + features } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1ecac248404..62d4653b404 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,12 +1,11 @@ use std::{ - collections::HashMap, fs, path::{Path, PathBuf}, process::Command, }; use anyhow::{bail, ensure, Context as _, Result}; -use clap::{Args, Parser}; +use clap::{Args, Parser, ValueEnum}; use esp_metadata::{Arch, Chip, Config}; use minijinja::Value; use strum::IntoEnumIterator; @@ -23,10 +22,10 @@ use xtask::{ #[derive(Debug, Parser)] enum Cli { - /// Build documentation for the specified chip. - BuildDocumentationIndex(BuildDocumentationArgs), /// Build documentation for the specified chip. BuildDocumentation(BuildDocumentationArgs), + /// Build documentation index including the specified packages. + BuildDocumentationIndex(BuildDocumentationIndexArgs), /// Build all examples for the specified chip. BuildExamples(ExampleArgs), /// Build the specified package with the given options. @@ -85,14 +84,21 @@ struct TestArgs { #[derive(Debug, Args)] struct BuildDocumentationArgs { - /// Package to build documentation for. - #[arg(long, value_enum, value_delimiter(','))] + /// Package(s) to document. + #[arg(long, value_enum, value_delimiter = ',', default_values_t = Package::iter())] packages: Vec, - /// Which chip to build the documentation for. - #[arg(long, value_enum, value_delimiter(','), default_values_t = Chip::iter())] + /// Chip(s) to build documentation for. + #[arg(long, value_enum, value_delimiter = ',', default_values_t = Chip::iter())] chips: Vec, } +#[derive(Debug, Args)] +struct BuildDocumentationIndexArgs { + /// Package(s) to build documentation index for. + #[arg(long, value_enum, value_delimiter = ',', default_values_t = Package::iter())] + packages: Vec, +} + #[derive(Debug, Args)] struct BuildPackageArgs { /// Package to build. @@ -180,8 +186,7 @@ fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); let workspace = std::env::current_dir()?; - - let out_path = Path::new("target"); + let target_path = Path::new("target"); match Cli::parse() { Cli::BuildDocumentation(args) => build_documentation(&workspace, args), @@ -189,12 +194,14 @@ fn main() -> Result<()> { Cli::BuildExamples(args) => examples( &workspace, args, - CargoAction::Build(out_path.join("examples")), + CargoAction::Build(target_path.join("examples")), ), Cli::BuildPackage(args) => build_package(&workspace, args), - Cli::BuildTests(args) => { - tests(&workspace, args, CargoAction::Build(out_path.join("tests"))) - } + Cli::BuildTests(args) => tests( + &workspace, + args, + CargoAction::Build(target_path.join("tests")), + ), Cli::BumpVersion(args) => bump_version(&workspace, args), Cli::FmtPackages(args) => fmt_packages(&workspace, args), Cli::GenerateEfuseFields(args) => generate_efuse_src(&workspace, args), @@ -467,48 +474,129 @@ fn tests(workspace: &Path, args: TestArgs, action: CargoAction) -> Result<()> { } } -fn build_documentation(workspace: &Path, args: BuildDocumentationArgs) -> Result<()> { +fn build_documentation(workspace: &Path, mut args: BuildDocumentationArgs) -> Result<()> { let output_path = workspace.join("docs"); fs::create_dir_all(&output_path) .with_context(|| format!("Failed to create {}", output_path.display()))?; - let mut packages = HashMap::new(); + args.packages.sort(); + for package in args.packages { - packages.insert( + // Not all packages need documentation built: + if matches!( package, - build_documentation_for_package(workspace, package, &args.chips)?, - ); - } + Package::Examples | Package::HilTest | Package::QaTest + ) { + continue; + } + + // If the package does not have chip features, then just ignore + // whichever chip(s) were specified as arguments: + let chips = if package.has_chip_features() { + // Some packages have chip features, but they have no effect on the public API; + // in this case, there's no point building it multiple times, so just build one + // copy of the docs. Otherwise, use the provided chip arguments: + match package { + _ if package.chip_features_matter() => args.chips.clone(), + Package::XtensaLxRt => vec![Chip::Esp32s3], + _ => vec![Chip::Esp32c6], + } + } else { + log::warn!("Package '{package}' does not have chip features, ignoring argument"); + vec![] + }; - generate_index(workspace, &packages)?; + if chips.is_empty() { + build_documentation_for_package(workspace, package, None)?; + } else { + for chip in chips { + build_documentation_for_package(workspace, package, Some(chip))?; + } + } + } Ok(()) } -fn build_documentation_index(workspace: &Path, args: BuildDocumentationArgs) -> Result<()> { - let mut packages = HashMap::new(); +fn build_documentation_index( + workspace: &Path, + mut args: BuildDocumentationIndexArgs, +) -> Result<()> { + let docs_path = workspace.join("docs"); + + args.packages.sort(); + for package in args.packages { - packages.insert( + // Not all packages have documentation built: + if matches!( package, - generate_documentation_meta_for_package(workspace, package, &args.chips)?, - ); - } + Package::Examples | Package::HilTest | Package::QaTest + ) { + continue; + } - generate_index(workspace, &packages)?; + // If the chip features are not relevant, then there is no need to generate an + // index for the given package's documentation: + if !package.chip_features_matter() { + log::warn!("Package '{package}' does not have device-specific documentation, no need to generate an index"); + continue; + } + + let package_docs_path = docs_path.join(package.to_string()); + let mut device_doc_paths = Vec::new(); + + // Each path we iterate over should be the directory for a given version of + // the package's documentation: + for path in fs::read_dir(package_docs_path)? { + let path = path?.path(); + if path.is_file() { + log::warn!("Path is not a directory, skipping: '{}'", path.display()); + continue; + } + + for path in fs::read_dir(&path)? { + let path = path?.path(); + if path.is_dir() { + device_doc_paths.push(path); + } + } + + let mut chips = device_doc_paths + .iter() + .map(|path| { + let chip = path + .components() + .into_iter() + .last() + .unwrap() + .as_os_str() + .to_string_lossy(); + let chip = Chip::from_str(&chip, true).unwrap(); + + chip + }) + .collect::>(); + + chips.sort(); + + let meta = generate_documentation_meta_for_package(workspace, package, &chips)?; + generate_index(workspace, &path, &meta)?; + } + } Ok(()) } -fn generate_index(workspace: &Path, packages: &HashMap>) -> Result<()> { - let output_path = workspace.join("docs"); +fn generate_index(workspace: &Path, package_version_path: &Path, meta: &[Value]) -> Result<()> { let resources = workspace.join("resources"); - fs::create_dir_all(&output_path) - .with_context(|| format!("Failed to create {}", output_path.display()))?; // Copy any additional assets to the documentation's output path: - fs::copy(resources.join("esp-rs.svg"), output_path.join("esp-rs.svg")) - .context("Failed to copy esp-rs.svg")?; + fs::copy( + resources.join("esp-rs.svg"), + package_version_path.join("esp-rs.svg"), + ) + .context("Failed to copy esp-rs.svg")?; // Render the index and write it out to the documentaiton's output path: let source = fs::read_to_string(resources.join("index.html.jinja")) @@ -518,9 +606,10 @@ fn generate_index(workspace: &Path, packages: &HashMap>) -> env.add_template("index", &source)?; let tmpl = env.get_template("index")?; - let html = tmpl.render(minijinja::context! { packages => packages })?; + let html = tmpl.render(minijinja::context! { metadata => meta })?; - fs::write(output_path.join("index.html"), html).context("Failed to write index.html")?; + fs::write(package_version_path.join("index.html"), html) + .context("Failed to write index.html")?; Ok(()) } @@ -528,48 +617,56 @@ fn generate_index(workspace: &Path, packages: &HashMap>) -> fn build_documentation_for_package( workspace: &Path, package: Package, - chips: &[Chip], -) -> Result> { - let output_path = workspace.join("docs"); - + chip: Option, +) -> Result<()> { let version = xtask::package_version(workspace, package)?; - for chip in chips { - // Ensure that the package/chip combination provided are valid: - validate_package_chip(&package, chip)?; + // Ensure that the package/chip combination provided are valid: + if let Some(chip) = chip { + if let Err(err) = validate_package_chip(&package, &chip) { + log::warn!("{err}"); + return Ok(()); + } + } - // Build the documentation for the specified package, targeting the - // specified chip: - let docs_path = xtask::build_documentation(workspace, package, *chip)?; + // Build the documentation for the specified package, targeting the + // specified chip: + let docs_path = xtask::build_documentation(workspace, package, chip)?; - ensure!( - docs_path.exists(), - "Documentation not found at {}", - docs_path.display() - ); + ensure!( + docs_path.exists(), + "Documentation not found at {}", + docs_path.display() + ); - let output_path = output_path - .join(package.to_string()) - .join(version.to_string()) - .join(chip.to_string()); - let output_path = xtask::windows_safe_path(&output_path); - - // Create the output directory, and copy the built documentation into it: - fs::create_dir_all(&output_path) - .with_context(|| format!("Failed to create {}", output_path.display()))?; - - copy_dir_all(&docs_path, &output_path).with_context(|| { - format!( - "Failed to copy {} to {}", - docs_path.display(), - output_path.display() - ) - })?; + let mut output_path = workspace + .join("docs") + .join(package.to_string()) + .join(version.to_string()); + + if let Some(chip) = chip { + // Sometimes we need to specify a chip feature, but it does not affect the + // public API; so, only append the chip name to the path if it is significant: + if package.chip_features_matter() { + output_path = output_path.join(chip.to_string()); + } } - Ok(generate_documentation_meta_for_package( - workspace, package, chips, - )?) + let output_path = xtask::windows_safe_path(&output_path); + + // Create the output directory, and copy the built documentation into it: + fs::create_dir_all(&output_path) + .with_context(|| format!("Failed to create {}", output_path.display()))?; + + copy_dir_all(&docs_path, &output_path).with_context(|| { + format!( + "Failed to copy {} to {}", + docs_path.display(), + output_path.display() + ) + })?; + + Ok(()) } fn generate_documentation_meta_for_package(