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
84 changes: 84 additions & 0 deletions ci/check_symbols.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -eEuo pipefail

echo "checking for symbol visibility issues"

LIBRARY="${1}"

echo ""
echo "Checking exported symbols in '${LIBRARY}'"
symbol_file="$(mktemp)"
match_file="$(mktemp)"
trap 'rm -f "${symbol_file}" "${match_file}"' EXIT

# Ignore WEAK and UNIQUE symbols since UNIQUE symbols should be exported and
# WEAK symbols may come from template instantiations.
# Ignore symbols containing "_error" since these are likely exception types
# and should be exported.

readelf --dyn-syms --wide "${LIBRARY}" \
| awk '$7 != "UND" && $5 != "WEAK" && $5 != "UNIQUE"' \
| c++filt --no-params \
| awk '$0 !~ /_error/' \
> "${symbol_file}"

patterns=(
'cub::'
'thrust::'
'raft::'
'rmm::'
'cuopt::mathematical_optimization::detail'
'cuopt::routing::detail'
'grpc::'
'google::protobuf'
'tbb::'
'absl::'
'dejavu::'
'papilo::'
'boost::'
'pslp_'
)

failed=0

for pattern in "${patterns[@]}"; do
echo "Checking for '${pattern}' symbols..."

awk -v pattern="${pattern}" '
BEGIN { has_trailing_scope = (substr(pattern, length(pattern) - 1) == "::") }
$1 ~ /^[0-9]+:/ {
symbol = ""
for (i = 8; i <= NF; ++i) {
symbol = symbol (i == 8 ? "" : " ") $i
}

sub(/<.*/, "", symbol)
sub(/^.*[[:space:]](for|to)[[:space:]]+/, "", symbol)

if (has_trailing_scope) {
matched = (index(symbol, pattern) == 1)
} else {
matched = (symbol == pattern || index(symbol, pattern "::") == 1)
Comment on lines +61 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== ci/check_symbols.sh (relevant lines) ==\n'
cat -n ci/check_symbols.sh | sed -n '1,180p'

printf '\n== occurrences of pslp_ and pattern handling ==\n'
rg -n 'pslp_|has_trailing_scope|index\\(symbol, pattern \\|\\| index\\(symbol, pattern "::"\\) == 1' -S .

Repository: NVIDIA/cuopt

Length of output: 3260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ci/check_symbols.sh excerpt =="
sed -n '50,80p' ci/check_symbols.sh | cat -n

echo
echo "== pslp_ references =="
rg -n '\bpslp_' ci . || true

Repository: NVIDIA/cuopt

Length of output: 4006


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '55,70p' ci/check_symbols.sh

Repository: NVIDIA/cuopt

Length of output: 621


Make pslp_ match the prefix. pslp_ goes through the non-:: branch, so it only matches the exact symbol pslp_ or symbols starting with pslp_::; plain prefixed symbols like pslp_foo are skipped. If this entry is meant to cover the PSLP symbol prefix, add explicit prefix handling and a regression fixture.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/check_symbols.sh` around lines 61 - 64, Update the non-trailing-scope
matching logic in has_trailing_scope so the pslp_ pattern matches symbols
beginning with the plain prefix pslp_, while preserving existing exact and
::-qualified matching behavior for other patterns. Add a regression fixture
covering a symbol such as pslp_foo.

Source: Path instructions

}

if (matched) { print }
}
' "${symbol_file}" > "${match_file}"

matches=$(awk 'END { print NR }' "${match_file}")
if [[ "${matches}" -ne 0 ]]; then
sed -n '1,20p' "${match_file}"
echo "ERROR: Found exported symbols in ${LIBRARY} matching the pattern ${pattern}."
echo "ERROR: Total matching symbols: ${matches}"
failed=1
fi
done

if [[ "${failed}" -ne 0 ]]; then
exit 1
fi

echo "No symbol visibility issues found in ${LIBRARY}"
4 changes: 3 additions & 1 deletion conda/recipes/libcuopt/recipe.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
schema_version: 1

Expand Down Expand Up @@ -107,6 +107,7 @@ outputs:
script:
content: |
cmake --install cpp/build
./ci/check_symbols.sh cpp/build/libcuopt.so
dynamic_linking:
overlinking_behavior: "error"
prefix_detection:
Expand All @@ -118,6 +119,7 @@ outputs:
build:
- cmake ${{ cmake_version }}
- ${{ stdlib("c") }}
- binutils
host:
- libboost-devel
- cuda-version =${{ cuda_version }}
Expand Down
150 changes: 121 additions & 29 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -481,58 +481,48 @@ if (NOT SKIP_GRPC_BUILD)
# at runtime with "undefined symbol: absl::…::Mutex::Dtor".
set_property(SOURCE ${GRPC_INFRA_FILES} DIRECTORY ${CMAKE_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-DNDEBUG")
set_property(SOURCE ${PROTO_SRCS} ${GRPC_PROTO_SRCS} ${GRPC_SERVICE_SRCS} ${DATA_PROTO_SRCS} DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
APPEND PROPERTY COMPILE_OPTIONS "$<$<COMPILE_LANGUAGE:CXX>:-fvisibility=default>")
endif (NOT SKIP_GRPC_BUILD)

add_library(cuopt SHARED
add_library(cuopt_objs OBJECT
${CUOPT_SRC_FILES}
)

set_target_properties(cuopt
PROPERTIES BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN"
INTERFACE_POSITION_INDEPENDENT_CODE ON
set_target_properties(cuopt_objs
PROPERTIES POSITION_INDEPENDENT_CODE ON
CXX_VISIBILITY_PRESET hidden
CUDA_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
CXX_SCAN_FOR_MODULES OFF
)

target_compile_definitions(cuopt
target_compile_definitions(cuopt_objs
PUBLIC "CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
PUBLIC CUSPARSE_ENABLE_EXPERIMENTAL_API
)

target_compile_options(cuopt
target_compile_options(cuopt_objs
PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CUOPT_CXX_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:${CUOPT_CUDA_FLAGS}>"
)

if (WRITE_FATBIN)
file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld"
[=[
SECTIONS
{
.nvFatBinSegment : { *(.nvFatBinSegment) }
.nv_fatbin : { *(.nv_fatbin) }
}
]=])
target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld")
endif ()

add_library(cuopt::cuopt ALIAS cuopt)
# ##################################################################################################
# - include paths ---------------------------------------------------------------------------------
message(STATUS "target include directories CUDSS_INCLUDES = ${CUDSS_INCLUDE}")

# Adding Papilo as a system include messes up clang's include resolution if papilo is already installed as a conda package
target_include_directories(cuopt PRIVATE
target_include_directories(cuopt_objs PRIVATE
"${papilo_SOURCE_DIR}/src"
"${papilo_BINARY_DIR}"
)

target_include_directories(cuopt SYSTEM PRIVATE
target_include_directories(cuopt_objs SYSTEM PRIVATE
"${pslp_SOURCE_DIR}/include"
"${dejavu_SOURCE_DIR}"
)

target_include_directories(cuopt
target_include_directories(cuopt_objs
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
Expand All @@ -547,13 +537,12 @@ target_include_directories(cuopt
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
INTERFACE
"$<INSTALL_INTERFACE:include>"
)

# Link PSLP by file to avoid export dependency tracking
target_link_libraries(cuopt PRIVATE $<TARGET_FILE:PSLP>)
add_dependencies(cuopt PSLP)
target_link_libraries(cuopt_objs PRIVATE $<TARGET_FILE:PSLP>)
add_dependencies(cuopt_objs PSLP)

# ##################################################################################################
# - link libraries --------------------------------------------------------------------------------
Expand All @@ -568,7 +557,7 @@ list(PREPEND CUOPT_PRIVATE_CUDA_LIBS CUDA::cublasLt)

# Pass CUDSS_MT_LIB_FILE_NAME as a compile definition
get_filename_component(CUDSS_MT_LIB_FILE_NAME "${CUDSS_MT_LIB_FILE}" NAME)
target_compile_definitions(cuopt PRIVATE CUDSS_MT_LIB_FILE_NAME="${CUDSS_MT_LIB_FILE_NAME}")
target_compile_definitions(cuopt_objs PRIVATE CUDSS_MT_LIB_FILE_NAME="${CUDSS_MT_LIB_FILE_NAME}")

execute_process(
COMMAND git rev-parse --short HEAD
Expand All @@ -587,13 +576,101 @@ configure_file(
)

# Add the generated include directory
target_include_directories(cuopt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)
target_include_directories(cuopt_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include)

list(JOIN CMAKE_CUDA_ARCHITECTURES "," JOINED_CUDA_ARCHITECTURES)
target_compile_definitions(cuopt PUBLIC
target_compile_definitions(cuopt_objs PUBLIC
CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}"
CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}")

target_link_libraries(cuopt_objs
PUBLIC
CUDA::cublas
CUDA::cusparse
rmm::rmm
rapids_logger::rapids_logger
CCCL::CCCL
raft::raft
${CUDSS_LIB_FILE}
PRIVATE
${CUOPT_PRIVATE_CUDA_LIBS}
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:protobuf::libprotobuf>
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
)

if (BUILD_TESTS)
add_library(cuopt_static STATIC $<TARGET_OBJECTS:cuopt_objs>)

target_link_libraries(cuopt_static
PUBLIC
CUDA::cublas
CUDA::cusparse
rmm::rmm
rapids_logger::rapids_logger
CCCL::CCCL
raft::raft
${CUDSS_LIB_FILE}
PRIVATE
${CUOPT_PRIVATE_CUDA_LIBS}
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:protobuf::libprotobuf>
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
)

target_include_directories(cuopt_static
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
)

target_compile_options(cuopt_static
PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CUOPT_CXX_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:${CUOPT_CUDA_FLAGS}>"
)

target_compile_definitions(cuopt_static PUBLIC
CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}"
CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}"
"CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
CUSPARSE_ENABLE_EXPERIMENTAL_API
)

target_link_libraries(cuopt_static PRIVATE $<TARGET_FILE:PSLP>)
add_dependencies(cuopt_static PSLP)
endif ()

add_library(cuopt SHARED $<TARGET_OBJECTS:cuopt_objs>)
add_library(cuopt::cuopt ALIAS cuopt)

set_target_properties(cuopt
PROPERTIES BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN"
INTERFACE_POSITION_INDEPENDENT_CODE ON
CXX_SCAN_FOR_MODULES OFF
LINKER_LANGUAGE CUDA
)

target_include_directories(cuopt
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<INSTALL_INTERFACE:include>"
)

target_compile_options(cuopt
PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CUOPT_CXX_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:${CUOPT_CUDA_FLAGS}>"
)

target_compile_definitions(cuopt PUBLIC
CUOPT_CUDA_ARCHITECTURES="${JOINED_CUDA_ARCHITECTURES}"
CUOPT_CPU_ARCHITECTURE="${CMAKE_SYSTEM_PROCESSOR}"
"CUOPT_LOG_ACTIVE_LEVEL=RAPIDS_LOGGER_LOG_LEVEL_${LIBCUOPT_LOGGING_LEVEL}"
CUSPARSE_ENABLE_EXPERIMENTAL_API
)

target_link_options(cuopt PRIVATE "LINKER:--exclude-libs=ALL")

target_link_libraries(cuopt
PUBLIC
CUDA::cublas
Expand All @@ -609,6 +686,21 @@ target_link_libraries(cuopt
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
)

target_link_libraries(cuopt PRIVATE $<TARGET_FILE:PSLP>)
add_dependencies(cuopt PSLP)

if (WRITE_FATBIN)
file(WRITE "${CUOPT_BINARY_DIR}/fatbin.ld"
[=[
SECTIONS
{
.nvFatBinSegment : { *(.nvFatBinSegment) }
.nv_fatbin : { *(.nv_fatbin) }
}
]=])
target_link_options(cuopt PRIVATE "${CUOPT_BINARY_DIR}/fatbin.ld")
endif ()


# ##################################################################################################
# - generate tests --------------------------------------------------------------------------------
Expand Down
5 changes: 3 additions & 2 deletions cpp/include/cuopt/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
/* clang-format on */
#pragma once

#include <cuopt/export.hpp>
#include "cuopt/mathematical_optimization/constants.h"

#include <stdarg.h>

#include <raft/core/error.hpp>

namespace cuopt {
namespace CUOPT_EXPORT cuopt {

/**
* @brief Indicates different type of exceptions which cuOpt might throw
Expand Down Expand Up @@ -168,4 +169,4 @@ void execute_cuopt_fail(Args... args)
throw cuopt::logic_error(msg, error_type_t::RuntimeError);
}

} // namespace cuopt
} // namespace CUOPT_EXPORT cuopt
18 changes: 18 additions & 0 deletions cpp/include/cuopt/export.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* clang-format off */
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* clang-format on */

#pragma once

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the repository-required include-guard form.

Both changed headers use #pragma once, while the supplied coding guideline requires #define include guards.

  • cpp/include/cuopt/export.hpp#L8-L8: replace #pragma once with a unique #ifndef/#define guard and closing #endif.
  • cpp/include/cuopt/routing/assignment.hpp#L8-L8: replace #pragma once with a unique #ifndef/#define guard and closing #endif.

As per coding guidelines, C++ headers must use #define include guards.

📍 Affects 2 files
  • cpp/include/cuopt/export.hpp#L8-L8 (this comment)
  • cpp/include/cuopt/routing/assignment.hpp#L8-L8
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/include/cuopt/export.hpp` at line 8, Replace `#pragma` once with a unique
`#ifndef/`#define include guard and matching closing `#endif` in
cpp/include/cuopt/export.hpp (lines 8-8) and
cpp/include/cuopt/routing/assignment.hpp (lines 8-8), ensuring each header uses
a distinct guard name.

Source: Coding guidelines


#if defined(__GNUC__) || defined(__clang__)
#define CUOPT_EXPORT __attribute__((visibility("default")))
// Marks internal symbols that are exported solely for test access.
// These are NOT part of the stable public API and may change without notice.
#define CUOPT_INTERNAL_EXPORT __attribute__((visibility("default")))
#else
#define CUOPT_EXPORT
#define CUOPT_INTERNAL_EXPORT
#endif
7 changes: 5 additions & 2 deletions cpp/include/cuopt/grpc/cython_grpc_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#pragma once

#include <cuopt/export.hpp>
#include <cuopt/mathematical_optimization/utilities/cython_solve.hpp>

#include <cstddef>
Expand All @@ -22,7 +23,8 @@ class data_model_view_t;
} // namespace io
} // namespace cuopt::mathematical_optimization

namespace cuopt::cython {
namespace cuopt {
namespace CUOPT_EXPORT cython {

/** Mirrors cuopt::mathematical_optimization::job_status_t for the Python bindings. */
enum class grpc_job_status_t : int {
Expand Down Expand Up @@ -166,4 +168,5 @@ class grpc_python_client_t {
std::unique_ptr<impl_t> impl_;
};

} // namespace cuopt::cython
} // namespace CUOPT_EXPORT cython
} // namespace cuopt
Loading
Loading