From ddec8feab31886cae2133bd3929b8000bcaa66e4 Mon Sep 17 00:00:00 2001 From: stelzo Date: Tue, 21 Jan 2025 23:13:33 +0100 Subject: [PATCH] add vendoring --- crates/typst-cli/src/args.rs | 19 ++++++ crates/typst-cli/src/init.rs | 4 +- crates/typst-cli/src/main.rs | 2 + crates/typst-cli/src/vendor.rs | 100 ++++++++++++++++++++++++++++++++ crates/typst-cli/src/world.rs | 6 +- crates/typst-kit/src/package.rs | 12 ++++ 6 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 crates/typst-cli/src/vendor.rs diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index d6855d100..0b09094ff 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -75,6 +75,9 @@ pub enum Command { /// Processes an input file to extract provided metadata. Query(QueryCommand), + /// Create a vendor directory with all used packages. + Vendor(VendorCommand), + /// Lists all discovered fonts in system and custom font paths. Fonts(FontsCommand), @@ -160,6 +163,22 @@ pub struct QueryCommand { pub process: ProcessArgs, } +/// Create a vendor directory with all used packages in the current directory. +#[derive(Debug, Clone, Parser)] +pub struct VendorCommand { + /// Path to input Typst file. Use `-` to read input from stdin. + #[clap(value_parser = input_value_parser(), value_hint = ValueHint::FilePath)] + pub input: Input, + + /// World arguments. + #[clap(flatten)] + pub world: WorldArgs, + + /// Processing arguments. + #[clap(flatten)] + pub process: ProcessArgs, +} + /// Lists all discovered fonts in system and custom font paths. #[derive(Debug, Clone, Parser)] pub struct FontsCommand { diff --git a/crates/typst-cli/src/init.rs b/crates/typst-cli/src/init.rs index 9a77fb470..0e0a5c646 100644 --- a/crates/typst-cli/src/init.rs +++ b/crates/typst-cli/src/init.rs @@ -28,9 +28,9 @@ pub fn init(command: &InitCommand) -> StrResult<()> { StrResult::Ok(spec.at(version)) })?; - // Find or download the package. + // Find or download the package. Vendoring does not make sense for initialization, so project_root is not needed. let package_path = - package_storage.prepare_package(&spec, &mut PrintDownload(&spec))?; + package_storage.prepare_package(&spec, &mut PrintDownload(&spec), None)?; // Parse the manifest. let manifest = parse_manifest(&package_path)?; diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index 14f8a665d..4e313552e 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -12,6 +12,7 @@ mod terminal; mod timings; #[cfg(feature = "self-update")] mod update; +mod vendor; mod watch; mod world; @@ -69,6 +70,7 @@ fn dispatch() -> HintedStrResult<()> { Command::Watch(command) => crate::watch::watch(&mut timer, command)?, Command::Init(command) => crate::init::init(command)?, Command::Query(command) => crate::query::query(command)?, + Command::Vendor(command) => crate::vendor::vendor(command)?, Command::Fonts(command) => crate::fonts::fonts(command), Command::Update(command) => crate::update::update(command)?, } diff --git a/crates/typst-cli/src/vendor.rs b/crates/typst-cli/src/vendor.rs new file mode 100644 index 000000000..08bd4c1bd --- /dev/null +++ b/crates/typst-cli/src/vendor.rs @@ -0,0 +1,100 @@ +use std::fs::{create_dir, create_dir_all}; + +use ecow::eco_format; +use typst::{ + diag::{bail, HintedStrResult, Warned}, + layout::PagedDocument, +}; +use typst_kit::package::DEFAULT_PACKAGES_SUBDIR; + +use crate::{ + args::VendorCommand, compile::print_diagnostics, set_failed, world::SystemWorld, +}; +use typst::World; + +/// Execute a vendor command. +pub fn vendor(command: &VendorCommand) -> HintedStrResult<()> { + let mut world = SystemWorld::new(&command.input, &command.world, &command.process)?; + + // Reset everything and ensure that the main file is present. + world.reset(); + world.source(world.main()).map_err(|err| err.to_string())?; + + let Warned { output, warnings } = typst::compile::(&world); + + match output { + Ok(_) => { + copy_deps(&mut world)?; + print_diagnostics(&world, &[], &warnings, command.process.diagnostic_format) + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; + } + + // Print diagnostics. + Err(errors) => { + set_failed(); + print_diagnostics( + &world, + &errors, + &warnings, + command.process.diagnostic_format, + ) + .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; + } + } + + Ok(()) +} + +fn copy_deps(world: &mut SystemWorld) -> HintedStrResult<()> { + let vendor_dir = world.workdir().join("vendor"); + + match vendor_dir.try_exists() { + Ok(false) => { + if let Err(err) = create_dir(vendor_dir.clone()) { + bail!("failed to create vendor directory: {:?}", err); + } + } + Err(err) => { + bail!("failed to check existence of vendor directory: {:?}", err); + } + _ => {} + } + + // Must iterate two times in total. As soon as the parent directory is created, + // world tries to read the subsequent files from the same package + // from the vendor directory since it is higher priority. + let all_deps = world + .dependencies() + .filter_map(|dep_path| { + let path = dep_path.to_str().unwrap(); + path.find(DEFAULT_PACKAGES_SUBDIR).map(|pos| { + let dependency_path = &path[pos + DEFAULT_PACKAGES_SUBDIR.len() + 1..]; + (dep_path.clone(), vendor_dir.join(dependency_path)) + }) + }) + .collect::>(); + + for (from_data_path, to_vendor_path) in all_deps { + if let Some(parent) = to_vendor_path.parent() { + match parent.try_exists() { + Ok(false) => { + if let Err(err) = create_dir_all(parent) { + bail!( + "failed to create package inside the vendor directory: {:?}", + err + ); + } + } + Err(err) => { + bail!("failed to check existence of a package inside the vendor directory: {:?}", err); + } + _ => {} + } + } + + if let Err(err) = std::fs::copy(from_data_path, to_vendor_path) { + bail!("failed to copy dependency to vendor directory: {:?}", err); + } + } + Ok(()) +} diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 12e80d273..c9058b5e9 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -381,7 +381,11 @@ fn system_path( let buf; let mut root = project_root; if let Some(spec) = id.package() { - buf = package_storage.prepare_package(spec, &mut PrintDownload(&spec))?; + buf = package_storage.prepare_package( + spec, + &mut PrintDownload(&spec), + Some(root), + )?; root = &buf; } diff --git a/crates/typst-kit/src/package.rs b/crates/typst-kit/src/package.rs index e7eb71ee4..0ab8e943d 100644 --- a/crates/typst-kit/src/package.rs +++ b/crates/typst-kit/src/package.rs @@ -71,6 +71,7 @@ impl PackageStorage { &self, spec: &PackageSpec, progress: &mut dyn Progress, + project_root: Option<&Path>, ) -> PackageResult { let subdir = format!("{}/{}/{}", spec.namespace, spec.name, spec.version); @@ -81,6 +82,17 @@ impl PackageStorage { } } + // Read from vendor dir if it exists. + if let Some(project_root) = project_root { + let vendor_dir = project_root.join("vendor"); + if let Ok(true) = vendor_dir.try_exists() { + let dir = vendor_dir.join(&subdir); + if dir.exists() { + return Ok(dir); + } + } + } + if let Some(cache_dir) = &self.package_cache_path { let dir = cache_dir.join(&subdir); if dir.exists() {