Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,49 @@ orfs.default(

# --- External archives ---

# --- Pandoc pre-built binaries (used by //docs:man_pages) ---
# Pandoc is not on BCR; download platform-specific release archives.
# SHA-256 values come from the GitHub Releases API digest field.
_PANDOC_VERSION = "3.10"

_PANDOC_BUILD_FILE = """\
filegroup(
name = "pandoc",
srcs = glob(["**/bin/pandoc"]),
visibility = ["//visibility:public"],
)
"""

http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update the Bazel module lockfile

Adding these root-module http_archive repos changes Bzlmod state, but MODULE.bazel.lock was not updated (it has no pandoc_* repo specs). The PR CI workflow .github/workflows/github-actions-check-bazel-lock.yml runs bazelisk mod deps --lockfile_mode=error; Bazel's CLI reference describes error mode as using the lockfile but throwing if it is not up to date, so this will fail before //docs:man_pages is built. Please run bazelisk mod deps --lockfile_mode=update and commit the lockfile.

Useful? React with 👍 / 👎.

name = "pandoc_linux_amd64",
build_file_content = _PANDOC_BUILD_FILE,
sha256 = "e0f8af62d0f267d22baa5bcefe6d5dda3a097ccc60de794b759fe03159923244",
url = "https://github.com/jgm/pandoc/releases/download/{v}/pandoc-{v}-linux-amd64.tar.gz".format(v = _PANDOC_VERSION),
)

http_archive(
name = "pandoc_linux_arm64",
build_file_content = _PANDOC_BUILD_FILE,
sha256 = "55413dfb0c1aec861641fe858f1f73e84848f3db497b1c0c02e62887ea76f4a4",
url = "https://github.com/jgm/pandoc/releases/download/{v}/pandoc-{v}-linux-arm64.tar.gz".format(v = _PANDOC_VERSION),
)

http_archive(
name = "pandoc_macos_x86_64",
build_file_content = _PANDOC_BUILD_FILE,
sha256 = "6334f4d9af7c9e37e761dfad56fa5507685f6d29724ebf31c4be6d5c654a3161",
url = "https://github.com/jgm/pandoc/releases/download/{v}/pandoc-{v}-x86_64-macOS.zip".format(v = _PANDOC_VERSION),
)

http_archive(
name = "pandoc_macos_arm64",
build_file_content = _PANDOC_BUILD_FILE,
sha256 = "d9cad01d96ae774a0dc8c8c45bb1ad3e4c5ff2cc2e24f45958f5f9b7974aee34",
url = "https://github.com/jgm/pandoc/releases/download/{v}/pandoc-{v}-arm64-macOS.zip".format(v = _PANDOC_VERSION),
)

# Alias @slang -> @sv-lang//:libsvlang so submodule BUILD files that
# reference @slang resolve without modification. OpenROAD source uses
# @sv-lang directly; this alias only exists for the slang-elab submodule.
Expand Down
58 changes: 58 additions & 0 deletions bazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,69 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
load("@rules_pkg//pkg:zip.bzl", "pkg_zip")
load("@rules_python//python:defs.bzl", "py_binary")
load("@rules_python//python:pip.bzl", "compile_pip_requirements")
load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")

package(features = ["layering_check"])

# ---------------------------------------------------------------------------
# Pandoc platform selection
# ---------------------------------------------------------------------------

config_setting(
name = "_linux_x86_64",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
)

config_setting(
name = "_linux_aarch64",
constraint_values = [
"@platforms//os:linux",
"@platforms//cpu:aarch64",
],
)

config_setting(
name = "_macos_x86_64",
constraint_values = [
"@platforms//os:macos",
"@platforms//cpu:x86_64",
],
)

config_setting(
name = "_macos_aarch64",
constraint_values = [
"@platforms//os:macos",
"@platforms//cpu:aarch64",
],
)

# Pre-built pandoc binary, platform-selected at build time.
filegroup(
name = "pandoc",
srcs = select({
":_linux_aarch64": ["@pandoc_linux_arm64//:pandoc"],
":_linux_x86_64": ["@pandoc_linux_amd64//:pandoc"],
":_macos_aarch64": ["@pandoc_macos_arm64//:pandoc"],
":_macos_x86_64": ["@pandoc_macos_x86_64//:pandoc"],
}),
visibility = ["//visibility:public"],
)

# Driver script that orchestrates man page generation inside Bazel's sandbox.
py_binary(
name = "manpages_impl",
srcs = ["manpages_impl.py"],
visibility = ["//visibility:public"],
)

# ---------------------------------------------------------------------------

