Skip to content

Commit

Permalink
Add CONAN_DOWNLOAD and CONAN_ISOLATE_HOME options
Browse files Browse the repository at this point in the history
  • Loading branch information
valgur committed Jun 30, 2024
1 parent 144e101 commit 4bae8a1
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 16 deletions.
68 changes: 55 additions & 13 deletions conan_provider.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

set(CONAN_MINIMUM_VERSION 2.0.5)
# Configurable variables
set(CONAN_MINIMUM_VERSION "2.0.5" CACHE STRING "Minimum required Conan version")
set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile")
set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile")
set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install")
set(CONAN_DOWNLOAD "if-missing" CACHE STRING "Download the Conan client (always, if-missing or never)")
set(CONAN_DOWNLOAD_VERSION "latest" CACHE STRING "Download a specific Conan version")
set(CONAN_ISOLATE_HOME "if-downloaded" CACHE STRING "Set $CONAN_HOME to \${CMAKE_BINARY_DIR}/conan_home (always, if-downloaded or never)")

# Create a new policy scope and set the minimum required cmake version so the
# features behind a policy setting like if(... IN_LIST ...) behaves as expected
Expand Down Expand Up @@ -445,7 +452,7 @@ function(conan_install)
set(CONAN_OUTPUT_FOLDER ${CMAKE_BINARY_DIR}/conan)
# Invoke "conan install" with the provided arguments
set(CONAN_ARGS ${CONAN_ARGS} -of=${CONAN_OUTPUT_FOLDER})
message(STATUS "CMake-Conan: conan install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}")
message(STATUS "CMake-Conan: ${CONAN_COMMAND} install ${CMAKE_SOURCE_DIR} ${CONAN_ARGS} ${ARGN}")


# In case there was not a valid cmake executable in the PATH, we inject the
Expand Down Expand Up @@ -506,7 +513,7 @@ endfunction()

function(conan_version_check)
set(options )
set(oneValueArgs MINIMUM CURRENT)
set(oneValueArgs MINIMUM CURRENT RESULT)
set(multiValueArgs )
cmake_parse_arguments(CONAN_VERSION_CHECK
"${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
Expand All @@ -519,7 +526,19 @@ function(conan_version_check)
endif()

if(CONAN_VERSION_CHECK_CURRENT VERSION_LESS CONAN_VERSION_CHECK_MINIMUM)
message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later")
if(CONAN_VERSION_CHECK_RESULT)
set(${CONAN_VERSION_CHECK_RESULT} FALSE PARENT_SCOPE)
endif()
if(CONAN_DOWNLOAD STREQUAL "if-missing")
message(STATUS "CMake-Conan: Found Conan but its version (${CONAN_VERSION_CHECK_CURRENT}) is older than "
"required (${CONAN_VERSION_CHECK_MINIMUM}). Will download the latest version instead.")
else()
message(FATAL_ERROR "CMake-Conan: Conan version must be ${CONAN_VERSION_CHECK_MINIMUM} or later")
endif()
else()
if(CONAN_VERSION_CHECK_RESULT)
set(${CONAN_VERSION_CHECK_RESULT} TRUE PARENT_SCOPE)
endif()
endif()
endfunction()

Expand Down Expand Up @@ -573,7 +592,7 @@ function(download_conan)
set(CONAN_FILE "conan-${CONAN_VERSION}-${HOST_OS}-${HOST_ARCH}.${FILE_EXT}")
set(CONAN_URL "https://github.com/conan-io/conan/releases/download/${CONAN_VERSION}/${CONAN_FILE}")

message(STATUS "Downloading Conan ${CONAN_VERSION} from ${CONAN_URL}")
message(STATUS "CMake-Conan: Downloading Conan ${CONAN_VERSION} from ${CONAN_URL}")
include(FetchContent)
FetchContent_Declare(
Conan
Expand Down Expand Up @@ -608,9 +627,37 @@ macro(conan_provide_dependency method package_name)
set_property(GLOBAL PROPERTY CONAN_PROVIDE_DEPENDENCY_INVOKED TRUE)
get_property(_conan_install_success GLOBAL PROPERTY CONAN_INSTALL_SUCCESS)
if(NOT _conan_install_success)
find_program(CONAN_COMMAND "conan" REQUIRED)
conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION)
conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION})
if(NOT CONAN_DOWNLOAD STREQUAL "always")
find_program(CONAN_COMMAND "conan" QUIET)
if(CONAN_COMMAND)
conan_get_version(${CONAN_COMMAND} CONAN_CURRENT_VERSION)
conan_version_check(MINIMUM ${CONAN_MINIMUM_VERSION} CURRENT ${CONAN_CURRENT_VERSION}
RESULT _conan_version_check_result)
if(NOT _conan_version_check_result)
set(CONAN_COMMAND "-NOTFOUND")
endif()
endif()
endif()
if(NOT CONAN_COMMAND)
if(CONAN_DOWNLOAD STREQUAL "never")
message(FATAL_ERROR "CMake-Conan: Conan executable not found. "
"Please install Conan, set CONAN_COMMAND or enable CONAN_DOWNLOAD")
endif()
if(CONAN_DOWNLOAD_VERSION STREQUAL "latest")
get_latest_conan_version(_download_version)
else()
set(_download_version ${CONAN_DOWNLOAD_VERSION})
endif()
download_conan(VERSION ${_download_version} DESTINATION "${CMAKE_BINARY_DIR}/conan_client")
set(CONAN_COMMAND "${CMAKE_BINARY_DIR}/conan_client/conan")
set(_conan_downloaded TRUE)
endif()

