summaryrefslogtreecommitdiff
path: root/crates/typst-cli
diff options
context:
space:
mode:
authorMatthew Toohey <contact@mtoohey.com>2024-04-09 07:50:41 -0400
committerGitHub <noreply@github.com>2024-04-09 11:50:41 +0000
commit72dd79210602ecc799726fc096b078afbb47f299 (patch)
treea48a5c7ce8aa99048e40b3138b5138a448d76e1a /crates/typst-cli
parent847b710480d388b890f13c2c0a20c4e8b7e16cc2 (diff)
Add --make-deps CLI argument (#2877) (#3890)
Diffstat (limited to 'crates/typst-cli')
-rw-r--r--crates/typst-cli/src/args.rs4
-rw-r--r--crates/typst-cli/src/compile.rs86
2 files changed, 87 insertions, 3 deletions
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs
index 689785df..bed5f49a 100644
--- a/crates/typst-cli/src/args.rs
+++ b/crates/typst-cli/src/args.rs
@@ -72,6 +72,10 @@ pub struct CompileCommand {
#[clap(required_if_eq("input", "-"), value_parser = ValueParser::new(output_value_parser))]
pub output: Option<Output>,
+ /// Output a Makefile rule describing the current compilation
+ #[clap(long = "make-deps", value_name = "PATH")]
+ pub make_deps: Option<PathBuf>,
+
/// The format of the output file, inferred from the extension by default
#[arg(long = "format", short = 'f')]
pub format: Option<OutputFormat>,
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index 6273615c..ce03fd45 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -1,6 +1,6 @@
-use std::fs;
-use std::io::Write;
-use std::path::Path;
+use std::fs::{self, File};
+use std::io::{self, Write};
+use std::path::{Path, PathBuf};
use chrono::{Datelike, Timelike};
use codespan_reporting::diagnostic::{Diagnostic, Label};
@@ -118,6 +118,8 @@ pub fn compile_once(
print_diagnostics(world, &[], &warnings, command.common.diagnostic_format)
.map_err(|err| eco_format!("failed to print diagnostics ({err})"))?;
+ write_make_deps(world, command)?;
+
if let Some(open) = command.open.take() {
if let Output::Path(file) = command.output() {
open_file(open.as_deref(), &file)?;
@@ -335,6 +337,84 @@ 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, command: &CompileCommand) -> StrResult<()> {
+ let Some(ref make_deps_path) = command.make_deps else { return Ok(()) };
+ let Output::Path(output_path) = command.output() else {
+ bail!("failed to create make dependencies file because output was stdout")
+ };
+ let Ok(output_path) = output_path.into_os_string().into_string() else {
+ bail!("failed to create make dependencies file because output path was not valid unicode")
+ };
+
+ // Based on `munge` in libcpp/mkdeps.cc from the GCC source code. This isn't
+ // perfect as some special characters can't be escaped.
+ fn munge(s: &str) -> String {
+ let mut res = String::with_capacity(s.len());
+ let mut slashes = 0;
+ for c in s.chars() {
+ match c {
+ '\\' => slashes += 1,
+ '$' => {
+ 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
+ // backslashes followed by space..."
+ for _ in 0..slashes + 1 {
+ res.push('\\');
+ }
+ slashes = 0;
+ }
+ '#' => {
+ res.push('\\');
+ slashes = 0;
+ }
+ _ => slashes = 0,
+ };
+ res.push(c);
+ }
+ res
+ }
+
+ fn write(
+ make_deps_path: &Path,
+ output_path: String,
+ root: PathBuf,
+ dependencies: impl Iterator<Item = PathBuf>,
+ ) -> io::Result<()> {
+ let mut file = File::create(make_deps_path)?;
+
+ 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 {
+ // 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.
+ continue;
+ };
+
+ file.write_all(b" ")?;
+ file.write_all(munge(dependency).as_bytes())?;
+ }
+ file.write_all(b"\n")?;
+
+ Ok(())
+ }
+
+ write(make_deps_path, output_path, world.root().to_owned(), world.dependencies())
+ .map_err(|err| {
+ eco_format!("failed to create make dependencies file due to IO error ({err})")
+ })
+}
+
/// Opens the given file using:
/// - The default file viewer if `open` is `None`.
/// - The given viewer provided by `open` if it is `Some`.