Skip to content
Closed
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
10 changes: 2 additions & 8 deletions bazel/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
89 changes: 3 additions & 86 deletions bazel/InitRunFiles.cpp
Original file line number Diff line number Diff line change
@@ -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 <unistd.h> // readlink()

#include <climits>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <system_error>

#if defined(__APPLE__)
#include <mach-o/dyld.h>
#include <sys/param.h>
#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<std::string> 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(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.
15 changes: 15 additions & 0 deletions bazel/tcl_library_init.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ static std::optional<std::string> 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a test that can show that this is necessary ?

// 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");
Comment thread
oharboe marked this conversation as resolved.
#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.
Expand Down
14 changes: 14 additions & 0 deletions test/runfiles/BUILD
Original file line number Diff line number Diff line change
@@ -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"],
)
43 changes: 43 additions & 0 deletions test/runfiles/runfiles_env_test.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading