Handle local TEXINPUTS in latexmkrc

This commit is contained in:
Patrick Förster 2025-04-18 09:16:16 +02:00
parent c609974cfc
commit 6dfbb02aae
12 changed files with 131 additions and 36 deletions

3
Cargo.lock generated
View file

@ -1325,6 +1325,7 @@ dependencies = [
name = "parser"
version = "0.0.0"
dependencies = [
"distro",
"expect-test",
"log",
"logos",
@ -1333,6 +1334,7 @@ dependencies = [
"regex",
"rowan",
"rustc-hash 2.1.0",
"shellexpand",
"syntax",
"tempfile",
"versions",
@ -1782,6 +1784,7 @@ dependencies = [
name = "syntax"
version = "0.0.0"
dependencies = [
"distro",
"itertools 0.13.0",
"rowan",
"rustc-hash 2.1.0",

View file

@ -28,6 +28,7 @@ rowan = "0.15.16"
rustc-hash = "2.1.0"
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
shellexpand = "3.1.0"
tempfile = "3.19.1"
titlecase = "3.3.0"
unicode-normalization = "0.1.24"

View file

@ -21,7 +21,7 @@ percent-encoding = "2.3.0"
regex.workspace = true
rowan.workspace = true
rustc-hash.workspace = true
shellexpand = "3.1.0"
shellexpand.workspace = true
syntax = { path = "../syntax" }
titlecase.workspace = true
url.workspace = true

View file

@ -13,14 +13,14 @@ use super::ProjectRoot;
pub static HOME_DIR: Lazy<Option<PathBuf>> = Lazy::new(dirs::home_dir);
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, Clone)]
pub struct Edge {
pub source: Url,
pub target: Url,
pub data: EdgeData,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, Clone)]
pub enum EdgeData {
DirectLink(Box<DirectLinkData>),
FileList(Arc<ProjectRoot>),
@ -28,7 +28,7 @@ pub enum EdgeData {
Artifact,
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[derive(Debug, Clone)]
pub struct DirectLinkData {
pub link: semantics::tex::Link,
pub new_root: Option<ProjectRoot>,
@ -196,7 +196,11 @@ impl Graph {
let file_name_db = &workspace.distro().file_name_db;
let distro_files = file_names
.iter()
.filter_map(|name| file_name_db.get(name))
.filter_map(|name| {
file_name_db
.get(name)
.or_else(|| start.root.file_name_db.get(name))
})
.filter(|path| {
home_dir.map_or(false, |dir| path.starts_with(dir))
|| Language::from_path(path) == Some(Language::Bib)

View file

@ -1,10 +1,13 @@
use std::sync::Arc;
use distro::FileNameDB;
use url::Url;
use crate::{util, DocumentData, Workspace};
use super::graph::HOME_DIR;
#[derive(PartialEq, Eq, Clone, Hash)]
#[derive(Clone)]
pub struct ProjectRoot {
pub compile_dir: Url,
pub src_dir: Url,
@ -12,6 +15,7 @@ pub struct ProjectRoot {
pub log_dir: Url,
pub pdf_dir: Url,
pub additional_files: Vec<Url>,
pub file_name_db: Arc<FileNameDB>,
}
impl ProjectRoot {
@ -70,6 +74,7 @@ impl ProjectRoot {
log_dir,
pdf_dir,
additional_files,
file_name_db: Default::default(),
})
}
@ -108,6 +113,8 @@ impl ProjectRoot {
let additional_files = vec![];
let file_name_db = Arc::clone(&rcfile.file_name_db);
Some(Self {
compile_dir,
src_dir,
@ -115,6 +122,7 @@ impl ProjectRoot {
log_dir,
pdf_dir,
additional_files,
file_name_db,
})
}
@ -150,6 +158,7 @@ impl ProjectRoot {
log_dir,
pdf_dir,
additional_files,
file_name_db: Default::default(),
}
}
}
@ -172,6 +181,7 @@ impl std::fmt::Debug for ProjectRoot {
.field("log_dir", &self.log_dir.as_str())
.field("pdf_dir", &self.pdf_dir.as_str())
.field("additional_files", &self.additional_files)
.field("file_name_db", &"...")
.finish()
}
}

View file

@ -1,7 +1,7 @@
use std::io::Write;
use anyhow::Result;
use base_db::Workspace;
use base_db::{deps::Edge, Workspace};
use itertools::Itertools;
use rustc_hash::FxHashMap;
@ -40,16 +40,11 @@ pub fn show_dependency_graph(workspace: &Workspace) -> Result<String> {
.graphs()
.values()
.flat_map(|graph| &graph.edges)
.unique()
.unique_by(|edge| (&edge.source, &edge.target, edge_label(&edge)))
{
let source = &documents[&edge.source];
let target = &documents[&edge.target];
let label = match &edge.data {
base_db::deps::EdgeData::DirectLink(data) => &data.link.path.text,
base_db::deps::EdgeData::AdditionalFiles => "<project>",
base_db::deps::EdgeData::Artifact => "<artifact>",
base_db::deps::EdgeData::FileList(_) => "<fls>",
};
let label = edge_label(edge);
writeln!(&mut writer, "\t{source} -> {target} [label=\"{label}\"];")?;
}
@ -57,3 +52,12 @@ pub fn show_dependency_graph(workspace: &Workspace) -> Result<String> {
writeln!(&mut writer, "}}")?;
Ok(String::from_utf8(writer)?)
}
fn edge_label(edge: &Edge) -> &str {
match &edge.data {
base_db::deps::EdgeData::DirectLink(data) => &data.link.path.text,
base_db::deps::EdgeData::AdditionalFiles => "<project>",
base_db::deps::EdgeData::Artifact => "<artifact>",
base_db::deps::EdgeData::FileList(_) => "<fls>",
}
}

View file

@ -41,7 +41,7 @@ impl Borrow<str> for DistroFile {
}
}
#[derive(Debug, Default)]
#[derive(Debug, PartialEq, Eq, Default)]
pub struct FileNameDB {
files: FxHashSet<DistroFile>,
}
@ -86,4 +86,16 @@ impl FileNameDB {
Ok(Self { files })
}
pub fn read_dir(&mut self, dir: impl AsRef<Path>) {
if let Ok(entries) = std::fs::read_dir(dir) {
for file in entries
.flatten()
.filter(|entry| entry.file_type().map_or(false, |ty| ty.is_file()))
.map(|entry| entry.path())
{
self.insert(file);
}
}
}
}

View file

@ -80,16 +80,8 @@ impl Distro {
fn read_env_dir(file_name_db: &mut FileNameDB, env_var: &str) {
if let Some(paths) = env::var_os(env_var) {
for dir in env::split_paths(&paths) {
if let Ok(entries) = std::fs::read_dir(dir) {
for file in entries
.flatten()
.filter(|entry| entry.file_type().map_or(false, |ty| ty.is_file()))
.map(|entry| entry.path())
{
file_name_db.insert(file);
}
}
for dir in std::env::split_paths(&paths) {
file_name_db.read_dir(dir);
}
}
}

View file

@ -7,6 +7,7 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
distro = { path = "../distro" }
log.workspace = true
logos = "0.15.0"
once_cell.workspace = true
@ -14,6 +15,7 @@ pathdiff = "0.2.3"
regex.workspace = true
rowan.workspace = true
rustc-hash.workspace = true
shellexpand.workspace = true
syntax = { path = "../syntax" }
tempfile.workspace = true
versions = "6.3.2"

View file

@ -1,19 +1,25 @@
use std::path::{Path, PathBuf};
use std::{
env,
path::{Path, PathBuf},
};
use distro::FileNameDB;
use syntax::latexmkrc::LatexmkrcData;
mod v483 {
use std::path::Path;
use std::{path::Path, sync::Arc};
use syntax::latexmkrc::LatexmkrcData;
use tempfile::tempdir;
use crate::latexmkrc::change_root;
use super::{append_texinputs, parse_texinputs};
pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<LatexmkrcData> {
let temp_dir = tempdir()?;
let non_existent_tex = temp_dir.path().join("NONEXISTENT.tex");
std::fs::write(temp_dir.path().join(".latexmkrc"), input)?;
std::fs::write(temp_dir.path().join(".latexmkrc"), &append_texinputs(input))?;
// Run `latexmk -dir-report $TMPDIR/NONEXISTENT.tex` to obtain out_dir
// and aux_dir values. We pass nonexistent file to prevent latexmk from
@ -30,7 +36,9 @@ mod v483 {
let stdout = String::from_utf8_lossy(&output.stdout);
let (aux_dir, out_dir) = stdout.lines().find_map(extract_dirs).ok_or_else(|| {
let (file_name_db, mut lines) = parse_texinputs(&stdout, src_dir, temp_dir.path());
let (aux_dir, out_dir) = lines.find_map(extract_dirs).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Normalized aux and out dir were not found in latexmk output",
@ -39,7 +47,12 @@ mod v483 {
let aux_dir = change_root(src_dir, temp_dir.path(), &aux_dir);
let out_dir = change_root(src_dir, temp_dir.path(), &out_dir);
Ok(LatexmkrcData { aux_dir, out_dir })
Ok(LatexmkrcData {
aux_dir,
out_dir,
file_name_db: Arc::new(file_name_db),
})
}
/// Extracts $aux_dir and $out_dir from lines of the form
@ -63,16 +76,16 @@ mod v483 {
}
mod v484 {
use std::{path::Path, str::Lines};
use std::{path::Path, str::Lines, sync::Arc};
use syntax::latexmkrc::LatexmkrcData;
use tempfile::tempdir;
use super::change_root;
use super::{append_texinputs, change_root, parse_texinputs};
pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<LatexmkrcData> {
let temp_dir = tempdir()?;
std::fs::write(temp_dir.path().join(".latexmkrc"), input)?;
std::fs::write(temp_dir.path().join(".latexmkrc"), &append_texinputs(input))?;
// Create an empty dummy TeX file to let latexmk continue
std::fs::write(temp_dir.path().join("dummy.tex"), "")?;
@ -85,7 +98,9 @@ mod v484 {
let stdout = String::from_utf8_lossy(&output.stdout);
let (aux_dir, out_dir) = extract_dirs(stdout.lines()).ok_or_else(|| {
let (file_name_db, lines) = parse_texinputs(&stdout, src_dir, temp_dir.path());
let (aux_dir, out_dir) = extract_dirs(lines).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Normalized aux and out dir were not found in latexmk output",
@ -95,7 +110,11 @@ mod v484 {
let aux_dir = change_root(src_dir, temp_dir.path(), &aux_dir);
let out_dir = change_root(src_dir, temp_dir.path(), &out_dir);
Ok(LatexmkrcData { aux_dir, out_dir })
Ok(LatexmkrcData {
aux_dir,
out_dir,
file_name_db: Arc::new(file_name_db),
})
}
/// Extracts $aux_dir and $out_dir from lines of the form
@ -145,7 +164,7 @@ pub fn parse_latexmkrc(input: &str, src_dir: &Path) -> std::io::Result<Latexmkrc
result
}
fn change_root(src_dir: &Path, tmp_dir: &Path, out_dir: &str) -> Option<String> {
fn change_root(src_dir: &Path, tmp_dir: &Path, out_dir: impl AsRef<Path>) -> Option<String> {
let out_dir = tmp_dir.join(out_dir);
let relative_to_tmp = pathdiff::diff_paths(out_dir, tmp_dir)?;
let relative_to_src = pathdiff::diff_paths(src_dir.join(relative_to_tmp), src_dir)?;
@ -155,3 +174,45 @@ fn change_root(src_dir: &Path, tmp_dir: &Path, out_dir: &str) -> Option<String>
Some(relative_to_src.to_str()?.to_string())
}
fn append_texinputs(latexmkrc: &str) -> String {
let mut output = String::new();
output.push_str(latexmkrc);
output.push('\n');
output.push_str(r#"print "TEXINPUTS=" . $ENV{'TEXINPUTS'} . "\n";"#);
output.push_str(r#"print "BIBINPUTS=" . $ENV{'BIBINPUTS'} . "\n";"#);
output
}
fn parse_texinputs<'a>(
stdout: &'a str,
src_dir: &Path,
tmp_dir: &Path,
) -> (FileNameDB, std::str::Lines<'a>) {
let mut paths = Vec::new();
let mut file_name_db = FileNameDB::default();
let mut lines = stdout.lines();
while let Some(line) = lines.next() {
if let Some(inputs) = line.strip_prefix("TEXINPUTS=") {
paths.extend(env::split_paths(inputs));
} else if let Some(inputs) = line.strip_prefix("BIBINPUTS=") {
paths.extend(env::split_paths(inputs));
break;
}
}
for path in paths
.into_iter()
.filter_map(|path| change_root(src_dir, tmp_dir, path))
.map(|path| src_dir.join(shellexpand::tilde(&path).as_ref()))
{
eprintln!("Adding path: {path:?}");
file_name_db.read_dir(path);
}
eprintln!("File name db: {file_name_db:?}");
(file_name_db, lines)
}

View file

@ -7,6 +7,7 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
distro = { path = "../distro" }
itertools.workspace = true
rowan.workspace = true
rustc-hash.workspace = true

View file

@ -1,5 +1,10 @@
use std::sync::Arc;
use distro::FileNameDB;
#[derive(Debug, Clone, Default)]
pub struct LatexmkrcData {
pub aux_dir: Option<String>,
pub out_dir: Option<String>,
pub file_name_db: Arc<FileNameDB>,
}