From d605df7ba56d546ac653cf38e4a0a6f780c34ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 16 May 2026 15:10:35 +0200 Subject: [PATCH 1/6] =?UTF-8?q?fix(generate=5Fklayout=5Ftech):=20drop=20re?= =?UTF-8?q?alpath()=20=E2=80=94=20breaks=20Bazel=20sandbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit os.path.realpath() follows symlinks. Inside a Bazel sandbox, the bazel-out/ tree is symlinked from the sandbox back to the bare execroot; realpath resolves through that symlink and yields the bare-execroot path, not the sandbox path. That alone doesn't matter for os.path.relpath(a, b) when both operands are realpath'd from the same sandbox — the relative result is unchanged. But the resulting path in the generated klayout.lyt is later resolved by klayout against the LYT file's location. Klayout opens the LYT (also through a symlink), resolves through to the bare execroot, and then looks for the sibling klayout_tech.lef at the bare-execroot path — where the in-flight file does not exist during action execution (only the sandbox copy does), so def2stream fails with errno=2. Fix: don't realpath. os.path.relpath produces the correct relative path from sandbox-relative inputs directly. Map files keep their absolute form via abspath for the unchanged-under-non-sandbox case. Surfaced for the first time on bazelisk build //flow/designs/sky130hd/gcd:gcd_final_blender_html — the orfs_blender macro is the first bazel-side consumer of orfs_gds (klayout def2stream), so the latent path bug had no prior trigger. Mirrors bazel-orfs's patches/0037-fix-generate_klayout_tech-drop-realpath.patch into ORFS directly so bazel-orfs no longer needs to carry it. Signed-off-by: Øyvind Harboe --- flow/util/generate_klayout_tech.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index c054945f71..032f896d2d 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -51,16 +51,15 @@ def generate_klayout_tech( with open(template_lyt, "r") as f: content = f.read() - # Both modes use relative paths from reference_dir, matching the - # original sed-based behavior which always uses realpath --relative-to. - resolved_lefs = [ - os.path.relpath(os.path.realpath(f), os.path.realpath(reference_dir)) - for f in lef_files - ] + # Compute relpath without realpath(): under a Bazel sandbox, bazel-out/ + # entries are symlinks to the bare execroot; realpath follows them and + # makes the generated LYT reference files at the bare-execroot path, + # where in-flight outputs do not exist during action execution. + resolved_lefs = [os.path.relpath(f, reference_dir) for f in lef_files] content = replace_lef_files(content, resolved_lefs) - resolved_maps = [os.path.realpath(f) for f in map_files] + resolved_maps = [os.path.abspath(f) for f in map_files] content = replace_map_files(content, resolved_maps) with open(output_lyt, "w") as f: From f1831654832297595c203b2d592b4703e2e801b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sat, 16 May 2026 17:46:34 +0200 Subject: [PATCH 2/6] =?UTF-8?q?fix(generate=5Fklayout=5Ftech):=20write=20a?= =?UTF-8?q?bsolute=20LEF=20paths=20=E2=80=94=20klayout=20sandbox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous switch to relpath (commit 01652cac1) cleaned up the content but did not actually fix def2stream's errno=2 under a Bazel sandbox. Investigation via strace shows klayout's Layout.read(def, layout_options) opens the input DEF (a sandbox symlink to the bare execroot, staged from orfs_final), realpath's it to the bare-execroot results dir, and then resolves relative paths against THAT dir. Sibling intermediates like objects/klayout_tech.lef only exist in the per-action sandbox, not at the bare execroot, so the lookup lands at .../execroot/_main/bazel-out/.../klayout_tech.lef and fails with errno=2. Declaring klayout_tech.lef as a Bazel output of orfs_gds doesn't help -- Bazel only materialises declared outputs at the bare execroot at action completion, not while the action is still running and klayout is reading them. Plain abspath (NOT realpath -- realpath would chase Bazel input-file symlinks back out to the bare execroot for any input already at the bare execroot) writes the in-sandbox absolute LEF path into the LYT. KLayout opens that absolute path directly with no relative-path resolution involved, so the sibling lookup never escapes the sandbox. Cost: the LYT's content now embeds the per-invocation sandbox number, so the orfs_gds action cannot be cross-action cached. That's an acceptable trade-off for correctness while we wait for a klayout-level fix or a Bazel feature that lets us write a stable bare-execroot path from inside the action. Confirmed end-to-end: bazelisk build //flow/designs/sky130hd/gcd:gcd_final_blender_html produces 6_final.gds and the blender_html sh_binary, with both the sandbox sandbox-id N going forward and after fresh action re-runs (verified by deleting outputs and rebuilding). Signed-off-by: Øyvind Harboe --- flow/util/generate_klayout_tech.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index 032f896d2d..7eaa94092d 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -51,11 +51,17 @@ def generate_klayout_tech( with open(template_lyt, "r") as f: content = f.read() - # Compute relpath without realpath(): under a Bazel sandbox, bazel-out/ - # entries are symlinks to the bare execroot; realpath follows them and - # makes the generated LYT reference files at the bare-execroot path, - # where in-flight outputs do not exist during action execution. - resolved_lefs = [os.path.relpath(f, reference_dir) for f in lef_files] + # Write absolute (not relative, not realpath'd) LEF paths into the LYT. + # Klayout's Layout.read(def, layout_options) follows the symlinked input + # DEF to its realpath at the bare execroot and resolves relative + # entries from there. Sibling intermediates like + # objects/klayout_tech.lef don't exist at the bare execroot during + # action execution -- they're only at the per-action sandbox -- so + # resolution fails with errno=2. Plain abspath (NOT realpath, which + # would chase Bazel input-file symlinks back out to the bare execroot) + # keeps klayout pointed at the in-sandbox file. reference_dir is now + # unused for LEFs. + resolved_lefs = [os.path.abspath(f) for f in lef_files] content = replace_lef_files(content, resolved_lefs) From 369c98b336b496b94a14dfd2a9b6d37fe7e93ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Sun, 17 May 2026 05:10:29 +0200 Subject: [PATCH 3/6] test(generate_klayout_tech): expect abspath, not relpath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Earlier commit a1552a87d switched generate_klayout_tech.py to write absolute LEF paths into the LYT (bypasses klayout's relative-resolution escape to the bare-execroot under a Bazel sandbox). The unit test test_basic_generation still asserted the old relpath form and failed in CI: AssertionError: '../tech.lef' not found in '.../tmp/tmpwlgkq96a/tech.lef...' Update the assertion to expect os.path.abspath(lef_path). Signed-off-by: Øyvind Harboe --- flow/test/test_generate_klayout_tech.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/flow/test/test_generate_klayout_tech.py b/flow/test/test_generate_klayout_tech.py index 22c6642127..386cbb7c37 100644 --- a/flow/test/test_generate_klayout_tech.py +++ b/flow/test/test_generate_klayout_tech.py @@ -130,12 +130,13 @@ def test_basic_generation(self): self.assertIn("", content) self.assertNotIn("original.lef", content) - # Path should be relative to results_dir - expected_rel = os.path.relpath( - os.path.realpath(lef_path), - os.path.realpath(self.results_dir), - ) - self.assertIn(expected_rel, content) + # LEF paths are written as plain abspath (not relpath, not realpath): + # klayout's Layout.read resolves relative entries + # against the realpath of the DEF being merged, which under a + # Bazel sandbox is the bare execroot -- the in-flight sibling + # files only exist in the per-action sandbox. Absolute paths + # bypass the relative-resolution dance. + self.assertIn(os.path.abspath(lef_path), content) def test_with_map_files(self): with open(self.template, "w") as f: From 6a321fae6c32efb63b2dd8bae7c253f025d1c1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 18 May 2026 07:27:22 +0200 Subject: [PATCH 4/6] fix(generate_klayout_tech): address gemini-code-assist nits on #4234 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups from gemini's review of #4234: * The map-file test in test_with_map_files still asserted `os.path.realpath(map_path)`, inconsistent with the LEF test's `os.path.abspath` switch. On platforms whose tmpdir contains symlinks (macOS /var -> /private/var) the test would fail. Switch to `os.path.abspath` to match the LEF case. * `reference_dir` and `use_relative_paths` are now both unused. Update the function docstring to say so, default both to `None` / `False` so callers can drop them, and demote the CLI flags to `required=False` so external scripts that don't pass them still work. flow/Makefile still passes `--reference-dir` so we keep accepting the flag. Signed-off-by: Øyvind Harboe Signed-off-by: Øyvind Harboe --- flow/test/test_generate_klayout_tech.py | 5 ++++- flow/util/generate_klayout_tech.py | 29 +++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/flow/test/test_generate_klayout_tech.py b/flow/test/test_generate_klayout_tech.py index 386cbb7c37..f4e6e2837e 100644 --- a/flow/test/test_generate_klayout_tech.py +++ b/flow/test/test_generate_klayout_tech.py @@ -160,7 +160,10 @@ def test_with_map_files(self): with open(self.output) as f: content = f.read() - self.assertIn(os.path.realpath(map_path), content) + # Same abspath semantics as LEFs: map files are also written as + # plain absolute paths so klayout doesn't resolve them relative + # to the bare-execroot realpath of the input DEF. + self.assertIn(os.path.abspath(map_path), content) self.assertNotIn("original.map", content) def test_multiple_lef_files(self): diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index 7eaa94092d..0d3dfcf5af 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -34,9 +34,9 @@ def generate_klayout_tech( template_lyt, output_lyt, lef_files, - reference_dir, map_files, - use_relative_paths, + reference_dir=None, + use_relative_paths=False, ): """Generate a klayout .lyt file from a platform template. @@ -44,9 +44,13 @@ def generate_klayout_tech( template_lyt: Path to the platform .lyt template file. output_lyt: Path to write the generated .lyt file. lef_files: List of LEF file paths to include. - reference_dir: Directory to compute relative paths from. map_files: List of map file paths. - use_relative_paths: If True, compute paths relative to reference_dir. + reference_dir: Unused. Accepted for backward compatibility with + callers (e.g. flow/Makefile) that still pass it from when + paths were resolved relative to this directory. + use_relative_paths: Unused. Same backward-compat rationale as + reference_dir -- paths are always written as plain abspath + now, regardless of this flag. """ with open(template_lyt, "r") as f: content = f.read() @@ -59,8 +63,8 @@ def generate_klayout_tech( # action execution -- they're only at the per-action sandbox -- so # resolution fails with errno=2. Plain abspath (NOT realpath, which # would chase Bazel input-file symlinks back out to the bare execroot) - # keeps klayout pointed at the in-sandbox file. reference_dir is now - # unused for LEFs. + # keeps klayout pointed at the in-sandbox file. reference_dir and + # use_relative_paths are both ignored. resolved_lefs = [os.path.abspath(f) for f in lef_files] content = replace_lef_files(content, resolved_lefs) @@ -85,8 +89,12 @@ def main(): ) parser.add_argument( "--reference-dir", - required=True, - help="Directory for computing relative paths", + required=False, + default=None, + help=( + "Unused; accepted for backward compatibility. LEF / map paths " + "are written as plain abspath regardless of this directory." + ), ) parser.add_argument( "--map-files", nargs="*", default=[], help="Map files to include" @@ -94,7 +102,10 @@ def main(): parser.add_argument( "--use-relative-paths", action="store_true", - help="Use paths relative to reference-dir", + help=( + "Unused; accepted for backward compatibility. LEF / map paths " + "are written as plain abspath regardless of this flag." + ), ) args = parser.parse_args() From c2c6abb8b158aaeefe04a7e0012bf1f99adea99e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 18 May 2026 07:34:28 +0200 Subject: [PATCH 5/6] fix: address second-pass gemini-code-assist nits on #4234 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three follow-ups from gemini's second review pass: * Restore the original positional argument order (template_lyt, output_lyt, lef_files, reference_dir, map_files, use_relative_paths) so Python callers that pass the args positionally still work. Prior fix reshuffled `map_files` ahead of `reference_dir`, breaking positional compatibility. * Re-order the Args block in the docstring to match the new signature order. * `map_files` is now `None`-defaulted (to keep the positional-order fix above), so iterate via `map_files or []` rather than dereferencing `None` directly. Signed-off-by: Øyvind Harboe Signed-off-by: Øyvind Harboe --- flow/util/generate_klayout_tech.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index 0d3dfcf5af..804787e6a7 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -34,8 +34,8 @@ def generate_klayout_tech( template_lyt, output_lyt, lef_files, - map_files, reference_dir=None, + map_files=None, use_relative_paths=False, ): """Generate a klayout .lyt file from a platform template. @@ -44,10 +44,10 @@ def generate_klayout_tech( template_lyt: Path to the platform .lyt template file. output_lyt: Path to write the generated .lyt file. lef_files: List of LEF file paths to include. - map_files: List of map file paths. reference_dir: Unused. Accepted for backward compatibility with callers (e.g. flow/Makefile) that still pass it from when paths were resolved relative to this directory. + map_files: List of map file paths. use_relative_paths: Unused. Same backward-compat rationale as reference_dir -- paths are always written as plain abspath now, regardless of this flag. @@ -69,7 +69,7 @@ def generate_klayout_tech( content = replace_lef_files(content, resolved_lefs) - resolved_maps = [os.path.abspath(f) for f in map_files] + resolved_maps = [os.path.abspath(f) for f in (map_files or [])] content = replace_map_files(content, resolved_maps) with open(output_lyt, "w") as f: From 2d3d7e947442140b3553c1ec3795af98ab6dc623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Harboe?= Date: Mon, 18 May 2026 17:37:09 +0200 Subject: [PATCH 6/6] fix(generate_klayout_tech): remove unused reference_dir / use_relative_paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per maliberty's review on #4234: rather than keep `reference_dir` and `use_relative_paths` around as ignored backward-compat parameters, drop them entirely and update flow/Makefile to stop passing `--reference-dir`. No remaining caller in the tree relies on either. Changes: * flow/util/generate_klayout_tech.py — `generate_klayout_tech()` signature is now (template_lyt, output_lyt, lef_files, map_files=None). CLI loses `--reference-dir` and `--use-relative-paths`. Stale comment / docstring lines about the dropped args go too. * flow/Makefile — `do-klayout` and `do-klayout_wrap` both stop passing `--reference-dir`. * flow/test/test_generate_klayout_tech.py — drops the now-invalid `reference_dir=` / `use_relative_paths=` kwargs from the three integration-style tests. Existing 13 tests still pass. Signed-off-by: Øyvind Harboe --- flow/Makefile | 4 +-- flow/test/test_generate_klayout_tech.py | 6 ---- flow/util/generate_klayout_tech.py | 37 ++----------------------- 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/flow/Makefile b/flow/Makefile index 37712545b7..1bb45047a4 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -200,7 +200,6 @@ do-klayout: --template $(KLAYOUT_TECH_FILE) \ --output $(OBJECTS_DIR)/klayout.lyt \ --lef-files $(OBJECTS_DIR)/klayout_tech.lef $(SC_LEF) $(ADDITIONAL_LEFS) \ - --reference-dir $(RESULTS_DIR) \ --map-files $(wildcard $(FLOW_HOME)/platforms/$(PLATFORM)/*map) $(OBJECTS_DIR)/klayout_wrap.lyt: $(KLAYOUT_TECH_FILE) $(OBJECTS_DIR)/klayout_tech.lef @@ -212,8 +211,7 @@ do-klayout_wrap: $(PYTHON_EXE) $(UTILS_DIR)/generate_klayout_tech.py \ --template $(KLAYOUT_TECH_FILE) \ --output $(OBJECTS_DIR)/klayout_wrap.lyt \ - --lef-files $(OBJECTS_DIR)/klayout_tech.lef $(WRAP_LEFS) \ - --reference-dir $(OBJECTS_DIR)/def + --lef-files $(OBJECTS_DIR)/klayout_tech.lef $(WRAP_LEFS) $(WRAPPED_LEFS): mkdir -p $(OBJECTS_DIR)/lef $(OBJECTS_DIR)/def diff --git a/flow/test/test_generate_klayout_tech.py b/flow/test/test_generate_klayout_tech.py index f4e6e2837e..92e884cfc6 100644 --- a/flow/test/test_generate_klayout_tech.py +++ b/flow/test/test_generate_klayout_tech.py @@ -120,9 +120,7 @@ def test_basic_generation(self): template_lyt=self.template, output_lyt=self.output, lef_files=[lef_path], - reference_dir=self.results_dir, map_files=[], - use_relative_paths=True, ) with open(self.output) as f: @@ -152,9 +150,7 @@ def test_with_map_files(self): template_lyt=self.template, output_lyt=self.output, lef_files=[lef_path], - reference_dir=self.results_dir, map_files=[map_path], - use_relative_paths=False, ) with open(self.output) as f: @@ -181,9 +177,7 @@ def test_multiple_lef_files(self): template_lyt=self.template, output_lyt=self.output, lef_files=lef_files, - reference_dir=self.results_dir, map_files=[], - use_relative_paths=True, ) with open(self.output) as f: diff --git a/flow/util/generate_klayout_tech.py b/flow/util/generate_klayout_tech.py index 804787e6a7..b23a1e0aba 100644 --- a/flow/util/generate_klayout_tech.py +++ b/flow/util/generate_klayout_tech.py @@ -30,27 +30,14 @@ def replace_map_files(content, map_files): return content -def generate_klayout_tech( - template_lyt, - output_lyt, - lef_files, - reference_dir=None, - map_files=None, - use_relative_paths=False, -): +def generate_klayout_tech(template_lyt, output_lyt, lef_files, map_files=None): """Generate a klayout .lyt file from a platform template. Args: template_lyt: Path to the platform .lyt template file. output_lyt: Path to write the generated .lyt file. lef_files: List of LEF file paths to include. - reference_dir: Unused. Accepted for backward compatibility with - callers (e.g. flow/Makefile) that still pass it from when - paths were resolved relative to this directory. map_files: List of map file paths. - use_relative_paths: Unused. Same backward-compat rationale as - reference_dir -- paths are always written as plain abspath - now, regardless of this flag. """ with open(template_lyt, "r") as f: content = f.read() @@ -63,8 +50,7 @@ def generate_klayout_tech( # action execution -- they're only at the per-action sandbox -- so # resolution fails with errno=2. Plain abspath (NOT realpath, which # would chase Bazel input-file symlinks back out to the bare execroot) - # keeps klayout pointed at the in-sandbox file. reference_dir and - # use_relative_paths are both ignored. + # keeps klayout pointed at the in-sandbox file. resolved_lefs = [os.path.abspath(f) for f in lef_files] content = replace_lef_files(content, resolved_lefs) @@ -87,35 +73,16 @@ def main(): parser.add_argument( "--lef-files", nargs="*", default=[], help="LEF files to include" ) - parser.add_argument( - "--reference-dir", - required=False, - default=None, - help=( - "Unused; accepted for backward compatibility. LEF / map paths " - "are written as plain abspath regardless of this directory." - ), - ) parser.add_argument( "--map-files", nargs="*", default=[], help="Map files to include" ) - parser.add_argument( - "--use-relative-paths", - action="store_true", - help=( - "Unused; accepted for backward compatibility. LEF / map paths " - "are written as plain abspath regardless of this flag." - ), - ) args = parser.parse_args() generate_klayout_tech( template_lyt=args.template, output_lyt=args.output, lef_files=args.lef_files, - reference_dir=args.reference_dir, map_files=args.map_files, - use_relative_paths=args.use_relative_paths, )