Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
673e59e
web: make the saved report's inlined script parse
jorge-ferreira-pii Aug 16, 2026
0872206
web: check the report's script for the syntax the bundler must strip
jorge-ferreira-pii Aug 16, 2026
dd71e3f
web: vendor the browser libraries under third-party
jorge-ferreira-pii Aug 13, 2026
77e985f
web: serve the vendored libraries instead of CDNs
jorge-ferreira-pii Aug 13, 2026
5ec7472
web: make the saved timing report self-contained
jorge-ferreira-pii Aug 13, 2026
aed3368
web: add a CSP and a test that no asset loads remote code
jorge-ferreira-pii Aug 13, 2026
a956ff8
web: trim the policy and pin the vendoring to what it verifies
jorge-ferreira-pii Aug 13, 2026
a4a5cd4
web: fix the findings from the review of the CDN work
jorge-ferreira-pii Aug 13, 2026
60a0d66
web: simplify the CDN work after review
jorge-ferreira-pii Aug 13, 2026
f02a5b3
web: close the gaps the second review found in the report path
jorge-ferreira-pii Aug 16, 2026
a3b5683
web: fix the findings from the third review
jorge-ferreira-pii Aug 17, 2026
c3e97b7
web: let the report's writer report its own missing assets
jorge-ferreira-pii Aug 17, 2026
ac9a254
web: fix the findings from the fourth review
jorge-ferreira-pii Aug 17, 2026
a949ec2
web: test the report's css scanner instead of mirroring it
jorge-ferreira-pii Aug 17, 2026
c71e946
web: trim the comments to what the code cannot say
jorge-ferreira-pii Aug 17, 2026
249e060
web: fetch the browser libraries instead of checking them in
jorge-ferreira-pii Aug 18, 2026
1188e4e
web: drop the vendor lock test and the wording it belonged to
jorge-ferreira-pii Aug 19, 2026
321b901
web: make the fetch reconfigure, and stop it emptying what it did not…
jorge-ferreira-pii Aug 19, 2026
a39fd8b
web: refuse a report whose assets were not all inlined
jorge-ferreira-pii Aug 19, 2026
a8a5a00
web: state the esbuild platform once, and stop copying tarballs around
jorge-ferreira-pii Aug 19, 2026
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
16 changes: 16 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,22 @@ npm.npm_translate_lock(
)
use_repo(npm, "npm")

# The browser libraries the web viewer serves from the binary. Not a
# dev_dependency: without them the viewer would fall back to a CDN, which is the
# bug (issue #11065). Versions and digests live in the manifest the extension
# reads, //src/web/third-party:packages.json.
web_third_party = use_extension("//bazel:web_third_party.bzl", "web_third_party")
use_repo(
web_third_party,
"esbuild",
"web_elkjs",
"web_golden_layout",
"web_leaflet",
"web_netlistsvg",
"web_third_party",
"web_three",
)

