diff --git a/bazel/BUILD b/bazel/BUILD index a15adee8d12..66f99d2775b 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -124,18 +124,12 @@ cc_library( }), ) -# shim old library as it is still referenced in src/sta, implementing the -# old behavior. +# Stub library kept for src/sta:opensta compatibility. +# Tcl setup is now handled by //bazel:tcl_library_init. cc_library( name = "runfiles", srcs = ["InitRunFiles.cpp"], - data = [":tcl_resources_dir"], visibility = ["//visibility:public"], - deps = [ - "@rules_cc//cc/runfiles", - "@tcl_lang//:tcl", - ], - alwayslink = True, ) # small build test to check if "bazel build //src/sta:StaTclInitVar" works diff --git a/bazel/InitRunFiles.cpp b/bazel/InitRunFiles.cpp index 06517742174..ece50b0a09b 100644 --- a/bazel/InitRunFiles.cpp +++ b/bazel/InitRunFiles.cpp @@ -1,86 +1,3 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2026, The OpenROAD Authors - -// This file to go away as soon as we use the tcl_library_init initialization -// in OpenSTA. - -#include // readlink() - -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__APPLE__) -#include -#include -#endif - -#include "rules_cc/cc/runfiles/runfiles.h" - -namespace { -// Avoid adding any dependencies like boost.filesystem -// Returns path to running binary if possible, otherwise nullopt. -static std::optional GetProgramLocation() -{ -#if defined(_WIN32) - char result[MAX_PATH + 1] = {'\0'}; - auto path_len = GetModuleFileNameA(NULL, result, MAX_PATH); -#elif defined(__APPLE__) - char result[MAXPATHLEN + 1] = {'\0'}; - uint32_t path_len = MAXPATHLEN; - if (_NSGetExecutablePath(result, &path_len) != 0) { - path_len = readlink(result, result, MAXPATHLEN); - } -#else - char result[PATH_MAX + 1] = {'\0'}; - ssize_t path_len = readlink("/proc/self/exe", result, PATH_MAX); -#endif - if (path_len > 0) { - return result; - } - return std::nullopt; -} - -std::string GetTclLibraryBaseLocation() -{ - using rules_cc::cc::runfiles::Runfiles; - std::string error; - std::unique_ptr runfiles(Runfiles::Create( - *GetProgramLocation(), BAZEL_CURRENT_REPOSITORY, &error)); - if (!runfiles) { - std::cerr << "[Warning] Failed to create bazel runfiles: " << error << "\n"; - return ""; - } - - std::error_code ec; - for (const std::string loc : {"openroad", "opensta", "_main"}) { - const std::string check_loc = loc + "/bazel/tcl_resources_dir"; - const std::string path = runfiles->Rlocation(check_loc); - if (!path.empty() && std::filesystem::exists(path, ec)) { - return path; - } - } - return ""; -} - -class BazelInitializer -{ - public: - BazelInitializer() - { - const std::string lib_mount_point = GetTclLibraryBaseLocation(); - if (!lib_mount_point.empty()) { - const std::string tcl_path = lib_mount_point + "/library"; - setenv("TCL_LIBRARY", tcl_path.c_str(), true); - } - } -}; - -// Provide via global constructor. -static BazelInitializer bazel_initializer; -} // namespace +// Intentionally empty: tcl_library_init now handles Tcl setup. +// This file is kept because src/sta:opensta still references +// //bazel:runfiles in its srcs. diff --git a/bazel/tcl_library_init.cc b/bazel/tcl_library_init.cc index 62d73ad0284..cfbd5bead06 100644 --- a/bazel/tcl_library_init.cc +++ b/bazel/tcl_library_init.cc @@ -40,6 +40,21 @@ static std::optional TclLibraryMountPoint(Tcl_Interp* interp) return Tcl_GetStringResult(interp); #else using rules_cc::cc::runfiles::Runfiles; + + // Clear inherited RUNFILES_* env vars: when OpenROAD is invoked by + // a build system that previously ran another Bazel binary (e.g. a + // Python wrapper), those variables point to the *other* binary's + // runfiles tree. Runfiles::Create() checks env vars first, so it + // would resolve paths in the wrong tree. Unsetting forces it to + // fall back to the exe path, which is always correct. +#ifdef _WIN32 + _putenv_s("RUNFILES_DIR", ""); + _putenv_s("RUNFILES_MANIFEST_FILE", ""); +#else + unsetenv("RUNFILES_DIR"); + unsetenv("RUNFILES_MANIFEST_FILE"); +#endif + std::string error; // Use /proc/self/exe to resolve the real binary path, as argv[0] may // point into a sandbox where the .runfiles tree does not exist. diff --git a/test/runfiles/BUILD b/test/runfiles/BUILD new file mode 100644 index 00000000000..80a22cd3fe2 --- /dev/null +++ b/test/runfiles/BUILD @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026, The OpenROAD Authors + +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +# Prove that the unsetenv(RUNFILES_DIR) fix in //bazel:tcl_library_init +# is necessary: this Bazel test has RUNFILES_DIR pointing to its own +# runfiles tree, yet OpenROAD must still find *its* TCL library. +sh_test( + name = "runfiles_env_test", + srcs = ["runfiles_env_test.sh"], + args = ["$(rootpath //:openroad)"], + data = ["//:openroad"], +) diff --git a/test/runfiles/runfiles_env_test.sh b/test/runfiles/runfiles_env_test.sh new file mode 100755 index 00000000000..dcd6cf37c94 --- /dev/null +++ b/test/runfiles/runfiles_env_test.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Test that OpenROAD works when invoked from a Bazel-built process +# that has RUNFILES_DIR / RUNFILES_MANIFEST_FILE set in the environment. +# +# This is the scenario that happens when a Bazel py_binary (e.g. a +# Python wrapper) runs OpenROAD as a subprocess. The inherited +# RUNFILES_* variables point to the wrapper's runfiles tree, not +# OpenROAD's. Without the unsetenv() fix in tcl_library_init.cc, +# Runfiles::Create() resolves paths in the wrong tree and OpenROAD +# fails to find its TCL library. +# +# We simulate this by pointing RUNFILES_DIR at an empty directory +# before invoking OpenROAD. + +set -euo pipefail + +OPENROAD="$1" + +# Simulate cross-binary env leak: point RUNFILES_DIR at an empty +# directory that does NOT contain OpenROAD's tcl resources. +FAKE_RUNFILES=$(mktemp -d) +trap 'rm -rf "$FAKE_RUNFILES"' EXIT +export RUNFILES_DIR="$FAKE_RUNFILES" +export RUNFILES_MANIFEST_FILE="" +echo "Fake RUNFILES_DIR=$RUNFILES_DIR" + +# Run OpenROAD with a real Tcl command (not -version, which exits +# before Tcl init). Capture both stdout and stderr. +output=$("$OPENROAD" -no_splash -exit <<< 'puts "TCL_OK"' 2>&1) || { + echo "FAIL: openroad exited with $?" + echo "$output" + exit 1 +} + +# Check for Tcl initialization failure +if echo "$output" | grep -q "application-specific initialization failed"; then + echo "FAIL: Tcl library initialization failed despite exe-path fallback" + echo "$output" + exit 1 +fi + +echo "$output" +echo "PASS: OpenROAD initialized Tcl correctly despite misleading RUNFILES_DIR"