How to add a file to the cpack archive that needs to be downloaded first using cmake?

I need a way to include the esbuild binary from the node_modules directory of my project in the cpack archive in a way that npm ci, which downloads the esbuild package from npm, gets run before the copy operation is started.

From what I learnt, copying a file to the cpack archive needs to be done with an install() statement, so I have successfully done that using this:

set(ESBUILD_CLI "${PROJECT_SOURCE_DIR}/node_modules/esbuild/bin/esbuild")
install(PROGRAMS
  "${ESBUILD_CLI}"
  DESTINATION "${CMAKE_INSTALL_BINDIR}")

However, I need to make sure that ${PROJECT_SOURCE_DIR}/node_modules gets created by running npm ci before this code is run. I already have the code to run npm ci:

find_program(NPM_BIN NAMES npm)

if(NPM_BIN)
  add_custom_command(OUTPUT "${PROJECT_SOURCE_DIR}/node_modules"
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    COMMAND ${NPM_BIN} ci
    DEPENDS ${PROJECT_SOURCE_DIR}/package.json ${PROJECT_SOURCE_DIR}/package-lock.json
    COMMENT "Install Node.js modules")
else()
  message(WARNING "npm: command not found")
endif()

but I’m not able to find a way to link these 2 together because the install() statement doesn’t accept a DEPENDS field where I can pass ${PROJECT_SOURCE_DIR}/node_modules.

Is there any way of accomplishing what I want, even without using the install() statement?

I believe the trick is that you need to turn the esbuild binary into a target, then it’s trivial to add the dependency. Something like this should work:

add_custom_target(esbuild_bin ALL
    COMMAND echo "Preparing esbuild binary"
    DEPENDS ${PROJECT_SOURCE_DIR}/node_modules
)

Right but how do I add the esbuild_bin target as a dependency to the install()? IIUC, there’s no way of specifying a dependency on install() currently.

It’s not possible today. The issue tracking it is old.

13 years old, woah!

Btw, I have come up with a workaround and it seems to be working as expected:

I made a change to the npm ci custom command and turned it into a custom target:

find_program(NPM_BIN NAMES npm)

if(NPM_BIN)
  add_custom_target(npm-ci
    WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
    COMMAND ${NPM_BIN} ci
    DEPENDS ${PROJECT_SOURCE_DIR}/package.json ${PROJECT_SOURCE_DIR}/package-lock.json
    COMMENT "Install Node.js modules")
else()
  message(WARNING "npm: command not found")
endif()

And finally, I added a dummy ExternalProject_Add() that depends on the npm-ci target and it looks like this:

set(ESBUILD_CLI "${PROJECT_SOURCE_DIR}/node_modules/esbuild/bin/esbuild")

ExternalProject_Add(esbuild
  DEPENDS npm-ci
  DOWNLOAD_COMMAND ""
  BUILD_COMMAND ""
  CONFIGURE_COMMAND ""
  INSTALL_COMMAND "")

install(PROGRAMS
  "${ESBUILD_CLI}"
  DESTINATION "${CMAKE_INSTALL_BINDIR}")

So my conclusion is that we can keep install()s as is and if we want to add a dependency to it, we can add it to ExternalProject_Add().