if(CONAN_ISOLATE_HOME STREQUAL "always" OR (CONAN_ISOLATE_HOME STREQUAL "if-downloaded" AND _conan_downloaded))
message(STATUS "CMake-Conan: Setting CONAN_HOME to '${CMAKE_BINARY_DIR}/conan_home'")
set(ENV{CONAN_HOME} "${CMAKE_BINARY_DIR}/conan_home")
endif()

message(STATUS "CMake-Conan: first find_package() found. Installing dependencies with Conan")
if("default" IN_LIST CONAN_HOST_PROFILE OR "default" IN_LIST CONAN_BUILD_PROFILE)
conan_profile_detect_default()
Expand Down Expand Up @@ -711,11 +758,6 @@ endmacro()
# to check if the dependency provider was invoked at all.
cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL conan_provide_dependency_check)

# Configurable variables for Conan profiles
set(CONAN_HOST_PROFILE "default;auto-cmake" CACHE STRING "Conan host profile")
set(CONAN_BUILD_PROFILE "default" CACHE STRING "Conan build profile")
set(CONAN_INSTALL_ARGS "--build=missing" CACHE STRING "Command line arguments for conan install")

find_program(_cmake_program NAMES cmake NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_FIND_ROOT_PATH)
if(NOT _cmake_program)
get_filename_component(PATH_TO_CMAKE_BIN "${CMAKE_COMMAND}" DIRECTORY)
Expand Down
6 changes: 6 additions & 0 deletions tests/resources/download/auto_download/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.24)
project(MyApp CXX)

find_package(hello REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app hello::hello)
5 changes: 5 additions & 0 deletions tests/resources/download/auto_download/conanfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[requires]
hello/0.1

[generators]
CMakeDeps
1 change: 0 additions & 1 deletion tests/resources/download/download_function/conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
[requires]
hello/0.1
1 change: 0 additions & 1 deletion tests/resources/download/get_latest_version/conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
[requires]
hello/0.1
77 changes: 76 additions & 1 deletion tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,4 +772,79 @@ def test_download_function(self, capfd, basic_cmake_project):
out, _ = capfd.readouterr()
assert 'Downloading Conan ' in out
assert (os.path.exists(os.path.join(binary_dir, 'conan', 'conan')) or
os.path.exists(os.path.join(binary_dir, 'conan', 'conan.exe')))
os.path.exists(os.path.join(binary_dir, 'conan', 'conan.exe')))

@staticmethod
def _prepare_isolated_home(binary_dir):
# Copy everything except settings.yml from the real CONAN_HOME to the expected isolated CONAN_HOME.
# The settings.yml should be re-created after CMake configure.
conan_home = os.getenv("CONAN_HOME")
isolated_home = os.path.join(binary_dir, "conan_home")
shutil.copytree(conan_home, isolated_home)
isolated_settings_yml = os.path.join(isolated_home, "settings.yml")
os.unlink(isolated_settings_yml)
return isolated_settings_yml

