Skip to content

Commit

Permalink
Feature/toolchain cmake simplify (#7738)
Browse files Browse the repository at this point in the history
* moving logic from cmake to python for cmake toolchain

* fix test

* working

* removed print

* fix some tests

* fixing linux tests

* fixing apple tests

* fix MacOS test

* reuse existing function

* variables and preprocessor_definitions

* fixing test in Linux

* fixing test

* remove add_compile_definitions
  • Loading branch information
memsharded authored Sep 28, 2020
1 parent c42738d commit a77b978
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 100 deletions.
233 changes: 147 additions & 86 deletions conans/client/toolchain/cmake.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# coding=utf-8
import os
import textwrap
from collections import OrderedDict, defaultdict

from jinja2 import Template

from conans.client.build.cmake_flags import get_generator, get_generator_platform, \
CMakeDefinitionsBuilder, get_toolset, is_multi_configuration
from conans.client.generators.cmake_common import CMakeCommonMacros
from conans.client.build.cmake_flags import get_generator, get_generator_platform, get_toolset, \
is_multi_configuration
from conans.client.build.compiler_flags import architecture_flag
from conans.client.tools import cpu_count
from conans.errors import ConanException
from conans.util.files import save


Expand All @@ -16,16 +17,16 @@
# https://github.com/microsoft/vcpkg/tree/master/scripts/buildsystems


class Definitions(OrderedDict):
class Variables(OrderedDict):
_configuration_types = None # Needed for py27 to avoid infinite recursion

def __init__(self):
super(Definitions, self).__init__()
super(Variables, self).__init__()
self._configuration_types = {}

def __getattribute__(self, config):
try:
return super(Definitions, self).__getattribute__(config)
return super(Variables, self).__getattribute__(config)
except AttributeError:
return self._configuration_types.setdefault(config, dict())

Expand All @@ -43,8 +44,8 @@ class CMakeToolchain(object):
filename = "conan_toolchain.cmake"

_template_toolchain = textwrap.dedent("""
# Conan generated toolchain file
cmake_minimum_required(VERSION 3.0) # Needed for targets
# Conan automatically generated toolchain file
# DO NOT EDIT MANUALLY, it will be overwritten
# Avoid including toolchain file several times (bad if appending to variables like
# CMAKE_CXX_FLAGS. See https://github.com/android/ndk/issues/323
Expand All @@ -53,31 +54,18 @@ class CMakeToolchain(object):
endif()
set(CONAN_TOOLCHAIN_INCLUDED TRUE)
########### Utility macros and functions ###########
{{ cmake_macros_and_functions }}
########### End of Utility macros and functions ###########
# Configure
{% if generator_platform %}
{%- if generator_platform %}
set(CMAKE_GENERATOR_PLATFORM "{{ generator_platform }}" CACHE STRING "" FORCE)
{% endif %}
{% if toolset %}
{%- endif %}
{%- if toolset %}
set(CMAKE_GENERATOR_TOOLSET "{{ toolset }}" CACHE STRING "" FORCE)
{% endif%}
{%- endif %}
# build_type (Release, Debug, etc) is only defined for single-config generators
{% if build_type %}
{%- if build_type %}
set(CMAKE_BUILD_TYPE "{{ build_type }}" CACHE STRING "Choose the type of build." FORCE)
{% endif %}
# -- - CMake.flags --> CMakeDefinitionsBuilder::get_definitions
{%- for it, value in definitions.items() %}
{%- if it.startswith('CONAN_') %}
set({{ it }} "{{ value }}")
{%- else %}
set({{ it }} "{{ value }}" CACHE STRING "Value assigned from the Conan toolchain" FORCE)
{%- endif %}
{%- endfor %}
get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE )
if(_CMAKE_IN_TRY_COMPILE)
Expand Down Expand Up @@ -106,46 +94,106 @@ class CMakeToolchain(object):
# We are going to adjust automagically many things as requested by Conan
# these are the things done by 'conan_basic_setup()'
# To support the cmake_find_package generators:
{% if cmake_module_path %}
set(CMAKE_EXPORT_NO_PACKAGE_REGISTRY ON)
# To support the cmake_find_package generators
{% if cmake_module_path -%}
set(CMAKE_MODULE_PATH {{ cmake_module_path }} ${CMAKE_MODULE_PATH})
{% endif%}
{% if cmake_prefix_path %}
{%- endif %}
{% if cmake_prefix_path -%}
set(CMAKE_PREFIX_PATH {{ cmake_prefix_path }} ${CMAKE_PREFIX_PATH})
{% endif%}
{%- endif %}
# shared libs
{% if shared_libs -%}
message(STATUS "Conan toolchain: Setting BUILD_SHARED_LIBS= {{ shared_libs }}")
set(BUILD_SHARED_LIBS {{ shared_libs }})
{%- endif %}
{% if fpic %}
# fPIC
{% if fpic -%}
message(STATUS "Conan toolchain: Setting CMAKE_POSITION_INDEPENDENT_CODE=ON (options.fPIC)")
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
{% endif %}
{%- endif %}
{% if set_rpath %}conan_set_rpath(){% endif %}
{% if set_std %}conan_set_std(){% endif %}
# SKIP_RPATH
{% if skip_rpath -%}
set(CMAKE_SKIP_RPATH 1 CACHE BOOL "rpaths" FORCE)
# Policy CMP0068
# We want the old behavior, in CMake >= 3.9 CMAKE_SKIP_RPATH won't affect install_name in OSX
set(CMAKE_INSTALL_NAME_DIR "")
{% endif -%}
# Parallel builds
{% if parallel -%}
set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ parallel }}")
set(CONAN_C_FLAGS "${CONAN_C_FLAGS} {{ parallel }}")
{%- endif %}
# Architecture
{% if architecture -%}
set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ architecture }}")
set(CONAN_C_FLAGS "${CONAN_C_FLAGS} {{ architecture }}")
set(CONAN_SHARED_LINKER_FLAGS "${CONAN_SHARED_LINKER_FLAGS} {{ architecture }}")
set(CONAN_EXE_LINKER_FLAGS "${CONAN_EXE_LINKER_FLAGS} {{ architecture }}")
{%- endif %}
# C++ Standard Library
{%- if set_libcxx %}
{% if set_libcxx -%}
set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ set_libcxx }}")
{%- endif %}
{%- if glibcxx %}
{% if glibcxx -%}
add_definitions(-D_GLIBCXX_USE_CXX11_ABI={{ glibcxx }})
{%- endif %}
{% if install_prefix %}
# C++ Standard
{% if cppstd -%}
message(STATUS "Conan C++ Standard {{ cppstd }} with extensions {{ cppstd_extensions }}}")
set(CMAKE_CXX_STANDARD {{ cppstd }})
set(CMAKE_CXX_EXTENSIONS {{ cppstd_extensions }})
{%- endif %}
# Install prefix
{% if install_prefix -%}
set(CMAKE_INSTALL_PREFIX {{install_prefix}} CACHE STRING "" FORCE)
{% endif %}
{%- endif %}
# Variables scoped to a configuration
{%- for it, values in configuration_types_definitions.items() -%}
# Variables
{% for it, value in variables.items() -%}
set({{ it }} "{{ value }}")
{%- endfor %}
# Variables per configuration
{% for it, values in variables_config.items() -%}
{%- set genexpr = namespace(str='') %}
{%- for conf, value in values -%}
{%- set genexpr.str = genexpr.str +
'$<IF:$<CONFIG:' + conf + '>,"' + value|string + '",' %}
{%- if loop.last %}{% set genexpr.str = genexpr.str + '""' %}{% endif %}
{%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%}
{%- endfor -%}
{% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}
{%- endfor -%}
{% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}{% endfor %}
set({{ it }} {{ genexpr.str }})
{%- endfor %}
# Preprocessor definitions
{% for it, value in preprocessor_definitions.items() -%}
# add_compile_definitions only works in cmake >= 3.12
add_definitions(-D{{ it }}="{{ value }}")
{%- endfor %}
# Preprocessor definitions per configuration
{% for it, values in preprocessor_definitions_config.items() -%}
{%- set genexpr = namespace(str='') %}
{%- for conf, value in values -%}
{%- set genexpr.str = genexpr.str +
'$<IF:$<CONFIG:' + conf + '>,"' + value|string + '",' %}
{%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%}
{%- endfor -%}
{% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}
{%- endfor -%}
add_definitions(-D{{ it }}={{ genexpr.str }})
{%- endfor %}
set(CMAKE_CXX_FLAGS_INIT "${CONAN_CXX_FLAGS}" CACHE STRING "" FORCE)
set(CMAKE_C_FLAGS_INIT "${CONAN_C_FLAGS}" CACHE STRING "" FORCE)
set(CMAKE_SHARED_LINKER_FLAGS_INIT "${CONAN_SHARED_LINKER_FLAGS}" CACHE STRING "" FORCE)
Expand All @@ -161,11 +209,16 @@ class CMakeToolchain(object):
endif()
########### Utility macros and functions ###########
{{ cmake_macros_and_functions }}
function(conan_get_policy policy_id policy)
if(POLICY "${policy_id}")
cmake_policy(GET "${policy_id}" _policy)
set(${policy} "${_policy}" PARENT_SCOPE)
else()
set(${policy} "" PARENT_SCOPE)
endif()
endfunction()
########### End of Utility macros and functions ###########
# Adjustments that depends on the build_type
{% if vs_static_runtime %}
conan_get_policy(CMP0091 policy_0091)
Expand All @@ -185,16 +238,12 @@ class CMakeToolchain(object):
""")

def __init__(self, conanfile, generator=None, generator_platform=None, build_type=None,
cmake_system_name=True, toolset=None, parallel=True, make_program=None,
# cmake_program=None, # TODO: cmake program should be considered in the environment
):
toolset=None, parallel=True):
self._conanfile = conanfile

