Skip to content

[BUG] build.sh: --cmake-args are mangled for the cuopt Python package #1587

Description

@mlubin

Summary

When build.sh builds the cuopt Python package, it forwards --cmake-args to
scikit-build via SKBUILD_CMAKE_ARGS. The expression that assembles that string
uses ${EXTRA_CMAKE_ARGS[*]// /;}, which does not join the array elements
with ;. As a result, whenever the user passes --cmake-args, the user's flag
and the auto-appended -DFIND_CUOPT_CPP=ON are concatenated into a single
cmake argument separated by a space, corrupting both.

Version

  • NVIDIA/cuopt main (VERSION 26.08.00), build.sh line 420, in the
    cuopt Python package build block.

Affected code

build.sh line 420, in the if buildAll || hasArg cuopt; then block:

cuopt/build.sh

Lines 419 to 421 in 37a7b63

# $EXTRA_CMAKE_ARGS gets concatenated into a string with [*] and then we find/replace spaces with semi-colons
SKBUILD_CMAKE_ARGS="-DCMAKE_PREFIX_PATH=${INSTALL_PREFIX};-DCMAKE_LIBRARY_PATH=${LIBCUOPT_BUILD_DIR};-DCMAKE_CUDA_ARCHITECTURES=${CUOPT_CMAKE_CUDA_ARCHITECTURES};${EXTRA_CMAKE_ARGS[*]// /;}" \
python "${PYTHON_ARGS_FOR_INSTALL[@]}" .

Relevant surrounding code:

# EXTRA_CMAKE_ARGS is populated by word-splitting the --cmake-args value:
read -ra EXTRA_CMAKE_ARGS <<< "$EXTRA_CMAKE_ARGS"

# ...and build.sh always appends this unless the user already set it:
if ! contains_string "DFIND_CUOPT_CPP" "${EXTRA_CMAKE_ARGS[@]}"; then
    EXTRA_CMAKE_ARGS+=("-DFIND_CUOPT_CPP=ON")
fi

Root cause

${arr[*]//pattern/repl} in bash applies the substitution to each element
individually
, and only then joins the elements using the first character of
IFS. IFS is unset/default here, so the first character is a space.

Because EXTRA_CMAKE_ARGS is built with read -ra (word-split on whitespace),
its elements are individual, space-free tokens. The space -> ; substitution
therefore matches nothing inside any element, and the spaces that join the
elements are left untouched
. The intended transformation ("replace the joining
spaces with semicolons") never happens — the code assumes the join occurs before
the substitution, but the order is the reverse.

Reproduction

# Minimal, using the exact upstream expression and the array build.sh produces:
EXTRA_CMAKE_ARGS=()
read -ra EXTRA_CMAKE_ARGS <<< "-DCMAKE_CUDA_ARCHITECTURES=90-real"   # user --cmake-args
EXTRA_CMAKE_ARGS+=("-DFIND_CUOPT_CPP=ON")                            # auto-appended

printf '%s\n' "${EXTRA_CMAKE_ARGS[*]// /;}"
# Actual:   -DCMAKE_CUDA_ARCHITECTURES=90-real -DFIND_CUOPT_CPP=ON     <-- space
# Expected: -DCMAKE_CUDA_ARCHITECTURES=90-real;-DFIND_CUOPT_CPP=ON     <-- semicolon

End to end:

./build.sh cuopt --cmake-args="-DCMAKE_CUDA_ARCHITECTURES=90-real"

SKBUILD_CMAKE_ARGS ends as:

...;-DCMAKE_CUDA_ARCHITECTURES=<default>;-DCMAKE_CUDA_ARCHITECTURES=90-real -DFIND_CUOPT_CPP=ON

scikit-build splits SKBUILD_CMAKE_ARGS on ;, so the final element becomes a
single cmake argument:

-DCMAKE_CUDA_ARCHITECTURES=90-real -DFIND_CUOPT_CPP=ON

i.e. cmake sees CMAKE_CUDA_ARCHITECTURES set to the literal string
90-real -DFIND_CUOPT_CPP=ON, and FIND_CUOPT_CPP is never set.

Expected vs. actual

  • Expected: each --cmake-args flag (and the auto-appended
    -DFIND_CUOPT_CPP=ON) is passed to cmake as a separate -D… argument.
  • Actual: when --cmake-args is supplied, the user flag and
    -DFIND_CUOPT_CPP=ON collapse into one malformed argument; the CUDA
    architecture value is corrupted and FIND_CUOPT_CPP is dropped.

Impact

  • Latent, but a hard failure on the affected path. It only triggers when
    EXTRA_CMAKE_ARGS has ≥2 elements, which happens whenever the user passes
    any --cmake-args (their token + the always-appended -DFIND_CUOPT_CPP=ON).
  • With no --cmake-args, the array has a single element, there is nothing to
    join, and the expression is a harmless no-op — which is why default and CI
    builds don't hit it.
  • Notably breaks the common need to pin -DCMAKE_CUDA_ARCHITECTURES explicitly
    (e.g. building in a container where the default NATIVE cannot query a GPU).

Suggested fix

Join the array elements with ; explicitly instead of relying on the (incorrect)
per-element substitution. Set IFS for just the [*] expansion:

SKBUILD_CMAKE_ARGS="-DCMAKE_PREFIX_PATH=${INSTALL_PREFIX};-DCMAKE_LIBRARY_PATH=${LIBCUOPT_BUILD_DIR};-DCMAKE_CUDA_ARCHITECTURES=${CUOPT_CMAKE_CUDA_ARCHITECTURES};$(IFS=';'; echo "${EXTRA_CMAKE_ARGS[*]}")" \
    python "${PYTHON_ARGS_FOR_INSTALL[@]}" .

With this change the reproduction above yields the expected
...;-DCMAKE_CUDA_ARCHITECTURES=90-real;-DFIND_CUOPT_CPP=ON, and each flag reaches
cmake as its own argument.

The buggy pattern appears once (the cuopt block); no other SKBUILD_CMAKE_ARGS
assignment in build.sh uses it.

Metadata

Metadata

Labels

awaiting responseThis expects a response from maintainer or contributor depending on who requested in last comment.bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions