diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 515a777a2..2b6a7d820 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -6,8 +6,9 @@ use std::path::{Path, PathBuf}; use chrono::{DateTime, Datelike, Timelike, Utc}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term; -use ecow::{eco_format, EcoString}; +use ecow::eco_format; use parking_lot::RwLock; +use pathdiff::diff_paths; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use typst::diag::{ bail, At, Severity, SourceDiagnostic, SourceResult, StrResult, Warned, @@ -188,7 +189,7 @@ pub fn compile_once( match output { // Export the PDF / PNG. - Ok(()) => { + Ok(outputs) => { let duration = start.elapsed(); if config.watching { @@ -202,7 +203,7 @@ pub fn compile_once( print_diagnostics(world, &[], &warnings, config.diagnostic_format) .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; - write_make_deps(world, config)?; + write_make_deps(world, config, outputs)?; open_output(config)?; } @@ -226,12 +227,15 @@ pub fn compile_once( fn compile_and_export( world: &mut SystemWorld, config: &mut CompileConfig, -) -> Warned> { +) -> Warned>> { match config.output_format { OutputFormat::Html => { let Warned { output, warnings } = typst::compile::(world); let result = output.and_then(|document| export_html(&document, config)); - Warned { output: result, warnings } + Warned { + output: result.map(|()| vec![config.output.clone()]), + warnings, + } } _ => { let Warned { output, warnings } = typst::compile::(world); @@ -257,9 +261,14 @@ fn export_html(document: &HtmlDocument, config: &CompileConfig) -> SourceResult< } /// Export to a paged target format. -fn export_paged(document: &PagedDocument, config: &CompileConfig) -> SourceResult<()> { +fn export_paged( + document: &PagedDocument, + config: &CompileConfig, +) -> SourceResult> { match config.output_format { - OutputFormat::Pdf => export_pdf(document, config), + OutputFormat::Pdf => { + export_pdf(document, config).map(|()| vec![config.output.clone()]) + } OutputFormat::Png => { export_image(document, config, ImageExportFormat::Png).at(Span::detached()) } @@ -327,7 +336,7 @@ fn export_image( document: &PagedDocument, config: &CompileConfig, fmt: ImageExportFormat, -) -> StrResult<()> { +) -> StrResult> { // Determine whether we have indexable templates in output let can_handle_multiple = match config.output { Output::Stdout => false, @@ -383,7 +392,7 @@ fn export_image( && config.export_cache.is_cached(*i, &page.frame) && path.exists() { - return Ok(()); + return Ok(Output::Path(path.to_path_buf())); } Output::Path(path.to_owned()) @@ -392,11 +401,9 @@ fn export_image( }; export_image_page(config, page, &output, fmt)?; - Ok(()) + Ok(output) }) - .collect::, EcoString>>()?; - - Ok(()) + .collect::>>() } mod output_template { @@ -501,14 +508,25 @@ impl ExportCache { /// Writes a Makefile rule describing the relationship between the output and /// its dependencies to the path specified by the --make-deps argument, if it /// was provided. -fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult<()> { +fn write_make_deps( + world: &mut SystemWorld, + config: &CompileConfig, + outputs: Vec, +) -> StrResult<()> { let Some(ref make_deps_path) = config.make_deps else { return Ok(()) }; - let Output::Path(output_path) = &config.output else { - bail!("failed to create make dependencies file because output was stdout") - }; - let Some(output_path) = output_path.as_os_str().to_str() else { + let Ok(output_paths) = outputs + .into_iter() + .filter_map(|o| match o { + Output::Path(path) => Some(path.into_os_string().into_string()), + Output::Stdout => None, + }) + .collect::, _>>() + else { bail!("failed to create make dependencies file because output path was not valid unicode") }; + if output_paths.is_empty() { + bail!("failed to create make dependencies file because output was stdout") + } // Based on `munge` in libcpp/mkdeps.cc from the GCC source code. This isn't // perfect as some special characters can't be escaped. @@ -522,6 +540,10 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult res.push('$'); slashes = 0; } + ':' => { + res.push('\\'); + slashes = 0; + } ' ' | '\t' => { // `munge`'s source contains a comment here that says: "A // space or tab preceded by 2N+1 backslashes represents N @@ -544,18 +566,29 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult fn write( make_deps_path: &Path, - output_path: &str, + output_paths: Vec, root: PathBuf, dependencies: impl Iterator, ) -> io::Result<()> { let mut file = File::create(make_deps_path)?; + let current_dir = std::env::current_dir()?; + let relative_root = diff_paths(&root, ¤t_dir).unwrap_or(root.clone()); - file.write_all(munge(output_path).as_bytes())?; + for (i, output_path) in output_paths.into_iter().enumerate() { + if i != 0 { + file.write_all(b" ")?; + } + file.write_all(munge(&output_path).as_bytes())?; + } file.write_all(b":")?; for dependency in dependencies { - let Some(dependency) = - dependency.strip_prefix(&root).unwrap_or(&dependency).to_str() - else { + let relative_dependency = match dependency.strip_prefix(&root) { + Ok(root_relative_dependency) => { + relative_root.join(root_relative_dependency) + } + Err(_) => dependency, + }; + let Some(relative_dependency) = relative_dependency.to_str() else { // Silently skip paths that aren't valid unicode so we still // produce a rule that will work for the other paths that can be // processed. @@ -563,14 +596,14 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult }; file.write_all(b" ")?; - file.write_all(munge(dependency).as_bytes())?; + file.write_all(munge(relative_dependency).as_bytes())?; } file.write_all(b"\n")?; Ok(()) } - write(make_deps_path, output_path, world.root().to_owned(), world.dependencies()) + write(make_deps_path, output_paths, world.root().to_owned(), world.dependencies()) .map_err(|err| { eco_format!("failed to create make dependencies file due to IO error ({err})") })