diff --git a/libmamba/CMakeLists.txt b/libmamba/CMakeLists.txt index aaa4f944d4..6e34a0b533 100644 --- a/libmamba/CMakeLists.txt +++ b/libmamba/CMakeLists.txt @@ -12,6 +12,8 @@ project(libmamba) set(LIBMAMBA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) set(LIBMAMBA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) set(LIBMAMBA_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data) +# Store the binary directory for use in component CMakeLists.txt files +set(LIBMAMBA_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) # Versioning # =========== @@ -119,6 +121,8 @@ endif() find_package(Python3 COMPONENTS Interpreter) +# Shell scripts need to be defined before including component CMakeLists.txt files so they can be +# added to the common component sources set( SHELL_SCRIPTS mamba.sh @@ -135,12 +139,16 @@ set( mamba_completion.posix ) +# Generate shell script .cpp files before including component CMakeLists.txt so the components can +# reference them file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/shell_scripts) foreach(script ${SHELL_SCRIPTS}) string(REPLACE "." "_" script_var ${script}) + # Use absolute path for OUTPUT to ensure consistent path resolution when referenced from + # component CMakeLists.txt files add_custom_command( - OUTPUT shell_scripts/${script}.cpp - DEPENDS data/${script} + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/shell_scripts/${script}.cpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/data/${script} COMMAND ${Python3_EXECUTABLE} ${LIBMAMBA_DATA_DIR}/bin2header.py --extern -v data_${script_var} -i ${CMAKE_CURRENT_SOURCE_DIR}/data/${script} -o @@ -148,118 +156,19 @@ foreach(script ${SHELL_SCRIPTS}) ) endforeach() +# Include component CMakeLists.txt files +# ======================================= +include(${CMAKE_CURRENT_SOURCE_DIR}/common/CMakeLists.txt) +include(${CMAKE_CURRENT_SOURCE_DIR}/solver/CMakeLists.txt) +include(${CMAKE_CURRENT_SOURCE_DIR}/network/CMakeLists.txt) +include(${CMAKE_CURRENT_SOURCE_DIR}/archive/CMakeLists.txt) + +# Source files are now organized in component CMakeLists.txt files Legacy LIBMAMBA_SOURCES kept for +# API files that depend on all components set( - LIBMAMBA_SOURCES + LIBMAMBA_API_SOURCES longpath.manifest - ${LIBMAMBA_SOURCE_DIR}/version.cpp - # Filesystem library - ${LIBMAMBA_SOURCE_DIR}/fs/filesystem.cpp - # C++ utility library - ${LIBMAMBA_SOURCE_DIR}/util/cfile.cpp - ${LIBMAMBA_SOURCE_DIR}/util/cryptography.cpp - ${LIBMAMBA_SOURCE_DIR}/util/encoding.cpp - ${LIBMAMBA_SOURCE_DIR}/util/environment.cpp - ${LIBMAMBA_SOURCE_DIR}/util/os_linux.cpp - ${LIBMAMBA_SOURCE_DIR}/util/os_osx.cpp - ${LIBMAMBA_SOURCE_DIR}/util/os_unix.cpp - ${LIBMAMBA_SOURCE_DIR}/util/os_win.cpp - ${LIBMAMBA_SOURCE_DIR}/util/parsers.cpp - ${LIBMAMBA_SOURCE_DIR}/util/path_manip.cpp - ${LIBMAMBA_SOURCE_DIR}/util/random.cpp - ${LIBMAMBA_SOURCE_DIR}/util/string.cpp - ${LIBMAMBA_SOURCE_DIR}/util/url_manip.cpp - ${LIBMAMBA_SOURCE_DIR}/util/url.cpp - # Implementation of version and matching specs - ${LIBMAMBA_SOURCE_DIR}/specs/archive.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/authentication_info.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/build_number_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/channel.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/chimera_string_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/glob_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/match_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/package_info.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/regex_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/unresolved_channel.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/version_spec.cpp - ${LIBMAMBA_SOURCE_DIR}/specs/version.cpp - # Solver generic interface - ${LIBMAMBA_SOURCE_DIR}/solver/helpers.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/problems_graph.cpp - # Solver libsolv implementation - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/database.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/helpers.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/matcher.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/parameters.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/repo_info.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/solver.cpp - ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/unsolvable.cpp - # Artifacts validation - ${LIBMAMBA_SOURCE_DIR}/validation/errors.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/keys.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/repo_checker.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/tools.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v0_6.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v1.cpp - ${LIBMAMBA_SOURCE_DIR}/validation/update_framework.cpp - # Downloaders and mirrors - ${LIBMAMBA_SOURCE_DIR}/download/compression.cpp - ${LIBMAMBA_SOURCE_DIR}/download/compression.hpp - ${LIBMAMBA_SOURCE_DIR}/download/curl.cpp - ${LIBMAMBA_SOURCE_DIR}/download/curl.hpp - ${LIBMAMBA_SOURCE_DIR}/download/downloader_impl.hpp - ${LIBMAMBA_SOURCE_DIR}/download/downloader.cpp - ${LIBMAMBA_SOURCE_DIR}/download/mirror_impl.cpp - ${LIBMAMBA_SOURCE_DIR}/download/mirror_impl.hpp - ${LIBMAMBA_SOURCE_DIR}/download/mirror_map.cpp - ${LIBMAMBA_SOURCE_DIR}/download/mirror.cpp - ${LIBMAMBA_SOURCE_DIR}/download/request.cpp - # Core API (low-level) - ${LIBMAMBA_SOURCE_DIR}/core/activation.cpp - ${LIBMAMBA_SOURCE_DIR}/core/channel_context.cpp - ${LIBMAMBA_SOURCE_DIR}/core/context.cpp - ${LIBMAMBA_SOURCE_DIR}/core/download_progress_bar.cpp - ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile.cpp - ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile_impl.hpp - ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile_conda.cpp - ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile_mambajs.cpp - ${LIBMAMBA_SOURCE_DIR}/core/environments_manager.cpp - ${LIBMAMBA_SOURCE_DIR}/core/error_handling.cpp - ${LIBMAMBA_SOURCE_DIR}/core/execution.cpp - ${LIBMAMBA_SOURCE_DIR}/core/fsutil.cpp - ${LIBMAMBA_SOURCE_DIR}/core/history.cpp - ${LIBMAMBA_SOURCE_DIR}/core/link.cpp - ${LIBMAMBA_SOURCE_DIR}/core/link.hpp - ${LIBMAMBA_SOURCE_DIR}/core/logging.cpp - ${LIBMAMBA_SOURCE_DIR}/core/menuinst.cpp - ${LIBMAMBA_SOURCE_DIR}/core/output.cpp - ${LIBMAMBA_SOURCE_DIR}/core/package_cache.cpp - ${LIBMAMBA_SOURCE_DIR}/core/package_database_loader.cpp - ${LIBMAMBA_SOURCE_DIR}/core/package_fetcher.cpp - ${LIBMAMBA_SOURCE_DIR}/core/package_handling.cpp - ${LIBMAMBA_SOURCE_DIR}/core/package_paths.cpp - ${LIBMAMBA_SOURCE_DIR}/core/pinning.cpp - ${LIBMAMBA_SOURCE_DIR}/core/prefix_data.cpp - ${LIBMAMBA_SOURCE_DIR}/core/progress_bar_impl.cpp - ${LIBMAMBA_SOURCE_DIR}/core/progress_bar.cpp - ${LIBMAMBA_SOURCE_DIR}/core/query.cpp - ${LIBMAMBA_SOURCE_DIR}/core/repo_checker_store.cpp - ${LIBMAMBA_SOURCE_DIR}/core/run.cpp - ${LIBMAMBA_SOURCE_DIR}/core/shell_init.cpp - ${LIBMAMBA_SOURCE_DIR}/core/shard_types.cpp - ${LIBMAMBA_SOURCE_DIR}/core/singletons.cpp - ${LIBMAMBA_SOURCE_DIR}/core/subdir_index.cpp - ${LIBMAMBA_SOURCE_DIR}/core/thread_utils.cpp - ${LIBMAMBA_SOURCE_DIR}/core/timeref.cpp - ${LIBMAMBA_SOURCE_DIR}/core/transaction_context.cpp - ${LIBMAMBA_SOURCE_DIR}/core/transaction_context.hpp - ${LIBMAMBA_SOURCE_DIR}/core/transaction.cpp - ${LIBMAMBA_SOURCE_DIR}/core/util_os.cpp - ${LIBMAMBA_SOURCE_DIR}/core/util.cpp - ${LIBMAMBA_SOURCE_DIR}/core/virtual_packages.cpp - # API (high-level) + # API (high-level) - depends on all components ${LIBMAMBA_SOURCE_DIR}/api/c_api.cpp ${LIBMAMBA_SOURCE_DIR}/api/channel_loader.cpp ${LIBMAMBA_SOURCE_DIR}/api/clean.cpp @@ -276,6 +185,9 @@ set( ${LIBMAMBA_SOURCE_DIR}/api/repoquery.cpp ${LIBMAMBA_SOURCE_DIR}/api/shell.cpp ${LIBMAMBA_SOURCE_DIR}/api/update.cpp + # Core API files that depend on all components + ${LIBMAMBA_SOURCE_DIR}/core/repo_checker_store.cpp + ${LIBMAMBA_SOURCE_DIR}/core/transaction.cpp ) # TODO: remove when switch to C++20 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") @@ -286,128 +198,13 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "App ) endif() -foreach(script ${SHELL_SCRIPTS}) - list(APPEND LIBMAMBA_SOURCES shell_scripts/${script}.cpp) -endforeach() +# Shell scripts are used by common component (shell_init.cpp, activation.cpp, +# transaction_context.cpp) They're added to common component's sources, not API sources +# Public headers are now organized in component CMakeLists.txt files This list is for API headers +# that are part of the aggregated libmamba target set( - LIBMAMBA_PUBLIC_HEADERS - ${LIBMAMBA_INCLUDE_DIR}/mamba/version.hpp - # Filesystem library - ${LIBMAMBA_INCLUDE_DIR}/mamba/fs/filesystem.hpp - # Utility library - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/build.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cast.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cfile.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/conditional.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cryptography.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/deprecation.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/encoding.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/environment.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_binary_tree.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_bool_expr_tree.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_set.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/graph.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/heap_optional.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/iterator.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/json.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/loop_control.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_linux.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_osx.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_unix.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_win.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/parsers.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/path_manip.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/random.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/string.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/synchronized_value.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/tuple_hash.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/type_traits.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url_manip.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/variant_cmp.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/util/weakening_map.hpp - # Implementation of version and matching specs - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/archive.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentication_info.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/build_number_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/chimera_string_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/error.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/glob_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/match_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/package_info.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/platform.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/regex_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/repo_data.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/unresolved_channel.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version_spec.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version.hpp - # Solver generic interface - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/problems_graph.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/request.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/solution.hpp - # Solver libsolv implementation - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/database.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/parameters.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/repo_info.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/solver.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/unsolvable.hpp - # Artifacts validation - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/errors.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/keys.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/repo_checker.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/tools.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v0_6.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v1.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework.hpp - # Downloaders and mirrors - ${LIBMAMBA_INCLUDE_DIR}/mamba/download/downloader.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/download/mirror_map.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/download/mirror.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/download/request.hpp - # Core API (low-level) - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/activation.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/channel_context.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/context.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/context_params.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/download_progress_bar.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/env_lockfile.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/environments_manager.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/error_handling.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/execution.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/fsutil.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/history.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/invoke.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/logging.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/logging_tools.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/menuinst.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/output.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_cache.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_database_loader.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_fetcher.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_handling.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_paths.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/palette.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/pinning.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/prefix_data.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/progress_bar.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/query.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/repo_checker_store.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/run.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/shell_init.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/shard_types.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/subdir_index.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/tasksync.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/thread_utils.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/timeref.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/transaction.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_os.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_scope.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util.hpp - ${LIBMAMBA_INCLUDE_DIR}/mamba/core/virtual_packages.hpp + LIBMAMBA_API_PUBLIC_HEADERS # API (high-level) ${LIBMAMBA_INCLUDE_DIR}/mamba/api/c_api.h ${LIBMAMBA_INCLUDE_DIR}/mamba/api/channel_loader.hpp @@ -425,6 +222,9 @@ set( ${LIBMAMBA_INCLUDE_DIR}/mamba/api/repoquery.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/api/shell.hpp ${LIBMAMBA_INCLUDE_DIR}/mamba/api/update.hpp + # Core API files + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/repo_checker_store.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/transaction.hpp ) # Targets and link @@ -441,15 +241,156 @@ find_package(Libsolv MODULE REQUIRED) find_package(msgpack-c CONFIG REQUIRED) add_subdirectory(ext/solv-cpp) +# Build components +# ================ +# Build order: common -> network -> solver -> archive Note: common depends on network (Context has +# mirror_map), creating a circular dependency We handle this by building common first, then network, +# then linking common to network +if(BUILD_SHARED) + libmamba_common_create_target(libmamba-common-dyn SHARED libmamba-common) + libmamba_network_create_target(libmamba-network-dyn SHARED libmamba-network) + libmamba_solver_create_target(libmamba-solver-dyn SHARED libmamba-solver) + libmamba_archive_create_target(libmamba-archive-dyn SHARED libmamba-archive) + # Link network to archive after archive is built (network uses MultiPackageCache and + # PackageExtractTask) Note: Cannot link archive to network for shared libraries due to circular + # dependency (archive needs network symbols, network needs archive symbols). Archive will use + # undefined symbol flags to allow linking, and symbols will be resolved by the aggregated + # target. + if(TARGET libmamba-archive-dyn) + target_link_libraries(libmamba-network-dyn PUBLIC mamba::libmamba-archive-dyn) + endif() + # Note: Cannot link common to network/solver for shared libraries due to circular dependency + # (network depends on common, solver depends on common and network) However, we need to add + # DT_NEEDED entries to libmamba-common.so so that when it's loaded at runtime, the dynamic + # linker will also load libmamba-solver.so and libmamba-network.so to resolve undefined symbols. + # We do this by setting LINK_LIBRARIES property directly after all components are built. This + # adds the libraries to the link line without creating CMake dependency edges that would trigger + # cycle detection. Note: This must be done after all component targets are created to avoid + # generator expression evaluation issues during add_library. +endif() + +# Add DT_NEEDED entries to libmamba-common-dyn after all components are built This ensures the +# dynamic linker loads solver and network libraries when common is loaded We use a post-build step +# with patchelf (on Linux) to add DT_NEEDED entries after all libraries are built, avoiding the need +# for the libraries to exist at link time and avoiding circular dependency issues +if(BUILD_SHARED) + if( + TARGET libmamba-common-dyn + AND TARGET libmamba-solver-dyn + AND TARGET libmamba-network-dyn + AND UNIX + AND NOT APPLE + ) + # Find patchelf utility + find_program(PATCHELF_EXECUTABLE patchelf) + if(PATCHELF_EXECUTABLE) + # Add post-build step to add DT_NEEDED entries using patchelf This adds the solver and + # network libraries to common's DT_NEEDED list Note: This runs after common is built, + # but solver/network might not be built yet. We'll create a separate custom target that + # runs after all libraries are built. + add_custom_target( + libmamba-common-dyn-add-dt-needed + COMMAND + ${PATCHELF_EXECUTABLE} --add-needed + $ + $ + COMMAND + ${PATCHELF_EXECUTABLE} --add-needed + $ + $ + DEPENDS libmamba-common-dyn libmamba-solver-dyn libmamba-network-dyn + COMMENT "Adding DT_NEEDED entries to libmamba-common.so" + ) + # Make the aggregated target depend on this custom target to ensure it runs + if(TARGET libmamba-dyn) + add_dependencies(libmamba-dyn libmamba-common-dyn-add-dt-needed) + endif() + endif() + endif() +endif() + +if(BUILD_STATIC) + if(CMAKE_HOST_WIN32) + libmamba_common_create_target(libmamba-common-static STATIC libmamba_common_static) + libmamba_network_create_target(libmamba-network-static STATIC libmamba_network_static) + # Link common to network after network is built (circular dependency resolution) For static + # libraries, PUBLIC is fine as CMake allows circular dependencies for static libs + if(TARGET libmamba-network-static) + target_link_libraries(libmamba-common-static PUBLIC libmamba-network-static) + endif() + libmamba_solver_create_target(libmamba-solver-static STATIC libmamba_solver_static) + libmamba_archive_create_target(libmamba-archive-static STATIC libmamba_archive_static) + # Link network to archive (network uses MultiPackageCache and PackageExtractTask) + if(TARGET libmamba-archive-static) + target_link_libraries(libmamba-network-static PUBLIC mamba::libmamba-archive-static) + endif() + # Link archive to network (archive uses download::Request and util::URL) For static + # libraries, circular dependencies are allowed + if(TARGET libmamba-network-static) + target_link_libraries(libmamba-archive-static PUBLIC mamba::libmamba-network-static) + endif() + else() + libmamba_common_create_target(libmamba-common-static STATIC libmamba-common) + libmamba_network_create_target(libmamba-network-static STATIC libmamba-network) + # Link common to network after network is built (circular dependency resolution) For static + # libraries, PUBLIC is fine as CMake allows circular dependencies for static libs + if(TARGET libmamba-network-static) + target_link_libraries(libmamba-common-static PUBLIC libmamba-network-static) + endif() + libmamba_solver_create_target(libmamba-solver-static STATIC libmamba-solver) + libmamba_archive_create_target(libmamba-archive-static STATIC libmamba-archive) + # Link network to archive (network uses MultiPackageCache and PackageExtractTask) + if(TARGET libmamba-archive-static) + target_link_libraries(libmamba-network-static PUBLIC mamba::libmamba-archive-static) + endif() + # Link archive to network (archive uses download::Request and util::URL) For static + # libraries, circular dependencies are allowed + if(TARGET libmamba-network-static) + target_link_libraries(libmamba-archive-static PUBLIC mamba::libmamba-network-static) + endif() + endif() +endif() + +# Create aggregated libmamba target for backward compatibility +# ============================================================ +# This target links all components together for backward compatibility macro(libmamba_create_target target_name linkage output_name) string(TOUPPER "${linkage}" linkage_upper) if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") message(FATAL_ERROR "Invalid library linkage: ${linkage}") endif() - # Output - # ====== - add_library(${target_name} ${linkage_upper} ${LIBMAMBA_PUBLIC_HEADERS} ${LIBMAMBA_SOURCES}) + # Determine component target names + if(${linkage_upper} STREQUAL "SHARED") + set(common_target libmamba-common-dyn) + set(solver_target libmamba-solver-dyn) + set(network_target libmamba-network-dyn) + set(archive_target libmamba-archive-dyn) + else() + set(common_target libmamba-common-static) + set(solver_target libmamba-solver-static) + set(network_target libmamba-network-static) + set(archive_target libmamba-archive-static) + endif() + + # Create a real library that aggregates all components and includes API sources API sources + # depend on all components, so they need to be in this aggregated target + add_library( + ${target_name} ${linkage_upper} ${LIBMAMBA_API_PUBLIC_HEADERS} ${LIBMAMBA_API_SOURCES} + ) + + # Link all components + target_link_libraries( + ${target_name} + PUBLIC + mamba::${common_target} + mamba::${solver_target} + mamba::${network_target} + mamba::${archive_target} + ) + + # Context uses get_root_prefix from API, so link API target to itself (get_root_prefix is + # implemented in api/configuration.cpp which is in this target) target_include_directories( ${target_name} @@ -457,9 +398,6 @@ macro(libmamba_create_target target_name linkage output_name) PRIVATE ${LIBMAMBA_SOURCE_DIR} ) - # Header only libraries are always linked the same way - target_link_libraries(${target_name} PUBLIC tl::expected nlohmann_json::nlohmann_json msgpack-c) - target_compile_features(${target_name} PUBLIC cxx_std_20) set_target_properties( ${target_name} @@ -472,193 +410,11 @@ macro(libmamba_create_target target_name linkage output_name) mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) mamba_target_set_lto(${target_name} MODE ${MAMBA_LTO}) - if(${linkage_upper} STREQUAL "STATIC") - message(" -> Statically linking against libmamba (static) dependencies") - - mamba_target_check_type(yaml-cpp::yaml-cpp STATIC_LIBRARY FATAL_ERROR) - mamba_target_check_type(reproc STATIC_LIBRARY FATAL_ERROR) - mamba_target_check_type(reproc++ STATIC_LIBRARY FATAL_ERROR) - - target_link_libraries( - ${target_name} - PUBLIC fmt::fmt-header-only yaml-cpp::yaml-cpp msgpack-c - PRIVATE - reproc - reproc++ - simdjson::simdjson_static - solv::libsolv_static - solv::libsolvext_static - solv::cpp - ) - - if(UNIX) - - set( - REQUIRED_STATIC_DEPS - libcurl.a - libssh2.a - libgssapi_krb5.a - libkrb5.a - libk5crypto.a - libkrb5support.a - libcom_err.a - libssl.a - libcrypto.a - libarchive.a - libiconv.a - libbz2.a - liblz4.a - libzstd.a - libz.a - liblzma.a - libnghttp2.a - ) - if(APPLE) - set(REQUIRED_STATIC_DEPS ${REQUIRED_STATIC_DEPS} libc++.a) - endif() - - if(UNIX AND NOT APPLE) - list(REMOVE_ITEM REQUIRED_STATIC_DEPS libiconv.a) - endif() - - set(STATIC_DEPS "") - foreach(LIB ${REQUIRED_STATIC_DEPS}) - set(TMP_LIB "${LIB}-NOTFOUND") - find_library(TMP_LIB NAMES "${LIB}") - if(NOT ${TMP_LIB} STREQUAL "TMP_LIB-NOTFOUND") - list(APPEND STATIC_DEPS "${TMP_LIB}") - else() - list(APPEND STATIC_DEPS "${LIB}-NOTFOUND") - endif() - endforeach(LIB) - - if(APPLE) - find_library(SECURITY_LIBRARY Security) - find_library(SYSTEMCONFIGURATION_LIBRARY SystemConfiguration) - find_library(COREFOUNDATION_LIBRARY CoreFoundation) - message("Found library: ${SECURITY_LIBRARY}\n${COREFOUNDATION_LIBRARY}") - list( - APPEND - STATIC_DEPS - ${COREFOUNDATION_LIBRARY} - ${SECURITY_LIBRARY} - ${SYSTEMCONFIGURATION_LIBRARY} - ) - endif() - - message(" -> Found static dependencies:") - foreach(LIB ${STATIC_DEPS}) - message(" - ${LIB}") - endforeach(LIB) - - if(APPLE) - set(MAMBA_FORCE_DYNAMIC_LIBS resolv c++abi) - target_link_options(${target_name} PRIVATE -nostdlib++) - elseif(UNIX) - set(MAMBA_FORCE_DYNAMIC_LIBS rt dl resolv) - target_link_options(${target_name} PUBLIC -static-libstdc++ -static-libgcc) - endif() - - target_link_libraries(${target_name} PUBLIC ${STATIC_DEPS} ${MAMBA_FORCE_DYNAMIC_LIBS}) - - elseif(WIN32) - - set(CMAKE_PREFIX_PATH "$ENV{VCPKG_ROOT}/installed/x64-windows-static-md/") - - # TODO AND CONTEXT: We found a link error in libarchive which lacked a link to XmlLite - # which is provided by Windows. libarchive has cmake scripts doing the necessary work to - # link that library but for some reason we couldnt identify it is not linking in this - # specific case (it was before but the version changed apparently). As a workaround we - # manually link with that required library but a better solution would be to find why - # libarchive doesnt do it itself. - set(SYSTEM_PROVIDED_LIBRARIES XmlLite.lib) # required by libarchive - set(ENABLE_WIN32_XMLLITE ON) - - # For Windows we have a vcpkg based build system right now. - find_package(LibArchive MODULE REQUIRED) - find_package(CURL CONFIG REQUIRED) - find_library(LIBLZMA_LIBRARIES lzma REQUIRED) - find_library(LZ4_LIBRARY NAMES lz4) - find_library(LZO2_LIBRARY NAMES lzo2) - find_package(zstd CONFIG REQUIRED) - find_library(BZIP2_LIBRARIES NAMES bz2) - find_library(CRYPTO_LIBRARIES NAMES libcrypto) - - find_library(LIBXML2_LIBRARY NAMES libxml2) - find_library(ICONV_LIBRARY NAMES libiconv iconv) - find_library(CHARSET_LIBRARY NAMES libcharset charset) - message("Found: ${LIBXML2_LIBRARY} ${ICONV_LIBRARY} ${CHARSET_LIBRARY}") - - target_link_libraries( - ${target_name} - PUBLIC - ${CRYPTO_LIBRARIES} - ${SYSTEM_PROVIDED_LIBRARIES} - ${LibArchive_LIBRARY} - ${LIBXML2_LIBRARY} - ${ICONV_LIBRARY} - ${CHARSET_LIBRARY} - zstd::libzstd_static - ${LZ4_LIBRARY} - ${LZO2_LIBRARY} - ${BZIP2_LIBRARIES} - ${LIBLZMA_LIBRARIES} - CURL::libcurl - ${sodium_LIBRARY_RELEASE} - ) - - add_compile_definitions(LIBARCHIVE_STATIC CURL_STATICLIB) - include_directories($ENV{CONDA_PREFIX}/Library/include/) - include_directories($ENV{VCPKG_ROOT}/installed/x64-windows-static-md/include/) - endif() - else() - message(" -> Dynamically linking against libmamba (shared) dependencies") - - mamba_target_check_type(yaml-cpp::yaml-cpp SHARED_LIBRARY WARNING) - - find_package(CURL REQUIRED) - find_package(LibArchive REQUIRED) - find_package(zstd REQUIRED) - find_package(BZip2 REQUIRED) - find_package(OpenSSL REQUIRED) - - target_link_libraries( - ${target_name} - PUBLIC - ${LIBSOLV_LIBRARIES} ${LIBSOLVEXT_LIBRARIES} yaml-cpp::yaml-cpp fmt::fmt msgpack-c - PRIVATE - ${LibArchive_LIBRARIES} - ${CURL_LIBRARIES} - ${OPENSSL_LIBRARIES} - BZip2::BZip2 - reproc - reproc++ - simdjson::simdjson - zstd::libzstd_shared - solv::libsolv - solv::libsolvext - solv::cpp - ) - # CMake 3.17 provides a LibArchive::LibArchive target that could be used instead of - # LIBRARIES/INCLUDE_DIRS - target_include_directories(${target_name} PRIVATE "${LibArchive_INCLUDE_DIRS}") - endif() - - if(WIN32) - find_path( - WINREG_INCLUDE_DIR - NAMES WinReg.hpp - PATH_SUFFIXES winreg - ) - target_include_directories(${target_name} PRIVATE ${WINREG_INCLUDE_DIR}) - endif() - if(UNIX) math(EXPR LIBMAMBA_BINARY_COMPATIBLE "${LIBMAMBA_BINARY_CURRENT} - ${LIBMAMBA_BINARY_AGE}") set_target_properties( ${target_name} PROPERTIES - # PUBLIC_HEADER "${LIBMAMBA_PUBLIC_HEADERS}" COMPILE_DEFINITIONS "LIBMAMBA_EXPORTS" PREFIX "" VERSION @@ -670,7 +426,6 @@ macro(libmamba_create_target target_name linkage output_name) set_target_properties( ${target_name} PROPERTIES - # PUBLIC_HEADER "${LIBMAMBA_PUBLIC_HEADERS}" COMPILE_DEFINITIONS "LIBMAMBA_EXPORTS" PREFIX "" VERSION ${LIBMAMBA_BINARY_VERSION} @@ -680,12 +435,6 @@ macro(libmamba_create_target target_name linkage output_name) target_compile_definitions(${target_name} PUBLIC GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) endif() - if(${linkage_upper} STREQUAL "STATIC") - find_package(Threads REQUIRED) - - target_link_libraries(${target_name} PUBLIC Threads::Threads) - endif() - list(APPEND libmamba_targets ${target_name}) add_library(mamba::${target_name} ALIAS ${target_name}) endmacro() @@ -699,7 +448,7 @@ option( ) if(BUILD_SHARED) - message(STATUS "Adding shared libmamba target") + message(STATUS "Adding shared libmamba target (aggregated)") libmamba_create_target(libmamba-dyn SHARED libmamba) if(ENABLE_MAMBA_ROOT_PREFIX_FALLBACK) # Use mamba installation prefix to set root prefix (base) @@ -708,7 +457,7 @@ if(BUILD_SHARED) endif() if(BUILD_STATIC) - message(STATUS "Adding static libmamba target") + message(STATUS "Adding static libmamba target (aggregated)") # On Windows, a static library should use a different output name to avoid the conflict with the # import library of a shared one. @@ -746,8 +495,34 @@ set( CACHE STRING "install path for libmambaConfig.cmake" ) +# Install all component targets and aggregated target Component targets must be explicitly included +# in the export set so that CMake can resolve dependencies when exporting the aggregated target +set(all_libmamba_targets ${libmamba_targets}) +if(BUILD_SHARED) + list( + APPEND + all_libmamba_targets + ${libmamba_common_targets} + ${libmamba_solver_targets} + ${libmamba_network_targets} + ${libmamba_archive_targets} + ) +endif() +if(BUILD_STATIC) + list( + APPEND + all_libmamba_targets + ${libmamba_common_targets} + ${libmamba_solver_targets} + ${libmamba_network_targets} + ${libmamba_archive_targets} + ) +endif() +# Remove duplicates to avoid CMake errors about targets being included more than once +list(REMOVE_DUPLICATES all_libmamba_targets) + install( - TARGETS ${libmamba_targets} + TARGETS ${all_libmamba_targets} EXPORT ${PROJECT_NAME}Targets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -791,6 +566,14 @@ install( ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake DESTINATION ${LIBMAMBA_CMAKECONFIG_INSTALL_DIR} ) +# Install FindLibsolv.cmake so that libmambaConfig.cmake can find it when used by consumers (e.g., +# libmambapy). The CMAKE_MODULE_PATH is set in libmambaConfig.cmake.in to include +# CMAKE_CURRENT_LIST_DIR, so FindLibsolv.cmake must be installed in the same directory. Use +# CMAKE_SOURCE_DIR to reference the root project directory where cmake/modules is located +install( + FILES ${CMAKE_SOURCE_DIR}/cmake/modules/FindLibsolv.cmake + DESTINATION ${LIBMAMBA_CMAKECONFIG_INSTALL_DIR} +) install( EXPORT ${PROJECT_NAME}Targets diff --git a/libmamba/archive/CMakeLists.txt b/libmamba/archive/CMakeLists.txt new file mode 100644 index 0000000000..73056dc998 --- /dev/null +++ b/libmamba/archive/CMakeLists.txt @@ -0,0 +1,233 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Archive component - depends on libmamba-common and libarchive + +# These variables should be set by the parent CMakeLists.txt +if(NOT DEFINED LIBMAMBA_SOURCE_DIR) + set(LIBMAMBA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src) +endif() +if(NOT DEFINED LIBMAMBA_INCLUDE_DIR) + set(LIBMAMBA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include) +endif() + +set( + LIBMAMBA_ARCHIVE_SOURCES + # Archive extraction and package handling + ${LIBMAMBA_SOURCE_DIR}/core/package_handling.cpp + ${LIBMAMBA_SOURCE_DIR}/core/package_fetcher.cpp + ${LIBMAMBA_SOURCE_DIR}/core/package_cache.cpp +) + +set( + LIBMAMBA_ARCHIVE_PUBLIC_HEADERS + # Archive extraction and package handling + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_handling.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_fetcher.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_cache.hpp +) + +# Create the archive library target +macro(libmamba_archive_create_target target_name linkage output_name) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + add_library( + ${target_name} + ${linkage_upper} ${LIBMAMBA_ARCHIVE_PUBLIC_HEADERS} ${LIBMAMBA_ARCHIVE_SOURCES} + ) + + target_include_directories( + ${target_name} + PUBLIC $ $ + PRIVATE ${LIBMAMBA_SOURCE_DIR} + ) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + mamba_target_set_lto(${target_name} MODE ${MAMBA_LTO}) + + # For shared libraries, archive uses symbols from network but can't link to it due to circular + # dependency (network also needs archive symbols). Allow undefined symbols - they'll be resolved + # when the aggregated libmamba target links all components together. + if(${linkage_upper} STREQUAL "SHARED") + if(APPLE) + target_link_options(${target_name} PRIVATE -Wl,-undefined,dynamic_lookup) + elseif(UNIX) + target_link_options(${target_name} PRIVATE -Wl,--allow-shlib-undefined) + elseif(WIN32) + # On Windows, use /FORCE:UNRESOLVED to allow unresolved symbols. The symbols will be + # resolved at runtime when the DLLs are loaded. The aggregated libmamba target will + # ensure all DLLs are available together. + target_link_options(${target_name} PRIVATE $<$:/FORCE:UNRESOLVED>) + endif() + endif() + + # Depend on common component + if(TARGET mamba::libmamba-common-dyn) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-dyn) + elseif(TARGET mamba::libmamba-common-static) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-static) + else() + message(FATAL_ERROR "libmamba-common must be built before libmamba-archive") + endif() + + # Depend on network component (package_fetcher uses download::Request, package_cache uses + # util::URL) Note: Archive is built after network, so this link will be added in main + # CMakeLists.txt + + if(${linkage_upper} STREQUAL "STATIC") + if(UNIX) + set( + REQUIRED_STATIC_DEPS + libarchive.a + libiconv.a + libbz2.a + liblz4.a + libzstd.a + libz.a + liblzma.a + ) + if(APPLE) + set(REQUIRED_STATIC_DEPS ${REQUIRED_STATIC_DEPS} libc++.a) + endif() + + if(UNIX AND NOT APPLE) + list(REMOVE_ITEM REQUIRED_STATIC_DEPS libiconv.a) + endif() + + set(STATIC_DEPS "") + foreach(LIB ${REQUIRED_STATIC_DEPS}) + set(TMP_LIB "${LIB}-NOTFOUND") + find_library(TMP_LIB NAMES "${LIB}") + if(NOT ${TMP_LIB} STREQUAL "TMP_LIB-NOTFOUND") + list(APPEND STATIC_DEPS "${TMP_LIB}") + else() + list(APPEND STATIC_DEPS "${LIB}-NOTFOUND") + endif() + endforeach(LIB) + + if(APPLE) + find_library(SECURITY_LIBRARY Security) + find_library(SYSTEMCONFIGURATION_LIBRARY SystemConfiguration) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + list( + APPEND + STATIC_DEPS + ${COREFOUNDATION_LIBRARY} + ${SECURITY_LIBRARY} + ${SYSTEMCONFIGURATION_LIBRARY} + ) + endif() + + target_link_libraries(${target_name} PUBLIC ${STATIC_DEPS}) + + if(APPLE) + set(MAMBA_FORCE_DYNAMIC_LIBS resolv c++abi) + target_link_options(${target_name} PRIVATE -nostdlib++) + elseif(UNIX) + set(MAMBA_FORCE_DYNAMIC_LIBS rt dl resolv) + target_link_options(${target_name} PUBLIC -static-libstdc++ -static-libgcc) + endif() + + target_link_libraries(${target_name} PUBLIC ${MAMBA_FORCE_DYNAMIC_LIBS}) + + # reproc is needed for package_handling.cpp + target_link_libraries(${target_name} PUBLIC reproc reproc++) + + elseif(WIN32) + set(CMAKE_PREFIX_PATH "$ENV{VCPKG_ROOT}/installed/x64-windows-static-md/") + set(SYSTEM_PROVIDED_LIBRARIES XmlLite.lib) + set(ENABLE_WIN32_XMLLITE ON) + + find_package(LibArchive MODULE REQUIRED) + find_library(LIBLZMA_LIBRARIES lzma REQUIRED) + find_library(LZ4_LIBRARY NAMES lz4) + find_library(LZO2_LIBRARY NAMES lzo2) + find_package(zstd CONFIG REQUIRED) + find_library(BZIP2_LIBRARIES NAMES bz2) + find_library(CRYPTO_LIBRARIES NAMES libcrypto) + find_library(LIBXML2_LIBRARY NAMES libxml2) + find_library(ICONV_LIBRARY NAMES libiconv iconv) + find_library(CHARSET_LIBRARY NAMES libcharset charset) + + target_link_libraries( + ${target_name} + PUBLIC + ${CRYPTO_LIBRARIES} + ${SYSTEM_PROVIDED_LIBRARIES} + ${LibArchive_LIBRARY} + ${LIBXML2_LIBRARY} + ${ICONV_LIBRARY} + ${CHARSET_LIBRARY} + zstd::libzstd_static + ${LZ4_LIBRARY} + ${LZO2_LIBRARY} + ${BZIP2_LIBRARIES} + ${LIBLZMA_LIBRARIES} + ) + + add_compile_definitions(LIBARCHIVE_STATIC) + include_directories($ENV{CONDA_PREFIX}/Library/include/) + include_directories($ENV{VCPKG_ROOT}/installed/x64-windows-static-md/include/) + endif() + else() + find_package(LibArchive REQUIRED) + find_package(zstd REQUIRED) + find_package(BZip2 REQUIRED) + find_package(OpenSSL REQUIRED) + + target_link_libraries( + ${target_name} + PUBLIC ${LibArchive_LIBRARIES} BZip2::BZip2 zstd::libzstd_shared ${OPENSSL_LIBRARIES} + ) + # LibArchive include directories must be PUBLIC so that API sources (e.g., info.cpp) can + # include + target_include_directories(${target_name} PUBLIC "${LibArchive_INCLUDE_DIRS}") + + # reproc is needed for package_handling.cpp + target_link_libraries(${target_name} PUBLIC reproc reproc++) + endif() + + if(UNIX) + math(EXPR LIBMAMBA_BINARY_COMPATIBLE "${LIBMAMBA_BINARY_CURRENT} - ${LIBMAMBA_BINARY_AGE}") + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_ARCHIVE_EXPORTS" + PREFIX "" + VERSION + "${LIBMAMBA_BINARY_COMPATIBLE}.${LIBMAMBA_BINARY_REVISION}.${LIBMAMBA_BINARY_AGE}" + SOVERSION ${LIBMAMBA_BINARY_COMPATIBLE} + OUTPUT_NAME "${output_name}" + ) + else() + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_ARCHIVE_EXPORTS" + PREFIX "" + VERSION ${LIBMAMBA_BINARY_VERSION} + SOVERSION ${LIBMAMBA_BINARY_CURRENT} + OUTPUT_NAME "${output_name}" + ) + endif() + + list(APPEND libmamba_archive_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmamba_archive_targets "") diff --git a/libmamba/common/CMakeLists.txt b/libmamba/common/CMakeLists.txt new file mode 100644 index 0000000000..bc14a2fe6f --- /dev/null +++ b/libmamba/common/CMakeLists.txt @@ -0,0 +1,385 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Common component - core utilities, specs, context No heavy dependencies (no libarchive, no +# libcurl) + +# These variables should be set by the parent CMakeLists.txt +if(NOT DEFINED LIBMAMBA_SOURCE_DIR) + set(LIBMAMBA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src) +endif() +if(NOT DEFINED LIBMAMBA_INCLUDE_DIR) + set(LIBMAMBA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include) +endif() +if(NOT DEFINED SHELL_SCRIPTS) + # Define empty if not set + set(SHELL_SCRIPTS "") +endif() + +set( + LIBMAMBA_COMMON_SOURCES + ${LIBMAMBA_SOURCE_DIR}/version.cpp + # Filesystem library + ${LIBMAMBA_SOURCE_DIR}/fs/filesystem.cpp + # C++ utility library (excluding url.cpp which needs curl) + ${LIBMAMBA_SOURCE_DIR}/util/cfile.cpp + ${LIBMAMBA_SOURCE_DIR}/util/cryptography.cpp + ${LIBMAMBA_SOURCE_DIR}/util/encoding.cpp + ${LIBMAMBA_SOURCE_DIR}/util/environment.cpp + ${LIBMAMBA_SOURCE_DIR}/util/os_linux.cpp + ${LIBMAMBA_SOURCE_DIR}/util/os_osx.cpp + ${LIBMAMBA_SOURCE_DIR}/util/os_unix.cpp + ${LIBMAMBA_SOURCE_DIR}/util/os_win.cpp + ${LIBMAMBA_SOURCE_DIR}/util/parsers.cpp + ${LIBMAMBA_SOURCE_DIR}/util/path_manip.cpp + ${LIBMAMBA_SOURCE_DIR}/util/random.cpp + ${LIBMAMBA_SOURCE_DIR}/util/string.cpp + ${LIBMAMBA_SOURCE_DIR}/util/url_manip.cpp + # Implementation of version and matching specs + ${LIBMAMBA_SOURCE_DIR}/specs/archive.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/authentication_info.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/build_number_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/channel.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/chimera_string_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/conda_url.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/glob_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/match_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/package_info.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/platform.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/regex_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/unresolved_channel.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/version_spec.cpp + ${LIBMAMBA_SOURCE_DIR}/specs/version.cpp + # Core API (low-level) - common parts + ${LIBMAMBA_SOURCE_DIR}/core/activation.cpp + ${LIBMAMBA_SOURCE_DIR}/core/channel_context.cpp + ${LIBMAMBA_SOURCE_DIR}/core/context.cpp + ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile.cpp + ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile_conda.cpp + ${LIBMAMBA_SOURCE_DIR}/core/env_lockfile_mambajs.cpp + ${LIBMAMBA_SOURCE_DIR}/core/environments_manager.cpp + ${LIBMAMBA_SOURCE_DIR}/core/error_handling.cpp + ${LIBMAMBA_SOURCE_DIR}/core/execution.cpp + ${LIBMAMBA_SOURCE_DIR}/core/fsutil.cpp + ${LIBMAMBA_SOURCE_DIR}/core/history.cpp + ${LIBMAMBA_SOURCE_DIR}/core/link.cpp + ${LIBMAMBA_SOURCE_DIR}/core/logging.cpp + ${LIBMAMBA_SOURCE_DIR}/core/menuinst.cpp + ${LIBMAMBA_SOURCE_DIR}/core/output.cpp + ${LIBMAMBA_SOURCE_DIR}/core/package_paths.cpp + ${LIBMAMBA_SOURCE_DIR}/core/pinning.cpp + ${LIBMAMBA_SOURCE_DIR}/core/prefix_data.cpp + ${LIBMAMBA_SOURCE_DIR}/core/progress_bar_impl.cpp + ${LIBMAMBA_SOURCE_DIR}/core/progress_bar.cpp + ${LIBMAMBA_SOURCE_DIR}/core/query.cpp + ${LIBMAMBA_SOURCE_DIR}/core/run.cpp + ${LIBMAMBA_SOURCE_DIR}/core/shell_init.cpp + ${LIBMAMBA_SOURCE_DIR}/core/singletons.cpp + ${LIBMAMBA_SOURCE_DIR}/core/thread_utils.cpp + ${LIBMAMBA_SOURCE_DIR}/core/timeref.cpp + ${LIBMAMBA_SOURCE_DIR}/core/transaction_context.cpp + ${LIBMAMBA_SOURCE_DIR}/core/util_os.cpp + ${LIBMAMBA_SOURCE_DIR}/core/util.cpp + ${LIBMAMBA_SOURCE_DIR}/core/virtual_packages.cpp + # Artifacts validation (no download dependency) Note: repo_checker.cpp and + # update_framework_v0_6.cpp need download, so they're in network component + ${LIBMAMBA_SOURCE_DIR}/validation/errors.cpp + ${LIBMAMBA_SOURCE_DIR}/validation/keys.cpp + ${LIBMAMBA_SOURCE_DIR}/validation/tools.cpp + ${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v1.cpp + ${LIBMAMBA_SOURCE_DIR}/validation/update_framework.cpp +) + +set( + LIBMAMBA_COMMON_PUBLIC_HEADERS + ${LIBMAMBA_INCLUDE_DIR}/mamba/version.hpp + # Filesystem library + ${LIBMAMBA_INCLUDE_DIR}/mamba/fs/filesystem.hpp + # Utility library + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/build.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cast.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cfile.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/conditional.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/cryptography.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/deprecation.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/encoding.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/environment.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_binary_tree.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_bool_expr_tree.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/flat_set.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/graph.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/heap_optional.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/iterator.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/json.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/loop_control.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_linux.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_osx.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_unix.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os_win.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/os.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/parsers.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/path_manip.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/random.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/string.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/synchronized_value.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/tuple_hash.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/type_traits.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url_manip.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/variant_cmp.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/weakening_map.hpp + # Implementation of version and matching specs + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/archive.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/authentication_info.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/build_number_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/channel.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/chimera_string_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/conda_url.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/error.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/glob_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/match_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/package_info.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/platform.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/regex_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/repo_data.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/unresolved_channel.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version_spec.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version.hpp + # Core API (low-level) + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/activation.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/channel_context.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/context.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/context_params.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/env_lockfile.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/environments_manager.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/error_handling.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/execution.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/fsutil.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/history.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/invoke.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/logging.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/logging_tools.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/menuinst.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/output.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_paths.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/palette.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/pinning.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/prefix_data.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/progress_bar.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/query.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/run.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/shell_init.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/tasksync.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/thread_utils.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/timeref.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_os.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_scope.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/util.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/virtual_packages.hpp + # Artifacts validation + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/errors.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/keys.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/tools.hpp + # Note: repo_checker.hpp and update_framework_v0_6.hpp headers stay here for API compatibility + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/repo_checker.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v0_6.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v1.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework.hpp +) + +# Shell scripts are generated in the parent CMakeLists.txt They need to be added to common component +# since shell_init.cpp, activation.cpp, and transaction_context.cpp use them The parent generates +# them in ${CMAKE_CURRENT_BINARY_DIR}/shell_scripts/ (which is libmamba/shell_scripts/) We reference +# them with the correct path +if(DEFINED SHELL_SCRIPTS AND SHELL_SCRIPTS) + # Use LIBMAMBA_BINARY_DIR if defined (set by parent), otherwise compute it + if(DEFINED LIBMAMBA_BINARY_DIR) + set(PARENT_BINARY_DIR "${LIBMAMBA_BINARY_DIR}") + else() + get_filename_component(PARENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) + get_filename_component(PARENT_BINARY_DIR "${PARENT_BINARY_DIR}" ABSOLUTE) + endif() + foreach(script IN LISTS SHELL_SCRIPTS) + # The custom command in the parent CMakeLists.txt uses OUTPUT shell_scripts/${script}.cpp + # which is relative to LIBMAMBA_BINARY_DIR. Build the absolute path to match. + set(SHELL_SCRIPT_PATH "${PARENT_BINARY_DIR}/shell_scripts/${script}.cpp") + list(APPEND LIBMAMBA_COMMON_SOURCES "${SHELL_SCRIPT_PATH}") + endforeach() +endif() + +# Create the common library target +macro(libmamba_common_create_target target_name linkage output_name) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + add_library( + ${target_name} + ${linkage_upper} ${LIBMAMBA_COMMON_PUBLIC_HEADERS} ${LIBMAMBA_COMMON_SOURCES} + ) + + # Mark shell scripts as generated files so CMake knows they'll be created + if(DEFINED SHELL_SCRIPTS AND SHELL_SCRIPTS) + # Use LIBMAMBA_BINARY_DIR if defined (set by parent), otherwise compute it + if(DEFINED LIBMAMBA_BINARY_DIR) + set(PARENT_BINARY_DIR "${LIBMAMBA_BINARY_DIR}") + else() + get_filename_component(PARENT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) + get_filename_component(PARENT_BINARY_DIR "${PARENT_BINARY_DIR}" ABSOLUTE) + endif() + foreach(script IN LISTS SHELL_SCRIPTS) + set(SHELL_SCRIPT_PATH "${PARENT_BINARY_DIR}/shell_scripts/${script}.cpp") + set_source_files_properties("${SHELL_SCRIPT_PATH}" PROPERTIES GENERATED TRUE) + endforeach() + endif() + + # Context depends on mirror_map (network) - this dependency will be added after network is built + # to resolve the circular dependency (network also depends on common) + + target_include_directories( + ${target_name} + PUBLIC $ $ + PRIVATE ${LIBMAMBA_SOURCE_DIR} + ) + + # Header only libraries + target_link_libraries(${target_name} PUBLIC tl::expected nlohmann_json::nlohmann_json msgpack-c) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + mamba_target_set_lto(${target_name} MODE ${MAMBA_LTO}) + + # For shared libraries, common uses symbols from network and solver but can't link to them due + # to circular dependencies. Allow undefined symbols - they'll be resolved when the aggregated + # libmamba target links all components together. We also need to add DT_NEEDED entries so that + # when libmamba-common.so is loaded, the dynamic linker will also load libmamba-solver.so and + # libmamba-network.so. We do this using linker flags to avoid creating circular dependencies. + if(${linkage_upper} STREQUAL "SHARED") + if(APPLE) + target_link_options(${target_name} PRIVATE -Wl,-undefined,dynamic_lookup) + elseif(UNIX) + # On Linux, we can use --allow-shlib-undefined or rely on the aggregated target to + # provide all symbols at link time + target_link_options(${target_name} PRIVATE -Wl,--allow-shlib-undefined) + # Add DT_NEEDED entries for solver and network libraries so they're loaded at runtime We + # use -Wl,--no-as-needed to ensure these libraries are added to DT_NEEDED even if + # they're not directly referenced at link time. The actual library paths will be + # resolved by the aggregated target linking all components together. Note: We can't use + # target_link_libraries here because it would create a circular dependency. Instead, + # we'll add these as linker options after the components are built. + elseif(WIN32) + # On Windows, use /FORCE:UNRESOLVED to allow unresolved symbols. The symbols will be + # resolved at runtime when the DLLs are loaded. The aggregated libmamba target will + # ensure all DLLs are available together. Note: We can't use delay loading (/DELAYLOAD) + # because the DLLs don't exist yet at link time. /FORCE:UNRESOLVED allows the DLL to be + # created with unresolved symbols, which will be resolved when the DLLs are loaded + # together. + target_link_options(${target_name} PRIVATE $<$:/FORCE:UNRESOLVED>) + endif() + endif() + + # singletons.cpp uses CURL, so we need it even in common This is unfortunate but necessary for + # the singleton initialization order + if(${linkage_upper} STREQUAL "STATIC") + mamba_target_check_type(yaml-cpp::yaml-cpp STATIC_LIBRARY FATAL_ERROR) + mamba_target_check_type(reproc STATIC_LIBRARY FATAL_ERROR) + mamba_target_check_type(reproc++ STATIC_LIBRARY FATAL_ERROR) + + target_link_libraries( + ${target_name} + PUBLIC fmt::fmt-header-only yaml-cpp::yaml-cpp msgpack-c + PRIVATE reproc reproc++ simdjson::simdjson_static + ) + + # CURL is needed for singletons.cpp (curl_global_init) + if(UNIX) + find_library(CURL_LIB_STATIC NAMES libcurl.a curl) + if(CURL_LIB_STATIC) + target_link_libraries(${target_name} PRIVATE ${CURL_LIB_STATIC}) + endif() + elseif(WIN32) + find_package(CURL CONFIG QUIET) + if(TARGET CURL::libcurl) + target_link_libraries(${target_name} PRIVATE CURL::libcurl) + add_compile_definitions(CURL_STATICLIB) + endif() + endif() + else() + mamba_target_check_type(yaml-cpp::yaml-cpp SHARED_LIBRARY WARNING) + + target_link_libraries( + ${target_name} + PUBLIC yaml-cpp::yaml-cpp fmt::fmt msgpack-c + PRIVATE reproc reproc++ simdjson::simdjson + ) + + # CURL is needed for singletons.cpp (curl_global_init) + find_package(CURL QUIET) + if(CURL_FOUND) + target_link_libraries(${target_name} PRIVATE ${CURL_LIBRARIES}) + target_include_directories(${target_name} PRIVATE ${CURL_INCLUDE_DIRS}) + endif() + + # OpenSSL is needed for validation/tools.cpp (EVP functions) + find_package(OpenSSL QUIET) + if(OpenSSL_FOUND) + target_link_libraries(${target_name} PRIVATE OpenSSL::SSL OpenSSL::Crypto) + endif() + endif() + + if(WIN32) + find_path( + WINREG_INCLUDE_DIR + NAMES WinReg.hpp + PATH_SUFFIXES winreg + ) + target_include_directories(${target_name} PRIVATE ${WINREG_INCLUDE_DIR}) + endif() + + if(UNIX) + math(EXPR LIBMAMBA_BINARY_COMPATIBLE "${LIBMAMBA_BINARY_CURRENT} - ${LIBMAMBA_BINARY_AGE}") + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_COMMON_EXPORTS" + PREFIX "" + VERSION + "${LIBMAMBA_BINARY_COMPATIBLE}.${LIBMAMBA_BINARY_REVISION}.${LIBMAMBA_BINARY_AGE}" + SOVERSION ${LIBMAMBA_BINARY_COMPATIBLE} + OUTPUT_NAME "${output_name}" + ) + else() + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_COMMON_EXPORTS" + PREFIX "" + VERSION ${LIBMAMBA_BINARY_VERSION} + SOVERSION ${LIBMAMBA_BINARY_CURRENT} + OUTPUT_NAME "${output_name}" + ) + target_compile_definitions(${target_name} PUBLIC GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) + endif() + + if(${linkage_upper} STREQUAL "STATIC") + find_package(Threads REQUIRED) + target_link_libraries(${target_name} PUBLIC Threads::Threads) + endif() + + list(APPEND libmamba_common_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmamba_common_targets "") diff --git a/libmamba/libmambaConfig.cmake.in b/libmamba/libmambaConfig.cmake.in index 8c82060d6a..9e24d0ac1a 100644 --- a/libmamba/libmambaConfig.cmake.in +++ b/libmamba/libmambaConfig.cmake.in @@ -27,6 +27,11 @@ find_dependency(tl-expected) find_dependency(nlohmann_json) find_dependency(yaml-cpp) find_dependency(reproc++) +# Dependencies required by exported targets +find_dependency(Libsolv MODULE) +find_dependency(BZip2) +find_dependency(simdjson) +find_dependency(zstd) if(NOT (TARGET libmamba-dyn OR TARGET libmamba-static)) include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") diff --git a/libmamba/network/CMakeLists.txt b/libmamba/network/CMakeLists.txt new file mode 100644 index 0000000000..3cb7138c3a --- /dev/null +++ b/libmamba/network/CMakeLists.txt @@ -0,0 +1,202 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Network component - depends on libmamba-common and libcurl + +# These variables should be set by the parent CMakeLists.txt +if(NOT DEFINED LIBMAMBA_SOURCE_DIR) + set(LIBMAMBA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src) +endif() +if(NOT DEFINED LIBMAMBA_INCLUDE_DIR) + set(LIBMAMBA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include) +endif() + +set( + LIBMAMBA_NETWORK_SOURCES + # URL utilities that need curl + ${LIBMAMBA_SOURCE_DIR}/util/url.cpp + # Downloaders and mirrors + ${LIBMAMBA_SOURCE_DIR}/download/compression.cpp + ${LIBMAMBA_SOURCE_DIR}/download/curl.cpp + ${LIBMAMBA_SOURCE_DIR}/download/downloader.cpp + ${LIBMAMBA_SOURCE_DIR}/download/mirror_impl.cpp + ${LIBMAMBA_SOURCE_DIR}/download/mirror_map.cpp + ${LIBMAMBA_SOURCE_DIR}/download/mirror.cpp + ${LIBMAMBA_SOURCE_DIR}/download/request.cpp + # Core files that use download + ${LIBMAMBA_SOURCE_DIR}/core/download_progress_bar.cpp + ${LIBMAMBA_SOURCE_DIR}/core/subdir_index.cpp + ${LIBMAMBA_SOURCE_DIR}/core/shard_types.cpp + # Validation that needs download + ${LIBMAMBA_SOURCE_DIR}/validation/repo_checker.cpp + ${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v0_6.cpp +) + +set( + LIBMAMBA_NETWORK_PUBLIC_HEADERS + # URL utilities + ${LIBMAMBA_INCLUDE_DIR}/mamba/util/url.hpp + # Downloaders and mirrors + ${LIBMAMBA_INCLUDE_DIR}/mamba/download/downloader.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/download/mirror_map.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/download/mirror.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/download/request.hpp + # Core files + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/download_progress_bar.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/subdir_index.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/shard_types.hpp + # Note: singletons.hpp is in common but singletons.cpp needs CURL For now, singletons.cpp is in + # common but it will need CURL dependency Validation + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/repo_checker.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v0_6.hpp +) + +# Create the network library target +macro(libmamba_network_create_target target_name linkage output_name) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + add_library( + ${target_name} + ${linkage_upper} ${LIBMAMBA_NETWORK_PUBLIC_HEADERS} ${LIBMAMBA_NETWORK_SOURCES} + ) + + target_include_directories( + ${target_name} + PUBLIC $ $ + PRIVATE ${LIBMAMBA_SOURCE_DIR} + ) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + mamba_target_set_lto(${target_name} MODE ${MAMBA_LTO}) + + # Depend on common component + if(TARGET mamba::libmamba-common-dyn) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-dyn) + elseif(TARGET mamba::libmamba-common-static) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-static) + else() + message(FATAL_ERROR "libmamba-common must be built before libmamba-network") + endif() + # Note: Archive dependency will be added after archive is built (in main CMakeLists.txt) + + if(${linkage_upper} STREQUAL "STATIC") + if(UNIX) + set( + REQUIRED_STATIC_DEPS + libcurl.a + libssh2.a + libgssapi_krb5.a + libkrb5.a + libk5crypto.a + libkrb5support.a + libcom_err.a + libssl.a + libcrypto.a + libnghttp2.a + ) + if(APPLE) + set(REQUIRED_STATIC_DEPS ${REQUIRED_STATIC_DEPS} libc++.a) + endif() + + if(UNIX AND NOT APPLE) + list(REMOVE_ITEM REQUIRED_STATIC_DEPS libiconv.a) + endif() + + set(STATIC_DEPS "") + foreach(LIB ${REQUIRED_STATIC_DEPS}) + set(TMP_LIB "${LIB}-NOTFOUND") + find_library(TMP_LIB NAMES "${LIB}") + if(NOT ${TMP_LIB} STREQUAL "TMP_LIB-NOTFOUND") + list(APPEND STATIC_DEPS "${TMP_LIB}") + else() + list(APPEND STATIC_DEPS "${LIB}-NOTFOUND") + endif() + endforeach(LIB) + + if(APPLE) + find_library(SECURITY_LIBRARY Security) + find_library(SYSTEMCONFIGURATION_LIBRARY SystemConfiguration) + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + list( + APPEND + STATIC_DEPS + ${COREFOUNDATION_LIBRARY} + ${SECURITY_LIBRARY} + ${SYSTEMCONFIGURATION_LIBRARY} + ) + endif() + + target_link_libraries(${target_name} PUBLIC ${STATIC_DEPS}) + + if(APPLE) + set(MAMBA_FORCE_DYNAMIC_LIBS resolv c++abi) + target_link_options(${target_name} PRIVATE -nostdlib++) + elseif(UNIX) + set(MAMBA_FORCE_DYNAMIC_LIBS rt dl resolv) + target_link_options(${target_name} PUBLIC -static-libstdc++ -static-libgcc) + endif() + + target_link_libraries(${target_name} PUBLIC ${MAMBA_FORCE_DYNAMIC_LIBS}) + + elseif(WIN32) + set(CMAKE_PREFIX_PATH "$ENV{VCPKG_ROOT}/installed/x64-windows-static-md/") + find_package(CURL CONFIG REQUIRED) + target_link_libraries(${target_name} PUBLIC CURL::libcurl) + add_compile_definitions(CURL_STATICLIB) + endif() + else() + find_package(CURL REQUIRED) + find_package(OpenSSL REQUIRED) + find_package(zstd REQUIRED) + find_package(BZip2 REQUIRED) + + target_link_libraries( + ${target_name} + PUBLIC ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} BZip2::BZip2 zstd::libzstd_shared + ) + endif() + + if(UNIX) + math(EXPR LIBMAMBA_BINARY_COMPATIBLE "${LIBMAMBA_BINARY_CURRENT} - ${LIBMAMBA_BINARY_AGE}") + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_NETWORK_EXPORTS" + PREFIX "" + VERSION + "${LIBMAMBA_BINARY_COMPATIBLE}.${LIBMAMBA_BINARY_REVISION}.${LIBMAMBA_BINARY_AGE}" + SOVERSION ${LIBMAMBA_BINARY_COMPATIBLE} + OUTPUT_NAME "${output_name}" + ) + else() + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_NETWORK_EXPORTS" + PREFIX "" + VERSION ${LIBMAMBA_BINARY_VERSION} + SOVERSION ${LIBMAMBA_BINARY_CURRENT} + OUTPUT_NAME "${output_name}" + ) + endif() + + list(APPEND libmamba_network_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmamba_network_targets "") diff --git a/libmamba/solver/CMakeLists.txt b/libmamba/solver/CMakeLists.txt new file mode 100644 index 0000000000..7a91821bfa --- /dev/null +++ b/libmamba/solver/CMakeLists.txt @@ -0,0 +1,140 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Solver component - depends on libmamba-common and libsolv + +# These variables should be set by the parent CMakeLists.txt +if(NOT DEFINED LIBMAMBA_SOURCE_DIR) + set(LIBMAMBA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../src) +endif() +if(NOT DEFINED LIBMAMBA_INCLUDE_DIR) + set(LIBMAMBA_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include) +endif() + +set( + LIBMAMBA_SOLVER_SOURCES + # Solver generic interface + ${LIBMAMBA_SOURCE_DIR}/solver/helpers.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/problems_graph.cpp + # Solver libsolv implementation + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/database.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/helpers.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/matcher.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/parameters.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/repo_info.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/solver.cpp + ${LIBMAMBA_SOURCE_DIR}/solver/libsolv/unsolvable.cpp + # Core files that depend on solver + ${LIBMAMBA_SOURCE_DIR}/core/package_database_loader.cpp +) + +set( + LIBMAMBA_SOLVER_PUBLIC_HEADERS + # Solver generic interface + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/problems_graph.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/request.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/solution.hpp + # Solver libsolv implementation + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/database.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/parameters.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/repo_info.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/solver.hpp + ${LIBMAMBA_INCLUDE_DIR}/mamba/solver/libsolv/unsolvable.hpp + # Core files that depend on solver + ${LIBMAMBA_INCLUDE_DIR}/mamba/core/package_database_loader.hpp +) + +# Create the solver library target +macro(libmamba_solver_create_target target_name linkage output_name) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + add_library( + ${target_name} + ${linkage_upper} ${LIBMAMBA_SOLVER_PUBLIC_HEADERS} ${LIBMAMBA_SOLVER_SOURCES} + ) + + target_include_directories( + ${target_name} + PUBLIC $ $ + PRIVATE ${LIBMAMBA_SOURCE_DIR} + ) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + mamba_target_set_lto(${target_name} MODE ${MAMBA_LTO}) + + # Depend on common component + if(TARGET mamba::libmamba-common-dyn) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-dyn) + elseif(TARGET mamba::libmamba-common-static) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-common-static) + else() + message(FATAL_ERROR "libmamba-common must be built before libmamba-solver") + endif() + + # Depend on network component (package_database_loader needs subdir_index) + if(TARGET mamba::libmamba-network-dyn) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-network-dyn) + elseif(TARGET mamba::libmamba-network-static) + target_link_libraries(${target_name} PUBLIC mamba::libmamba-network-static) + else() + message(FATAL_ERROR "libmamba-network must be built before libmamba-solver") + endif() + + # solv-cpp is added in main CMakeLists.txt, just link to it + + if(${linkage_upper} STREQUAL "STATIC") + target_link_libraries( + ${target_name} PUBLIC solv::libsolv_static solv::libsolvext_static solv::cpp + ) + # simdjson is needed for helpers.cpp (JSON parsing) + target_link_libraries(${target_name} PUBLIC simdjson::simdjson_static) + else() + target_link_libraries(${target_name} PUBLIC solv::libsolv solv::libsolvext solv::cpp) + # simdjson is needed for helpers.cpp (JSON parsing) + target_link_libraries(${target_name} PUBLIC simdjson::simdjson) + endif() + + if(UNIX) + math(EXPR LIBMAMBA_BINARY_COMPATIBLE "${LIBMAMBA_BINARY_CURRENT} - ${LIBMAMBA_BINARY_AGE}") + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_SOLVER_EXPORTS" + PREFIX "" + VERSION + "${LIBMAMBA_BINARY_COMPATIBLE}.${LIBMAMBA_BINARY_REVISION}.${LIBMAMBA_BINARY_AGE}" + SOVERSION ${LIBMAMBA_BINARY_COMPATIBLE} + OUTPUT_NAME "${output_name}" + ) + else() + set_target_properties( + ${target_name} + PROPERTIES + COMPILE_DEFINITIONS "LIBMAMBA_SOLVER_EXPORTS" + PREFIX "" + VERSION ${LIBMAMBA_BINARY_VERSION} + SOVERSION ${LIBMAMBA_BINARY_CURRENT} + OUTPUT_NAME "${output_name}" + ) + endif() + + list(APPEND libmamba_solver_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmamba_solver_targets "") diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 9a6510de84..42b2798c51 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -603,172 +603,13 @@ namespace mamba } } - auto validate_existing_root_prefix(const fs::u8path& candidate) -> expected_t - { - auto prefix = fs::u8path(util::expand_home(candidate.string())); - - if (prefix.empty()) - { - return make_unexpected("Empty root prefix.", mamba_error_code::incorrect_usage); - } - - // TODO: consider the conjunction (i.e. &&-chaining) of the following conditions. - auto qualifies_as_root_prefix = (fs::exists(prefix / "pkgs") || fs::exists(prefix / "conda-meta") || fs::exists(prefix / "envs")); - if (!qualifies_as_root_prefix) - { - return make_unexpected( - fmt::format( - R"(Path "{}" is not an existing root prefix.)" - R"( Please set explicitly `MAMBA_ROOT_PREFIX` to "{}" to skip this error.)", - prefix.string(), - prefix.string() - ), - mamba_error_code::incorrect_usage - ); - } - - return { fs::weakly_canonical(std::move(prefix)) }; - } - - auto validate_root_prefix(const fs::u8path& candidate) -> expected_t - { - auto prefix = fs::u8path(util::expand_home(candidate.string())); - - if (prefix.empty()) - { - return make_unexpected("Empty root prefix.", mamba_error_code::incorrect_usage); - } - - if (fs::exists(prefix)) - { - if (fs::is_directory(prefix)) - { - if (auto maybe_prefix = validate_existing_root_prefix(prefix); - maybe_prefix.has_value()) - { - return maybe_prefix; - } - - return make_unexpected( - fmt::format( - R"(Could not use default root_prefix "{}":)" - R"( Directory exists, is not empty and not a conda prefix.)" - R"( Please set explicitly `MAMBA_ROOT_PREFIX` to "{}" to skip this error.)", - prefix.string(), - prefix.string() - ), - mamba_error_code::incorrect_usage - ); - } - return make_unexpected( - fmt::format( - R"(Could not use default root_prefix "{}": Not a directory.)", - prefix.string() - ), - mamba_error_code::incorrect_usage - ); - } - - return { fs::weakly_canonical(std::move(prefix)) }; - } - - auto get_root_prefix() -> fs::u8path - { - fs::u8path root_prefix = util::get_env("MAMBA_ROOT_PREFIX").value_or(""); - - if (!root_prefix.empty()) - { - LOG_TRACE << "Using root prefix set in `MAMBA_ROOT_PREFIX`: " << root_prefix; - return root_prefix; - } - - root_prefix = util::get_env("MAMBA_DEFAULT_ROOT_PREFIX").value_or(""); - - if (!root_prefix.empty()) - { - LOG_WARNING << unindent(R"( - 'MAMBA_DEFAULT_ROOT_PREFIX' is meant for testing purpose. - Consider using 'MAMBA_ROOT_PREFIX' instead)"); - LOG_TRACE << "Using root prefix set in `MAMBA_DEFAULT_ROOT_PREFIX`: " << root_prefix; - return root_prefix; - } - - // Find the location of libmamba - const fs::u8path libmamba_path = get_libmamba_path(); - - // Find the supposed environment prefix of libmamba. - // `libmamba` is installed at: - // - `${PREFIX}/lib/libmamba${SHLIB_EXT}` on Unix - // - `${PREFIX}/Library/bin/libmamba$.dll` on Windows - const fs::u8path libmamba_env_prefix = fs::weakly_canonical( - util::on_win ? libmamba_path.parent_path().parent_path().parent_path() - : libmamba_path.parent_path().parent_path() - ); - - // If `libmamba` is installed in another environment than `base`, then the - // root prefix is likely the grand-parent directory (i.e. - // `$ROOT_PREFIX/envs/libmamba_env_prefix`). - const fs::u8path inferred_root_prefix = fs::weakly_canonical( - libmamba_env_prefix.parent_path().parent_path() - ); - - if (auto maybe_prefix = validate_existing_root_prefix(inferred_root_prefix); - maybe_prefix.has_value()) - { - LOG_TRACE << "Inferring and using the root prefix from `libmamba`'s current environment' as: " - << maybe_prefix.value(); - return maybe_prefix.value(); - } - - // Otherwise `libmamba` might be directly installed in the root prefix. - if (auto maybe_prefix = validate_existing_root_prefix(libmamba_env_prefix); - maybe_prefix.has_value()) - { - LOG_TRACE << "Using `libmamba`'s current environment as the root prefix: " - << maybe_prefix.value(); - return maybe_prefix.value(); - } - -#ifdef MAMBA_USE_INSTALL_PREFIX_AS_BASE - // libmamba case: set the root prefix as libmamba's installation path as a last resort. - LOG_TRACE << "Using libmamba's installation path as the root prefix: " - << libmamba_env_prefix; - return libmamba_env_prefix; -#else - // micromamba case - // In 1.0, only micromamba was using this location. - const fs::u8path default_root_prefix_v1 = fs::u8path(util::user_home_dir()) - / "micromamba"; - - // In 2.0, we change the default location. - // We unconditionally name the subfolder "mamba" for compatibility between ``mamba`` - // and ``micromamba``, as well as consistency with ``MAMBA_`` environment variables. - const fs::u8path default_root_prefix_v2 = fs::u8path(util::user_data_dir()) / "mamba"; - - auto result = validate_existing_root_prefix(default_root_prefix_v1) - .or_else([&default_root_prefix_v2](const auto& /* error */) - { return validate_root_prefix(default_root_prefix_v2); }); - if (result) - { - root_prefix = std::move(result).value(); - } - else - { - throw std::move(result).value(); - } - - LOG_TRACE << "Using default root prefix for micromamba: " << root_prefix; -#endif - return root_prefix; - } - void root_prefix_hook(Configuration& config, fs::u8path& prefix) { auto& env_name = config.at("env_name"); if (prefix.empty()) { - prefix = get_root_prefix(); + prefix = detail::get_root_prefix(); if (env_name.configured()) { diff --git a/libmamba/src/core/util_os.cpp b/libmamba/src/core/util_os.cpp index 474dc2afdf..582f792161 100644 --- a/libmamba/src/core/util_os.cpp +++ b/libmamba/src/core/util_os.cpp @@ -34,11 +34,15 @@ #include #include "mamba/core/error_handling.hpp" +#include "mamba/core/fsutil.hpp" +#include "mamba/core/logging.hpp" #include "mamba/core/output.hpp" +#include "mamba/core/util.hpp" #include "mamba/core/util_os.hpp" #include "mamba/util/build.hpp" #include "mamba/util/environment.hpp" #include "mamba/util/os_win.hpp" +#include "mamba/util/path_manip.hpp" #include "mamba/util/string.hpp" #ifdef _WIN32 @@ -532,4 +536,176 @@ namespace mamba throw std::runtime_error(std::string("Could not codesign executable: ") + ec.message()); } } + + namespace + { + auto validate_existing_root_prefix(const fs::u8path& candidate) -> expected_t + { + auto prefix = fs::u8path(mamba::util::expand_home(candidate.string())); + + if (prefix.empty()) + { + return make_unexpected("Empty root prefix.", mamba_error_code::incorrect_usage); + } + + // TODO: consider the conjunction (i.e. &&-chaining) of the following conditions. + auto qualifies_as_root_prefix = (fs::exists(prefix / "pkgs") || fs::exists(prefix / "conda-meta") || fs::exists(prefix / "envs")); + if (!qualifies_as_root_prefix) + { + return make_unexpected( + fmt::format( + R"(Path "{}" is not an existing root prefix.)" + R"( Please set explicitly `MAMBA_ROOT_PREFIX` to "{}" to skip this error.)", + prefix.string(), + prefix.string() + ), + mamba_error_code::incorrect_usage + ); + } + + return expected_t(fs::weakly_canonical(std::move(prefix))); + } + + auto validate_root_prefix(const fs::u8path& candidate) -> expected_t + { + auto prefix = fs::u8path(mamba::util::expand_home(candidate.string())); + + if (prefix.empty()) + { + return make_unexpected("Empty root prefix.", mamba_error_code::incorrect_usage); + } + + if (fs::exists(prefix)) + { + if (fs::is_directory(prefix)) + { + if (auto maybe_prefix = validate_existing_root_prefix(prefix); + maybe_prefix.has_value()) + { + return maybe_prefix; + } + + return make_unexpected( + fmt::format( + R"(Could not use default root_prefix "{}":)" + R"( Directory exists, is not empty and not a conda prefix.)" + R"( Please set explicitly `MAMBA_ROOT_PREFIX` to "{}" to skip this error.)", + prefix.string(), + prefix.string() + ), + mamba_error_code::incorrect_usage + ); + } + return make_unexpected( + fmt::format( + R"(Could not use default root_prefix "{}": Not a directory.)", + prefix.string() + ), + mamba_error_code::incorrect_usage + ); + } + + return expected_t(fs::weakly_canonical(std::move(prefix))); + } + + // get_root_prefix implementation - needs to be in anonymous namespace to access validate_* + // functions + auto get_root_prefix_impl() -> fs::u8path + { + fs::u8path root_prefix = util::get_env("MAMBA_ROOT_PREFIX").value_or(""); + + if (!root_prefix.empty()) + { + LOG_TRACE << "Using root prefix set in `MAMBA_ROOT_PREFIX`: " << root_prefix; + return root_prefix; + } + + root_prefix = util::get_env("MAMBA_DEFAULT_ROOT_PREFIX").value_or(""); + + if (!root_prefix.empty()) + { + LOG_WARNING << unindent(R"( + 'MAMBA_DEFAULT_ROOT_PREFIX' is meant for testing purpose. + Consider using 'MAMBA_ROOT_PREFIX' instead)"); + LOG_TRACE << "Using root prefix set in `MAMBA_DEFAULT_ROOT_PREFIX`: " << root_prefix; + return root_prefix; + } + + // Find the location of libmamba + const fs::u8path libmamba_path = get_libmamba_path(); + + // Find the supposed environment prefix of libmamba. + // `libmamba` is installed at: + // - `${PREFIX}/lib/libmamba${SHLIB_EXT}` on Unix + // - `${PREFIX}/Library/bin/libmamba$.dll` on Windows + const fs::u8path libmamba_env_prefix = fs::weakly_canonical( + util::on_win ? libmamba_path.parent_path().parent_path().parent_path() + : libmamba_path.parent_path().parent_path() + ); + + // If `libmamba` is installed in another environment than `base`, then the + // root prefix is likely the grand-parent directory (i.e. + // `$ROOT_PREFIX/envs/libmamba_env_prefix`). + const fs::u8path inferred_root_prefix = fs::weakly_canonical( + libmamba_env_prefix.parent_path().parent_path() + ); + + if (auto maybe_prefix = validate_existing_root_prefix(inferred_root_prefix); + maybe_prefix.has_value()) + { + LOG_TRACE << "Inferring and using the root prefix from `libmamba`'s current environment' as: " + << maybe_prefix.value(); + return maybe_prefix.value(); + } + + // Otherwise `libmamba` might be directly installed in the root prefix. + if (auto maybe_prefix = validate_existing_root_prefix(libmamba_env_prefix); + maybe_prefix.has_value()) + { + LOG_TRACE << "Using `libmamba`'s current environment as the root prefix: " + << maybe_prefix.value(); + return maybe_prefix.value(); + } + +#ifdef MAMBA_USE_INSTALL_PREFIX_AS_BASE + // libmamba case: set the root prefix as libmamba's installation path as a last resort. + LOG_TRACE << "Using libmamba's installation path as the root prefix: " + << libmamba_env_prefix; + return libmamba_env_prefix; +#else + // micromamba case + // In 1.0, only micromamba was using this location. + const fs::u8path default_root_prefix_v1 = fs::u8path(util::user_home_dir()) + / "micromamba"; + + // In 2.0, we change the default location. + // We unconditionally name the subfolder "mamba" for compatibility between ``mamba`` + // and ``micromamba``, as well as consistency with ``MAMBA_`` environment variables. + const fs::u8path default_root_prefix_v2 = fs::u8path(util::user_data_dir()) / "mamba"; + + auto result = validate_existing_root_prefix(default_root_prefix_v1) + .or_else([&default_root_prefix_v2](const auto& /* error */) + { return validate_root_prefix(default_root_prefix_v2); }); + if (result) + { + root_prefix = std::move(result).value(); + } + else + { + throw std::move(result).value(); + } + + LOG_TRACE << "Using default root prefix for micromamba: " << root_prefix; +#endif + return root_prefix; + } + } // namespace + + namespace detail + { + auto get_root_prefix() -> fs::u8path + { + return get_root_prefix_impl(); + } + } // namespace detail } diff --git a/libmamba/tests/CMakeLists.txt b/libmamba/tests/CMakeLists.txt index 93fc413e43..046a375f69 100644 --- a/libmamba/tests/CMakeLists.txt +++ b/libmamba/tests/CMakeLists.txt @@ -229,6 +229,20 @@ mamba_target_add_compile_warnings(test_libmamba_logging WARNING_AS_ERROR ${MAMBA # ################################################################################################## +# Ensure DT_NEEDED entries are added to libmamba-common.so before tests run This is needed on Linux +# where libmamba-common.so needs to load libmamba-solver.so and libmamba-network.so at runtime +if( + BUILD_SHARED + AND UNIX + AND NOT APPLE +) + if(TARGET libmamba-common-dyn-add-dt-needed) + add_dependencies(testing_libmamba_lock libmamba-common-dyn-add-dt-needed) + add_dependencies(test_libmamba libmamba-common-dyn-add-dt-needed) + add_dependencies(test_libmamba_logging libmamba-common-dyn-add-dt-needed) + endif() +endif() + add_custom_target( test COMMAND test_libmamba_logging diff --git a/libmambapy/CMakeLists.txt b/libmambapy/CMakeLists.txt index 4e5896ade8..799711ff8e 100644 --- a/libmambapy/CMakeLists.txt +++ b/libmambapy/CMakeLists.txt @@ -9,11 +9,9 @@ include("../cmake/CompilerWarnings.cmake") project(libmambapy) -if(NOT TARGET mamba::libmamba) +# Find libmamba components +if(NOT TARGET mamba::libmamba-common-dyn AND NOT TARGET mamba::libmamba-common-static) find_package(libmamba CONFIG REQUIRED) - set(libmamba_target mamba::libmamba-dyn) -else() - set(libmamba_target mamba::libmamba) endif() if(NOT TARGET mamba::libmamba-dyn-spdlog) @@ -24,6 +22,75 @@ find_package(Python COMPONENTS Interpreter Development.Module) find_package(pybind11 REQUIRED) find_package(msgpack-c REQUIRED) +# Set variables for component CMakeLists.txt files +set(LIBMAMBAPY_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bindings) +set(LIBMAMBAPY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +# Build component bindings +# ======================== +# Build order: common → solver (mirrors libmamba component order) + +# Include component CMakeLists.txt files +add_subdirectory(common) +add_subdirectory(solver) + +# Determine linkage type from libmamba components +if(TARGET mamba::libmamba-common-dyn) + set(BUILD_SHARED_BINDINGS ON) + set(BUILD_STATIC_BINDINGS OFF) +elseif(TARGET mamba::libmamba-common-static) + set(BUILD_SHARED_BINDINGS OFF) + set(BUILD_STATIC_BINDINGS ON) +else() + message(FATAL_ERROR "Neither libmamba-common-dyn nor libmamba-common-static found") +endif() + +# Create component binding targets +if(BUILD_SHARED_BINDINGS) + libmambapy_common_create_target(libmambapy-common SHARED) + libmambapy_solver_create_target(libmambapy-solver SHARED) +endif() + +if(BUILD_STATIC_BINDINGS) + libmambapy_common_create_target(libmambapy-common-static STATIC) + libmambapy_solver_create_target(libmambapy-solver-static STATIC) +endif() + +# Create aggregated bindings module for backward compatibility +# ============================================================ +# This module includes legacy.cpp and re-exports component modules + +# Determine which libmamba target to use for aggregated module +if(BUILD_SHARED_BINDINGS) + if(TARGET mamba::libmamba-dyn) + set(libmamba_target mamba::libmamba-dyn) + else() + # Fall back to component targets + set( + libmamba_target + mamba::libmamba-common-dyn + mamba::libmamba-solver-dyn + mamba::libmamba-network-dyn + mamba::libmamba-archive-dyn + ) + endif() +else() + if(TARGET mamba::libmamba-static) + set(libmamba_target mamba::libmamba-static) + else() + # Fall back to component targets + set( + libmamba_target + mamba::libmamba-common-static + mamba::libmamba-solver-static + mamba::libmamba-network-static + mamba::libmamba-archive-static + ) + endif() +endif() + +# Create aggregated bindings module for backward compatibility This module includes all bindings and +# maintains the same API as before pybind11_add_module( bindings bindings/longpath.manifest @@ -31,12 +98,14 @@ pybind11_add_module( bindings/bindings.cpp # All bindings used to live in a global module bindings/legacy.cpp - # Submodules + # Component bindings (included for backward compatibility) Note: Component modules are separate + # optional modules bindings/utils.cpp bindings/specs.cpp bindings/solver.cpp bindings/solver_libsolv.cpp ) + # TODO: remove when `SubdirData::cache_path()` is removed if( CMAKE_CXX_COMPILER_ID STREQUAL "Clang" @@ -56,6 +125,11 @@ mamba_target_add_compile_warnings(bindings WARNING_AS_ERROR ${MAMBA_WARNING_AS_E target_link_libraries( bindings PRIVATE pybind11::pybind11 ${libmamba_target} mamba::libmamba-dyn-spdlog msgpack-c ) +# Note: Component modules (libmambapy-common, libmambapy-solver) are separate Python extension +# modules and cannot be linked to the aggregated bindings module. They are optional and can be +# imported independently. The aggregated bindings module includes all source files for backward +# compatibility. + target_compile_features(bindings PUBLIC cxx_std_20) set_target_properties( bindings @@ -66,15 +140,32 @@ set_target_properties( ) # Installation +# ============ if(SKBUILD) + # Install all component modules and aggregated module install(TARGETS bindings DESTINATION libmambapy) + if(BUILD_SHARED_BINDINGS) + if(TARGET libmambapy-common) + install(TARGETS libmambapy-common DESTINATION libmambapy) + endif() + if(TARGET libmambapy-solver) + install(TARGETS libmambapy-solver DESTINATION libmambapy) + endif() + else() + if(TARGET libmambapy-common-static) + install(TARGETS libmambapy-common-static DESTINATION libmambapy) + endif() + if(TARGET libmambapy-solver-static) + install(TARGETS libmambapy-solver-static DESTINATION libmambapy) + endif() + endif() else() # WARNING: this default should probably not be used for installation but only for local # development and testing. Proper installation should be controlled externally by a Python # packager tool - # Build bindings in a self-contain libmambapy/ folder inside the build tree + # Build bindings in a self-contained libmambapy/ folder inside the build tree set_target_properties( bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libmambapy" ) @@ -95,16 +186,89 @@ else() ) add_dependencies(bindings libmambapy_copy_files) + # Also copy component module directories (if they exist) These are included in the main + # copy_directory command above, so no separate commands needed + + # Set output directories for component modules to be in the same location as bindings + if(BUILD_SHARED_BINDINGS) + if(TARGET libmambapy-common) + set_target_properties( + libmambapy-common + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libmambapy" + ) + add_dependencies(libmambapy-common libmambapy_copy_files) + endif() + if(TARGET libmambapy-solver) + set_target_properties( + libmambapy-solver + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libmambapy" + ) + add_dependencies(libmambapy-solver libmambapy_copy_files) + endif() + else() + if(TARGET libmambapy-common-static) + set_target_properties( + libmambapy-common-static + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libmambapy" + ) + add_dependencies(libmambapy-common-static libmambapy_copy_files) + endif() + if(TARGET libmambapy-solver-static) + set_target_properties( + libmambapy-solver-static + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/libmambapy" + ) + add_dependencies(libmambapy-solver-static libmambapy_copy_files) + endif() + endif() + set( MAMBA_INSTALL_PYTHON_EXT_LIBDIR "lib" CACHE PATH "Installation directory for Python extension" ) + # Install all targets install( TARGETS bindings EXCLUDE_FROM_ALL COMPONENT Mamba_Python_Extension DESTINATION ${MAMBA_INSTALL_PYTHON_EXT_LIBDIR} ) + + if(BUILD_SHARED_BINDINGS) + if(TARGET libmambapy-common) + install( + TARGETS libmambapy-common + EXCLUDE_FROM_ALL + COMPONENT Mamba_Python_Extension + DESTINATION ${MAMBA_INSTALL_PYTHON_EXT_LIBDIR} + ) + endif() + if(TARGET libmambapy-solver) + install( + TARGETS libmambapy-solver + EXCLUDE_FROM_ALL + COMPONENT Mamba_Python_Extension + DESTINATION ${MAMBA_INSTALL_PYTHON_EXT_LIBDIR} + ) + endif() + else() + if(TARGET libmambapy-common-static) + install( + TARGETS libmambapy-common-static + EXCLUDE_FROM_ALL + COMPONENT Mamba_Python_Extension + DESTINATION ${MAMBA_INSTALL_PYTHON_EXT_LIBDIR} + ) + endif() + if(TARGET libmambapy-solver-static) + install( + TARGETS libmambapy-solver-static + EXCLUDE_FROM_ALL + COMPONENT Mamba_Python_Extension + DESTINATION ${MAMBA_INSTALL_PYTHON_EXT_LIBDIR} + ) + endif() + endif() endif() diff --git a/libmambapy/common/CMakeLists.txt b/libmambapy/common/CMakeLists.txt new file mode 100644 index 0000000000..3e37acffe1 --- /dev/null +++ b/libmambapy/common/CMakeLists.txt @@ -0,0 +1,78 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Common component bindings - specs and utils These variables should be set by the parent +# CMakeLists.txt +if(NOT DEFINED LIBMAMBAPY_SOURCE_DIR) + set(LIBMAMBAPY_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../bindings) +endif() +if(NOT DEFINED LIBMAMBAPY_BINARY_DIR) + set(LIBMAMBAPY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() + +# Capture the directory containing this CMakeLists.txt file Use CMAKE_CURRENT_LIST_DIR which is set +# when this file is processed +set( + LIBMAMBAPY_COMMON_DIR + "${CMAKE_CURRENT_LIST_DIR}" + CACHE INTERNAL "Directory containing common CMakeLists.txt" +) + +# Create the common bindings target +macro(libmambapy_common_create_target target_name linkage) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + # Determine libmamba component target names + if(${linkage_upper} STREQUAL "SHARED") + set(common_target libmamba-common-dyn) + else() + set(common_target libmamba-common-static) + endif() + + # Create pybind11 module for common bindings This creates a separate Python extension module + # (libmambapy_common) that can be imported independently. The aggregated bindings module will + # also include these bindings for backward compatibility. + pybind11_add_module( + ${target_name} + ${LIBMAMBAPY_COMMON_DIR}/bindings_common.cpp + ${LIBMAMBAPY_SOURCE_DIR}/specs.cpp + ${LIBMAMBAPY_SOURCE_DIR}/utils.cpp + ) + + target_include_directories( + ${target_name} PRIVATE ${LIBMAMBAPY_SOURCE_DIR} ${LIBMAMBAPY_COMMON_DIR}/.. + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + + # Link to libmamba-common component + if(TARGET mamba::${common_target}) + target_link_libraries(${target_name} PRIVATE mamba::${common_target}) + else() + message(FATAL_ERROR "libmamba-common must be built before libmambapy-common") + endif() + + target_link_libraries(${target_name} PRIVATE pybind11::pybind11) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + # Output directory will be set by parent CMakeLists.txt + + list(APPEND libmambapy_common_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmambapy_common_targets "") diff --git a/libmambapy/common/bindings_common.cpp b/libmambapy/common/bindings_common.cpp new file mode 100644 index 0000000000..5377a93860 --- /dev/null +++ b/libmambapy/common/bindings_common.cpp @@ -0,0 +1,14 @@ +// Copyright (c) 2019, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include "../bindings/bindings.hpp" + +PYBIND11_MODULE(libmambapy_common, m) +{ + // Create submodules matching the aggregated module structure + mambapy::bind_submodule_utils(m.def_submodule("utils")); + mambapy::bind_submodule_specs(m.def_submodule("specs")); +} diff --git a/libmambapy/solver/CMakeLists.txt b/libmambapy/solver/CMakeLists.txt new file mode 100644 index 0000000000..5fa177cca1 --- /dev/null +++ b/libmambapy/solver/CMakeLists.txt @@ -0,0 +1,84 @@ +# Copyright (c) 2019, QuantStack and Mamba Contributors +# +# Distributed under the terms of the BSD 3-Clause License. +# +# The full license is in the file LICENSE, distributed with this software. + +# Solver component bindings - solver and solver_libsolv These variables should be set by the parent +# CMakeLists.txt +if(NOT DEFINED LIBMAMBAPY_SOURCE_DIR) + set(LIBMAMBAPY_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../bindings) +endif() +if(NOT DEFINED LIBMAMBAPY_BINARY_DIR) + set(LIBMAMBAPY_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() + +# Capture the directory containing this CMakeLists.txt file Use CMAKE_CURRENT_LIST_DIR which is set +# when this file is processed +set( + LIBMAMBAPY_SOLVER_DIR + "${CMAKE_CURRENT_LIST_DIR}" + CACHE INTERNAL "Directory containing solver CMakeLists.txt" +) + +# Create the solver bindings target +macro(libmambapy_solver_create_target target_name linkage) + string(TOUPPER "${linkage}" linkage_upper) + if(NOT ${linkage_upper} MATCHES "^(SHARED|STATIC)$") + message(FATAL_ERROR "Invalid library linkage: ${linkage}") + endif() + + # Determine libmamba component target names + if(${linkage_upper} STREQUAL "SHARED") + set(common_target libmamba-common-dyn) + set(solver_target libmamba-solver-dyn) + else() + set(common_target libmamba-common-static) + set(solver_target libmamba-solver-static) + endif() + + # Create pybind11 module for solver bindings This creates a separate Python extension module + # (libmambapy_solver) that can be imported independently. The aggregated bindings module will + # also include these bindings for backward compatibility. + pybind11_add_module( + ${target_name} + ${LIBMAMBAPY_SOLVER_DIR}/bindings_solver.cpp + ${LIBMAMBAPY_SOURCE_DIR}/solver.cpp + ${LIBMAMBAPY_SOURCE_DIR}/solver_libsolv.cpp + ) + + target_include_directories( + ${target_name} PRIVATE ${LIBMAMBAPY_SOURCE_DIR} ${LIBMAMBAPY_SOLVER_DIR}/.. + ) + + mamba_target_add_compile_warnings(${target_name} WARNING_AS_ERROR ${MAMBA_WARNING_AS_ERROR}) + + # Link to libmamba components (solver depends on common) + if(TARGET mamba::${common_target} AND TARGET mamba::${solver_target}) + target_link_libraries( + ${target_name} PRIVATE mamba::${common_target} mamba::${solver_target} + ) + else() + message( + FATAL_ERROR "libmamba-common and libmamba-solver must be built before libmambapy-solver" + ) + endif() + + target_link_libraries(${target_name} PRIVATE pybind11::pybind11) + + target_compile_features(${target_name} PUBLIC cxx_std_20) + set_target_properties( + ${target_name} + PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + + # Output directory will be set by parent CMakeLists.txt + + list(APPEND libmambapy_solver_targets ${target_name}) + add_library(mamba::${target_name} ALIAS ${target_name}) +endmacro() + +set(libmambapy_solver_targets "") diff --git a/libmambapy/solver/bindings_solver.cpp b/libmambapy/solver/bindings_solver.cpp new file mode 100644 index 0000000000..c003ca3e9d --- /dev/null +++ b/libmambapy/solver/bindings_solver.cpp @@ -0,0 +1,15 @@ +// Copyright (c) 2019, QuantStack and Mamba Contributors +// +// Distributed under the terms of the BSD 3-Clause License. +// +// The full license is in the file LICENSE, distributed with this software. + +#include "../bindings/bindings.hpp" + +PYBIND11_MODULE(libmambapy_solver, m) +{ + // Create submodules matching the aggregated module structure + auto solver_submodule = m.def_submodule("solver"); + mambapy::bind_submodule_solver(solver_submodule); + mambapy::bind_submodule_solver_libsolv(solver_submodule.def_submodule("libsolv")); +} diff --git a/libmambapy/src/libmambapy/__init__.py b/libmambapy/src/libmambapy/__init__.py index e780c64b3d..3f076e215c 100644 --- a/libmambapy/src/libmambapy/__init__.py +++ b/libmambapy/src/libmambapy/__init__.py @@ -1,7 +1,26 @@ # Import all submodules so that one can use them directly with `import libmambapy` -import libmambapy.utils +# Try to import from component modules first, fall back to aggregated bindings module +try: + # Import from component modules if available (separate extension modules) + import libmambapy_common # noqa: F401 + import libmambapy_solver # noqa: F401 + + # Re-export component submodules + from libmambapy_common import utils, specs # noqa: F401 + from libmambapy_solver.solver import * # noqa: F403 + + # Import libsolv submodule + try: + import libmambapy_solver.solver.libsolv # noqa: F401 + except ImportError: + pass +except ImportError: + # Fall back to aggregated bindings module for backward compatibility + import libmambapy.bindings # noqa: F401 + from libmambapy.bindings import utils, specs # noqa: F401 + from libmambapy.bindings.solver import * # noqa: F403 + import libmambapy.version -import libmambapy.specs import libmambapy.solver # Legacy which used to combine everything diff --git a/libmambapy/src/libmambapy/common/__init__.py b/libmambapy/src/libmambapy/common/__init__.py new file mode 100644 index 0000000000..4248b6881d --- /dev/null +++ b/libmambapy/src/libmambapy/common/__init__.py @@ -0,0 +1,8 @@ +# Import component submodules +# Try component module first, fall back to aggregated bindings +try: + from libmambapy_common import utils, specs # noqa: F401 +except ImportError: + from libmambapy.bindings import utils, specs # noqa: F401 + +__all__ = ["utils", "specs"] diff --git a/libmambapy/src/libmambapy/solver/__init__.py b/libmambapy/src/libmambapy/solver/__init__.py index 519c9af385..413dd1ec46 100644 --- a/libmambapy/src/libmambapy/solver/__init__.py +++ b/libmambapy/src/libmambapy/solver/__init__.py @@ -1,2 +1,16 @@ -from libmambapy.bindings.solver import * # noqa: F403 -import libmambapy.solver.libsolv # noqa: F401 +# Import solver submodules +# Try component module first, fall back to aggregated bindings +try: + from libmambapy_solver.solver import * # noqa: F403 + + try: + import libmambapy_solver.solver.libsolv # noqa: F401 + except ImportError: + pass +except ImportError: + from libmambapy.bindings.solver import * # noqa: F403 + + try: + import libmambapy.solver.libsolv # noqa: F401 + except ImportError: + pass diff --git a/libmambapy/src/libmambapy/solver/libsolv.py b/libmambapy/src/libmambapy/solver/libsolv.py index 8dc155f4dd..28040a962c 100644 --- a/libmambapy/src/libmambapy/solver/libsolv.py +++ b/libmambapy/src/libmambapy/solver/libsolv.py @@ -1,2 +1,6 @@ # This file exists on its own rather than in `__init__.py` to make `import libmambapy.solver.libsolv` work. -from libmambapy.bindings.solver.libsolv import * # noqa: F403 +# Try component module first, fall back to aggregated bindings module +try: + from libmambapy_solver.solver.libsolv import * # noqa: F403 +except ImportError: + from libmambapy.bindings.solver.libsolv import * # noqa: F403 diff --git a/libmambapy/src/libmambapy/specs.py b/libmambapy/src/libmambapy/specs.py index b7c25d844a..394f01ec8e 100644 --- a/libmambapy/src/libmambapy/specs.py +++ b/libmambapy/src/libmambapy/specs.py @@ -1,2 +1,6 @@ # This file exists on its own rather than in `__init__.py` to make `import libmambapy.specs` work. -from libmambapy.bindings.specs import * # noqa: F403 +# Try component module first, fall back to aggregated bindings module +try: + from libmambapy_common.specs import * # noqa: F403 +except ImportError: + from libmambapy.bindings.specs import * # noqa: F403 diff --git a/libmambapy/src/libmambapy/utils.py b/libmambapy/src/libmambapy/utils.py index febe6dfedc..09907a63c7 100644 --- a/libmambapy/src/libmambapy/utils.py +++ b/libmambapy/src/libmambapy/utils.py @@ -1,2 +1,6 @@ # This file exists on its own rather than in `__init__.py` to make `import libmambapy.utils` work. -from libmambapy.bindings.utils import * # noqa: F403 +# Try component module first, fall back to aggregated bindings module +try: + from libmambapy_common.utils import * # noqa: F403 +except ImportError: + from libmambapy.bindings.utils import * # noqa: F403