exports_files(
[
"bzl_fmt_test.sh",
Expand Down
101 changes: 60 additions & 41 deletions bazel/man_pages.bzl
Original file line number Diff line number Diff line change
@@ -1,57 +1,66 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026, The OpenROAD Authors

"""Bazel rule that invokes docs/Makefile to produce cat/ and html/ man pages.
"""Hermetic Bazel rule that generates cat/ and HTML/ man pages.

The output filenames aren't known at analysis time (they depend on which
modules exist under src/*/), so outputs are declared as TreeArtifacts and
the real work is delegated to the `bazel-manpages` Makefile target.
All tools (pandoc, Python interpreter) and all inputs (README files,
messages.txt files, man1 source markdown, Python scripts) are declared
as explicit Bazel dependencies. No host PATH look-ups, no make, no
nroff — pandoc --to=plain replaces nroff+col for cat pages.

Host requirements: pandoc, nroff (groff), col (bsdextrautils), python3>=3.10.
Output filenames are not known at analysis time (they depend on how
many Tcl commands each module exposes), so outputs are declared as
TreeArtifacts.
"""

def _man_pages_resource_set(_os, _num_inputs):
# The 'cat web' make below fans out with -j$(nproc), so this action uses
# the whole host. Reserve all local CPUs to keep Bazel from co-scheduling
# other heavy actions alongside it and oversubscribing the machine. Bazel
# clamps the request to the cores actually available, so this is safe on
# small CI hosts too.
return {"cpu": 512.0}

def _man_pages_impl(ctx):
cat_dir = ctx.actions.declare_directory("cat")
html_dir = ctx.actions.declare_directory("html")

command = """
set -euo pipefail
CAT_OUT="$PWD/{cat_out}"
HTML_OUT="$PWD/{html_out}"
# Two phases: 'preprocess' (serial) generates the md/man*/*.md sources, then
# 'cat web' fan out pandoc/nroff in parallel. They cannot share one -j make
# invocation: cat/web read the md files preprocess produces, and a parallel
# build has no dependency edge forcing preprocess to finish first. Running
# 'cat web' as a second invocation also re-parses the Makefile so its
# $(wildcard md/man*/*.md) picks up the freshly generated sources.
# nproc is GNU coreutils (absent on stock macOS); fall back to sysctl, then 4.
JOBS="$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)"
make --no-print-directory -C docs -f Makefile preprocess \\
CAT_ROOT_DIR="$CAT_OUT" HTML_ROOT_DIR="$HTML_OUT"
make --no-print-directory -j"$JOBS" -C docs -f Makefile cat web \\
CAT_ROOT_DIR="$CAT_OUT" HTML_ROOT_DIR="$HTML_OUT"
""".format(
cat_out = cat_dir.path,
html_out = html_dir.path,
pandoc = ctx.file._pandoc
impl = ctx.executable._manpages_impl

args = ctx.actions.args()
args.add("--pandoc", pandoc)
args.add("--cat-out", cat_dir.path)
args.add("--html-out", html_dir.path)

for f in ctx.files.scripts:
if f.basename.endswith(".py"):
args.add("--script", f)

for readme in ctx.files.readmes:
# Derive module name: src/ant/README.md → ant
parts = readme.short_path.split("/")
module = parts[-2] if len(parts) >= 2 else readme.basename
args.add("--readme", "{}:{}".format(module, readme.path))

for msg in ctx.files.messages:
# Derive module name: src/ant/messages.txt → ant
parts = msg.short_path.split("/")
module = parts[-2] if len(parts) >= 2 else msg.basename
args.add("--messages", "{}:{}".format(module, msg.path))

for f in ctx.files.docs_srcs:
if "/man1/" in f.path and f.basename.endswith(".md"):
args.add("--man1-src", f)

all_inputs = depset(
ctx.files.docs_srcs +
ctx.files.scripts +
ctx.files.readmes +
ctx.files.messages +
[pandoc],
transitive = [ctx.attr._manpages_impl[DefaultInfo].default_runfiles.files],
)

ctx.actions.run_shell(
resource_set = _man_pages_resource_set,
ctx.actions.run(
outputs = [cat_dir, html_dir],
inputs = ctx.files.docs_srcs + ctx.files.scripts + ctx.files.readmes + ctx.files.messages,
command = command,
inputs = all_inputs,
executable = impl,
arguments = [args],
mnemonic = "ManPages",
progress_message = "Generating man pages (cat + html)",
use_default_shell_env = True,
execution_requirements = {"no-sandbox": "1"},
)

return [DefaultInfo(
Expand All @@ -63,20 +72,30 @@ man_pages = rule(
implementation = _man_pages_impl,
attrs = {
"docs_srcs": attr.label_list(
doc = "All source files under docs/ needed by the Makefile.",
doc = "Source .md files under docs/md/ needed for man page generation.",
allow_files = True,
),
"messages": attr.label_list(
doc = "Module messages.txt files needed for man3 page generation.",
doc = "Module messages.txt files for man3 page generation.",
allow_files = [".txt"],
),
"readmes": attr.label_list(
doc = "Module README.md files (src/*/README.md).",
allow_files = [".md"],
),
"scripts": attr.label_list(
doc = "Python/shell scripts for man page generation.",
doc = "Python scripts for man page generation.",
allow_files = True,
),
"_manpages_impl": attr.label(
default = "//bazel:manpages_impl",
executable = True,
cfg = "exec",
),
"_pandoc": attr.label(
default = "//bazel:pandoc",
allow_single_file = True,
cfg = "exec",
),
},
)
Loading
Loading