Skip to content

Commit

Permalink
Keep single file output deterministic for write check
Browse files Browse the repository at this point in the history
  • Loading branch information
darrell-roberts committed Apr 14, 2024
1 parent b7c2ee2 commit e747d2e
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 21 deletions.
1 change: 1 addition & 0 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Command line argument parsing.
use clap::{command, Arg, ArgGroup, Command};

const VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down
7 changes: 5 additions & 2 deletions cli/src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Source file parsing.
use anyhow::Context;
use ignore::WalkBuilder;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
Expand All @@ -6,20 +7,22 @@ use std::{
ops::Not,
path::PathBuf,
};
#[cfg(feature = "go")]
use typeshare_core::language::Go;
use typeshare_core::{
language::{CrateName, CrateTypes, SupportedLanguage},
parser::ParsedData,
};

/// Input data for parsing each source file.
pub struct ParserInput {
/// Rust source file path.
file_path: PathBuf,
/// File name source from crate for output.
file_name: String,
/// The crate name the source file belongs to.
crate_name: CrateName,
}

/// Walk the source folder and collect all parser inputs.
pub fn parser_inputs(
walker_builder: WalkBuilder,
language_type: SupportedLanguage,
Expand Down
55 changes: 45 additions & 10 deletions cli/src/writer.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
//! Generated source file output.
use crate::args::{ARG_OUTPUT_FILE, ARG_OUTPUT_FOLDER};
use anyhow::Context;
use clap::ArgMatches;
use std::{
collections::HashMap,
fs,
ops::ControlFlow,
path::{Path, PathBuf},
};
use typeshare_core::{
language::{CrateName, CrateTypes, Language},
parser::ParsedData,
rust_types::RustItem,
};

/// Write the parsed data to the one or more files depending on command line options.
pub fn write_generated(
options: ArgMatches,
lang: Box<dyn Language>,
Expand All @@ -30,6 +32,7 @@ pub fn write_generated(
}
}

/// Write multiple module files.
fn write_multiple_files(
mut lang: Box<dyn Language>,
output_folder: &str,
Expand All @@ -40,47 +43,79 @@ fn write_multiple_files(
let outfile = Path::new(output_folder).join(&parsed_data.file_name);
let mut generated_contents = Vec::new();
lang.generate_types(&mut generated_contents, &import_candidates, parsed_data)?;
if let ControlFlow::Break(_) = check_write_file(&outfile, generated_contents)? {
continue;
}
check_write_file(&outfile, generated_contents)?;
}
Ok(())
}

fn check_write_file(outfile: &PathBuf, output: Vec<u8>) -> anyhow::Result<ControlFlow<()>> {
/// Write the file if the contents have changed.
fn check_write_file(outfile: &PathBuf, output: Vec<u8>) -> anyhow::Result<()> {
match fs::read(outfile) {
Ok(buf) if buf == output => {
// avoid writing the file to leave the mtime intact
// for tools which might use it to know when to
// rebuild.
println!("Skipping writing to {outfile:?} no changes");
return Ok(ControlFlow::Break(()));
return Ok(());
}
_ => {}
}

if !output.is_empty() {
let out_dir = outfile.parent().unwrap();
let out_dir = outfile
.parent()
.context(format!("Could not get parent for {outfile:?}"))?;
// If the output directory doesn't already exist, create it.
if !out_dir.exists() {
fs::create_dir_all(out_dir).context("failed to create output directory")?;
}

fs::write(outfile, output).context("failed to write output")?;
}
Ok(ControlFlow::Continue(()))
Ok(())
}

/// Write all types to a single file.
fn write_single_file(
mut lang: Box<dyn Language>,
file_name: &str,
crate_parsed_data: HashMap<CrateName, ParsedData>,
) -> Result<(), anyhow::Error> {
let mut output = Vec::new();
for data in crate_parsed_data.into_values() {
lang.generate_types(&mut output, &HashMap::new(), data)?;

let mut sorted_types = crate_parsed_data
.into_values()
.flat_map(|parsed_data| {
parsed_data
.aliases
.into_iter()
.map(RustItem::Alias)
.chain(parsed_data.structs.into_iter().map(RustItem::Struct))
.chain(parsed_data.enums.into_iter().map(RustItem::Enum))
})
.collect::<Vec<_>>();

sorted_types.sort_by(|item1, item2| item1.renamed_type_name().cmp(item2.renamed_type_name()));

let mut parsed_data_combined = ParsedData::default();

for item in sorted_types {
match item {
RustItem::Struct(s) => {
parsed_data_combined.structs.push(s);
}
RustItem::Enum(e) => {
parsed_data_combined.enums.push(e);
}
RustItem::Alias(a) => {
parsed_data_combined.aliases.push(a);
}
t => eprintln!("Unsupported type {t:?}"),
}
}

lang.generate_types(&mut output, &HashMap::new(), parsed_data_combined)?;

let outfile = Path::new(file_name).to_path_buf();
check_write_file(&outfile, output)?;
Ok(())
Expand Down
7 changes: 3 additions & 4 deletions core/src/language/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ impl Language for Go {
) -> std::io::Result<()> {
// Generate a list of all types that either are a struct or are aliased to a struct.
// This is used to determine whether a type should be defined as a pointer or not.
let mut types_mapping_to_struct = HashSet::new();
for s in &data.structs {
types_mapping_to_struct.insert(s.id.original.clone());
}
let mut types_mapping_to_struct =
HashSet::from_iter(data.structs.iter().map(|s| s.id.original.clone()));

for alias in &data.aliases {
if types_mapping_to_struct.contains(alias.r#type.id()) {
types_mapping_to_struct.insert(alias.id.original.clone());
Expand Down
2 changes: 1 addition & 1 deletion core/src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ fn used_imports<'a, 'b: 'a>(
})
.or_insert(BTreeSet::from([ty.as_str()]));
} else {
println!("Could not lookup reference {referenced_import:?}");
// println!("Could not lookup reference {referenced_import:?}");
}
};

Expand Down
4 changes: 0 additions & 4 deletions core/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ pub struct ParsedData {
pub multi_file: bool,
}

// pub struct ParsedModule {
// pub module: HashMap<String, Vec<ParsedData>>,
// }

impl ParsedData {
/// Create a new parsed data.
pub fn new(crate_name: CrateName, file_name: String, multi_file: bool) -> Self {
Expand Down
11 changes: 11 additions & 0 deletions core/src/rust_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,3 +625,14 @@ pub enum RustItem {
/// A `type` definition or newtype struct.
Alias(RustTypeAlias),
}

impl RustItem {
/// Get the renamed type name for this type.
pub fn renamed_type_name(&self) -> &str {
match self {
RustItem::Struct(s) => s.id.renamed.as_str(),
RustItem::Enum(e) => e.shared().id.renamed.as_str(),
RustItem::Alias(a) => a.id.renamed.as_str(),
}
}
}

0 comments on commit e747d2e

Please sign in to comment.