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:
|
# $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.
Summary
When
build.shbuilds thecuoptPython package, it forwards--cmake-argstoscikit-build via
SKBUILD_CMAKE_ARGS. The expression that assembles that stringuses
${EXTRA_CMAKE_ARGS[*]// /;}, which does not join the array elementswith
;. As a result, whenever the user passes--cmake-args, the user's flagand the auto-appended
-DFIND_CUOPT_CPP=ONare concatenated into a singlecmake argument separated by a space, corrupting both.
Version
NVIDIA/cuoptmain(VERSION26.08.00),build.shline 420, in thecuoptPython package build block.Affected code
build.shline 420, in theif buildAll || hasArg cuopt; thenblock:cuopt/build.sh
Lines 419 to 421 in 37a7b63
Relevant surrounding code:
Root cause
${arr[*]//pattern/repl}in bash applies the substitution to each elementindividually, and only then joins the elements using the first character of
IFS.IFSis unset/default here, so the first character is a space.Because
EXTRA_CMAKE_ARGSis built withread -ra(word-split on whitespace),its elements are individual, space-free tokens. The
space -> ;substitutiontherefore 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
End to end:
./build.sh cuopt --cmake-args="-DCMAKE_CUDA_ARCHITECTURES=90-real"SKBUILD_CMAKE_ARGSends as:scikit-build splits
SKBUILD_CMAKE_ARGSon;, so the final element becomes asingle cmake argument:
i.e. cmake sees
CMAKE_CUDA_ARCHITECTURESset to the literal string90-real -DFIND_CUOPT_CPP=ON, andFIND_CUOPT_CPPis never set.Expected vs. actual
--cmake-argsflag (and the auto-appended-DFIND_CUOPT_CPP=ON) is passed to cmake as a separate-D…argument.--cmake-argsis supplied, the user flag and-DFIND_CUOPT_CPP=ONcollapse into one malformed argument; the CUDAarchitecture value is corrupted and
FIND_CUOPT_CPPis dropped.Impact
EXTRA_CMAKE_ARGShas ≥2 elements, which happens whenever the user passesany
--cmake-args(their token + the always-appended-DFIND_CUOPT_CPP=ON).--cmake-args, the array has a single element, there is nothing tojoin, and the expression is a harmless no-op — which is why default and CI
builds don't hit it.
-DCMAKE_CUDA_ARCHITECTURESexplicitly(e.g. building in a container where the default
NATIVEcannot query a GPU).Suggested fix
Join the array elements with
;explicitly instead of relying on the (incorrect)per-element substitution. Set
IFSfor just the[*]expansion:With this change the reproduction above yields the expected
...;-DCMAKE_CUDA_ARCHITECTURES=90-real;-DFIND_CUOPT_CPP=ON, and each flag reachescmake as its own argument.
The buggy pattern appears once (the
cuoptblock); no otherSKBUILD_CMAKE_ARGSassignment in
build.shuses it.