Skip to content

feat(bazel): add Bazel build on top of main with relative paths#1

Open
hugooole wants to merge 3 commits into
developfrom
zhiguo/bazel_on_main
Open

feat(bazel): add Bazel build on top of main with relative paths#1
hugooole wants to merge 3 commits into
developfrom
zhiguo/bazel_on_main

Conversation

@hugooole

@hugooole hugooole commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Rebuild Bazel support on the current main branch (the old zhiguo/bazel_build fork had diverged ~1000 commits). CMake bakes absolute compile-time paths that cannot exist under Bazel's sandbox/runfiles model; this makes the Bazel build fully relative and keeps CMake/xmake untouched.

Key pieces:

  • src/BUILD.bazel, src/core/BUILD.bazel: define UIPC_RELATIVE_SOURCE_FILE=FILE (copts), UIPC_PROJECT_DIR="" and UIPC_BACKEND_DIR="src/backends" so BackendPathTool computes output subdirs purely lexically with no absolute paths. FILE is exec-root-relative under Bazel for both gcc and nvcc.
  • MODULE.bazel: rules_cuda pinned to v0.3.0; cpptrace patched to the libgcc unwind backend (patches/cpptrace_unwind.patch) to avoid the LLVM libunwind Unwind* ABI interposition that aborted exception unwinding.
  • external/METIS, external/GKlib: Bazel BUILD files for the vendored graph partitioner main now links. .bazelrc enables the sibling repository layout so the source external/ dir is not shadowed by Bazel's reserved external/; .bazelignore excludes external/muda (consumed via bazel_dep).
  • apps/bazel_app: runfiles-based helper (module_dir staging, asset/output path resolution) replacing the CMake app/AssetDir absolute-path library.
  • apps/examples/*/main_bazel.cpp: Bazel-specific entry points using runfiles + uipc::init(); CMake main.cpp left untouched.

Verified: bazel build //src/... //apps/... green; hello_affine_body, hello_simplicial_complex, and wrecking_ball all run (sanity_check 0 errors, world.dump() works without the backend-path assertion, no unwind abort).

Rebuild Bazel support on the current main branch (the old zhiguo/bazel_build
fork had diverged ~1000 commits). CMake bakes absolute compile-time paths that
cannot exist under Bazel's sandbox/runfiles model; this makes the Bazel build
fully relative and keeps CMake/xmake untouched.

Key pieces:
- src/BUILD.bazel, src/core/BUILD.bazel: define UIPC_RELATIVE_SOURCE_FILE=__FILE__
  (copts), UIPC_PROJECT_DIR="" and UIPC_BACKEND_DIR="src/backends" so
  BackendPathTool computes output subdirs purely lexically with no absolute
  paths. __FILE__ is exec-root-relative under Bazel for both gcc and nvcc.
- MODULE.bazel: rules_cuda pinned to v0.3.0; cpptrace patched to the libgcc
  unwind backend (patches/cpptrace_unwind.patch) to avoid the LLVM libunwind
  _Unwind_* ABI interposition that aborted exception unwinding.
- external/METIS, external/GKlib: Bazel BUILD files for the vendored graph
  partitioner main now links. .bazelrc enables the sibling repository layout so
  the source external/ dir is not shadowed by Bazel's reserved external/;
  .bazelignore excludes external/muda (consumed via bazel_dep).
- apps/bazel_app: runfiles-based helper (module_dir staging, asset/output path
  resolution) replacing the CMake app/AssetDir absolute-path library.
- apps/examples/*/main_bazel.cpp: Bazel-specific entry points using runfiles +
  uipc::init(); CMake main.cpp left untouched.

Verified: bazel build //src/... //apps/... green; hello_affine_body,
hello_simplicial_complex, and wrecking_ball all run (sanity_check 0 errors,
world.dump() works without the backend-path assertion, no unwind abort).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Bazel build system support for the libuipc project, adding workspace configuration files, build targets for core libraries and modules, and Bazel-specific entry points for example applications using a runfiles environment helper. The review feedback highlights several opportunities to improve code safety and build robustness, such as initializing Vector3 variables to avoid undefined behavior, validating file streams and symlink operations to prevent silent failures, handling potential null pointers in runfiles initialization, and using std::filesystem::path for path concatenation. Additionally, it is recommended to avoid disabling Bazel sandboxing globally and to remove fragile, hardcoded Bzlmod-mangled paths from the build configurations.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.


