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

build_requires visible can be overriden #13712

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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