Skip to content

bazel: build and package man pages as runfiles#9766

Open
alokkumardalei-wq wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
alokkumardalei-wq:feature/bazel-manpage
Open

bazel: build and package man pages as runfiles#9766
alokkumardalei-wq wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
alokkumardalei-wq:feature/bazel-manpage

Conversation

@alokkumardalei-wq
Copy link
Copy Markdown
Contributor

@alokkumardalei-wq alokkumardalei-wq commented Mar 14, 2026

What does this PR do?

Fixes #7117 — Bazel build now generates and packages man pages

The Bazel build was missing man pages for both the man command and the GUI help browser.

Added a man_pages Bazel rule (
man_pages.bzl
) that runs the existing docs Makefile to generate cat/ and html/ pages from the module READMEs. The output is packaged as runfiles alongside the openroad binary.

Updated getDocsPath() to check for the Bazel runfiles layout (.runfiles/openroad/docs/) before falling back to the installed path.

Verification/Testing

bazel build //docs:man_pages

Expected output:

INFO: Invocation ID: e5407d31-853c-4d14-ba5d-f6352d3f1eb0
INFO: Analyzed target //docs:man_pages (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //docs:man_pages up-to-date:
  bazel-bin/docs/cat
  bazel-bin/docs/html
INFO: Elapsed time: 147.810s, Critical Path: 0.24s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new Bazel rule to build and package man pages, making them available to the openroad binary as runfiles. This is a valuable addition that improves the build process. My review focuses on enhancing the robustness and maintainability of the new Bazel rule and improving the portability of the C++ code that locates the generated documentation at runtime.

html_dir = ctx.actions.declare_directory("html")

# Build a map of script basename -> path for easy lookup
scripts_by_name = {f.basename: f.path for f in ctx.files.scripts}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The use of .get(script_name, "") on lines 77-80 can lead to cryptic cp errors if a required script is not provided in the scripts attribute. A more robust approach would be to explicitly check for the required scripts and fail with a clear error message if any are missing. After this check, you can directly access the script paths without .get().

For example, you could add this check after building scripts_by_name:

required_scripts = [
    "link_readmes.sh",
    "md_roff_compat.py",
    "manpage.py",
    "extract_utils.py",
]
for script in required_scripts:
    if script not in scripts_by_name:
        fail("Script '{}' is missing from the 'scripts' attribute".format(script))

And then in the .format() call, use direct access like scripts_by_name["link_readmes.sh"].

