Skip to content

Commit 9b4ab72

Browse files
authored
Merge pull request #1418 from ehuss/relative-renderer-command
Fix relative paths for renderer commands.
2 parents b1c2e46 + 9a65c8a commit 9b4ab72

File tree

2 files changed

+87
-7
lines changed

2 files changed

+87
-7
lines changed

src/renderer/mod.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ mod markdown_renderer;
2020
use shlex::Shlex;
2121
use std::fs;
2222
use std::io::{self, ErrorKind, Read};
23-
use std::path::PathBuf;
23+
use std::path::{Path, PathBuf};
2424
use std::process::{Command, Stdio};
2525

2626
use crate::book::Book;
@@ -130,14 +130,44 @@ impl CmdRenderer {
130130
CmdRenderer { name, cmd }
131131
}
132132

133-
fn compose_command(&self) -> Result<Command> {
133+
fn compose_command(&self, root: &Path, destination: &Path) -> Result<Command> {
134134
let mut words = Shlex::new(&self.cmd);
135-
let executable = match words.next() {
136-
Some(e) => e,
135+
let exe = match words.next() {
136+
Some(e) => PathBuf::from(e),
137137
None => bail!("Command string was empty"),
138138
};
139139

140-
let mut cmd = Command::new(executable);
140+
let exe = if exe.components().count() == 1 {
141+
// Search PATH for the executable.
142+
exe
143+
} else {
144+
// Relative paths are preferred to be relative to the book root.
145+
let abs_exe = root.join(&exe);
146+
if abs_exe.exists() {
147+
abs_exe
148+
} else {
149+
// Historically paths were relative to the destination, but
150+
// this is not the preferred way.
151+
let legacy_path = destination.join(&exe);
152+
if legacy_path.exists() {
153+
warn!(
154+
"Renderer command `{}` uses a path relative to the \
155+
renderer output directory `{}`. This was previously \
156+
accepted, but has been deprecated. Relative executable \
157+
paths should be relative to the book root.",
158+
exe.display(),
159+
destination.display()
160+
);
161+
legacy_path
162+
} else {
163+
// Let this bubble through to later be handled by
164+
// handle_render_command_error.
165+
abs_exe.to_path_buf()
166+
}
167+
}
168+
};
169+
170+
let mut cmd = Command::new(exe);
141171

142172
for arg in words {
143173
cmd.arg(arg);
@@ -192,7 +222,7 @@ impl Renderer for CmdRenderer {
192222
let _ = fs::create_dir_all(&ctx.destination);
193223

194224
let mut child = match self
195-
.compose_command()?
225+
.compose_command(&ctx.root, &ctx.destination)?
196226
.stdin(Stdio::piped())
197227
.stdout(Stdio::inherit())
198228
.stderr(Stdio::inherit())

tests/alternative_backends.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use mdbook::config::Config;
44
use mdbook::MDBook;
5-
#[cfg(not(windows))]
5+
use std::fs;
66
use std::path::Path;
77
use tempfile::{Builder as TempFileBuilder, TempDir};
88

@@ -71,6 +71,45 @@ fn backends_receive_render_context_via_stdin() {
7171
assert!(got.is_ok());
7272
}
7373

74+
#[test]
75+
fn relative_command_path() {
76+
// Checks behavior of relative paths for the `command` setting.
77+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
78+
let renderers = temp.path().join("renderers");
79+
fs::create_dir(&renderers).unwrap();
80+
rust_exe(
81+
&renderers,
82+
"myrenderer",
83+
r#"fn main() {
84+
std::fs::write("output", "test").unwrap();
85+
}"#,
86+
);
87+
let do_test = |cmd_path| {
88+
let mut config = Config::default();
89+
config
90+
.set("output.html", toml::value::Table::new())
91+
.unwrap();
92+
config.set("output.myrenderer.command", cmd_path).unwrap();
93+
let md = MDBook::init(&temp.path())
94+
.with_config(config)
95+
.build()
96+
.unwrap();
97+
let output = temp.path().join("book/myrenderer/output");
98+
assert!(!output.exists());
99+
md.build().unwrap();
100+
assert!(output.exists());
101+
fs::remove_file(output).unwrap();
102+
};
103+
// Legacy paths work, relative to the output directory.
104+
if cfg!(windows) {
105+
do_test("../../renderers/myrenderer.exe");
106+
} else {
107+
do_test("../../renderers/myrenderer");
108+
}
109+
// Modern path, relative to the book directory.
110+
do_test("renderers/myrenderer");
111+
}
112+
74113
fn dummy_book_with_backend(
75114
name: &str,
76115
command: &str,
@@ -112,3 +151,14 @@ fn success_cmd() -> &'static str {
112151
"true"
113152
}
114153
}
154+
155+
fn rust_exe(temp: &Path, name: &str, src: &str) {
156+
let rs = temp.join(name).with_extension("rs");
157+
fs::write(&rs, src).unwrap();
158+
let status = std::process::Command::new("rustc")
159+
.arg(rs)
160+
.current_dir(temp)
161+
.status()
162+
.expect("rustc should run");
163+
assert!(status.success());
164+
}

0 commit comments

Comments
 (0)