Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: add preprocessor_definitions to Meson + CC/CXX from build requirements #8353

Merged
merged 5 commits into from Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions conan/tools/env/environment.py
Expand Up @@ -236,6 +236,12 @@ def _get_final_value(self, name):
def __getitem__(self, name):
return self._get_final_value(name)

def get(self, name, default=None):
try:
return self._get_final_value(name)
except KeyError:
return default

def keys(self):
return self._values.keys()

Expand Down
100 changes: 70 additions & 30 deletions conan/tools/meson/toolchain.py
@@ -1,5 +1,6 @@
import os

from conan.tools.env import VirtualEnv
from conan.tools.microsoft.toolchain import write_conanvcvars
from conans.client.build.cppstd_flags import cppstd_from_settings
from conans.client.tools.oss import cross_building, get_cross_building_settings
Expand Down Expand Up @@ -29,17 +30,19 @@ class MesonToolchain(object):
{% if pkgconfig %}pkgconfig = {{pkgconfig}}{% endif %}

[built-in options]
preprocessor_definitions = [{% for it, value in preprocessor_definitions.items() -%}
'-D{{ it }}="{{ value}}"'{%- if not loop.last %}, {% endif %}{% endfor %}]
{% if buildtype %}buildtype = {{buildtype}}{% endif %}
{% if debug %}debug = {{debug}}{% endif %}
{% if default_library %}default_library = {{default_library}}{% endif %}
{% if b_vscrt %}b_vscrt = {{b_vscrt}}{% endif %}
{% if b_ndebug %}b_ndebug = {{b_ndebug}}{% endif %}
{% if b_staticpic %}b_staticpic = {{b_staticpic}}{% endif %}
{% if cpp_std %}cpp_std = {{cpp_std}}{% endif %}
{% if c_args %}c_args = {{c_args}}{% endif %}
{% if c_link_args %}c_link_args = {{c_link_args}}{% endif %}
{% if cpp_args %}cpp_args = {{cpp_args}}{% endif %}
{% if cpp_link_args %}cpp_link_args = {{cpp_link_args}}{% endif %}
c_args = {{c_args}} + preprocessor_definitions
c_link_args = {{c_link_args}}
cpp_args = {{cpp_args}} + preprocessor_definitions
cpp_link_args = {{cpp_link_args}}
{% if pkg_config_path %}pkg_config_path = {{pkg_config_path}}{% endif %}
""")

Expand Down Expand Up @@ -71,8 +74,44 @@ def __init__(self, conanfile, env=os.environ):
self._cppstd = cppstd_from_settings(self._conanfile.settings)
self._shared = self._conanfile.options.get_safe("shared")
self._fpic = self._conanfile.options.get_safe("fPIC")
self._build_env = VirtualEnv(self._conanfile).build_environment()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have merged this PR, because it is already an improvement.
But this is a bit "hardcoded". The VirtualEnv might be used in generate() in a different way, to customize what gets pulled into the environment, and users might prioritize dependencies, [conf], environment, or other stuff, but that will not be taken into account here.

It seems the ideal is that the MesonToolchain could use the conanbuildenv.xx script, or some other way to reuse the build environment. But I ignore how Meson works for this, I guess the activation of conanbuildenv.xx is still necessary?


self.definitions = dict()
self._env = env
self.preprocessor_definitions = dict()

def from_build_env(name):
return self._to_meson_value(self._build_env.get(name, None))

self.c = from_build_env("CC")
self.cpp = from_build_env("CXX")
self.c_ld = from_build_env("CC_LD") or from_build_env("LD")
self.cpp_ld = from_build_env("CXX_LD") or from_build_env("LD")
self.ar = from_build_env("AR")
self.strip = from_build_env("STRIP")
self.as_ = from_build_env("AS")
self.windres = from_build_env("WINDRES")
self.pkgconfig = from_build_env("PKG_CONFIG")

# https://mesonbuild.com/Builtin-options.html#core-options
# Do not adjust "debug" if already adjusted "buildtype"
self.buildtype = self._to_meson_build_type(self._build_type) if self._build_type else None
self.default_library = self._to_meson_shared(self._shared) \
if self._shared is not None else None

# https://mesonbuild.com/Builtin-options.html#base-options
self.b_vscrt = self._to_meson_vscrt(self._vscrt)
self.b_staticpic = self._to_meson_value(self._fpic) \
if (self._shared is False and self._fpic is not None) else None
self.b_ndebug = self._to_meson_value(self._ndebug) if self._build_type else None

# https://mesonbuild.com/Builtin-options.html#compiler-options
self.cpp_std = self._to_meson_cppstd(self._cppstd) if self._cppstd else None
self.c_args = self._to_meson_value(self._env_array('CPPFLAGS') + self._env_array('CFLAGS'))
self.c_link_args = self._to_meson_value(self._env_array('LDFLAGS'))
self.cpp_args = self._to_meson_value(self._env_array('CPPFLAGS') +
self._env_array('CXXFLAGS'))
self.cpp_link_args = self._to_meson_value(self._env_array('LDFLAGS'))
self.pkg_config_path = "'%s'" % self._conanfile.generators_folder

@staticmethod
def _to_meson_value(value):
Expand Down Expand Up @@ -138,6 +177,10 @@ def _to_meson_cppstd(self, cppstd):
def _none_if_empty(value):
return "'%s'" % value if value.strip() else None

def _env_array(self, name):
import shlex
return shlex.split(self._build_env.get(name, ''))

@property
def _context(self):
project_options = []
Expand All @@ -151,34 +194,31 @@ def _context(self):
# https://mesonbuild.com/Builtin-options.html#directories
# TODO : we don't manage paths like libdir here (yet?)
# https://mesonbuild.com/Machine-files.html#binaries
"c": self._to_meson_value(self._env.get("CC", None)),
"cpp": self._to_meson_value(self._env.get("CXX", None)),
"c_ld": self._to_meson_value(self._env.get("LD", None)),
"cpp_ld": self._to_meson_value(self._env.get("LD", None)),
"ar": self._to_meson_value(self._env.get("AR", None)),
"strip": self._to_meson_value(self._env.get("STRIP", None)),
"as": self._to_meson_value(self._env.get("AS", None)),
"windres": self._to_meson_value(self._env.get("WINDRES", None)),
"pkgconfig": self._to_meson_value(self._env.get("PKG_CONFIG", None)),
# https://mesonbuild.com/Reference-tables.html#compiler-and-linker-selection-variables
"c": self.c,
"cpp": self.cpp,
"c_ld": self.c_ld,
"cpp_ld": self.cpp_ld,
"ar": self.ar,
"strip": self.strip,
"as": self.as_,
"windres": self.windres,
"pkgconfig": self.pkgconfig,
# https://mesonbuild.com/Builtin-options.html#core-options
"buildtype": self._to_meson_build_type(self._build_type) if self._build_type else None,
"debug": self._to_meson_value(self._debug) if self._build_type else None,
"default_library": self._to_meson_shared(
self._shared) if self._shared is not None else None,
"buildtype": self.buildtype,
"default_library": self.default_library,
# https://mesonbuild.com/Builtin-options.html#base-options
"b_vscrt": self._to_meson_vscrt(self._vscrt),
"b_staticpic": self._to_meson_value(self._fpic) if (self._shared is False and self._fpic
is not None) else None,
"b_ndebug": self._to_meson_value(self._ndebug) if self._build_type else None,
"b_vscrt": self.b_vscrt,
"b_staticpic": self.b_staticpic,
"b_ndebug": self.b_ndebug,
# https://mesonbuild.com/Builtin-options.html#compiler-options
"cpp_std": self._to_meson_cppstd(self._cppstd) if self._cppstd else None,
"c_args": self._none_if_empty(self._env.get("CPPFLAGS", '') +
self._env.get("CFLAGS", '')),
"c_link_args": self._env.get("LDFLAGS", None),
"cpp_args": self._none_if_empty(self._env.get("CPPFLAGS", '') +
self._env.get("CXXFLAGS", '')),
"cpp_link_args": self._env.get("LDFLAGS", None),
"pkg_config_path": "'%s'" % os.getcwd()
"cpp_std": self.cpp_std,
"c_args": self.c_args,
"c_link_args": self.c_link_args,
"cpp_args": self.cpp_args,
"cpp_link_args": self.cpp_link_args,
"pkg_config_path": self.pkg_config_path,
"preprocessor_definitions": self.preprocessor_definitions
}
return context

Expand Down
4 changes: 3 additions & 1 deletion conans/test/functional/toolchains/meson/test_android.py
Expand Up @@ -101,12 +101,14 @@ def env(self):
ar = self._tool('ar')
cflags = '--target=%s' % self._target
cxxflags = '--target=%s' % self._target
ldflags = '--target=%s' % self._target

return {'CC': cc,
'CXX': cxx,
'AR': ar,
'CFLAGS': cflags,
'CXXFLAGS': cxxflags}
'CXXFLAGS': cxxflags,
'LDFLAGS': ldflags}

def profile(self):
template = textwrap.dedent("""
Expand Down
20 changes: 10 additions & 10 deletions conans/test/functional/toolchains/meson/test_ios.py
@@ -1,11 +1,10 @@
import os
import platform
import pytest
import textwrap
import unittest

from parameterized import parameterized
import pytest
from parameterized import parameterized

from conans.client.tools.apple import XCRun, apple_deployment_target_flag, to_apple_arch
from conans.test.assets.sources import gen_function_cpp, gen_function_h
Expand Down Expand Up @@ -67,26 +66,27 @@ def env(self):
cc = self.xcrun.cc
cxx = self.xcrun.cxx

cflags = apple_deployment_target_flag(self.os, self.os_version)
cflags += " -isysroot " + self.xcrun.sdk_path
cflags += " -arch " + to_apple_arch(self.arch)
cxxflags = cflags
deployment_flag = apple_deployment_target_flag(self.os, self.os_version)
sysroot_flag = " -isysroot " + self.xcrun.sdk_path
arch_flag = " -arch " + to_apple_arch(self.arch)
flags = deployment_flag + sysroot_flag + arch_flag

return {'CC': cc,
'CXX': cxx,
'CFLAGS': cflags,
'CXXFLAGS': cxxflags}
'CFLAGS': flags,
'CXXFLAGS': flags,
'LDFLAGS': flags}

def profile(self):
template = textwrap.dedent("""
include(default)
[settings]
{settings}
[env]
[buildenv]
{env}
""")
settings = '\n'.join(["%s = %s" % (s[0], s[1]) for s in self.settings()])
env = '\n'.join(["%s = %s" % (k, v) for k, v in self.env().items()])
env = '\n'.join(["%s=%s" % (k, v) for k, v in self.env().items()])
return template.format(settings=settings, env=env)

