diff --git a/conanfile.py b/conanfile.py index abdebc30..76b50460 100644 --- a/conanfile.py +++ b/conanfile.py @@ -28,17 +28,11 @@ from conan.errors import ConanInvalidConfiguration from conan.tools.build import can_run, default_cppstd, valid_min_cppstd from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout from conan.tools.files import copy, load, rmdir +from conan.tools.scm import Version required_conan_version = ">=2.0.0" -def loose_lt_semver(v1, v2): - lv1 = [int(v) for v in v1.split(".")] - lv2 = [int(v) for v in v2.split(".")] - min_length = min(len(lv1), len(lv2)) - return lv1[:min_length] < lv2[:min_length] - - class MPUnitsConan(ConanFile): name = "mp-units" homepage = "https://github.com/mpusz/mp-units" @@ -61,22 +55,21 @@ class MPUnitsConan(ConanFile): url = "https://github.com/mpusz/mp-units" settings = "os", "arch", "compiler", "build_type" options = { - "cxx_modules": ["auto", True, False], - "std_format": ["auto", True, False], - "string_view_ret": ["auto", True, False], - "no_crtp": ["auto", True, False], + "cxx_modules": [True, False], + "std_format": [True, False], + "string_view_ret": [True, False], + "no_crtp": [True, False], "contracts": ["none", "gsl-lite", "ms-gsl"], "freestanding": [True, False], } default_options = { - "cxx_modules": "auto", - "std_format": "auto", - "string_view_ret": "auto", - "no_crtp": "auto", + # "cxx_modules" default set in config_options() + # "std_format" default set in config_options() + # "string_view_ret" default set in config_options() + # "no_crtp" default set in config_options() "contracts": "gsl-lite", - "freestanding": "False", + "freestanding": False, } - tool_requires = "cmake/[>=3.29]" implements = "auto_header_only" exports = "LICENSE.md" exports_sources = ( @@ -94,7 +87,7 @@ class MPUnitsConan(ConanFile): def _feature_compatibility(self): return { "minimum_support": { - "std": "20", + "min_cppstd": "20", "compiler": { "gcc": "12", "clang": "16", @@ -103,7 +96,7 @@ class MPUnitsConan(ConanFile): }, }, "std_format": { - "std": "20", + "min_cppstd": "20", "compiler": { "gcc": "13", "clang": "17", @@ -112,15 +105,15 @@ class MPUnitsConan(ConanFile): }, }, "cxx_modules": { - "std": "20", - "compiler": {"gcc": "14", "clang": "17", "apple-clang": "", "msvc": ""}, + "min_cppstd": "20", + "compiler": {"gcc": "", "clang": "17", "apple-clang": "", "msvc": ""}, }, "static_constexpr_vars_in_constexpr_func": { - "std": "23", + "min_cppstd": "23", "compiler": {"gcc": "13", "clang": "17", "apple-clang": "", "msvc": ""}, }, "explicit_this": { - "std": "23", + "min_cppstd": "23", "compiler": { "gcc": "14", "clang": "18", @@ -139,15 +132,30 @@ class MPUnitsConan(ConanFile): "no_crtp": "explicit_this", } + def _set_default_option(self, name): + compiler = self.settings.compiler + feature_name = self._option_feature_map[name] + feature = self._feature_compatibility[feature_name] + min_version = feature["compiler"].get(str(compiler)) + setattr( + self.options, + name, + bool( + min_version + and Version(compiler.version) >= min_version + and valid_min_cppstd(self, feature["min_cppstd"]) + ), + ) + def _check_feature_supported(self, name, feature_name=name): compiler = self.settings.compiler cppstd = compiler.get_safe("cppstd", default_cppstd(self)) feature = self._feature_compatibility[feature_name] # check C++ version - if not valid_min_cppstd(self, feature["std"]): + if not valid_min_cppstd(self, feature["min_cppstd"]): raise ConanInvalidConfiguration( - f"'{name}' requires at least cppstd={feature['std']} ({cppstd} in use)", + f"'{name}' requires at least cppstd={feature['min_cppstd']} ({cppstd} in use)", ) # check compiler version @@ -157,40 +165,17 @@ class MPUnitsConan(ConanFile): return if min_version == "": raise ConanInvalidConfiguration( - f"'{name}' is not yet supported by any known {compiler} compiler" + f"'{name}' is not yet supported by any known {compiler} version" ) - if loose_lt_semver(str(compiler.version), min_version): + if Version(compiler.version) < min_version: raise ConanInvalidConfiguration( f"'{name}' requires at least {compiler}-{min_version} ({compiler}-{compiler.version} in use)" ) - def _is_feature_enabled(self, name): - compiler = self.settings.compiler - opt = self.options.get_safe(name) - feature_name = self._option_feature_map[name] - feature = self._feature_compatibility[feature_name] - min_version = feature["compiler"].get(str(compiler)) - return bool( - opt == True - or ( - opt == "auto" - and min_version - and not loose_lt_semver(str(compiler.version), min_version) - ) - ) - @property def _build_all(self): return bool(self.conf.get("user.mp-units.build:all", default=False)) - @property - def _build_cxx_modules(self): - return self._is_feature_enabled("cxx_modules") - - @property - def _use_fmtlib(self): - return not self._is_feature_enabled("std_format") - @property def _skip_la(self): return bool(self.conf.get("user.mp-units.build:skip_la", default=False)) @@ -206,16 +191,25 @@ class MPUnitsConan(ConanFile): ).group(1) self.version = version.strip() + def config_options(self): + for key in self._option_feature_map.keys(): + self._set_default_option(key) + + def configure(self): + if self.options.freestanding: + self.options.rm_safe("std_format") + def requirements(self): if not self.options.freestanding: if self.options.contracts == "gsl-lite": self.requires("gsl-lite/0.41.0") elif self.options.contracts == "ms-gsl": self.requires("ms-gsl/4.0.0") - if self._use_fmtlib: + if not self.options.std_format: self.requires("fmt/10.2.1") def build_requirements(self): + self.tool_requires("cmake/[>=3.29 <4]") if self._build_all: if not self.options.freestanding: self.test_requires("catch2/3.5.1") @@ -244,21 +238,17 @@ class MPUnitsConan(ConanFile): tc.cache_variables["MP_UNITS_DEV_BUILD_LA"] = not self._skip_la if self._run_clang_tidy: tc.cache_variables["MP_UNITS_DEV_CLANG_TIDY"] = True - if self._build_cxx_modules: + if self.options.cxx_modules: tc.cache_variables["CMAKE_CXX_SCAN_FOR_MODULES"] = True - tc.cache_variables["MP_UNITS_BUILD_CXX_MODULES"] = str( - self.options.cxx_modules - ).upper() + tc.cache_variables["MP_UNITS_BUILD_CXX_MODULES"] = True if self.options.freestanding: tc.cache_variables["MP_UNITS_API_FREESTANDING"] = True else: - tc.cache_variables["MP_UNITS_API_STD_FORMAT"] = str( - self.options.std_format - ).upper() - tc.cache_variables["MP_UNITS_API_STRING_VIEW_RET"] = str( + tc.cache_variables["MP_UNITS_API_STD_FORMAT"] = self.options.std_format + tc.cache_variables["MP_UNITS_API_STRING_VIEW_RET"] = ( self.options.string_view_ret - ).upper() - tc.cache_variables["MP_UNITS_API_NO_CRTP"] = str(self.options.no_crtp).upper() + ) + tc.cache_variables["MP_UNITS_API_NO_CRTP"] = self.options.no_crtp tc.cache_variables["MP_UNITS_API_CONTRACTS"] = str( self.options.contracts ).upper() @@ -269,7 +259,8 @@ class MPUnitsConan(ConanFile): def build(self): cmake = CMake(self) cmake.configure(build_script_folder=None if self._build_all else "src") - cmake.build() + if self._build_all or self.options.cxx_modules: + cmake.build() if self._build_all: cmake.build(target="all_verify_interface_header_sets") if can_run(self): @@ -300,20 +291,17 @@ class MPUnitsConan(ConanFile): self.cpp_info.components["core"].defines.append("MP_UNITS_API_CONTRACTS=3") # handle API options - if self.options.string_view_ret != "auto": - self.cpp_info.components["core"].defines.append( - "MP_UNITS_API_STRING_VIEW_RET=" - + str(int(self.options.string_view_ret == True)) - ) - if self.options.no_crtp != "auto": - self.cpp_info.components["core"].defines.append( - "MP_UNITS_API_NO_CRTP=" + str(int(self.options.no_crtp == True)) - ) - if self.options.std_format != "auto": - self.cpp_info.components["core"].defines.append( - "MP_UNITS_API_STD_FORMAT=" + str(int(self.options.std_format == True)) - ) - if self._use_fmtlib: + self.cpp_info.components["core"].defines.append( + "MP_UNITS_API_STRING_VIEW_RET=" + + str(int(self.options.string_view_ret == True)) + ) + self.cpp_info.components["core"].defines.append( + "MP_UNITS_API_NO_CRTP=" + str(int(self.options.no_crtp == True)) + ) + self.cpp_info.components["core"].defines.append( + "MP_UNITS_API_STD_FORMAT=" + str(int(self.options.std_format == True)) + ) + if not self.options.std_format: self.cpp_info.components["core"].requires.append("fmt::fmt") # handle hosted configuration diff --git a/docs/getting_started/installation_and_usage.md b/docs/getting_started/installation_and_usage.md index b34f9f3b..d852e80f 100644 --- a/docs/getting_started/installation_and_usage.md +++ b/docs/getting_started/installation_and_usage.md @@ -231,7 +231,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`cxx_modules`](#cxx_modules){ #cxx_modules } -: [:octicons-tag-24: 2.2.0][conan C++ modules support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) +: [:octicons-tag-24: 2.2.0][conan C++ modules support] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) Configures CMake to add C++ modules to the list of default targets. @@ -239,7 +239,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`std_format`](#std_format){ #std_format } -: [:octicons-tag-24: 2.2.0][conan std::format support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) +: [:octicons-tag-24: 2.2.0][conan std::format support] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) Enables the usage of [`std::format`](https://en.cppreference.com/w/cpp/utility/format/format) and associated facilities for text formatting. If it is not supported, then @@ -249,7 +249,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`string_view_ret`](#string_view_ret){ #string_view_ret } -: [:octicons-tag-24: 2.2.0][conan returning string_view] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) +: [:octicons-tag-24: 2.2.0][conan returning string_view] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) Enables returning `std::string_view` from the [`unit_symbol()`](../users_guide/framework_basics/text_output.md#unit_symbol) @@ -261,7 +261,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`no_crtp`](#no_crtp){ #no_crtp } -: [:octicons-tag-24: 2.2.0][conan no crtp support] · :octicons-milestone-24: `auto`/`True`/`False` (Default: `auto`) +: [:octicons-tag-24: 2.2.0][conan no crtp support] · :octicons-milestone-24: `True`/`False` (Default: automatically determined from settings) Removes the need for the usage of the CRTP idiom in the [`quantity_spec` definitions](../users_guide/framework_basics/systems_of_quantities.md#defining-quantities). @@ -342,7 +342,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`MP_UNITS_API_STD_FORMAT`](#MP_UNITS_API_STD_FORMAT){ #MP_UNITS_API_STD_FORMAT } -: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `AUTO`/`TRUE`/`FALSE` (Default: `AUTO`) +: [:octicons-tag-24: 2.2.0][use fmtlib support] · :octicons-milestone-24: `ON`/`OFF` (Default: automatically determined) Enables the usage of [`std::format`](https://en.cppreference.com/w/cpp/utility/format/format) and associated facilities for text formatting. If it is not supported, then @@ -352,7 +352,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`MP_UNITS_API_STRING_VIEW_RET`](#MP_UNITS_API_STRING_VIEW_RET){ #MP_UNITS_API_STRING_VIEW_RET } -: [:octicons-tag-24: 2.2.0][cmake returning string_view] · :octicons-milestone-24: `AUTO`/`TRUE`/`FALSE` (Default: `AUTO`) +: [:octicons-tag-24: 2.2.0][cmake returning string_view] · :octicons-milestone-24: `ON`/`OFF` (Default: automatically determined) Enables returning `std::string_view` from the [`unit_symbol()`](../users_guide/framework_basics/text_output.md#unit_symbol) @@ -364,7 +364,7 @@ tools.build:compiler_executables={"c": "gcc-12", "cpp": "g++-12"} [`MP_UNITS_API_NO_CRTP`](#MP_UNITS_API_NO_CRTP){ #MP_UNITS_API_NO_CRTP } -: [:octicons-tag-24: 2.2.0][cmake no crtp support] · :octicons-milestone-24: `AUTO`/`TRUE`/`FALSE` (Default: `AUTO`) +: [:octicons-tag-24: 2.2.0][cmake no crtp support] · :octicons-milestone-24: `ON`/`OFF` (Default: automatically determined) Removes the need for the usage of the CRTP idiom in the [`quantity_spec` definitions](../users_guide/framework_basics/systems_of_quantities.md#defining-quantities). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d51ddfda..b8e4c56f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -37,48 +37,16 @@ check_libcxx_in_use(${projectPrefix}LIBCXX) # project build options option(${projectPrefix}BUILD_AS_SYSTEM_HEADERS "Exports library as system headers" OFF) -message(STATUS "${projectPrefix}BUILD_AS_SYSTEM_HEADERS: ${${projectPrefix}BUILD_AS_SYSTEM_HEADERS}") - option(${projectPrefix}BUILD_CXX_MODULES "Add C++ modules to the list of default targets" OFF) -message(STATUS "${projectPrefix}BUILD_CXX_MODULES: ${${projectPrefix}BUILD_CXX_MODULES}") -option(${projectPrefix}API_FREESTANDING "Builds only freestanding part of the library" OFF) -message(STATUS "${projectPrefix}API_FREESTANDING: ${${projectPrefix}API_FREESTANDING}") +message(STATUS "${projectPrefix}BUILD_AS_SYSTEM_HEADERS: ${${projectPrefix}BUILD_AS_SYSTEM_HEADERS}") +message(STATUS "${projectPrefix}BUILD_CXX_MODULES: ${${projectPrefix}BUILD_CXX_MODULES}") if(${projectPrefix}BUILD_AS_SYSTEM_HEADERS) set(${projectPrefix}_AS_SYSTEM SYSTEM) endif() -# project API settings -function(cache_var_values name) - set_property(CACHE ${projectPrefix}${name} PROPERTY STRINGS ${ARGN}) - if(NOT ${projectPrefix}${name} IN_LIST ARGN) - message(FATAL_ERROR - "Invalid value '${${projectPrefix}${name}}' provided for a cache variable ${projectPrefix}${name} (${ARGN} allowed)" - ) - endif() - message(STATUS "${projectPrefix}${name}: ${${projectPrefix}${name}}") -endfunction() - -set(${projectPrefix}API_STD_FORMAT AUTO CACHE STRING "Enable `std::format` support") -cache_var_values(API_STD_FORMAT AUTO TRUE FALSE) - -set(${projectPrefix}API_STRING_VIEW_RET AUTO CACHE STRING - "Enable returning `std::string_view` from `constexpr` functions" -) -cache_var_values(API_STRING_VIEW_RET AUTO TRUE FALSE) - -set(${projectPrefix}API_NO_CRTP AUTO CACHE STRING "Enable class definitions without CRTP idiom") -cache_var_values(API_NO_CRTP AUTO TRUE FALSE) - -set(${projectPrefix}API_CONTRACTS GSL-LITE CACHE STRING "Enable contract checking") -cache_var_values(API_CONTRACTS NONE GSL-LITE MS-GSL) - -if(${projectPrefix}API_FREESTANDING AND NOT ${projectPrefix}API_CONTRACTS STREQUAL "NONE") - message(FATAL_ERROR "'${projectPrefix}API_CONTRACTS' should be set to 'NONE' for a freestanding build") -endif() - -# C++ features +# check for C++ features check_cxx_feature_supported(__cpp_lib_format ${projectPrefix}LIB_FORMAT_SUPPORTED) check_cxx_feature_supported("__cpp_constexpr >= 202211L" ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) check_cxx_feature_supported(__cpp_explicit_this_parameter ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) @@ -91,21 +59,59 @@ if(NOT ${projectPrefix}LIB_FORMAT_SUPPORTED AND ${projectPrefix}LIBCXX ) message(STATUS "Clang 17+ with libc++ detected, overriding `std::format` support") - set(${projectPrefix}LIB_FORMAT_SUPPORTED 1) + set(${projectPrefix}LIB_FORMAT_SUPPORTED ON) endif() -# validate settings -if(NOT ${projectPrefix}API_FREESTANDING AND "${projectPrefix}API_STD_FORMAT" AND NOT - ${projectPrefix}LIB_FORMAT_SUPPORTED +# clang++-18 supports explicit `this` parameter +# https://github.com/llvm/llvm-project/issues/82780 +if(NOT ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED + AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "18") + message(STATUS "Clang 18+ detected, overriding `no CRTP` support") + set(${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED ON) +endif() + +# project API settings +option(${projectPrefix}API_STD_FORMAT "Enable `std::format` support" ${${projectPrefix}LIB_FORMAT_SUPPORTED}) +option(${projectPrefix}API_STRING_VIEW_RET "Enable returning `std::string_view` from `constexpr` functions" + ${${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS} ) +option(${projectPrefix}API_NO_CRTP "Enable class definitions without CRTP idiom" + ${${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED} +) +option(${projectPrefix}API_FREESTANDING "Builds only freestanding part of the library" OFF) +set(${projectPrefix}API_CONTRACTS GSL-LITE CACHE STRING "Enable contract checking") + +message(STATUS "${projectPrefix}API_STD_FORMAT: ${${projectPrefix}API_STD_FORMAT}") +message(STATUS "${projectPrefix}API_STRING_VIEW_RET: ${${projectPrefix}API_STRING_VIEW_RET}") +message(STATUS "${projectPrefix}API_NO_CRTP: ${${projectPrefix}API_NO_CRTP}") +message(STATUS "${projectPrefix}API_FREESTANDING: ${${projectPrefix}API_FREESTANDING}") + +function(cache_var_values name) + set_property(CACHE ${projectPrefix}${name} PROPERTY STRINGS ${ARGN}) + if(NOT ${projectPrefix}${name} IN_LIST ARGN) + message(FATAL_ERROR + "Invalid value '${${projectPrefix}${name}}' provided for a cache variable ${projectPrefix}${name} (${ARGN} allowed)" + ) + endif() + message(STATUS "${projectPrefix}${name}: ${${projectPrefix}${name}}") +endfunction() +cache_var_values(API_CONTRACTS NONE GSL-LITE MS-GSL) + +# validate options +if(${projectPrefix}API_FREESTANDING AND NOT ${projectPrefix}API_CONTRACTS STREQUAL "NONE") + message(FATAL_ERROR "'${projectPrefix}API_CONTRACTS' should be set to 'NONE' for a freestanding build") +endif() + +if(NOT ${projectPrefix}API_FREESTANDING AND ${projectPrefix}API_STD_FORMAT AND NOT ${projectPrefix}LIB_FORMAT_SUPPORTED) message(FATAL_ERROR "`std::format` enabled but not supported") endif() -if("${projectPrefix}API_STRING_VIEW_RET" AND NOT ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) +if(${projectPrefix}API_STRING_VIEW_RET AND NOT ${projectPrefix}STATIC_CONSTEXPR_VARS_IN_CONSTEXPR_FUNCTIONS) message(FATAL_ERROR "`std::string_view` returns enabled but not supported") endif() -if("${projectPrefix}API_NO_CRTP" AND NOT ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) +if(${projectPrefix}API_NO_CRTP AND NOT ${projectPrefix}EXPLICIT_THIS_PARAMETER_SUPPORTED) message(FATAL_ERROR "`NO_CRTP` mode enabled but explicit `this` parameter is not supported") endif()