Comment on lines +24 to +86
command = """
set -euo pipefail

# Ensure system tools like pandoc and nroff are available
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"

WORK_DIR=$(mktemp -d)
trap "rm -rf $WORK_DIR" EXIT
EXECROOT=$(pwd)

# Set up docs directory structure
mkdir -p "$WORK_DIR/md/man1"
mkdir -p "$WORK_DIR/md/man2"
mkdir -p "$WORK_DIR/md/man3"
mkdir -p "$WORK_DIR/src/scripts"

# Copy scripts and Makefile
cp {link_readmes} "$WORK_DIR/src/scripts/link_readmes.sh"
cp {md_roff_compat} "$WORK_DIR/src/scripts/md_roff_compat.py"
cp {manpage} "$WORK_DIR/src/scripts/manpage.py"
cp {extract_utils} "$WORK_DIR/src/scripts/extract_utils.py"
cp {makefile} "$WORK_DIR/Makefile"

# Copy man1 sources
for f in {man1_srcs}; do
cp "$f" "$WORK_DIR/md/man1/"
done

# Copy man2 README sources (named by module directory)
for f in {man2_srcs}; do
module=$(basename $(dirname $f))
cp "$f" "$WORK_DIR/md/man2/${{module}}.md"
done

# Run the Python preprocessor to generate per-function man2/man3 pages
cd "$WORK_DIR"
python3 src/scripts/md_roff_compat.py

# Remove any malformed output files (e.g. '#.md' from READMEs with bad function names)
find "$WORK_DIR/md" -name '#*.md' -delete 2>/dev/null || true

# Build all man page formats; use -k to continue past nroff warnings
make all -k -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 2) || true

# Ensure output subdirs exist even if some pages failed to build
mkdir -p "$WORK_DIR/cat/cat1" "$WORK_DIR/cat/cat2" "$WORK_DIR/cat/cat3"
mkdir -p "$WORK_DIR/html/html1" "$WORK_DIR/html/html2" "$WORK_DIR/html/html3"

# Copy outputs into the Bazel-declared output directories (paths are execroot-relative)
mkdir -p "$EXECROOT/{cat_out}" "$EXECROOT/{html_out}"
cp -r "$WORK_DIR/cat/." "$EXECROOT/{cat_out}/"
cp -r "$WORK_DIR/html/." "$EXECROOT/{html_out}/"
""".format(
link_readmes = scripts_by_name.get("link_readmes.sh", ""),
md_roff_compat = scripts_by_name.get("md_roff_compat.py", ""),
manpage = scripts_by_name.get("manpage.py", ""),
extract_utils = scripts_by_name.get("extract_utils.py", ""),
makefile = ctx.file.makefile.path,
man1_srcs = " ".join([f.path for f in ctx.files.man1_srcs]),
man2_srcs = " ".join([f.path for f in ctx.files.man2_srcs]),
cat_out = cat_dir.path,
html_out = html_dir.path,
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The shell script embedded in the command attribute is quite long and complex. For better maintainability, readability, and testability, consider moving this script to a separate file (e.g., man_pages.sh.tpl) and using ctx.actions.expand_template to substitute the values.

set -euo pipefail

# Ensure system tools like pandoc and nroff are available
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding paths like /opt/homebrew/bin can make the build less portable, as tool locations can vary between operating systems, distributions, and user configurations. While this might work for the current primary development environments, a more robust solution would be to rely on the tools being in the system PATH.

src/OpenRoad.cc Outdated
Comment on lines +721 to +725
const std::filesystem::path runfiles_docs
= std::filesystem::path(exe + ".runfiles") / "openroad" / "docs";
if (std::filesystem::exists(runfiles_docs)) {
return runfiles_docs;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Manually constructing the runfiles path with exe + ".runfiles" is not fully portable (e.g., it differs on Windows) and can be brittle. Since this target already depends on the Bazel runfiles library, it would be more robust to use it to locate the docs directory. This would abstract away the platform-specific details of the runfiles layout.

Example using the runfiles library:

#include "tools/cpp/runfiles/runfiles.h"
// ... in a function that has access to argv[0] or exe path
std::string error;
std::unique_ptr<bazel::tools::cpp::runfiles::Runfiles> runfiles(
    bazel::tools::cpp::runfiles::Runfiles::Create(getExePath(), &error));
// ... error handling ...
std::string docs_path = runfiles->Rlocation("openroad/docs");
if (!docs_path.empty() && std::filesystem::exists(docs_path)) {
  return docs_path;
}

This change would make your code more portable and less dependent on the specifics of Bazel's runfiles implementation.

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@alokkumardalei-wq alokkumardalei-wq force-pushed the feature/bazel-manpage branch 2 times, most recently from abb37f2 to 4f974a0 Compare March 14, 2026 20:41
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@gadfort
Copy link
Copy Markdown
Collaborator

gadfort commented Mar 15, 2026

When I try this I get:

make: mkdir: No such file or directory
make: *** [Makefile:76: man/man1] Error 127
make: mkdir: No such file or directory
make: *** [Makefile:78: man/man2] Error 127
make: mkdir: No such file or directory
make: *** [Makefile:82: html/html1] Error 127
make: mkdir: No such file or directory
make: *** [Makefile:84: html/html2] Error 127
make: mkdir: No such file or directory
make: *** [Makefile:88: cat/cat1] Error 127
make: mkdir: No such file or directory
make: *** [Makefile:90: cat/cat2] Error 127
make: Target 'all' not remade because of errors.

and no files are created in the runfiles.

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@alokkumardalei-wq
Copy link
Copy Markdown
Contributor Author

Hello @gadfort I think this is due to PATH environment problem in Bazel's sandboxed execution on macOS. I have fixed that I want you to have look into this when you have time and happy to address further feedback.

Thank you!

@alokkumardalei-wq
Copy link
Copy Markdown
Contributor Author

Hello @gadfort all checks are passing now ,please have a look onto this when you have time .
Thank you!

@gadfort
Copy link
Copy Markdown
Collaborator

gadfort commented Mar 17, 2026

It builds, but ord::get_docs_path is pointing to a directory that doesn't exist.

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@alokkumardalei-wq alokkumardalei-wq force-pushed the feature/bazel-manpage branch 2 times, most recently from 0070f75 to 693980d Compare March 17, 2026 18:32
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@alokkumardalei-wq
Copy link
Copy Markdown
Contributor Author

Hello @gadfort,
You're absolutely right - I had the wrong repository name in the runfiles path. Fixed in commit d00e786.
The code was looking for runfiles->Rlocation("openroad/docs") but the actual path is _main/docs because that's the Bazel repository name.

Please have a look onto this and happy to address any further feedback.

Thank you!

@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

clang-tidy review says "All clean, LGTM! 👍"

Signed-off-by: alokkumardalei-wq <alokkumardalei2@gmail.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

clang-tidy review says "All clean, LGTM! 👍"

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

clang-tidy review says "All clean, LGTM! 👍"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bazel - manpage missing

2 participants