@parameterized.expand([('armv8', 'iOS', '10.0', 'iphoneos'),
Expand Down
@@ -0,0 +1,53 @@
import pytest

from conans.test.assets.genconanfile import GenConanfile
from conans.test.functional.toolchains.meson._base import get_meson_version
from conans.test.utils.tools import TestClient

@pytest.mark.toolchain
@pytest.mark.tool_meson
@pytest.mark.skipif(get_meson_version() < "0.56.0", reason="requires meson >= 0.56.0")
def test_env_vars_from_build_require():
br = str(GenConanfile().with_name("hello_compiler").with_version("1.0").with_import("import os"))
br += """
def package_info(self):
{}
"""
vars = ["CC", "CC_LD", "CXX", "CXX_LD", "AR", "STRIP", "AS", "WINDRES", "PKG_CONFIG", "LD"]
lines = "\n ".join(['self.buildenv_info.define("{var}", "{var}_VALUE")'.format(var=var)
for var in vars])
cf = br.format(lines)

client = TestClient()
client.save({"conanfile.py": cf})
client.run("create .")

conanfile = GenConanfile().with_settings("os", "arch", "compiler", "build_type")\
.with_name("consumer").with_version("1.0").with_generator("MesonToolchain")\
.with_build_requirement("hello_compiler/1.0")
client.save({"conanfile.py": conanfile})
client.run("install . -pr:h=default -pr:b=default")
content = client.load("conan_meson_native.ini")
assert "c = 'CC_VALUE'" in content
assert "cpp = 'CXX_VALUE'" in content
assert "c_ld = 'CC_LD_VALUE'" in content
assert "cpp_ld = 'CXX_LD_VALUE'" in content
assert "ar = 'AR_VALUE'" in content
assert "strip = 'STRIP_VALUE'" in content
assert "as = 'AS_VALUE'" in content
assert "windres = 'WINDRES_VALUE'" in content
assert "pkgconfig = 'PKG_CONFIG_VALUE'" in content

# Now change the build require to declare only LD
lines = '\n self.buildenv_info.define("LD", "LD_VALUE")'
cf = br.format(lines)
client = TestClient()
client.save({"conanfile.py": cf})
client.run("create .")

# Create the consumer again, now the LD env var will be applied
client.save({"conanfile.py": conanfile})
client.run("install . -pr:h=default -pr:b=default")
content = client.load("conan_meson_native.ini")
assert "c_ld = 'LD_VALUE'" in content
assert "cpp_ld = 'LD_VALUE'" in content
@@ -0,0 +1,67 @@
import os
import textwrap

from conans.test.assets.sources import gen_function_cpp, gen_function_h
from conans.test.functional.toolchains.meson._base import TestMesonBase


class MesonPreprocessorDefinitionsTest(TestMesonBase):
_conanfile_py = textwrap.dedent("""
from conans import ConanFile, tools
from conan.tools.meson import Meson, MesonToolchain


class App(ConanFile):
settings = "os", "arch", "compiler", "build_type"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}

def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC

def generate(self):
tc = MesonToolchain(self)
tc.preprocessor_definitions["TEST_DEFINITION1"] = "TestPpdValue1"
tc.preprocessor_definitions["TEST_DEFINITION2"] = "TestPpdValue2"
tc.generate()

def build(self):
meson = Meson(self)
meson.configure()
meson.build()
""")

_meson_build = textwrap.dedent("""
project('tutorial', 'cpp')
hello = library('hello', 'hello.cpp')
executable('demo', 'main.cpp', link_with: hello)
""")

def test_build(self):
hello_h = gen_function_h(name="hello")
hello_cpp = gen_function_cpp(name="hello",
preprocessor=["TEST_DEFINITION1", "TEST_DEFINITION2"])
app = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])

self.t.save({"conanfile.py": self._conanfile_py,
"meson.build": self._meson_build,
"hello.h": hello_h,
"hello.cpp": hello_cpp,
"main.cpp": app})

self.t.run("install . %s" % self._settings_str)

content = self.t.load("conan_meson_native.ini")

self.assertIn("[built-in options]", content)
self.assertIn("buildtype = 'release'", content)

self.t.run("build .")
self.t.run_command(os.path.join("build", "demo"))

self.assertIn("hello: Release!", self.t.out)
self.assertIn("TEST_DEFINITION1: TestPpdValue1", self.t.out)
self.assertIn("TEST_DEFINITION2: TestPpdValue2", self.t.out)

self._check_binary()