@@ -105,23 +105,39 @@ def _rlocationpath(file, workspace_name):
105105
106106 return "{}/{}" .format (workspace_name , file .short_path )
107107
108+ def _src_dest_path (file ):
109+ """Returns the path of `file` inside the staged workdir, mirroring the build action's `_map_inputs`."""
110+ dest = file .short_path
111+ if dest .startswith ("../" ):
112+ dest = "external/" + dest .removeprefix ("../" )
113+ return dest
114+
108115def _mdbook_server_impl (ctx ):
109116 toolchain = ctx .toolchains ["@rules_rust_mdbook//:toolchain_type" ]
110117 book_info = ctx .attr .book [MdBookInfo ]
111118
112119 args = ctx .actions .args ()
113120
114- args .add ("--mdbook={}" .format (_rlocationpath (toolchain .mdbook , ctx .workspace_name )))
115- args .add ("--config={}" .format (_rlocationpath (book_info .config , ctx .workspace_name )))
121+ workspace_name = ctx .workspace_name
122+
123+ args .add ("--mdbook={}" .format (_rlocationpath (toolchain .mdbook , workspace_name )))
124+ args .add ("--config={}" .format (_src_dest_path (book_info .config )))
116125 args .add ("--hostname={}" .format (ctx .attr .hostname ))
117126 args .add ("--port={}" .format (ctx .attr .port ))
118127
119- workspace_name = ctx .workspace_name
128+ def _src_map (file ):
129+ return "--src={}={}" .format (_rlocationpath (file , workspace_name ), _src_dest_path (file ))
120130
121- def _runfile_map (file ):
131+ # The set of files that must be staged into the workdir for `mdbook serve` to
132+ # see a consistent source tree. `book.toml` is included so that referencing it
133+ # by `--config` resolves to a real file rather than a runfiles symlink.
134+ book_inputs = depset ([book_info .config ], transitive = [book_info .srcs ])
135+ args .add_all (book_inputs , map_each = _src_map , allow_closure = True )
136+
137+ def _plugin_map (file ):
122138 return "--plugin={}" .format (_rlocationpath (file , workspace_name ))
123139
124- args .add_all (depset (transitive = [book_info .plugins , toolchain .plugins ]), map_each = _runfile_map , allow_closure = True )
140+ args .add_all (depset (transitive = [book_info .plugins , toolchain .plugins ]), map_each = _plugin_map , allow_closure = True )
125141
126142 args_file = ctx .actions .declare_file ("{}.mdbook_serve_args.txt" .format (ctx .label .name ))
127143 ctx .actions .write (
@@ -167,7 +183,32 @@ def _mdbook_server_impl(ctx):
167183
168184mdbook_server = rule (
169185 implementation = _mdbook_server_impl ,
170- doc = "Spawn an mdbook server for a given `mdbook` target." ,
186+ doc = """\
187+ Spawn an mdbook server for a given `mdbook` target.
188+
189+ The server stages every input (including generated sources) into an isolated \
190+ working directory before invoking `mdbook serve`, so the running book always \
191+ reflects the bazel-built sources rather than the workspace checkout.
192+
193+ For live-reload during development, add `tags = ["ibazel_notify_changes"]` and \
194+ invoke with [ibazel](https://github.com/bazelbuild/bazel-watcher):
195+
196+ ```python
197+ mdbook_server(
198+ name = "book_server",
199+ book = ":book",
200+ tags = ["ibazel_notify_changes"],
201+ )
202+ ```
203+
204+ ```sh
205+ ibazel run //path/to:book_server
206+ ```
207+
208+ ibazel will rebuild on source changes and signal the running server via stdin; \
209+ the server re-stages the freshly built inputs and `mdbook serve` reloads any \
210+ connected browsers.
211+ """ ,
171212 attrs = {
172213 "book" : attr .label (
173214 doc = "The `mdbook` target to serve." ,
0 commit comments