How can I create a python3 venv in cmake install?
Question:
I have a medium sized project composed by many parts, mostly in C++, but testing and configuration relies on Python3 scripts.
The project buildsystem is generated using CMake and installed (by CMake rules) in a "deploy" directory.
I would like to create a Python venv to segregate changes.
I have the following CMake fragment:
set(VENV ${CMAKE_INSTALL_PREFIX}/venv)
set(REQUIREMENTS ${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt)
set(BIN_DIR ${VENV}/bin)
set(PYTHON ${BIN_DIR}/python)
set(OUTPUT_FILE ${VENV}/environment.txt)
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND ${Python3_EXECUTABLE} -m venv ${VENV}
COMMAND ${BIN_DIR}/pip install -U pip wheel
COMMAND ${BIN_DIR}/pip install -r ${REQUIREMENTS}
COMMAND ${BIN_DIR}/pip freeze > ${VENV}/environment.txt
DEPENDS ${REQUIREMENTS}
)
add_custom_target(venv DEPENDS ${PYTHON})
… but I don’t know how to trigger the venv
target while doing installation.
I am unsure if this is the right approach (comments welcome, of course), but need should be quite clear: I need to be able to run my scripts from the "deploy" directory using something like:
venv/bin/python test-script.py
or use a custom: #!venv/bin/python
line (I am working under Linux).
UPDATE:
My somewhat working code, based on @user Answer, is as follows:
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(VENV "${CMAKE_INSTALL_PREFIX}/venv")
set(REQUIREMENTS "${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt")
set(BIN_DIR "${VENV}/bin")
install(CODE "
MESSAGE("Creating VENV from ${Python3_EXECUTABLE} to ${VENV}")
execute_process(COMMAND_ECHO STDOUT COMMAND ${Python3_EXECUTABLE} -m venv ${VENV} )
execute_process(COMMAND_ECHO STDOUT COMMAND ${BIN_DIR}/pip install -U pip wheel )
execute_process(COMMAND_ECHO STDOUT COMMAND ${BIN_DIR}/pip install -r ${REQUIREMENTS} )
")
Equivalent code with set(...
and find_package(...
inside install(CODE...
did not work; also quoting seems wrong.
I will accept this answer, but I would like to clarify the above.
Answers:
You should be able to do this using install(SCRIPT)
or install(CODE)
:
The SCRIPT
form will invoke the given CMake script files during installation. If the script file name is a relative path it will be interpreted with respect to the current source directory. The CODE
form will invoke the given CMake code during installation. Code is specified as a single argument inside a double-quoted string.
I believe it would be done something like this (using the CODE
form here to make it easier to do variable substitution and use some of the variables that wouldn’t be available outside the context of the configuration phase (i.e. if you were to use the SCRIPT
mode) without explicitly passing them):
install(CODE "
set(VENV "${CMAKE_INSTALL_PREFIX}/venv")
set(REQUIREMENTS "${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt")
set(BIN_DIR "${VENV}/bin")
set(OUTPUT_FILE "${VENV}/environment.txt")
execute_process(COMMAND "${Python3_EXECUTABLE}" -m venv "${VENV}")
execute_process(COMMAND "${BIN_DIR}/pip" install -U pip wheel)
execute_process(COMMAND "${BIN_DIR}/pip" install -r "${REQUIREMENTS}")
execute_process(COMMAND "${BIN_DIR}/pip" freeze > "${VENV}/environment.txt")
execute_process(COMMAND "${REQUIREMENTS}")
")
See also the docs for execute_process
if you want to do things like handle command failures, etc.
I have a medium sized project composed by many parts, mostly in C++, but testing and configuration relies on Python3 scripts.
The project buildsystem is generated using CMake and installed (by CMake rules) in a "deploy" directory.
I would like to create a Python venv to segregate changes.
I have the following CMake fragment:
set(VENV ${CMAKE_INSTALL_PREFIX}/venv)
set(REQUIREMENTS ${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt)
set(BIN_DIR ${VENV}/bin)
set(PYTHON ${BIN_DIR}/python)
set(OUTPUT_FILE ${VENV}/environment.txt)
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND ${Python3_EXECUTABLE} -m venv ${VENV}
COMMAND ${BIN_DIR}/pip install -U pip wheel
COMMAND ${BIN_DIR}/pip install -r ${REQUIREMENTS}
COMMAND ${BIN_DIR}/pip freeze > ${VENV}/environment.txt
DEPENDS ${REQUIREMENTS}
)
add_custom_target(venv DEPENDS ${PYTHON})
… but I don’t know how to trigger the venv
target while doing installation.
I am unsure if this is the right approach (comments welcome, of course), but need should be quite clear: I need to be able to run my scripts from the "deploy" directory using something like:
venv/bin/python test-script.py
or use a custom: #!venv/bin/python
line (I am working under Linux).
UPDATE:
My somewhat working code, based on @user Answer, is as follows:
find_package(Python3 REQUIRED COMPONENTS Interpreter)
set(VENV "${CMAKE_INSTALL_PREFIX}/venv")
set(REQUIREMENTS "${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt")
set(BIN_DIR "${VENV}/bin")
install(CODE "
MESSAGE("Creating VENV from ${Python3_EXECUTABLE} to ${VENV}")
execute_process(COMMAND_ECHO STDOUT COMMAND ${Python3_EXECUTABLE} -m venv ${VENV} )
execute_process(COMMAND_ECHO STDOUT COMMAND ${BIN_DIR}/pip install -U pip wheel )
execute_process(COMMAND_ECHO STDOUT COMMAND ${BIN_DIR}/pip install -r ${REQUIREMENTS} )
")
Equivalent code with set(...
and find_package(...
inside install(CODE...
did not work; also quoting seems wrong.
I will accept this answer, but I would like to clarify the above.
You should be able to do this using install(SCRIPT)
or install(CODE)
:
The
SCRIPT
form will invoke the given CMake script files during installation. If the script file name is a relative path it will be interpreted with respect to the current source directory. TheCODE
form will invoke the given CMake code during installation. Code is specified as a single argument inside a double-quoted string.
I believe it would be done something like this (using the CODE
form here to make it easier to do variable substitution and use some of the variables that wouldn’t be available outside the context of the configuration phase (i.e. if you were to use the SCRIPT
mode) without explicitly passing them):
install(CODE "
set(VENV "${CMAKE_INSTALL_PREFIX}/venv")
set(REQUIREMENTS "${CMAKE_SOURCE_DIR}/tools/testing/scripts/requirements.txt")
set(BIN_DIR "${VENV}/bin")
set(OUTPUT_FILE "${VENV}/environment.txt")
execute_process(COMMAND "${Python3_EXECUTABLE}" -m venv "${VENV}")
execute_process(COMMAND "${BIN_DIR}/pip" install -U pip wheel)
execute_process(COMMAND "${BIN_DIR}/pip" install -r "${REQUIREMENTS}")
execute_process(COMMAND "${BIN_DIR}/pip" freeze > "${VENV}/environment.txt")
execute_process(COMMAND "${REQUIREMENTS}")
")
See also the docs for execute_process
if you want to do things like handle command failures, etc.