self._fpic = self._deduce_fpic()
self._vs_static_runtime = self._deduce_vs_static_runtime()

self._set_rpath = True
self._set_std = True
self._parallel = parallel

# To find the generated cmake_find_package finders
self._cmake_prefix_path = "${CMAKE_BINARY_DIR}"
Expand All @@ -207,24 +256,12 @@ def __init__(self, conanfile, generator=None, generator_platform=None, build_typ
self._toolset = toolset or get_toolset(self._conanfile.settings, self._generator)
self._build_type = build_type or self._conanfile.settings.get_safe("build_type")

builder = CMakeDefinitionsBuilder(self._conanfile,
cmake_system_name=cmake_system_name,
make_program=make_program, parallel=parallel,
generator=self._generator,
set_cmake_flags=False,
output=self._conanfile.output)
self.definitions = Definitions()
self.definitions.update(builder.get_definitions())
# FIXME: Removing too many things. We want to bring the new logic for the toolchain here
# so we don't mess with the common code.
self.definitions.pop("CMAKE_BUILD_TYPE", None)
self.definitions.pop("CONAN_IN_LOCAL_CACHE", None)
self.definitions.pop("CMAKE_PREFIX_PATH", None)
self.definitions.pop("CMAKE_MODULE_PATH", None)
self.definitions.pop("CONAN_LINK_RUNTIME", None)
for install in ("PREFIX", "BINDIR", "SBINDIR", "LIBEXECDIR", "LIBDIR", "INCLUDEDIR",
"OLDINCLUDEDIR", "DATAROOTDIR"):
self.definitions.pop("CMAKE_INSTALL_%s" % install, None)
self.variables = Variables()
self.preprocessor_definitions = Variables()
try:
self._build_shared_libs = "ON" if self._conanfile.options.shared else "OFF"
except ConanException:
self._build_shared_libs = None

def _deduce_fpic(self):
fpic = self._conanfile.options.get_safe("fPIC")
Expand All @@ -241,6 +278,10 @@ def _deduce_fpic(self):
return None
return fpic

def _get_architecture(self):
# This should be factorized and make it toolchain-private
return architecture_flag(self._conanfile.settings)

def _deduce_vs_static_runtime(self):
settings = self._conanfile.settings
if (settings.get_safe("compiler") == "Visual Studio" and
Expand Down Expand Up @@ -278,13 +319,24 @@ def _get_libcxx(self):
glib = "0"
return lib, glib

def _cppstd(self):
cppstd = cppstd_extensions = None
compiler_cppstd = self._conanfile.settings.get_safe("compiler.cppstd")
if compiler_cppstd:
if compiler_cppstd.startswith("gnu"):
cppstd = compiler_cppstd[3:]
cppstd_extensions = "ON"
else:
cppstd = compiler_cppstd
cppstd_extensions = "OFF"
return cppstd, cppstd_extensions

def write_toolchain_files(self):
# Make it absolute, wrt to current folder, set by the caller
conan_project_include_cmake = os.path.abspath("conan_project_include.cmake")
conan_project_include_cmake = conan_project_include_cmake.replace("\\", "/")
t = Template(self._template_project_include)
content = t.render(configuration_types_definitions=self.definitions.configuration_types,
vs_static_runtime=self._vs_static_runtime)
content = t.render(vs_static_runtime=self._vs_static_runtime)
save(conan_project_include_cmake, content)

# TODO: I need the profile_host and profile_build here!
Expand All @@ -301,28 +353,37 @@ def write_toolchain_files(self):

set_libcxx, glibcxx = self._get_libcxx()

parallel = None
if self._parallel:
if self._generator and "Visual Studio" in self._generator:
parallel = "/MP%s" % cpu_count(output=self._conanfile.output)

cppstd, cppstd_extensions = self._cppstd()

skip_rpath = True if self._conanfile.settings.get_safe("os") == "Macos" else False
architecture = self._get_architecture()

context = {
"configuration_types_definitions": self.definitions.configuration_types,
"variables": self.variables,
"variables_config": self.variables.configuration_types,
"preprocessor_definitions": self.preprocessor_definitions,
"preprocessor_definitions_config": self.preprocessor_definitions.configuration_types,
"build_type": build_type,
"generator_platform": self._generator_platform,
"toolset": self._toolset,
"definitions": self.definitions,
"cmake_prefix_path": self._cmake_prefix_path,
"cmake_module_path": self._cmake_module_path,
"fpic": self._fpic,
"set_rpath": self._set_rpath,
"set_std": self._set_std,
"skip_rpath": skip_rpath,
"set_libcxx": set_libcxx,
"glibcxx": glibcxx,
"install_prefix": install_prefix
"install_prefix": install_prefix,
"parallel": parallel,
"cppstd": cppstd,
"cppstd_extensions": cppstd_extensions,
"shared_libs": self._build_shared_libs,
"architecture": architecture
}
t = Template(self._template_toolchain)
content = t.render(conan_project_include_cmake=conan_project_include_cmake,
cmake_macros_and_functions="\n".join([
CMakeCommonMacros.conan_message,
CMakeCommonMacros.conan_get_policy,
CMakeCommonMacros.conan_set_rpath,
CMakeCommonMacros.conan_set_std,
]),
**context)
content = t.render(conan_project_include_cmake=conan_project_include_cmake, **context)
save(self.filename, content)
4 changes: 2 additions & 2 deletions conans/test/functional/toolchain/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def toolchain(self):

self.assertIn("conanfile.py: Generating toolchain files", client.out)
toolchain = client.load("conan_toolchain.cmake")
self.assertIn("cmake_minimum_required", toolchain)
self.assertIn("Conan automatically generated toolchain file", toolchain)

def test_declarative(self):
conanfile = textwrap.dedent("""
Expand All @@ -38,7 +38,7 @@ class Pkg(ConanFile):

self.assertIn("conanfile.py: Generating toolchain files", client.out)
toolchain = client.load("conan_toolchain.cmake")
self.assertIn("cmake_minimum_required", toolchain)
self.assertIn("Conan automatically generated toolchain file", toolchain)

def test_declarative_new_helper(self):
conanfile = textwrap.dedent("""
Expand Down
Loading

0 comments on commit a77b978

Please sign in to comment.