@pytest.mark.parametrize("isolate", ["always", "never"])
def test_isolate_home(self, capfd, basic_cmake_project, isolate):
"Test that CONAN_ISOLATE_HOME=always/never results in {build_dir}/conan_home being used / not used"
source_dir, binary_dir = basic_cmake_project
isolated_settings_yml = self._prepare_isolated_home(binary_dir)
assert not os.path.exists(isolated_settings_yml)
shutil.copytree(src_dir / 'tests' / 'resources' / 'download' / 'auto_download', source_dir, dirs_exist_ok=True)
run(f'cmake -S {source_dir} -B {binary_dir} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} -DCMAKE_BUILD_TYPE=Release'
f' -DCONAN_ISOLATE_HOME={isolate}')
out, _ = capfd.readouterr()
if isolate == "always":
assert 'Setting CONAN_HOME to ' in out
assert os.path.exists(isolated_settings_yml)
else:
assert 'Setting CONAN_HOME to ' not in out
assert not os.path.exists(isolated_settings_yml)

@pytest.mark.parametrize("conan_missing", [True, False])
def test_download_never(self, capfd, basic_cmake_project, conan_missing):
source_dir, binary_dir = basic_cmake_project
shutil.copytree(src_dir / 'tests' / 'resources' / 'download' / 'auto_download', source_dir, dirs_exist_ok=True)
run(f'cmake -S {source_dir} -B {binary_dir} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} -DCMAKE_BUILD_TYPE=Release'
' -DCONAN_DOWNLOAD=never' + (' -DCONAN_MINIMUM_VERSION=100.0.0' if conan_missing else ''), check=False)
out, err = capfd.readouterr()
assert 'Downloading Conan ' not in out
if conan_missing:
assert 'CMake-Conan: Conan version must be 100.0.0 or later' in err
else:
assert 'CMake-Conan: Conan version must be 100.0.0 or later' not in err

def test_download_always(self, capfd, basic_cmake_project):
source_dir, binary_dir = basic_cmake_project
isolated_settings_yml = self._prepare_isolated_home(binary_dir)
assert not os.path.exists(isolated_settings_yml)
shutil.copytree(src_dir / 'tests' / 'resources' / 'download' / 'auto_download', source_dir, dirs_exist_ok=True)
run(f'cmake -S {source_dir} -B {binary_dir} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} -DCMAKE_BUILD_TYPE=Release'
' -DCONAN_DOWNLOAD=always -DCONAN_ISOLATE_HOME=if-downloaded -DCONAN_MINIMUM_VERSION=100.0.0', check=False)
out, _ = capfd.readouterr()
assert 'Downloading Conan ' in out
assert 'Setting CONAN_HOME to ' in out
assert os.path.exists(isolated_settings_yml)
assert (os.path.exists(os.path.join(binary_dir, 'conan_client', 'conan')) or
os.path.exists(os.path.join(binary_dir, 'conan_client', 'conan.exe')))

@pytest.mark.parametrize("conan_missing", [True, False])
def test_download_if_missing(self, capfd, basic_cmake_project, conan_missing):
source_dir, binary_dir = basic_cmake_project
print(binary_dir)
shutil.copytree(src_dir / 'tests' / 'resources' / 'download' / 'auto_download', source_dir, dirs_exist_ok=True)
run(f'cmake -S {source_dir} -B {binary_dir} -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES={conan_provider} -DCMAKE_BUILD_TYPE=Release'
' -DCONAN_DOWNLOAD=if-missing -DCONAN_ISOLATE_HOME=never' + (' -DCONAN_MINIMUM_VERSION=100.0.0' if conan_missing else ''))
out, err = capfd.readouterr()
assert 'CMake-Conan: Conan version must be 100.0.0 or later' not in err
assert not os.path.exists(os.path.join(binary_dir, 'conan_home'))
if conan_missing:
assert 'Will download the latest version instead.' in out
assert 'Downloading Conan ' in out
assert re.search(r"conan_client/conan(\.exe)? install ", out)
assert (os.path.exists(os.path.join(binary_dir, 'conan_client', 'conan')) or
os.path.exists(os.path.join(binary_dir, 'conan_client', 'conan.exe')))
else:
assert 'Will download the latest version instead.' not in out
assert 'Downloading Conan ' not in out

0 comments on commit 4bae8a1

Please sign in to comment.