orfs = use_extension("@bazel-orfs//:extension.bzl", "orfs_repositories", dev_dependency = True)
orfs.default(
# Use OpenROAD of this repo instead of the one bundled with @orfs
Expand Down
84 changes: 84 additions & 0 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

164 changes: 164 additions & 0 deletions bazel/web_third_party.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2026, The OpenROAD Authors

"""The browser libraries the web viewer serves from the OpenROAD binary.

They are fetched rather than checked in, so third-party/packages.json is the one
place their versions live; this extension turns each entry there into an
http_archive, and hands //src/web the served path -> label mapping through a
generated assets.bzl. The CMake build reads the same manifest through
third-party/fetch_packages.py.
"""

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

_MANIFEST = Label("//src/web:third-party/packages.json")

# What Bazel calls this host -> what npm calls it. The manifest owns which
# platforms are pinned; this only names the one we are running on.
_OS = {
"linux": "linux",
"mac os x": "darwin",
}

_ARCH = {
"aarch64": "arm64",
"amd64": "x64",
"arm64": "arm64",
"x86_64": "x64",
}

def _tarball_url(registry, name, version):
# Scoped names live at @scope/name/-/name-version.tgz.
basename = name.split("/")[-1]
return "{}/{}/-/{}-{}.tgz".format(registry, name, basename, version)

def _host_platform(module_ctx):
os_name = _OS.get(module_ctx.os.name)
arch = _ARCH.get(module_ctx.os.arch)
if not os_name or not arch:
fail("no esbuild binary is pinned for {}/{}; add the platform to ".format(
module_ctx.os.name,
module_ctx.os.arch,
) + "esbuild.platforms in packages.json and to the maps in this file")
return os_name + "-" + arch

def _build_file(exported, bundle):
lines = [
'package(default_visibility = ["//visibility:public"])',
"",
"exports_files([",
]
lines += [' "{}",'.format(path) for path in exported]
lines.append("])")
if bundle:
# The source maps are a third of the tree and esbuild is not asked for
# one, so they would only be staged into the action for nothing.
lines += [
"",
"filegroup(",
' name = "bundle_tree",',
' srcs = glob(["{}/**"], exclude = ["**/*.map"]),'.format(bundle["tree"]),
")",
]
return "\n".join(lines) + "\n"

def _assets_bzl(assets, bundles):
"""The manifest as Starlark, so //src/web does not restate it."""
lines = [
"# Generated by //bazel:web_third_party.bzl -- do not edit.",
"",
"# (served path, label of the file to serve there).",
"WEB_THIRD_PARTY_ASSETS = [",
]
lines += [' ("{}", "{}"),'.format(served, label) for served, label in assets]
lines += [
"]",
"",
"# Packages that publish no browser build and are bundled by the build.",
"WEB_THIRD_PARTY_BUNDLES = [",
]
for bundle in bundles:
lines += [
" struct(",
' package = "{}",'.format(bundle["package"]),
' tree_target = "{}",'.format(bundle["tree_target"]),
' entry_target = "{}",'.format(bundle["entry_target"]),
' tree = "{}",'.format(bundle["tree"]),
' entry = "{}",'.format(bundle["entry"]),
' out = "{}",'.format(bundle["out"]),
" ),",
]
lines.append("]")
return "\n".join(lines) + "\n"

def _assets_repo_impl(repository_ctx):
repository_ctx.file("BUILD", "")
repository_ctx.file("assets.bzl", repository_ctx.attr.content)

_assets_repo = repository_rule(
implementation = _assets_repo_impl,
attrs = {"content": attr.string(mandatory = True)},
)

def _web_third_party_impl(module_ctx):
manifest = json.decode(module_ctx.read(_MANIFEST))
registry = manifest["registry"]

assets = []
bundles = []
for name, spec in manifest["packages"].items():
repo = "web_" + name.replace("-", "_")
bundle = spec.get("bundle")
exported = sorted(spec["files"].keys())
if bundle:
exported = sorted(exported + [bundle["entry"]])

http_archive(
name = repo,
build_file_content = _build_file(exported, bundle),
sha256 = spec["sha256"],
strip_prefix = "package",
urls = [_tarball_url(registry, name, spec["version"])],
)

for source, served in spec["files"].items():
assets.append(
("/third-party/" + served, "@{}//:{}".format(repo, source)),
)
if bundle:
bundles.append({
"entry": bundle["entry"],
"entry_target": "@{}//:{}".format(repo, bundle["entry"]),
# A genrule output, so it is a path in //src/web, not a label.
"out": "third-party/" + bundle["output"],
"package": name,
"tree": bundle["tree"],
"tree_target": "@{}//:bundle_tree".format(repo),
})

# esbuild runs on whoever is building, so the binary is picked here rather
# than through a select() on the target platform.
esbuild = manifest["esbuild"]
platform = _host_platform(module_ctx)
if platform not in esbuild["platforms"]:
fail("esbuild {} for {} is not pinned in packages.json".format(
esbuild["version"],
platform,
))
http_archive(
name = "esbuild",
build_file_content = (
'exports_files(["bin/esbuild"], visibility = ["//visibility:public"])\n'
),
sha256 = esbuild["platforms"][platform],
strip_prefix = "package",
urls = [_tarball_url(registry, "@esbuild/" + platform, esbuild["version"])],
)

_assets_repo(
name = "web_third_party",
content = _assets_bzl(sorted(assets), bundles),
)

web_third_party = module_extension(implementation = _web_third_party_impl)
Loading
Loading