Skip to content

Commit

Permalink
test for build-missing of transitive tool-requires (#15082)
Browse files Browse the repository at this point in the history
* test for build-missing of transitive tool-requires

* wip

* wip

* optimize
  • Loading branch information
memsharded authored Nov 13, 2023
1 parent 46e839b commit 0d55d67
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 19 deletions.
1 change: 1 addition & 0 deletions conans/client/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def __init__(self, ref, conanfile, context, recipe=None, path=None, test=False):
self.error = None
self.cant_build = False # It will set to a str with a reason if the validate_build() fails
self.should_build = False # If the --build or policy wants to build this binary
self.build_allowed = False

def __lt__(self, other):
"""
Expand Down
51 changes: 32 additions & 19 deletions conans/client/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def _evaluate_node(self, node, build_mode, remotes, update):

if node.binary == BINARY_MISSING and build_mode.allowed(node.conanfile):
node.should_build = True
node.build_allowed = True
node.binary = BINARY_BUILD if not node.cant_build else BINARY_INVALID

if (node.binary in (BINARY_BUILD, BINARY_MISSING) and node.conanfile.info.invalid and
Expand Down Expand Up @@ -370,27 +371,39 @@ def evaluate_graph(self, deps_graph, build_mode, lockfile, remotes, update, buil
@staticmethod
def _skip_binaries(graph):
required_nodes = set()
# Aggregate all necessary starting nodes
required_nodes.add(graph.root)
for node in graph.nodes:
if node.binary not in (BINARY_BUILD, BINARY_EDITABLE_BUILD, BINARY_EDITABLE) \
and node is not graph.root:
continue
# The nodes that are directly required by this one to build correctly
deps_required = set(d.node for d in node.transitive_deps.values() if d.require.files)

# second pass, transitive affected. Packages that have some dependency that is required
# cannot be skipped either. In theory the binary could be skipped, but build system
# integrations like CMakeDeps rely on find_package() to correctly find transitive deps
indirect = (d.node for d in node.transitive_deps.values()
if any(t.node in deps_required for t in d.node.transitive_deps.values()))
deps_required.update(indirect)

# Third pass, mark requires as skippeable
for dep in node.transitive_deps.values():
dep.require.skip = dep.node not in deps_required

# Finally accumulate for marking binaries as SKIP download
required_nodes.update(deps_required)
if node.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD, BINARY_EDITABLE):
if not node.build_allowed: # Only those that are forced to build, not only "missing"
required_nodes.add(node)

root_nodes = required_nodes.copy()
while root_nodes:
new_root_nodes = set()
for node in root_nodes:
# The nodes that are directly required by this one to build correctly
deps_required = set(d.node for d in node.transitive_deps.values() if d.require.files)

# second pass, transitive affected. Packages that have some dependency that is required
# cannot be skipped either. In theory the binary could be skipped, but build system
# integrations like CMakeDeps rely on find_package() to correctly find transitive deps
indirect = (d.node for d in node.transitive_deps.values()
if any(t.node in deps_required for t in d.node.transitive_deps.values()))
deps_required.update(indirect)

# Third pass, mark requires as skippeable
for dep in node.transitive_deps.values():
dep.require.skip = dep.node not in deps_required

# Finally accumulate all needed nodes for marking binaries as SKIP download
news_req = [r for r in deps_required
if r.binary in (BINARY_BUILD, BINARY_EDITABLE_BUILD, BINARY_EDITABLE)
if r not in required_nodes] # Avoid already expanded before
new_root_nodes.update(news_req) # For expanding the next iteration
required_nodes.update(deps_required)

root_nodes = new_root_nodes

for node in graph.nodes:
if node not in required_nodes and node.conanfile.conf.get("tools.graph:skip_binaries",
Expand Down
19 changes: 19 additions & 0 deletions conans/test/integration/build_requires/build_requires_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import os
import platform
import re
import textwrap
import unittest

Expand Down Expand Up @@ -969,3 +970,21 @@ def test_overriden_host_version_transitive_deps(self):
# lock can be used
c.run("install app --lockfile=app/conan.lock")
c.assert_listed_require({"protobuf/1.1": "Cache"}, build=True)


def test_build_missing_build_requires():
c = TestClient()
c.save({"tooldep/conanfile.py": GenConanfile("tooldep", "0.1"),
"tool/conanfile.py": GenConanfile("tool", "0.1").with_tool_requires("tooldep/0.1"),
"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_tool_requires("tool/0.1"),
"app/conanfile.py": GenConanfile().with_requires("pkg/0.1")})
c.run("create tooldep")
c.run("create tool")
c.run("create pkg")
c.run("remove tool*:* -c")
c.run("install app")
assert "- Build" not in c.out
assert re.search(r"Skipped binaries(\s*)tool/0.1, tooldep/0.1", c.out)
c.run("install app --build=missing")
assert "- Build" not in c.out
assert re.search(r"Skipped binaries(\s*)tool/0.1, tooldep/0.1", c.out)
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ def _create(self, client):
client.run("export . --user=lasote --channel=stable")

def test_profile_requires(self):
"""
cli -(tool-requires)-> tool/0.1
\\--(requires)->mylib/0.1 -(tool_requires)->tool/0.1 (skipped)
"""
client = TestClient()
self._create(client)

Expand Down

0 comments on commit 0d55d67

Please sign in to comment.