Skip to content
Draft
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
2 changes: 2 additions & 0 deletions conan/internal/graph/compute_pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def compute_package_id(node, modes, config_version, hook_manager):
data = OrderedDict()
build_data = OrderedDict()
for require, transitive in node.transitive_deps.items():
if require.vendor:
continue
dep_node = transitive.node
require.deduce_package_id_mode(conanfile.package_type, dep_node,
non_embed_mode, embed_mode, build_mode, unknown_mode)
Expand Down
4 changes: 2 additions & 2 deletions conan/internal/graph/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def propagate_downstream(self, require, node, visibility_conflicts, src_node=Non
e.dst = node
break

if self.conanfile.vendor:
if self.conanfile.vendor or require.vendor:
return
# Check if need to propagate downstream
if not self.dependants:
Expand Down Expand Up @@ -199,7 +199,7 @@ def check_downstream_exists(self, require):
# Check if need to propagate downstream
# Then propagate downstream

if self.conanfile.vendor:
if self.conanfile.vendor or require.vendor:
return result
# Seems the algrithm depth-first, would only have 1 dependant at most to propagate down
# at any given time
Expand Down
4 changes: 3 additions & 1 deletion conan/internal/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ def _evaluate_node(self, node, build_mode, remotes, update):

if node.binary == BINARY_BUILD:
conanfile = node.conanfile
if conanfile.vendor and not conanfile.conf.get("tools.graph:vendor", choices=("build",)):
req_vendor = any(r.vendor for r in node.conanfile.requires.values())
if (conanfile.vendor or req_vendor) and not conanfile.conf.get("tools.graph:vendor",
choices=("build",)):
node.conanfile.info.invalid = f"The package '{conanfile.ref}' is a vendoring one, " \
f"needs to be built from source, but it " \
"didn't enable 'tools.graph:vendor=build' to compute" \
Expand Down
6 changes: 5 additions & 1 deletion conan/internal/graph/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None):
(require, node) = open_requires.popleft()
if require.override:
continue
if require.vendor and not node.conanfile.conf.get("tools.graph:vendor",
choices=("build",)):
continue
new_node = self._expand_require(require, node, dep_graph, profile_host,
profile_build, graph_lock)
if new_node and (not new_node.conanfile.vendor
Expand Down Expand Up @@ -450,7 +453,8 @@ def _compute_down_options(node, require, new_ref):
@staticmethod
def _remove_overrides(dep_graph):
for node in dep_graph.nodes:
to_remove = [r for r in node.transitive_deps if r.override]
build = node.conanfile.conf.get("tools.graph:vendor", choices=("build",))
to_remove = [r for r in node.transitive_deps if r.override or (r.vendor and not build)]
for r in to_remove:
node.transitive_deps.pop(r)

Expand Down
3 changes: 2 additions & 1 deletion conan/internal/model/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Requirement:
"""
def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visible=None,
transitive_headers=None, transitive_libs=None, test=None, package_id_mode=None,
force=None, override=None, direct=None, options=None, no_skip=False):
force=None, override=None, direct=None, options=None, no_skip=False, vendor=False):
# * prevents the usage of more positional parameters, always ref + **kwargs
# By default this is a generic library requirement
self.ref = ref
Expand Down Expand Up @@ -37,6 +37,7 @@ def __init__(self, ref, *, headers=None, libs=None, build=False, run=None, visib
self.skip = False
self.required_nodes = set() # store which intermediate nodes are required, to compute "Skip"
self.no_skip = no_skip
self.vendor = vendor

@property
def files(self): # require needs some files in dependency package
Expand Down
83 changes: 83 additions & 0 deletions test/integration/test_package_vendor.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,86 @@ def package(self):
assert "pkga" not in c.out
assert c.load("full_deploy/host/app/0.1/app.exe") == "app"
assert c.load("full_deploy/host/app/0.1/pkga.dll") == "dll"


def test_requirement_vendor():
c = TestClient()
app = textwrap.dedent("""
import os
from conan import ConanFile
from conan.tools.files import copy, save

class App(ConanFile):
name = "app"
version = "0.1"
package_type = "application"

def requirements(self):
self.requires("pkga/0.1", vendor=True)
self.requires("pkgb/0.1")

def package(self):
copy(self, "*", src=self.dependencies["pkga"].package_folder,
dst=self.package_folder)
save(self, os.path.join(self.package_folder, "app.exe"), "app")
""")

c.save({"pkga/conanfile.py": GenConanfile("pkga", "0.1").with_package_type("shared-library")
.with_package_file("pkga.dll", "dll"),

"pkgb/conanfile.py": GenConanfile("pkgb", "0.1").with_package_type("shared-library")
.with_package_file("pkgb.dll", "dll"),
"app/conanfile.py": app
})
c.run("create pkga")
c.run("create pkgb")
c.run("create app -c &:tools.graph:vendor=build") # NOT automatic
assert "app/0.1: package(): Packaged 1 '.dll' file: pkga.dll" in c.out
assert "pkgb.dll" not in c.out

# we can safely remove pkga
c.run("remove pkga* -c")
c.run("list app:*")
assert "pkga" not in c.out # The binary doesn't depend on pkga
assert "pkgb/0.1.Z" in c.out

c.run("install --requires=app/0.1 --deployer=full_deploy")
assert "pkga" not in c.out
assert c.load("full_deploy/host/app/0.1/app.exe") == "app"
assert c.load("full_deploy/host/app/0.1/pkga.dll") == "dll"

# we can create a modified pkga
c.save({"pkga/conanfile.py": GenConanfile("pkga", "0.1").with_package_type("shared-library")
.with_package_file("pkga.dll", "newdll")})
c.run("create pkga")
# still using the re-packaged one
c.run("install --requires=app/0.1 --deployer=full_deploy")
assert "pkga" not in c.out
assert c.load("full_deploy/host/app/0.1/app.exe") == "app"
assert c.load("full_deploy/host/app/0.1/pkga.dll") == "dll"

# but we can force the expansion, still not the rebuild
c.run("install --requires=app/0.1 --deployer=full_deploy -c tools.graph:vendor=build")
assert "pkga" in c.out
assert c.load("full_deploy/host/app/0.1/app.exe") == "app"
assert c.load("full_deploy/host/app/0.1/pkga.dll") == "dll"

# and finally we can force the expansion and the rebuild
c.run("install --requires=app/0.1 --build=app* --deployer=full_deploy "
"-c tools.graph:vendor=build")
assert "pkga" in c.out
assert c.load("full_deploy/host/app/0.1/app.exe") == "app"
assert c.load("full_deploy/host/app/0.1/pkga.dll") == "newdll"
# This shoulnd't happen, no visibility over transitive dependencies of app
assert not os.path.exists(os.path.join(c.current_folder, "full_deploy", "host", "pkga"))

# lets remove the binary
c.run("remove app:* -c")
c.run("install --requires=app/0.1", assert_error=True)
assert "Missing binary" in c.out
c.run("install --requires=app/0.1 --build=missing", assert_error=True)
assert "app/0.1: Invalid: The package 'app/0.1' is a vendoring one, needs to be built " \
"from source, but it didn't enable 'tools.graph:vendor=build'" in c.out

c.run("install --requires=app/0.1 --build=missing -c tools.graph:vendor=build")
assert "pkga" in c.out # it works
Loading