Skip to content

Commit

Permalink
fixing application of build_requires to host context build_requires (#…
Browse files Browse the repository at this point in the history
…7500)

* fixing application of build_requires to host context build_requires

* more checks in tests

* refactoring build-require recursion and more checks in test

* add test for transitive host-build build_requires
  • Loading branch information
memsharded committed Aug 6, 2020
1 parent e157492 commit bdd2ce7
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 32 deletions.
56 changes: 29 additions & 27 deletions conans/client/graph/graph_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ def __call__(self, build_require, force_host_context=False):
context = CONTEXT_HOST if force_host_context else self._default_context
self.add(build_require, context)

def update(self, build_requires):
for build_require in build_requires:
self.add(build_require)

def __str__(self):
items = ["{} ({})".format(br, ctxt) for (_, ctxt), br in self.items()]
return ", ".join(items)
Expand Down Expand Up @@ -296,15 +292,18 @@ def _recurse_build_requires(self, graph, builder, check_updates,
continue
# Packages with PACKAGE_ID_UNKNOWN might be built in the future, need build requires
if (node.binary not in (BINARY_BUILD, BINARY_EDITABLE, BINARY_UNKNOWN)
and node.recipe != RECIPE_CONSUMER):
and node.recipe != RECIPE_CONSUMER):
continue
package_build_requires = self._get_recipe_build_requires(node.conanfile, default_context)
str_ref = str(node.ref)

# Compute the update of the current recipe build_requires when updated with the
# downstream profile-defined build-requires
new_profile_build_requires = []
for pattern, build_requires in profile_build_requires.items():
if ((node.recipe == RECIPE_CONSUMER and pattern == "&") or
(node.recipe != RECIPE_CONSUMER and pattern == "&!") or
fnmatch.fnmatch(str_ref, pattern)):
(node.recipe != RECIPE_CONSUMER and pattern == "&!") or
fnmatch.fnmatch(str_ref, pattern)):
for build_require in build_requires:
br_key = (build_require.name, default_context)
if br_key in package_build_requires: # Override defined
Expand All @@ -316,30 +315,33 @@ def _recurse_build_requires(self, graph, builder, check_updates,
elif build_require.name != node.name or default_context != node.context:
new_profile_build_requires.append((build_require, default_context))

if package_build_requires:
br_list = [(it, ctxt) for (_, ctxt), it in package_build_requires.items()]
nodessub = builder.extend_build_requires(graph, node,
br_list,
check_updates, update, remotes,
profile_host, profile_build, graph_lock)
build_requires = profile_build.build_requires if default_context == CONTEXT_BUILD \
else profile_build_requires
self._recurse_build_requires(graph, builder,
check_updates, update, build_mode,
remotes, build_requires, recorder,
def _recurse_build_requires(br_list, transitive_build_requires):
nodessub = builder.extend_build_requires(graph, node, br_list, check_updates,
update, remotes, profile_host,
profile_build, graph_lock)
self._recurse_build_requires(graph, builder, check_updates, update, build_mode,
remotes, transitive_build_requires, recorder,
profile_host, profile_build, graph_lock,
nodes_subset=nodessub, root=node)

if new_profile_build_requires:
nodessub = builder.extend_build_requires(graph, node, new_profile_build_requires,
check_updates, update, remotes,
profile_host, profile_build, graph_lock)
if package_build_requires:
if default_context == CONTEXT_BUILD:
br_build, br_host = [], []
for (_, ctxt), it in package_build_requires.items():
if ctxt == CONTEXT_BUILD:
br_build.append((it, ctxt))
else:
br_host.append((it, ctxt))
if br_build:
_recurse_build_requires(br_build, profile_build.build_requires)
if br_host:
_recurse_build_requires(br_host, profile_build_requires)
else:
br_all = [(it, ctxt) for (_, ctxt), it in package_build_requires.items()]
_recurse_build_requires(br_all, profile_build_requires)

self._recurse_build_requires(graph, builder,
check_updates, update, build_mode,
remotes, {}, recorder,
profile_host, profile_build, graph_lock,
nodes_subset=nodessub, root=node)
if new_profile_build_requires:
_recurse_build_requires(new_profile_build_requires, {})

def _load_graph(self, root_node, check_updates, update, build_mode, remotes,
recorder, profile_host, profile_build, apply_build_requires,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from conans.test.utils.tools import TestClient, GenConanfile


class BuildRequiresFromProfile(unittest.TestCase):
class BuildRequiresFromProfileTest(unittest.TestCase):
profile_host = textwrap.dedent("""
[settings]
os=Windows
Expand Down Expand Up @@ -49,8 +49,233 @@ def test_br_from_profile_host_and_profile_build(self):
t.run("export br1.py br1/version@")
t.run("export br2.py br2/version@")
t.run("export br3.py br3/version@")
t.run("create library.py --profile:host=profile_host --profile:build=profile_build --build *")
t.run("create library.py --profile:host=profile_host --profile:build=profile_build --build")
self.assertNotIn("br1/version: Applying build-requirement: br2/version", t.out)
self.assertIn("br1/version: Applying build-requirement: br3/version", t.out)
self.assertIn("library/version: Applying build-requirement: br2/version", t.out)
self.assertIn("library/version: Applying build-requirement: br1/version", t.out)


class BuildRequiresContextHostFromProfileTest(unittest.TestCase):
toolchain = textwrap.dedent("""
from conans import ConanFile
class Recipe(ConanFile):
name = "mytoolchain"
version = "1.0"
settings = "os"
def package_info(self):
self.env_info.MYTOOLCHAIN_VAR = "MYTOOLCHAIN_VALUE-" + str(self.settings.os)
""")
gtest = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "gtest"
version = "1.0"
settings = "os"
def build(self):
self.output.info("Building with: %s" % os.getenv("MYTOOLCHAIN_VAR"))
self.output.info("Build OS=%s" % self.settings.os)
def package_info(self):
self.output.info("PackageInfo OS=%s" % self.settings.os)
""")
library_conanfile = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "library"
version = "version"
settings = "os"
def build_requirements(self):
self.build_requires("gtest/1.0", force_host_context=True)
def build(self):
self.output.info("Building with: %s" % os.getenv("MYTOOLCHAIN_VAR"))
self.output.info("Build OS=%s" % self.settings.os)
""")
profile_host = textwrap.dedent("""
[settings]
os = Linux
[build_requires]
mytoolchain/1.0
""")
profile_build = textwrap.dedent("""
[settings]
os = Windows
""")

def test_br_from_profile_host_and_profile_build(self):
t = TestClient()
t.save({'profile_host': self.profile_host,
'profile_build': self.profile_build,
'library.py': self.library_conanfile,
'mytoolchain.py': self.toolchain,
"gtest.py": self.gtest})
t.run("create mytoolchain.py --profile=profile_build")
t.run("create mytoolchain.py --profile=profile_host")

# old way, the toolchain will have the same profile (profile_host=Linux) only
t.run("create gtest.py --profile=profile_host")
self.assertIn("gtest/1.0: Building with: MYTOOLCHAIN_VALUE-Linux", t.out)
self.assertIn("gtest/1.0: Build OS=Linux", t.out)

# new way, the toolchain can now run in Windows, but gtest in Linux
t.run("create gtest.py --profile=profile_host --profile:build=profile_build")
self.assertIn("gtest/1.0: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)

t.run("create gtest.py --profile=profile_host --profile:build=profile_build --build")
self.assertIn("gtest/1.0: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)
self.assertIn("gtest/1.0: Build OS=Linux", t.out)
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)

t.run("create library.py --profile:host=profile_host --profile:build=profile_build")
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)
self.assertIn("library/version: Build OS=Linux", t.out)
self.assertIn("library/version: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)

t.run("create library.py --profile:host=profile_host --profile:build=profile_build --build")
self.assertIn("gtest/1.0: Build OS=Linux", t.out)
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)
self.assertIn("library/version: Build OS=Linux", t.out)
self.assertIn("library/version: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)


class BuildRequiresBothContextsTest(unittest.TestCase):
toolchain_creator = textwrap.dedent("""
from conans import ConanFile
class Recipe(ConanFile):
name = "creator"
version = "1.0"
settings = "os"
def package_info(self):
self.env_info.MYCREATOR_VAR = "MYCREATOR_VALUE-" + str(self.settings.os)
""")
toolchain = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "mytoolchain"
version = "1.0"
settings = "os"
def build(self):
self.output.info("Building with: %s" % os.environ["MYCREATOR_VAR"])
self.output.info("Build OS=%s" % self.settings.os)
def package_info(self):
self.env_info.MYTOOLCHAIN_VAR = "MYTOOLCHAIN_VALUE-" + str(self.settings.os)
""")
gtest = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "gtest"
version = "1.0"
settings = "os"
def build(self):
self.output.info("Building with: %s" % os.environ["MYTOOLCHAIN_VAR"])
self.output.info("Build OS=%s" % self.settings.os)
def package_info(self):
self.output.info("PackageInfo OS=%s" % self.settings.os)
""")
library_conanfile = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "library"
version = "version"
settings = "os"
def build_requirements(self):
self.build_requires("gtest/1.0", force_host_context=True)
def build(self):
self.output.info("Building with: %s" % os.environ["MYTOOLCHAIN_VAR"])
self.output.info("Build OS=%s" % self.settings.os)
""")
profile_host = textwrap.dedent("""
[settings]
os = Linux
[build_requires]
mytoolchain/1.0
""")
profile_build = textwrap.dedent("""
[settings]
os = Windows
[build_requires]
creator/1.0
""")

def test_build_requires_both_contexts(self):
t = TestClient()
t.save({'profile_host': self.profile_host,
'profile_build': self.profile_build,
'library.py': self.library_conanfile,
'creator.py': self.toolchain_creator,
'mytoolchain.py': self.toolchain,
"gtest.py": self.gtest})
t.run("create creator.py --profile=profile_build")
t.run("create mytoolchain.py --profile=profile_build")
self.assertIn("mytoolchain/1.0: Building with: MYCREATOR_VALUE-Windows", t.out)
self.assertIn("mytoolchain/1.0: Build OS=Windows", t.out)

# new way, the toolchain can now run in Windows, but gtest in Linux
t.run("create gtest.py --profile=profile_host --profile:build=profile_build")
self.assertIn("gtest/1.0: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)

# FIXME: This isn't right, it should be CREATOR-Windows, but build_requires
# FIXME: from profiles are not transitive to build_requires in profiles
t.run("create gtest.py --profile=profile_host --profile:build=profile_build --build",
assert_error=True)
self.assertIn("ERROR: mytoolchain/1.0: Error in build() method, line 10", t.out)
self.assertIn("KeyError: 'MYCREATOR_VAR'", t.out)

# Declaring the build_requires in the recipe works, it is just the profile that is
# not transitive
toolchain = textwrap.dedent("""
from conans import ConanFile
import os
class Recipe(ConanFile):
name = "mytoolchain"
version = "1.0"
settings = "os"
build_requires = "creator/1.0"
def build(self):
self.output.info("Building with: %s" % os.environ["MYCREATOR_VAR"])
self.output.info("Build OS=%s" % self.settings.os)
def package_info(self):
self.env_info.MYTOOLCHAIN_VAR = "MYTOOLCHAIN_VALUE-" + str(self.settings.os)
""")
t.save({'mytoolchain.py': toolchain})
t.run("create mytoolchain.py --profile=profile_build")
self.assertIn("mytoolchain/1.0: Building with: MYCREATOR_VALUE-Windows", t.out)
self.assertIn("mytoolchain/1.0: Build OS=Windows", t.out)

t.run("create gtest.py --profile=profile_host --profile:build=profile_build --build")
self.assertIn("mytoolchain/1.0: Building with: MYCREATOR_VALUE-Windows", t.out)
self.assertIn("mytoolchain/1.0: Build OS=Windows", t.out)
self.assertIn("gtest/1.0: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)
self.assertIn("gtest/1.0: Build OS=Linux", t.out)

t.run("create library.py --profile:host=profile_host --profile:build=profile_build --build")
self.assertIn("mytoolchain/1.0: Building with: MYCREATOR_VALUE-Windows", t.out)
self.assertIn("mytoolchain/1.0: Build OS=Windows", t.out)
self.assertIn("gtest/1.0: Build OS=Linux", t.out)
self.assertIn("gtest/1.0: PackageInfo OS=Linux", t.out)
self.assertIn("library/version: Build OS=Linux", t.out)
self.assertIn("library/version: Building with: MYTOOLCHAIN_VALUE-Windows", t.out)
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def test_crossbuilding(self):
profile_build.settings["os"] = "Build"
profile_build.process_settings(self.cache)

deps_graph = self._build_graph(profile_host=profile_host, profile_build=profile_build, install=True)
deps_graph = self._build_graph(profile_host=profile_host, profile_build=profile_build,
install=True)

# Check HOST packages
# - Application
Expand Down Expand Up @@ -87,7 +88,7 @@ def test_crossbuilding(self):
_ = application.conanfile.deps_env_info["gtest"]

# - GTest
gtest_host = application.dependencies[1].dst
gtest_host = application.dependencies[2].dst
self.assertEqual(gtest_host.conanfile.name, "gtest")
self.assertEqual(gtest_host.context, CONTEXT_HOST)
self.assertEqual(gtest_host.conanfile.settings.os, profile_host.settings['os'])
Expand All @@ -109,7 +110,7 @@ def test_crossbuilding(self):
self.assertEqual(str(cmake_build.conanfile.settings.os), profile_build.settings['os'])

# - Protoc
protoc_build = application.dependencies[2].dst
protoc_build = application.dependencies[1].dst
self.assertEqual(protoc_build.conanfile.name, "protoc")
self.assertEqual(protoc_build.context, CONTEXT_BUILD)
self.assertEqual(str(protoc_build.conanfile.settings.os), profile_build.settings['os'])
Expand Down

0 comments on commit bdd2ce7

Please sign in to comment.