For add_custom_command to execute its COMMAND arguments, something has to create a process environment for the command to execute in. Whether that's done by a command shell or directly using exec(), the size limit for that environment is defined by the limits.h macro ARG_MAX. On POSIX systems, getconf ARG_MAX will show the compile-time value of that configuration. On my Fedora 35 system, the value is 2097152 which seems almost impossible to exceed, but maybe you're dealing with smaller limits.
(Also, the ARG_MAX limit represents the entire environment, not just command arguments. So if there are a lot of environment variables defined, that could eat into your max length.)
If you truly are exceeding that, you have two choices:
Option 1: Install the objects in the install stage, as intended
If I'm understanding the linked q&a correctly, the only reason you need the $<TARGET_OBJECTS> paths is so that you can copy them somewhere else in the build tree, in order to then install them from there?
If you just want to install them, a simpler solution is to write custom install code, e.g.
# necessary because config variables aren't present # in the install context. install(CODE "set(OBJECT_DEST \"${CMAKE_INSTALL_LIBDIR}\")" install(CODE [[ file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/${OBJECT_DEST}" FILES $<TARGET_OBJECTS:_actual_object_name_> )]])
Messing around with keeping the object name in a variable is a mistake when using custom install code, IMHO, as the evaluation contexts get too confusing. Instead of ${LIBNAME_OBJ}, just use the actual target name in the generator expression.1
Since the generator-expression expansion is handled by CMake when generating the cmake_install.cmake script, there shouldn't be any limit on the length it can have. (Not beyond whatever limits it has internally, if any.)
Option 2: Break up your targets
If you really want to use add_custom_command with the objects, you'll need smaller targets. Why do all of your sources have to be defined on a single object library? Just break them up:
set(SOURCES1 a.c b.c c.c d.c) set(SOURCES2 w.c x.c y.c z.c) add_library(s1_obj OBJECT ${SOURCES1}) add_library(s2_obj OBJECT ${SOURCES2}) add_executable(foo $<TARGET_OBJECTS:s1_obj> $<TARGET_OBJECTS:s2_obj> ) add_custom_target(copy_all_objs COMMAND ${CMAKE_COMMAND} -E make_directory ${OBJ_ROOT_DIR}/myobjects/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_OBJECTS:s1_obj> ${OBJ_ROOT_DIR}/myobjects/ COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_OBJECTS:s2_obj> ${OBJ_ROOT_DIR}/myobjects/ COMMAND_EXPAND_LISTS)
Notes
Actually, I typically recommend using real target names whenever possible in generator expressions.
With some targets — at least SHARED, STATIC, MODULE and ARCHIVE libraries, plus executables — the target name defines the default output file name. So, I get using a variable for the name. (Though, I did say default — it can be overridden with the OUTPUT_NAME target property, and often that's a better way to make filenames configurable. And it lets you keep static target names.)
But for targets like INTERFACE, OBJECT, and ALIAS libraries, the target name is meaningless — it's only used internally in the CMake code. There's no reason not to just hardcode it.
If you're doing this in a macro/function and the target name has to be in a variable, your best bet is probably to write the install() code as a quoted argument rather than a bracket argument, so that variables get evaluated immediately. Which means lots of fun escaping, for the things you don't want evaluated immediately. (Though you can lose the OBJECT_DEST variable, at least.):
install(CODE " file(INSTALL DESTINATION \"\$\{CMAKE_INSTALL_PREFIX\}/${CMAKE_INSTALL_LIBDIR}\" FILES $<TARGET_OBJECTS:${LIBNAME_OBJ}> )")
bash) and call that from a CMake custom command.