Skip to content

Commit

Permalink
[crater] Initial support for richer targets
Browse files Browse the repository at this point in the history
This will facilitate gftools mode.

Ultimately I decided to just rip out a bunch of the old functionality,
because keeping it working was becoming a chore and it really wasn't
being used much.
  • Loading branch information
cmyr committed Oct 24, 2024
1 parent ab35d44 commit 1dcc62d
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 512 deletions.
2 changes: 1 addition & 1 deletion fontc_crater/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
[dependencies]
fontc = { version = "0.0.1", path = "../fontc" }

google-fonts-sources = "0.3.1"
google-fonts-sources = "0.5.0"
maud = "0.26.0"
tidier = "0.5.3"

Expand Down
38 changes: 3 additions & 35 deletions fontc_crater/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,18 @@ The `fontc_crater` crate (named after [rust-lang/crater]) is a tool for
performing compilation and related actions across a large number of source
fonts.



```sh
# By default font repositories and results will be written to ~/.fontc_crater_cache

# Just see if we can compile with fontc
$ cargo run --release -p=fontc_crater -- compile

# To take it for a test-spin do a limited # of fonts
$ cargo run --release -p=fontc_crater -- compile --limit 8

# Build with fontmake and fontc and compare the results with ttx_diff
$ cargo run --release -p=fontc_crater -- diff
```

This is a binary for executing font compilation (and possibly other tasks) in
bulk.

Discovery of font sources is managed by a separate tool,
[google-fonts-sources][]; this tool checks out the
[github.com/google/fonts][google/fonts] repository and looks for fonts with
known source repos. You can use the `--fonts-repo` argument to pass the path to
an existing checkout of this repository, which saves time.

Once sources are identified, they are checked out into the `FONT_CACHE`
directory, where they can be reused between runs.

## output

For detailed output, use the `-o/--out` flag to specify a path where we should
dump a json dictionary containing the outcome for each source.

## reports

You can generate a report from the saved json by passing it back to
`fontc_crater`:

```sh
$ cargo run -p fontc_crater -- report
```
Once sources are identified, they are checked out into a cache directory, where
they can be reused between runs.

## CI

This binary is also run in CI. In that case, the execution is managed by a
This tool is mainly used in CI. In that case, the execution is managed by a
script in the [`fontc_crater` repo][crater-repo] and results are posted to
[github pages][crater-results].

Expand Down
73 changes: 49 additions & 24 deletions fontc_crater/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! CLI args

use std::path::PathBuf;
use std::path::{Path, PathBuf};

use clap::{Parser, Subcommand};

// this env var can be set by the runner in order to reuse git checkouts
// between runs.
static GIT_CACHE_DIR_VAR: &str = "CRATER_GIT_CACHE";
const DEFAULT_CACHE_DIR: &str = "~/.fontc_crater_cache";

#[derive(Debug, PartialEq, Parser)]
#[command(about = "compile multiple fonts and report the results")]
pub(super) struct Args {
Expand All @@ -13,33 +18,24 @@ pub(super) struct Args {

#[derive(Debug, Subcommand, PartialEq)]
pub(super) enum Commands {
Compile(RunArgs),
Diff(RunArgs),
Report(ReportArgs),
Ci(CiArgs),
}

#[derive(Debug, PartialEq, clap::Args)]
pub(super) struct RunArgs {
pub(super) struct CiArgs {
/// Path to a json list of repos + revs to run.
pub(super) to_run: PathBuf,
/// Directory to store font sources and the google/fonts repo.
///
/// Reusing this directory saves us having to clone all the repos on each run.
///
/// This directory is also used to write cached results during repo discovery.
#[arg(short, long, default_value = "~/.fontc_crater_cache")]
pub(super) cache_dir: PathBuf,
/// Optional path to write out results (as json)
#[arg(short = 'o', long = "out")]
pub(super) out_path: Option<PathBuf>,
/// for debugging, execute only a given number of fonts
#[arg(long)]
pub(super) limit: Option<usize>,
}
/// This can also be set via the CRATER_GIT_CACHE environment variable,
/// although the CLI argument takes precedence.
///
/// If no argument is provided, defaults to `~/.fontc_crater_cache`.
#[arg(short, long = "cache")]
cache_dir: Option<PathBuf>,

#[derive(Debug, PartialEq, clap::Args)]
pub(super) struct CiArgs {
/// Path to a json list of repos + revs to run.
pub(super) to_run: PathBuf,
/// Directory where results are written.
///
/// This should be consistent between runs.
Expand All @@ -50,9 +46,38 @@ pub(super) struct CiArgs {
pub(super) html_only: bool,
}

#[derive(Debug, PartialEq, clap::Args)]
pub(super) struct ReportArgs {
pub(super) json_path: PathBuf,
#[arg(short, long)]
pub(super) verbose: bool,
impl CiArgs {
/// Determine the directory to use for caching git checkouts.
///
/// This may be passed at the command line or via an environment variable.
pub(crate) fn cache_dir(&self) -> PathBuf {
let cache_dir = self
.cache_dir
.clone()
.or_else(|| std::env::var_os(GIT_CACHE_DIR_VAR).map(PathBuf::from))
.unwrap_or_else(|| PathBuf::from(DEFAULT_CACHE_DIR));

if cache_dir.components().any(|comp| comp.as_os_str() == "~") {
resolve_home(&cache_dir)
} else {
cache_dir
}
}
}

#[allow(deprecated)]
fn resolve_home(path: &Path) -> PathBuf {
let Some(home_dir) = std::env::home_dir() else {
log::warn!("No known home directory, ~ will not be resolved");
return path.to_path_buf();
};
let mut result = PathBuf::new();
for c in path.components() {
if c.as_os_str() == "~" {
result.push(home_dir.clone());
} else {
result.push(c);
}
}
result
}
79 changes: 57 additions & 22 deletions fontc_crater/src/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@
//! Unlike a normal run, this is expecting to have preexisting results, and to
//! generate a fuller report that includes comparison with past runs.

use std::path::{Path, PathBuf};
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};

use chrono::{DateTime, Utc};
use google_fonts_sources::RepoInfo;
use google_fonts_sources::{Config, RepoInfo};
use serde::de::DeserializeOwned;

use crate::{
args::CiArgs,
error::Error,
ttx_diff_runner::{DiffError, DiffOutput},
Results,
Results, Target,
};

mod html;

static SUMMARY_FILE: &str = "summary.json";
static SOURCES_FILE: &str = "sources.json";

// this env var can be set by the runner in order to reuse git checkouts
// between runs.
static GIT_CACHE_DIR_VAR: &str = "CRATER_GIT_CACHE";

type DiffResults = Results<DiffOutput, DiffError>;

/// A summary of a single CI run
Expand Down Expand Up @@ -98,22 +97,15 @@ fn run_crater_and_save_results(args: &CiArgs) -> Result<(), Error> {
let out_file = result_path_for_current_date();
let out_path = args.out_dir.join(&out_file);
// for now we are going to be cloning each repo freshly
let temp_dir = tempfile::tempdir().unwrap();
let user_cache_dir = std::env::var_os(GIT_CACHE_DIR_VAR).map(PathBuf::from);

if let Some(user_cache_dir) = user_cache_dir.as_ref() {
if !user_cache_dir.exists() {
super::try_create_dir(user_cache_dir)?;
}
}
let cache_dir = user_cache_dir
.as_ref()
.map(Path::new)
.unwrap_or(temp_dir.path());
log::info!("using working dir {}", cache_dir.display());
let cache_dir = args.cache_dir();
log::info!("using cache dir {}", cache_dir.display());

let (targets, source_repos) = make_targets(&cache_dir, &inputs);
let began = Utc::now();
let results = super::run_all(&inputs, cache_dir, super::ttx_diff_runner::run_ttx_diff)?;
let results = super::run_all(targets, &cache_dir, super::ttx_diff_runner::run_ttx_diff)?
.into_iter()
.map(|(target, result)| (target.id(), result))
.collect();
let finished = Utc::now();

super::try_write_json(&results, &out_path)?;
Expand All @@ -140,11 +132,54 @@ fn run_crater_and_save_results(args: &CiArgs) -> Result<(), Error> {
// we write the map of target -> source repo to a separate file because
// otherwise we're basically duplicating it for each run.
let sources_file = args.out_dir.join(SOURCES_FILE);
super::try_write_json(&results.source_repos, &sources_file)
super::try_write_json(&source_repos, &sources_file)
}

fn result_path_for_current_date() -> String {
let now = chrono::Utc::now();
let timestamp = now.format("%Y-%m-%d-%H%M%S");
format!("{timestamp}.json")
}

fn make_targets(cache_dir: &Path, repos: &[RepoInfo]) -> (Vec<Target>, BTreeMap<PathBuf, String>) {
let mut targets = Vec::new();
let mut repo_list = BTreeMap::new();
for repo in repos {
let Ok(iter) = repo.iter_configs(cache_dir) else {
log::warn!(
"error reading repo '{}'",
repo.repo_path(cache_dir).display()
);
continue;
};
for config_path in iter {
let config = match Config::load(&config_path) {
Ok(x) => x,
Err(e) => {
log::warn!("failed to load config '{}': '{e}'", config_path.display());
continue;
}
};
for source in &config.sources {
let src_path = config_path
.parent()
.expect("config path always in sources dir")
.join(source);
if !src_path.exists() {
log::warn!("source file '{}' is missing", src_path.display());
continue;
}
let src_path = src_path
.strip_prefix(cache_dir)
.expect("source is always in cache dir")
.to_path_buf();
repo_list.insert(src_path.clone(), repo.repo_url.clone());
targets.push(Target {
_config: config_path.to_owned(),
source: src_path,
})
}
}
}
(targets, repo_list)
}
Loading

0 comments on commit 1dcc62d

Please sign in to comment.