From a4f10d848735d63bbacacdb60bdea0bbba141981 Mon Sep 17 00:00:00 2001 From: Colin Rofls Date: Wed, 30 Oct 2024 15:04:50 -0400 Subject: [PATCH 1/2] [crater] Get rid of TargetId This was overly clever, because I wanted to avoid serializing the config file paths everywhere; however we're going to need them in order to generate the reproduction commands. --- fontc_crater/src/ci.rs | 24 ++++--- fontc_crater/src/ci/html.rs | 24 +++---- fontc_crater/src/main.rs | 10 +-- fontc_crater/src/target.rs | 106 ++++++++++++++++++---------- fontc_crater/src/ttx_diff_runner.rs | 4 +- 5 files changed, 103 insertions(+), 65 deletions(-) diff --git a/fontc_crater/src/ci.rs b/fontc_crater/src/ci.rs index c2a58f2a..f6eeaef6 100644 --- a/fontc_crater/src/ci.rs +++ b/fontc_crater/src/ci.rs @@ -125,7 +125,7 @@ fn run_crater_and_save_results(args: &CiArgs) -> Result<(), Error> { let began = Utc::now(); let results = super::run_all(targets, &context, super::ttx_diff_runner::run_ttx_diff)? .into_iter() - .map(|(target, result)| (target.id(), result)) + .map(|(target, result)| (target, result)) .collect(); let finished = Utc::now(); @@ -215,16 +215,18 @@ fn targets_for_source( config_path: &Path, config: &Config, ) -> impl Iterator { - let default = Some(Target { - config: config_path.to_owned(), - source: src_path.to_owned(), - build: BuildType::Default, - }); - - let gftools = should_build_in_gftools_mode(src_path, config).then(|| Target { - config: config_path.to_owned(), - source: src_path.to_owned(), - build: BuildType::GfTools, + let default = Some(Target::new( + src_path.to_owned(), + config_path.to_owned(), + BuildType::Default, + )); + + let gftools = should_build_in_gftools_mode(src_path, config).then(|| { + Target::new( + src_path.to_owned(), + config_path.to_owned(), + BuildType::GfTools, + ) }); [default, gftools].into_iter().flatten() } diff --git a/fontc_crater/src/ci/html.rs b/fontc_crater/src/ci/html.rs index 59b32bea..5710a314 100644 --- a/fontc_crater/src/ci/html.rs +++ b/fontc_crater/src/ci/html.rs @@ -10,7 +10,7 @@ use std::{ use crate::{ error::Error, ttx_diff_runner::{CompilerFailure, DiffError, DiffOutput, DiffValue}, - TargetId, + Target, }; use maud::{html, Markup, PreEscaped}; @@ -249,9 +249,9 @@ fn make_delta_decoration + Display + Defa } } -fn make_repro_cmd(repo_url: &str, repo_path: &TargetId) -> String { +fn make_repro_cmd(repo_url: &str, repo_path: &Target) -> String { // the first component of the path is the repo, drop that - let mut repo_path = repo_path.path.components(); + let mut repo_path = repo_path.source.components(); repo_path.next(); let repo_path = repo_path.as_path(); @@ -280,7 +280,7 @@ fn make_diff_report( prev: &DiffResults, sources: &BTreeMap, ) -> Markup { - fn get_total_diff_ratios(results: &DiffResults) -> BTreeMap<&TargetId, f32> { + fn get_total_diff_ratios(results: &DiffResults) -> BTreeMap<&Target, f32> { results .success .iter() @@ -296,7 +296,7 @@ fn make_diff_report( .collect() } - let get_repo_url = |id: &TargetId| sources.get(&id.path).map(String::as_str).unwrap_or("#"); + let get_repo_url = |id: &Target| sources.get(&id.source).map(String::as_str).unwrap_or("#"); let current_diff = get_total_diff_ratios(current); let prev_diff = get_total_diff_ratios(prev); @@ -580,8 +580,8 @@ fn format_diff_report_details( fn make_error_report_group<'a>( group_name: &str, - paths_and_if_is_new_error: impl Iterator, - details: impl Fn(&TargetId) -> Markup, + paths_and_if_is_new_error: impl Iterator, + details: impl Fn(&Target) -> Markup, sources: &BTreeMap, ) -> Markup { let items = make_error_report_group_items(paths_and_if_is_new_error, details, sources); @@ -598,11 +598,11 @@ fn make_error_report_group<'a>( } fn make_error_report_group_items<'a>( - paths_and_if_is_new_error: impl Iterator, - details: impl Fn(&TargetId) -> Markup, + paths_and_if_is_new_error: impl Iterator, + details: impl Fn(&Target) -> Markup, sources: &BTreeMap, ) -> Markup { - let get_repo_url = |id: &TargetId| sources.get(&id.path).map(String::as_str).unwrap_or("#"); + let get_repo_url = |id: &Target| sources.get(&id.source).map(String::as_str).unwrap_or("#"); html! { @for (path, is_new) in paths_and_if_is_new_error { details.report_group_item { @@ -615,7 +615,7 @@ fn make_error_report_group_items<'a>( } } } -fn get_other_failures(results: &DiffResults) -> BTreeMap<&TargetId, &str> { +fn get_other_failures(results: &DiffResults) -> BTreeMap<&Target, &str> { results .failure .iter() @@ -629,7 +629,7 @@ fn get_other_failures(results: &DiffResults) -> BTreeMap<&TargetId, &str> { fn get_compiler_failures<'a>( results: &'a DiffResults, compiler: &str, -) -> BTreeMap<&'a TargetId, &'a CompilerFailure> { +) -> BTreeMap<&'a Target, &'a CompilerFailure> { let get_err = |err: &'a DiffError| { let DiffError::CompileFailed(compfail) = err else { return None; diff --git a/fontc_crater/src/main.rs b/fontc_crater/src/main.rs index 5998711a..df75d48b 100644 --- a/fontc_crater/src/main.rs +++ b/fontc_crater/src/main.rs @@ -21,7 +21,7 @@ use serde::{de::DeserializeOwned, Serialize}; use args::{Args, Commands}; use error::Error; -use target::{BuildType, Target, TargetId}; +use target::{BuildType, Target}; fn main() { env_logger::init(); @@ -40,8 +40,8 @@ fn run(args: &Args) -> Result<(), Error> { /// Results of all runs #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] struct Results { - pub(crate) success: BTreeMap, - pub(crate) failure: BTreeMap, + pub(crate) success: BTreeMap, + pub(crate) failure: BTreeMap, } /// The output of trying to run on one font. @@ -129,8 +129,8 @@ fn pip_freeze_sha() -> String { .to_owned() } -impl FromIterator<(TargetId, RunResult)> for Results { - fn from_iter)>>(iter: I) -> Self { +impl FromIterator<(Target, RunResult)> for Results { + fn from_iter)>>(iter: I) -> Self { let mut out = Results::default(); for (path, reason) in iter.into_iter() { match reason { diff --git a/fontc_crater/src/target.rs b/fontc_crater/src/target.rs index 49445182..2b936768 100644 --- a/fontc_crater/src/target.rs +++ b/fontc_crater/src/target.rs @@ -4,23 +4,15 @@ use std::{fmt::Display, path::PathBuf, str::FromStr}; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Target { - // will be used in gftools mode - pub(crate) config: PathBuf, + /// Filename of config file, always a sibling of source. + pub(crate) config: Option, + /// Path to source, relative to the git cache directory. pub(crate) source: PathBuf, pub(crate) build: BuildType, } -/// Uniquely identify a source + build type (default, gftools) -/// -/// this is separate from 'target' because it doesn't preserve the config path. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub(crate) struct TargetId { - pub(crate) path: PathBuf, - pub(crate) build: BuildType, -} - #[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) enum BuildType { Default, @@ -28,20 +20,19 @@ pub(crate) enum BuildType { } impl Target { - pub(crate) fn id(&self) -> TargetId { - TargetId { - path: self.source.clone(), - build: self.build, + pub(crate) fn new( + source: PathBuf, + config: impl Into>, + build: BuildType, + ) -> Self { + Self { + config: config.into(), + source, + build, } } } -impl Display for Target { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.id().fmt(f) - } -} - impl BuildType { pub(crate) fn name(&self) -> &'static str { match self { @@ -57,19 +48,23 @@ impl Display for BuildType { } } -impl Display for TargetId { +impl Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} ({})", self.path.display(), self.build) + write!(f, "{}", self.source.display(),)?; + if let Some(config) = self.config.as_ref() { + write!(f, " ({})", config.display())? + } + write!(f, " ({})", self.build) } } -impl Serialize for TargetId { +impl Serialize for Target { fn serialize(&self, serializer: S) -> Result { self.to_string().serialize(serializer) } } -impl<'de> Deserialize<'de> for TargetId { +impl<'de> Deserialize<'de> for Target { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -79,31 +74,53 @@ impl<'de> Deserialize<'de> for TargetId { } } -impl FromStr for TargetId { +impl FromStr for Target { type Err = String; fn from_str(s: &str) -> Result { let s = s.trim(); + // before gftools we just identified targets as paths, so let's keep that working if !s.ends_with(')') { return Ok(Self { - path: PathBuf::from(s), + source: PathBuf::from(s), + config: None, build: BuildType::Default, }); } // else expect the format, - // PATH (default|gftools) - let (path, type_) = s + // PATH [(config)] (default|gftools) + let (head, type_) = s .rsplit_once('(') .ok_or_else(|| "missing opening paren".to_string())?; - let path = PathBuf::from(path.trim()); + let head = head.trim(); + + // now we may or may not have a config: + let (source_part, config_part) = if head.ends_with(')') { + let (source, config) = head + .rsplit_once('(') + .ok_or_else(|| format!("expected '(' in '{head}'"))?; + (source.trim(), config.trim_end_matches(')')) + } else { + (head, "") + }; + + let source = PathBuf::from(source_part.trim()); + let config = Some(config_part) + .filter(|s| !s.is_empty()) + .map(PathBuf::from); + let type_ = match type_.trim_end_matches(')') { "default" => BuildType::Default, "gftools" => BuildType::GfTools, other => return Err(format!("unknown build type '{other}'")), }; - Ok(TargetId { path, build: type_ }) + Ok(Target { + source, + config, + build: type_, + }) } } @@ -112,14 +129,31 @@ mod tests { use super::*; #[test] - fn serde_target_id() { - let id = TargetId { - path: PathBuf::from("../my/file.is_here"), + fn serde_target_full() { + let id = Target { + source: PathBuf::from("../my/file.is_here"), + config: Some("config.yaml".into()), build: BuildType::GfTools, }; let to_json = serde_json::to_string(&id).unwrap(); - let from_json: TargetId = serde_json::from_str(&to_json).unwrap(); + let from_json: Target = serde_json::from_str(&to_json).unwrap(); assert_eq!(id, from_json) } + + #[test] + fn serde_no_config() { + let json = "\"myfile.is_here (gftools)\""; + let from_json: Target = serde_json::from_str(json).unwrap(); + assert_eq!(from_json.source.as_os_str(), "myfile.is_here"); + assert!(from_json.build == BuildType::GfTools); + } + + #[test] + fn serde_path_only() { + let json = "\"mypath.hello\""; + let from_json: Target = serde_json::from_str(json).unwrap(); + assert_eq!(from_json.source.as_os_str(), "mypath.hello"); + assert_eq!(from_json.build, BuildType::Default); + } } diff --git a/fontc_crater/src/ttx_diff_runner.rs b/fontc_crater/src/ttx_diff_runner.rs index 563aa2b7..eb68f4b4 100644 --- a/fontc_crater/src/ttx_diff_runner.rs +++ b/fontc_crater/src/ttx_diff_runner.rs @@ -27,7 +27,9 @@ pub(super) fn run_ttx_diff(ctx: &TtxContext, target: &Target) -> RunResult Date: Wed, 30 Oct 2024 17:05:50 -0400 Subject: [PATCH 2/2] [crater] Tweak how we save target paths Instead of these being full paths, we save the path of the sources directory and then the config and source files are paths relative to that. This mostly just lets us be represented more efficiently in json. - This also makes config & source on target private, so the only way to get these paths is to pass in the `cache_dir` and receive the fully resolved path. --- fontc_crater/src/ci.rs | 31 +++-- fontc_crater/src/ci/html.rs | 16 ++- fontc_crater/src/target.rs | 187 +++++++++++++++++++++++----- fontc_crater/src/ttx_diff_runner.rs | 4 +- 4 files changed, 194 insertions(+), 44 deletions(-) diff --git a/fontc_crater/src/ci.rs b/fontc_crater/src/ci.rs index f6eeaef6..5ff9d98e 100644 --- a/fontc_crater/src/ci.rs +++ b/fontc_crater/src/ci.rs @@ -125,7 +125,6 @@ fn run_crater_and_save_results(args: &CiArgs) -> Result<(), Error> { let began = Utc::now(); let results = super::run_all(targets, &context, super::ttx_diff_runner::run_ttx_diff)? .into_iter() - .map(|(target, result)| (target, result)) .collect(); let finished = Utc::now(); @@ -189,21 +188,25 @@ fn make_targets(cache_dir: &Path, repos: &[RepoInfo]) -> (Vec, BTreeMap< continue; } }; + let relative_config_path = config_path + .strip_prefix(cache_dir) + .expect("config always in cache dir"); + let sources_dir = config_path + .parent() + .expect("config path always in sources dir"); + // config is always in sources, sources is always in org/repo + let repo_dir = relative_config_path.parent().unwrap().parent().unwrap(); + repo_list.insert(repo_dir.to_owned(), repo.repo_url.clone()); for source in &config.sources { - let src_path = config_path - .parent() - .expect("config path always in sources dir") - .join(source); + let src_path = 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.extend(targets_for_source(&src_path, &config_path, &config)) + .expect("source is always in cache dir"); + targets.extend(targets_for_source(src_path, relative_config_path, &config)) } } } @@ -228,7 +231,15 @@ fn targets_for_source( BuildType::GfTools, ) }); - [default, gftools].into_iter().flatten() + [default, gftools] + .into_iter() + .filter_map(|t| match t.transpose() { + Ok(t) => t, + Err(e) => { + log::warn!("failed to generate target: {e}"); + None + } + }) } fn should_build_in_gftools_mode(src_path: &Path, config: &Config) -> bool { diff --git a/fontc_crater/src/ci/html.rs b/fontc_crater/src/ci/html.rs index 5710a314..3e1621bc 100644 --- a/fontc_crater/src/ci/html.rs +++ b/fontc_crater/src/ci/html.rs @@ -251,7 +251,7 @@ fn make_delta_decoration + Display + Defa fn make_repro_cmd(repo_url: &str, repo_path: &Target) -> String { // the first component of the path is the repo, drop that - let mut repo_path = repo_path.source.components(); + let mut repo_path = repo_path.src_dir_path().components(); repo_path.next(); let repo_path = repo_path.as_path(); @@ -296,7 +296,12 @@ fn make_diff_report( .collect() } - let get_repo_url = |id: &Target| sources.get(&id.source).map(String::as_str).unwrap_or("#"); + let get_repo_url = |id: &Target| { + sources + .get(id.src_dir_path()) + .map(String::as_str) + .unwrap_or("#") + }; let current_diff = get_total_diff_ratios(current); let prev_diff = get_total_diff_ratios(prev); @@ -602,7 +607,12 @@ fn make_error_report_group_items<'a>( details: impl Fn(&Target) -> Markup, sources: &BTreeMap, ) -> Markup { - let get_repo_url = |id: &Target| sources.get(&id.source).map(String::as_str).unwrap_or("#"); + let get_repo_url = |id: &Target| { + sources + .get(id.src_dir_path()) + .map(String::as_str) + .unwrap_or("#") + }; html! { @for (path, is_new) in paths_and_if_is_new_error { details.report_group_item { diff --git a/fontc_crater/src/target.rs b/fontc_crater/src/target.rs index 2b936768..1a07812d 100644 --- a/fontc_crater/src/target.rs +++ b/fontc_crater/src/target.rs @@ -1,15 +1,22 @@ //! targets of a compilation -use std::{fmt::Display, path::PathBuf, str::FromStr}; +use std::{ + fmt::Display, + path::{Path, PathBuf}, + str::FromStr, +}; use serde::{Deserialize, Serialize}; +use thiserror::Error; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Target { - /// Filename of config file, always a sibling of source. - pub(crate) config: Option, - /// Path to source, relative to the git cache directory. - pub(crate) source: PathBuf, + /// path to the source dir for this target (relative to the git cache root) + source_dir: PathBuf, + /// Filename of config file, in the source directory. + config: Option, + /// Path to source file, relative to the source_dir + source: PathBuf, pub(crate) build: BuildType, } @@ -19,17 +26,103 @@ pub(crate) enum BuildType { GfTools, } +fn get_source_dir(source_path: &Path) -> Result { + let mut result = source_path.to_owned(); + loop { + match result.file_name() { + Some(name) if name == "sources" || name == "Sources" => return Ok(result), + Some(_) => { + if !result.pop() { + break; + } + } + None => break, + } + } + Err(InvalidTargetPath { + path: source_path.to_owned(), + reason: BadPathReason::NoSourceDir, + }) +} + +/// A source path must always be a file in side a directory named 'sources' or 'Sources' +#[derive(Clone, Debug, Error)] +#[error("invalid path '{path}' for target: {reason}")] +pub(crate) struct InvalidTargetPath { + path: PathBuf, + reason: BadPathReason, +} + +#[derive(Clone, Debug, PartialEq)] +enum BadPathReason { + NoSourceDir, + BadConfigPath, +} + +impl Display for BadPathReason { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BadPathReason::NoSourceDir => f.write_str("missing sources/Sources directory"), + BadPathReason::BadConfigPath => f.write_str("config is not relative to source"), + } + } +} + impl Target { pub(crate) fn new( source: PathBuf, config: impl Into>, build: BuildType, - ) -> Self { - Self { - config: config.into(), + ) -> Result { + let source_dir = get_source_dir(&source)?; + let source = source + .strip_prefix(&source_dir) + .expect("must be a base") + .to_owned(); + let config = match config.into() { + // if we're just a filename, that's fine; assume relative to source + Some(config) + if config.file_name().is_some() && config.parent() == Some(Path::new("")) => + { + Some(config) + } + // else we have to be in the same source directory as our source + Some(config) => Some( + config + .strip_prefix(&source_dir) + .map(PathBuf::from) + .map_err(|_| InvalidTargetPath { + path: config, + reason: BadPathReason::BadConfigPath, + })?, + ), + None => None, + }; + + Ok(Self { + source_dir, + config, source, build, - } + }) + } + + /// The path to the source directory, used for looking up repo urls + pub(crate) fn src_dir_path(&self) -> &Path { + &self.source_dir + } + + pub(crate) fn source_path(&self, git_cache: &Path) -> PathBuf { + let mut out = git_cache.join(&self.source_dir); + out.push(&self.source); + out + } + + pub(crate) fn config_path(&self, git_cache: &Path) -> Option { + let config = self.config.as_ref()?; + let mut out = git_cache.join(&self.source_dir); + out.push(config); + Some(out) } } @@ -50,7 +143,8 @@ impl Display for BuildType { impl Display for Target { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.source.display(),)?; + let source = self.source_dir.join(&self.source); + write!(f, "{}", source.display())?; if let Some(config) = self.config.as_ref() { write!(f, " ({})", config.display())? } @@ -81,11 +175,8 @@ impl FromStr for Target { let s = s.trim(); // before gftools we just identified targets as paths, so let's keep that working if !s.ends_with(')') { - return Ok(Self { - source: PathBuf::from(s), - config: None, - build: BuildType::Default, - }); + return Self::new(s.into(), None, BuildType::Default) + .map_err(|e| format!("failed to parse target '{s}': {e}")); } // else expect the format, // PATH [(config)] (default|gftools) @@ -116,11 +207,7 @@ impl FromStr for Target { other => return Err(format!("unknown build type '{other}'")), }; - Ok(Target { - source, - config, - build: type_, - }) + Self::new(source, config, type_).map_err(|e| format!("failed to parse target '{s}': {e}")) } } @@ -128,30 +215,72 @@ impl FromStr for Target { mod tests { use super::*; + #[test] + fn construct_target_with_source() { + let source = PathBuf::from("org/repo/sources/Mysource.glyphs"); + let target = Target::new(source, None, BuildType::Default).unwrap(); + assert_eq!(target.source_dir.as_os_str(), "org/repo/sources"); + assert_eq!(target.source.as_os_str(), "Mysource.glyphs"); + } + + #[test] + fn invalid_source_path() { + let source = PathBuf::from("org/repo/not_sources/Mysource.glyphs"); + let target = Target::new(source, None, BuildType::Default); + assert!(matches!(target, Err(e) if e.reason == BadPathReason::NoSourceDir)); + } + + #[test] + fn good_config_path() { + let source = PathBuf::from("org/repo/sources/Mysource.glyphs"); + let config = PathBuf::from("org/repo/sources/my-config.yaml"); + let target = Target::new(source, Some(config), BuildType::Default).unwrap(); + assert_eq!(target.source_dir.as_os_str(), "org/repo/sources"); + assert_eq!(target.source.as_os_str(), "Mysource.glyphs"); + assert_eq!(target.config.as_deref(), Some(Path::new("my-config.yaml"))); + } + + #[test] + fn bare_config_path() { + let source = PathBuf::from("org/repo/sources/Mysource.glyphs"); + let config = PathBuf::from("my-config.yaml"); + let target = Target::new(source, Some(config), BuildType::Default).unwrap(); + assert_eq!(target.source_dir.as_os_str(), "org/repo/sources"); + assert_eq!(target.source.as_os_str(), "Mysource.glyphs"); + assert_eq!(target.config.as_deref(), Some(Path::new("my-config.yaml"))); + } + + #[test] + fn bad_config_path() { + let source = PathBuf::from("org/repo/sources/Mysource.glyphs"); + let config = PathBuf::from("somewhere_else/my-config.yaml"); + let target = Target::new(source, Some(config), BuildType::Default); + assert!(matches!(target, Err(e) if e.reason == BadPathReason::BadConfigPath)); + } + #[test] fn serde_target_full() { - let id = Target { - source: PathBuf::from("../my/file.is_here"), - config: Some("config.yaml".into()), - build: BuildType::GfTools, - }; + let source = PathBuf::from("org/repo/sources/Mysource.glyphs"); + let config = PathBuf::from("config.yaml"); + let target = Target::new(source, Some(config), BuildType::Default).unwrap(); - let to_json = serde_json::to_string(&id).unwrap(); + let to_json = serde_json::to_string(&target).unwrap(); let from_json: Target = serde_json::from_str(&to_json).unwrap(); - assert_eq!(id, from_json) + assert_eq!(target, from_json) } #[test] fn serde_no_config() { - let json = "\"myfile.is_here (gftools)\""; + let json = "\"org/repo/sources/myfile.is_here (gftools)\""; let from_json: Target = serde_json::from_str(json).unwrap(); assert_eq!(from_json.source.as_os_str(), "myfile.is_here"); + assert!(from_json.config.is_none()); assert!(from_json.build == BuildType::GfTools); } #[test] fn serde_path_only() { - let json = "\"mypath.hello\""; + let json = "\"repo/Sources/mypath.hello\""; let from_json: Target = serde_json::from_str(json).unwrap(); assert_eq!(from_json.source.as_os_str(), "mypath.hello"); assert_eq!(from_json.build, BuildType::Default); diff --git a/fontc_crater/src/ttx_diff_runner.rs b/fontc_crater/src/ttx_diff_runner.rs index eb68f4b4..8d212390 100644 --- a/fontc_crater/src/ttx_diff_runner.rs +++ b/fontc_crater/src/ttx_diff_runner.rs @@ -17,7 +17,7 @@ pub(super) struct TtxContext { pub(super) fn run_ttx_diff(ctx: &TtxContext, target: &Target) -> RunResult { let tempdir = tempfile::tempdir().expect("couldn't create tempdir"); let outdir = tempdir.path(); - let source_path = ctx.cache_dir.join(&target.source); + let source_path = target.source_path(&ctx.cache_dir); let compare = target.build.name(); let mut cmd = Command::new("python"); cmd.args([SCRIPT_PATH, "--json", "--compare", compare, "--outdir"]) @@ -27,7 +27,7 @@ pub(super) fn run_ttx_diff(ctx: &TtxContext, target: &Target) -> RunResult