Skip to content

Commit a22fbf0

Browse files
committed
Provide custom environment to assist with custom aux-/output-directory
If custom "aux_directory" or "output_directory" is specified, help toolchain finding resources in TeX document's directory structure via custom environment variables. It simplifies execution of `bibtex` in basic builder or script builder, as required environment is already set-up. Even latexmk requires it to properly create bibliography, if -output-directory is specified.
1 parent 132ffe9 commit a22fbf0

File tree

4 files changed

+77
-42
lines changed

4 files changed

+77
-42
lines changed

docs/buildsystem.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,27 +287,25 @@ If [auxiliary or output directory settings](settings.md#output-directory-setting
287287
**BibTeX**
288288

289289
Unlike biber, bibtex (and bibtex8) does not support an output directory parameter.
290-
The following workaround can be used to run BibTeX _inside_ the output / auxiliary directory while making the directory containing your main file available to the `BIBINPUTS` environment variable.
290+
The following workaround can be used to run BibTeX _inside_ the output / auxiliary directory.
291291

292292
```json
293293
{
294294
"builder_settings": {
295295
"linux": {
296296
"script_commands": [
297-
"cd $output_directory; BIBINPUTS=\"$file_path;$BIBINPUTS\" bibtex \"$file_base_name\"",
297+
"cd $output_directory; bibtex \"$file_base_name\"",
298298
]
299299
},
300300
"windows": {
301301
"script_commands": [
302-
"cd $output_directory & set BIBINPUTS=\"$file_path:%BIBINPUTS%\" & bibtex \"$file_base_name\""
302+
"cd $output_directory & bibtex \"$file_base_name\""
303303
]
304304
}
305305
}
306306
}
307307
```
308308

309-
**Note:** If a custom style file is used in the same directory, a similar work-around for `BSTINPUTS` environment variable needs to be applied.
310-
311309
### Job Name
312310

313311
If jobname behaviour is used, `$jobname` is to be passed to relevant commands. In particular, a standard build cycle might look something like this:
@@ -556,6 +554,22 @@ class SimpleBuilder(PdfBuilder):
556554

557555
### Reference
558556

557+
#### Environment
558+
559+
If `aux_directory` or `output_directory` are specified, [$file_path](#expandable-variables) is prepended to relevant environment variables, to ensure resources are looked up relative to main TeX document's location, regardless effective working directory.
560+
561+
**MikTeX**
562+
563+
Document location is assigned to `TEXINPUTS`, `BIBINPUTS`, `BSTINPUTS`
564+
565+
**Note:** Specified values are prepended to existing variables and configurations by MikTeX.
566+
567+
**TeXLive**
568+
569+
Document location is assigned to `TEXMFDOTDIR`, which is prepended to all path environment variables.
570+
571+
**Note:** Customized `TEXINPUTS`, `BIBINPUTS`, `BSTINPUTS` variables replace any other configured path in TexLive. Hence it is not recommended to customize them, directly.
572+
559573
#### Expandable Variables
560574

561575
The `PdfBuilder` defines an `expandvars(template: str)` method, which can be called to expand following variables in strings.

latextools/system_check.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from functools import lru_cache
1414
from functools import partial
1515
from shutil import which
16-
from typing import Callable
16+
from typing import cast, Callable
1717

1818
import sublime
1919
import sublime_plugin
@@ -130,7 +130,7 @@ def __init__(self, view: sublime.View, on_done: Callable[[list[list]], None]):
130130
# CAUTION: logic must match make_pdf's logic
131131
self.env = os.environ.copy()
132132

133-
self.builder_settings = get_setting("builder_settings", {}, view)
133+
self.builder_settings = cast(dict, get_setting("builder_settings", {}, view))
134134
self.builder_platform_settings = self.builder_settings.get(self.platform, {})
135135
build_env = self.builder_platform_settings.get("env")
136136
if build_env is None:
@@ -141,6 +141,21 @@ def __init__(self, view: sublime.View, on_done: Callable[[list[list]], None]):
141141
if (texpath := get_texpath(view)) is not None:
142142
self.env["PATH"] = texpath
143143

144+
# prepand main tex document's location to all TeX related paths such as
145+
# TEXINPUTS, BIBINPUTS, BSTINPUTS, etc.
146+
self.tex_root = get_tex_root(self.view)
147+
if self.tex_root:
148+
tex_dir = os.path.dirname(self.tex_root)
149+
if self.uses_miktex:
150+
# MikTeX, prepends custom variables to its configuration.
151+
env_vars = ("TEXINPUTS", "BIBINPUTS", "BSTINPUTS")
152+
else:
153+
# TeXLive overwrites its configuration with custom variables.
154+
# Hence set `TEXMFDOTDIR`, which is prepended to all of them.
155+
env_vars = ("TEXMFDOTDIR",)
156+
for key in env_vars:
157+
self.env[key] = os.pathsep.join(filter(None, (tex_dir, self.env.get(key))))
158+
144159
def run(self):
145160
with ActivityIndicator("Checking system...") as activity_indicator:
146161
self.worker()
@@ -153,8 +168,10 @@ def worker(self):
153168

154169
table.append(["PATH", self.env.get("PATH", "")])
155170

156-
for var in ("TEXINPUTS", "BIBINPUTS", "BSTINPUTS"):
157-
table.append([var, self.get_tex_path_variable(var) or "missing"])
171+
table.extend(
172+
[var, self.get_tex_path_variable(var) or "missing"]
173+
for var in ("TEXINPUTS", "BIBINPUTS", "BSTINPUTS")
174+
)
158175

159176
if self.uses_miktex:
160177
for var in (
@@ -339,12 +356,11 @@ def worker(self):
339356
# is current view a TeX file?
340357
view = self.view
341358
if view and view.match_selector(0, "text.tex.latex"):
342-
tex_root = get_tex_root(view)
343359
tex_directives = parse_tex_directives(
344-
tex_root, multi_values=["options"], key_maps={"ts-program": "program"}
360+
self.tex_root, multi_values=["options"], key_maps={"ts-program": "program"}
345361
)
346362

347-
results.append([["TeX Root"], [tex_root]])
363+
results.append([["TeX Root"], [self.tex_root]])
348364

349365
results.append(
350366
[
@@ -361,7 +377,7 @@ def worker(self):
361377
if aux_directory:
362378
table.append(["aux_directory", aux_directory])
363379
jobname = get_jobname(view)
364-
if jobname and jobname != os.path.splitext(os.path.basename(tex_root))[0]:
380+
if jobname and jobname != os.path.splitext(os.path.basename(self.tex_root))[0]:
365381
table.append(["jobname", jobname])
366382

367383
if len(table) > 1:

plugins/builder/basic_builder.py

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,15 @@ def commands(self) -> CommandGenerator:
105105

106106
if run_bibtex:
107107
if use_bibtex:
108+
# set-up bibtex cmd line
109+
if bibtex is None:
110+
bibtex = [self.builder_settings.get("bibtex", "bibtex")]
111+
elif isinstance(bibtex, str):
112+
bibtex = [bibtex]
113+
bibtex.append(self.job_name)
108114
yield (
109-
self.run_bibtex(bibtex),
110-
f"running {bibtex or 'bibtex'}...",
115+
self.command(bibtex, cwd=self.aux_directory_full or self.tex_dir),
116+
f"running {bibtex[0]}..."
111117
)
112118
else:
113119
yield (biber + [self.job_name], "running biber...")
@@ -122,24 +128,3 @@ def commands(self) -> CommandGenerator:
122128
yield (latex, f"running {engine}...")
123129

124130
self.copy_assets_to_output()
125-
126-
def run_bibtex(self, cmd: CommandLine | None=None) -> Command:
127-
# set-up bibtex cmd line
128-
if cmd is None:
129-
cmd = [self.builder_settings.get("bibtex", "bibtex")]
130-
elif isinstance(cmd, str):
131-
cmd = [cmd]
132-
cmd.append(self.job_name)
133-
134-
# return default command line, if build output is tex_dir.
135-
if not self.aux_directory:
136-
return cmd
137-
138-
# to get bibtex to work with the output directory, we change the
139-
# cwd to the output directory and add the main directory to
140-
# BIBINPUTS and BSTINPUTS
141-
env = self.env.copy()
142-
# cwd is, at the point, the path to the main tex file
143-
env["BIBINPUTS"] = self.tex_dir + os.pathsep + env.get("BIBINPUTS", "")
144-
env["BSTINPUTS"] = self.tex_dir + os.pathsep + env.get("BSTINPUTS", "")
145-
return self.command(cmd, cwd=self.aux_directory_full, env=env)

plugins/builder/pdf_builder.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,18 @@ def __init__(
122122
self.builder_settings = builder_settings
123123
self.platform_settings = platform_settings
124124

125+
self.display_log = self.builder_settings.get("display_log", False)
126+
"""
127+
Specifies whether to display detailed command output in log panel.
128+
129+
Value of `builder_settings: { display_log: ... }` setting
130+
"""
131+
132+
distro = self.platform_settings.get("distro", "")
133+
self.uses_miktex = (
134+
distro != "texlive" if sublime.platform() == "windows" else distro == "miktex"
135+
)
136+
125137
# if output_directory and aux_directory can be specified as a path
126138
# relative to self.tex_dir, we use that instead of the absolute path
127139
# note that the full path for both is available as
@@ -150,12 +162,20 @@ def __init__(
150162
if self.output_directory:
151163
os.makedirs(self.output_directory_full, exist_ok=True)
152164

153-
self.display_log = self.builder_settings.get("display_log", False)
154-
"""
155-
Specifies whether to display detailed command output in log panel.
156-
157-
Value of `builder_settings: { display_log: ... }` setting
158-
"""
165+
# Help latex toolchain find resources by prepending TeX document's location to popular
166+
# environment variables. Even latexmk requires it to properly build bibliography.
167+
if self.aux_directory or self.output_directory:
168+
if env is None:
169+
env = os.environ.copy()
170+
if self.uses_miktex:
171+
# MikTeX, prepends custom variables to its configuration.
172+
env_vars = ("TEXINPUTS", "BIBINPUTS", "BSTINPUTS")
173+
else:
174+
# TeXLive overwrites its configuration with custom variables.
175+
# Hence set `TEXMFDOTDIR`, which is prepended to all of them.
176+
env_vars = ("TEXMFDOTDIR",)
177+
for key in env_vars:
178+
env[key] = os.pathsep.join(filter(None, (self.tex_dir, env.get(key))))
159179

160180
# finally expand variables in custom environment
161181
self.env = {k: self.expandvars(v) for k, v in env.items()} if env else None

0 commit comments

Comments
 (0)