auto build_mesh = [&](const Json& j, Object& obj, const SimplicialComplex& mesh)
{
Vector3 position;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

In Eigen, default-constructed fixed-size vectors like Vector3 are not initialized to zero and contain garbage values. If "position" is not found in the JSON object j, position remains uninitialized, leading to undefined behavior when it is multiplied by scale and used to translate the transform. Please initialize it to zero.

Suggested change
Vector3 position;
Vector3 position = Vector3::Zero();

Comment on lines +28 to +34
explicit Env(const char* argv0)
{
std::string error;
m_runfiles.reset(Runfiles::Create(argv0, &error));
if(!m_runfiles)
throw std::runtime_error("Failed to init runfiles: " + error);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If argc == 0, argv[0] can be nullptr according to the C++ standard. Passing a null pointer to Runfiles::Create will cause undefined behavior (likely a crash). It is safer to check if argv0 is null before initializing the runfiles.

Suggested change
explicit Env(const char* argv0)
{
std::string error;
m_runfiles.reset(Runfiles::Create(argv0, &error));
if(!m_runfiles)
throw std::runtime_error("Failed to init runfiles: " + error);
}
explicit Env(const char* argv0)
{
if(!argv0)
throw std::runtime_error("Failed to init runfiles: argv0 is null");
std::string error;
m_runfiles.reset(Runfiles::Create(argv0, &error));
if(!m_runfiles)
throw std::runtime_error("Failed to init runfiles: " + error);
}

Comment on lines +61 to +63
std::error_code ec;
fs::remove(dst, ec);
fs::create_symlink(src, dst, ec);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The std::error_code is passed to fs::create_symlink but is never checked. If the symlink creation fails (e.g., due to permission issues or missing source files), the failure will be silently ignored, leading to hard-to-debug runtime errors later when loading the modules. Please check ec and throw or handle the error.

            std::error_code ec;
            fs::remove(dst, ec);
            fs::create_symlink(src, dst, ec);
            if(ec)
                throw std::runtime_error("Failed to create symlink: " + ec.message());

Comment on lines +88 to +92
inline void dump_config(const Json& config, std::string_view workspace)
{
std::ofstream ofs(std::string{workspace} + "config.json");
ofs << config.dump(4);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Using raw string concatenation for file paths (std::string{workspace} + "config.json") is fragile and can lead to malformed paths if workspace does not end with a directory separator. Since C++20 is used, it is much more robust to use std::filesystem::path's / operator.

Suggested change
inline void dump_config(const Json& config, std::string_view workspace)
{
std::ofstream ofs(std::string{workspace} + "config.json");
ofs << config.dump(4);
}
inline void dump_config(const Json& config, std::string_view workspace)
{
std::ofstream ofs(fs::path(workspace) / "config.json");
ofs << config.dump(4);
}

Comment on lines +47 to +51
Json wrecking_ball_scene;
{
std::ifstream ifs(fmt::format("{}wrecking_ball.json", this_folder));
ifs >> wrecking_ball_scene;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The file stream ifs is not checked after opening. If wrecking_ball.json is missing or cannot be opened, reading from it will fail silently or throw an unhandled exception. It is safer to check ifs.is_open() and throw a descriptive error.

Suggested change
Json wrecking_ball_scene;
{
std::ifstream ifs(fmt::format("{}wrecking_ball.json", this_folder));
ifs >> wrecking_ball_scene;
}
Json wrecking_ball_scene;
{
std::string path = fmt::format("{}wrecking_ball.json", this_folder);
std::ifstream ifs(path);
if(!ifs.is_open())
throw std::runtime_error("Failed to open scene file: " + path);
ifs >> wrecking_ball_scene;
}

Comment thread .bazelrc
Comment on lines +24 to +25
# Work around the sandbox issue.
build --spawn_strategy=local

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Disabling sandboxing globally via build --spawn_strategy=local defeats one of Bazel's primary benefits (hermetic and reproducible builds). If there are specific actions or targets that fail inside the sandbox, it is highly recommended to disable sandboxing only for those specific targets (e.g., using tags = ["no-sandbox"] or target-specific strategies) rather than globally.

Comment thread src/core/BUILD.bazel
alwayslink = 1,
copts = [
"-std=c++20",
"-Iexternal/cppitertools+", # TODO: need to add the include path for cppitertools

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Hardcoding Bzlmod-mangled repository names like external/cppitertools+ in copts is highly fragile and non-portable, as the exact mangling scheme (e.g., using + or ~) can change across Bazel versions or dependency overrides. Since @cppitertools is already declared in deps, its include paths should be automatically propagated if the dependency is correctly packaged. If not, consider using includes or repository mapping instead of hardcoding the mangled path.

includes = ["."],
copts = [
"-std=c++20",
"-Iexternal/cppitertools+", # TODO: need to add the include path for cppitertools

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Hardcoding Bzlmod-mangled repository names like external/cppitertools+ in copts is highly fragile and non-portable, as the exact mangling scheme (e.g., using + or ~) can change across Bazel versions or dependency overrides. Since @cppitertools is already declared in deps, its include paths should be automatically propagated if the dependency is correctly packaged. If not, consider using includes or repository mapping instead of hardcoding the mangled path.

hugooole added 2 commits July 7, 2026 16:45
Add a Bazel-native wheel for pyuipc so the Python package can be built
entirely from Bazel (no CMake/vcpkg), for internal use with system CUDA.

- src/pybind/BUILD.bazel: real pybind_extension (was a commented stub);
  headers from *_lib, linked via dynamic_deps to the *_shared libs, the
  dlopen'd plugins (backend_cuda, sanity_check) as data; USD sources excluded.
- python/BUILD.bazel: genrule-stage the 7 runtime .so into uipc/_native/
  and py_wheel with strip_path_prefixes to flatten uipc/ + uipc/_native/.
- $ORIGIN rpath on every uipc_*_lib and both plugins: a wheel has no
  runfiles, so the flat uipc/_native/ siblings resolve via $ORIGIN.
- uipc_backend_cuda: linkstatic=True to absorb the cpp+cuda objects (else the
  _cpp dep leaks out as an unshippable intermediate solib), and add io as a
  real dep/dynamic_dep — the backend uses SimplicialComplexIO::write, which
  only resolved by accident under `bazel run` (io in the global scope) and
  failed when pyuipc.so is dlopen'd RTLD_LOCAL from the wheel.

Verified: pip install the wheel in a clean cp311 venv, import uipc, and run a
3-frame ABD contact sim on CUDA — all from the installed wheel.

Also fix .gitignore: bazel-* convenience links are symlinks, so the
trailing-slash pattern never matched them; fix the docs/doxygen typo; ignore
MODULE.bazel.lock.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant