Skip to content

Commit

Permalink
build_requires visible can be overriden (#13712)
Browse files Browse the repository at this point in the history
* build_requires visible can be overriden

* propose track-host too

* tests

* change brackets
  • Loading branch information
memsharded authored May 4, 2023
1 parent 1e116ea commit 8654d39
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 4 deletions.
11 changes: 11 additions & 0 deletions conans/client/graph/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,17 @@ def _resolved_system_tool(node, require, profile_build, profile_host, resolve_pr
return d, ConanFile(str(d)), RECIPE_SYSTEM_TOOL, None

def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock):
if require.ref.version == "<host_version>":
if not require.build or require.visible:
raise ConanException(f"{node.ref} require '{require.ref}': 'host_version' can only "
"be used for non-visible tool_requires")
req = Requirement(require.ref, headers=True, libs=True, visible=True)
transitive = node.transitive_deps.get(req)
if transitive is None:
raise ConanException(f"{node.ref} require '{require.ref}': didn't find a matching "
"host dependency")
require.ref.version = transitive.require.ref.version

if graph_lock is not None:
# Here is when the ranges and revisions are resolved
graph_lock.resolve_locked(node, require, self._resolve_prereleases)
Expand Down
9 changes: 5 additions & 4 deletions conans/model/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,11 @@ class BuildRequirements:
def __init__(self, requires):
self._requires = requires

def __call__(self, ref, package_id_mode=None, visible=False, run=None, options=None):
def __call__(self, ref, package_id_mode=None, visible=False, run=None, options=None,
override=None):
# TODO: Check which arguments could be user-defined
self._requires.build_require(ref, package_id_mode=package_id_mode, visible=visible, run=run,
options=options)
options=options, override=override)


class ToolRequirements:
Expand Down Expand Up @@ -485,7 +486,7 @@ def __call__(self, str_ref, **kwargs):
self._requires[req] = req

def build_require(self, ref, raise_if_duplicated=True, package_id_mode=None, visible=False,
run=None, options=None):
run=None, options=None, override=None):
"""
Represent a generic build require, could be a tool, like "cmake" or a bundle of build
scripts.
Expand All @@ -501,7 +502,7 @@ def build_require(self, ref, raise_if_duplicated=True, package_id_mode=None, vis
# FIXME: This raise_if_duplicated is ugly, possibly remove
ref = RecipeReference.loads(ref)
req = Requirement(ref, headers=False, libs=False, build=True, run=run, visible=visible,
package_id_mode=package_id_mode, options=options)
package_id_mode=package_id_mode, options=options, override=override)

if raise_if_duplicated and self._requires.get(req):
raise ConanException("Duplicated requirement: {}".format(ref))
Expand Down
146 changes: 146 additions & 0 deletions conans/test/integration/build_requires/build_requires_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
import platform
import textwrap
Expand Down Expand Up @@ -747,3 +748,148 @@ def requirements(self):
c.assert_listed_require({"dep/1.0": "Cache"})
c.run("create consumer --build-require")
assert "dep/1.0" not in c.out


class TestBuildTrackHost:

def test_overriden_host_but_not_build(self):
"""
Making the ``tool_requires(..., visible=True)`` works, and allows overriding, but
propagates the build-requirement to protobuf/protoc down the graph, and VirtualBuildEnv
will put ``protoc`` from it in the PATH. Not a problem in majority of cases, but not the
cleanest
"""
c = TestClient()
pkg = textwrap.dedent("""
from conan import ConanFile
class ProtoBuf(ConanFile):
name = "pkg"
version = "0.1"
def requirements(self):
self.requires("protobuf/1.0")
def build_requirements(self):
self.tool_requires("protobuf/1.0", visible=True)
""")
c.save({"protobuf/conanfile.py": GenConanfile("protobuf"),
"pkg/conanfile.py": pkg,
"app/conanfile.py": GenConanfile().with_requires("pkg/0.1")
.with_requirement("protobuf/1.1", override=True)
.with_build_requirement("protobuf/1.1",
override=True)})
c.run("create protobuf --version=1.0")
c.run("create protobuf --version=1.1")
c.run("create pkg")
c.run("install app")
c.assert_listed_require({"protobuf/1.1": "Cache"})
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)

def test_overriden_host_version(self):
"""
Make the tool_requires follow the regular require with the expression "<host_version>"
"""
c = TestClient()
pkg = textwrap.dedent("""
from conan import ConanFile
class ProtoBuf(ConanFile):
name = "pkg"
version = "0.1"
def requirements(self):
self.requires("protobuf/1.0")
def build_requirements(self):
self.tool_requires("protobuf/<host_version>")
""")
c.save({"protobuf/conanfile.py": GenConanfile("protobuf"),
"pkg/conanfile.py": pkg,
"app/conanfile.py": GenConanfile().with_requires("pkg/0.1")
.with_requirement("protobuf/1.1", override=True)})
c.run("create protobuf --version=1.0")
c.run("create protobuf --version=1.1")
c.run("create pkg")
c.run("install pkg") # make sure it doesn't crash
c.run("install app")
c.assert_listed_require({"protobuf/1.1": "Cache"})
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)
# verify locks work
c.run("lock create app")
lock = json.loads(c.load("app/conan.lock"))
build_requires = lock["build_requires"]
assert len(build_requires) == 1
assert "protobuf/1.1" in build_requires[0]
# lock can be used
c.run("install app --lockfile=app/conan.lock")
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)

def test_overriden_host_version_version_range(self):
"""
same as above, but using version ranges instead of overrides
"""
c = TestClient()
pkg = textwrap.dedent("""
from conan import ConanFile
class ProtoBuf(ConanFile):
name = "pkg"
version = "0.1"
def requirements(self):
self.requires("protobuf/[*]")
def build_requirements(self):
self.tool_requires("protobuf/<host_version>")
""")
c.save({"protobuf/conanfile.py": GenConanfile("protobuf"),
"pkg/conanfile.py": pkg,
"app/conanfile.py": GenConanfile().with_requires("pkg/0.1")})
c.run("create protobuf --version=1.0")
c.run("create pkg")
c.run("install pkg") # make sure it doesn't crash
c.run("install app")
c.assert_listed_require({"protobuf/1.0": "Cache"})
c.assert_listed_require({"protobuf/1.0": "Cache"}, build=True)

c.run("create protobuf --version=1.1")
c.run("install pkg") # make sure it doesn't crash
c.run("install app")
c.assert_listed_require({"protobuf/1.1": "Cache"})
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)
# verify locks work
c.run("lock create app")
lock = json.loads(c.load("app/conan.lock"))
build_requires = lock["build_requires"]
assert len(build_requires) == 1
assert "protobuf/1.1" in build_requires[0]
# lock can be used
c.run("install app --lockfile=app/conan.lock")
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)

def test_track_host_error_nothost(self):
"""
if no host requirement is defined, it will be an error
"""
c = TestClient()
pkg = textwrap.dedent("""
from conan import ConanFile
class ProtoBuf(ConanFile):
name = "protobuf"
def build_requirements(self):
self.tool_requires("protobuf/<host_version>")
""")

c.save({"pkg/conanfile.py": pkg})
c.run("install pkg", assert_error=True)
assert "ERROR: protobuf/None require 'protobuf/<host_version>': " \
"didn't find a matching host dependency" in c.out

def test_track_host_errors_trait(self):
"""
It is not possible to make host_version visible too
"""
c = TestClient()
pkg = textwrap.dedent("""
from conan import ConanFile
class ProtoBuf(ConanFile):
name = "protobuf"
def requirements(self):
self.tool_requires("other/<host_version>", visible=True)
""")
c.save({"pkg/conanfile.py": pkg})
c.run("install pkg", assert_error=True)
assert "ERROR: protobuf/None require 'other/<host_version>': 'host_version' " \
"can only be used for non-visible tool_requires" in c.out

0 comments on commit 8654d39

Please sign in to comment.