From 658916bc00555049a8a903d8ea5da05ab598e005 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 11 Apr 2023 14:14:58 +0200 Subject: [PATCH] extend --build-require to more commands and cases (#13669) --- conan/api/subapi/graph.py | 17 +++-- conan/cli/commands/graph.py | 5 +- conan/cli/commands/install.py | 5 +- conan/cli/commands/lock.py | 5 +- conans/client/graph/graph_builder.py | 4 -- conans/client/graph/profile_node_definer.py | 21 +++--- .../lockfile/test_lock_build_requires.py | 65 +++++++++++++++++++ 7 files changed, 100 insertions(+), 22 deletions(-) diff --git a/conan/api/subapi/graph.py b/conan/api/subapi/graph.py index 41990cf3478..8147e57f97f 100644 --- a/conan/api/subapi/graph.py +++ b/conan/api/subapi/graph.py @@ -2,7 +2,8 @@ from conan.api.output import ConanOutput from conan.internal.conan_app import ConanApp -from conans.client.graph.graph import Node, RECIPE_CONSUMER, CONTEXT_HOST, RECIPE_VIRTUAL +from conans.client.graph.graph import Node, RECIPE_CONSUMER, CONTEXT_HOST, RECIPE_VIRTUAL, \ + CONTEXT_BUILD from conans.client.graph.graph_binaries import GraphBinariesAnalyzer from conans.client.graph.graph_builder import DepsGraphBuilder from conans.client.graph.profile_node_definer import initialize_conanfile_profile, consumer_definer @@ -35,15 +36,16 @@ def _load_root_consumer_conanfile(self, path, profile_host, profile_build, update=update) ref = RecipeReference(conanfile.name, conanfile.version, conanfile.user, conanfile.channel) - initialize_conanfile_profile(conanfile, profile_build, profile_host, CONTEXT_HOST, + context = CONTEXT_BUILD if is_build_require else CONTEXT_HOST + initialize_conanfile_profile(conanfile, profile_build, profile_host, context, is_build_require, ref) if ref.name: profile_host.options.scope(ref) - root_node = Node(ref, conanfile, context=CONTEXT_HOST, recipe=RECIPE_CONSUMER, path=path) + root_node = Node(ref, conanfile, context=context, recipe=RECIPE_CONSUMER, path=path) root_node.should_build = True # It is a consumer, this is something we are building else: conanfile = app.loader.load_conanfile_txt(path) - consumer_definer(conanfile, profile_host) + consumer_definer(conanfile, profile_host, profile_build) root_node = Node(None, conanfile, context=CONTEXT_HOST, recipe=RECIPE_CONSUMER, path=path) return root_node @@ -86,12 +88,12 @@ def load_root_test_conanfile(self, path, tested_reference, profile_host, profile root_node = Node(ref, conanfile, recipe=RECIPE_CONSUMER, context=CONTEXT_HOST, path=path) return root_node - def _load_root_virtual_conanfile(self, profile_host, requires=None, tool_requires=None): + def _load_root_virtual_conanfile(self, profile_host, profile_build, requires, tool_requires): if not requires and not tool_requires: raise ConanException("Provide requires or tool_requires") app = ConanApp(self.conan_api.cache_folder) conanfile = app.loader.load_virtual(requires=requires, tool_requires=tool_requires) - consumer_definer(conanfile, profile_host) + consumer_definer(conanfile, profile_host, profile_build) root_node = Node(ref=None, conanfile=conanfile, context=CONTEXT_HOST, recipe=RECIPE_VIRTUAL) return root_node @@ -117,7 +119,8 @@ def load_graph_requires(self, requires, tool_requires, profile_host, profile_bui self._scope_options(profile_host, requires=requires, tool_requires=tool_requires) root_node = self._load_root_virtual_conanfile(requires=requires, tool_requires=tool_requires, - profile_host=profile_host) + profile_host=profile_host, + profile_build=profile_build) # check_updates = args.check_updates if "check_updates" in args else False deps_graph = self.load_graph(root_node, profile_host=profile_host, diff --git a/conan/cli/commands/graph.py b/conan/cli/commands/graph.py index c5234ef8ba4..b3ca1c7161a 100644 --- a/conan/cli/commands/graph.py +++ b/conan/cli/commands/graph.py @@ -118,6 +118,8 @@ def graph_info(conan_api, parser, subparser, *args): help='Print information only for packages that match the patterns') subparser.add_argument("--deploy", action="append", help='Deploy using the provided deployer to the output folder') + subparser.add_argument("--build-require", action='store_true', default=False, + help='Whether the provided reference is a build-require') args = parser.parse_args(*args) # parameter validation @@ -141,7 +143,8 @@ def graph_info(conan_api, parser, subparser, *args): args.user, args.channel, profile_host, profile_build, lockfile, remotes, args.update, - check_updates=args.check_updates) + check_updates=args.check_updates, + is_build_require=args.build_require) else: deps_graph = conan_api.graph.load_graph_requires(args.requires, args.tool_requires, profile_host, profile_build, lockfile, diff --git a/conan/cli/commands/install.py b/conan/cli/commands/install.py index 5f00ac81907..d4b3b79c8b0 100644 --- a/conan/cli/commands/install.py +++ b/conan/cli/commands/install.py @@ -36,6 +36,8 @@ def install(conan_api, parser, *args): help='The root output folder for generated and build files') parser.add_argument("--deploy", action="append", help='Deploy using the provided deployer to the output folder') + parser.add_argument("--build-require", action='store_true', default=False, + help='Whether the provided reference is a build-require') args = parser.parse_args(*args) validate_common_graph_args(args) @@ -64,7 +66,8 @@ def install(conan_api, parser, *args): deps_graph = conan_api.graph.load_graph_consumer(path, args.name, args.version, args.user, args.channel, profile_host, profile_build, lockfile, - remotes, args.update) + remotes, args.update, + is_build_require=args.build_require) else: deps_graph = conan_api.graph.load_graph_requires(args.requires, args.tool_requires, profile_host, profile_build, lockfile, diff --git a/conan/cli/commands/lock.py b/conan/cli/commands/lock.py index 90ed80aa47a..ac1e546181c 100644 --- a/conan/cli/commands/lock.py +++ b/conan/cli/commands/lock.py @@ -23,6 +23,8 @@ def lock_create(conan_api, parser, subparser, *args): Create a lockfile from a conanfile or a reference. """ common_graph_args(subparser) + subparser.add_argument("--build-require", action='store_true', default=False, + help='Whether the provided reference is a build-require') args = parser.parse_args(*args) # parameter validation @@ -39,7 +41,8 @@ def lock_create(conan_api, parser, subparser, *args): graph = conan_api.graph.load_graph_consumer(path, args.name, args.version, args.user, args.channel, profile_host, profile_build, lockfile, - remotes, args.build, args.update) + remotes, args.build, args.update, + is_build_require=args.build_require) else: graph = conan_api.graph.load_graph_requires(args.requires, args.tool_requires, profile_host, profile_build, lockfile, diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 695e962f81b..9b2981ac1f5 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -36,10 +36,6 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): # print("Loading graph") dep_graph = DepsGraph() - # TODO: Why assign here the settings_build and settings_target? - root_node.conanfile.settings_build = profile_build.processed_settings.copy() - root_node.conanfile.settings_target = None - self._prepare_node(root_node, profile_host, profile_build, Options()) self._initialize_requires(root_node, dep_graph, graph_lock) dep_graph.add_node(root_node) diff --git a/conans/client/graph/profile_node_definer.py b/conans/client/graph/profile_node_definer.py index 3bbf3cfbc34..3a5cd689d8a 100644 --- a/conans/client/graph/profile_node_definer.py +++ b/conans/client/graph/profile_node_definer.py @@ -1,4 +1,4 @@ -from conans.client.graph.graph import CONTEXT_HOST, CONTEXT_BUILD +from conans.client.graph.graph import CONTEXT_BUILD from conans.errors import ConanException from conans.model.recipe_ref import ref_matches @@ -28,12 +28,8 @@ def initialize_conanfile_profile(conanfile, profile_build, profile_host, base_co conanfile.settings_build = profile_build.processed_settings.copy() # profile target conanfile.settings_target = None - if base_context == CONTEXT_HOST: - if is_build_require: - conanfile.settings_target = profile_host.processed_settings.copy() - else: - if not is_build_require: - conanfile.settings_target = profile_build.processed_settings.copy() + if is_build_require or base_context == CONTEXT_BUILD: + conanfile.settings_target = profile_host.processed_settings.copy() def _initialize_conanfile(conanfile, profile, ref): @@ -65,7 +61,7 @@ def _initialize_conanfile(conanfile, profile, ref): conanfile.conf = profile.conf.get_conanfile_conf(ref, conanfile._conan_is_consumer) # Maybe this can be done lazy too -def consumer_definer(conanfile, profile_host): +def consumer_definer(conanfile, profile_host, profile_build): """ conanfile.txt does not declare settings, but it assumes it uses all the profile settings These settings are very necessary for helpers like generators to produce the right output """ @@ -76,7 +72,16 @@ def consumer_definer(conanfile, profile_host): if ref_matches(ref=None, pattern=pattern, is_consumer=True): tmp_settings.update_values(settings) + tmp_settings_build = profile_build.processed_settings.copy() + package_settings_values_build = profile_build.package_settings_values + + for pattern, settings in package_settings_values_build.items(): + if ref_matches(ref=None, pattern=pattern, is_consumer=True): + tmp_settings_build.update_values(settings) + conanfile.settings = tmp_settings + conanfile.settings_build = tmp_settings_build + conanfile.settings_target = None conanfile.settings._frozen = True conanfile._conan_buildenv = profile_host.buildenv conanfile._conan_runenv = profile_host.runenv diff --git a/conans/test/integration/lockfile/test_lock_build_requires.py b/conans/test/integration/lockfile/test_lock_build_requires.py index 25c55a6e6db..05a8d3c9403 100644 --- a/conans/test/integration/lockfile/test_lock_build_requires.py +++ b/conans/test/integration/lockfile/test_lock_build_requires.py @@ -1,4 +1,5 @@ import json +import textwrap from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient @@ -52,3 +53,67 @@ def test_lock_buildrequires_create_transitive(): lock = json.loads(c.load("conan.lock")) assert "tool/0.1#e4f0da4d9097c4da0725ea25b8bf83c8" in lock["build_requires"][0] assert "dep/0.1#f8c2264d0b32a4c33f251fe2944bb642" in lock["build_requires"][1] + + +def test_lock_create_build_require_transitive(): + """ cross compiling from Windows to Linux + """ + c = TestClient() + dep = textwrap.dedent(""" + from conan import ConanFile + class Dep(ConanFile): + name = "dep" + settings = "os" + def package_id(self): + self.output.info(f"MYOS:{self.info.settings.os}!!") + self.output.info(f"TARGET:{self.settings_target.os}!!") + """) + tool = textwrap.dedent(""" + from conan import ConanFile + class Tool(ConanFile): + name = "tool" + version = "0.1" + requires = "dep/[*]" + settings = "os" + def generate(self): + self.output.info(f"MYOS-GEN:{self.info.settings.os}!!") + self.output.info(f"TARGET-GEN:{self.settings_target.os}!!") + def package_id(self): + self.output.info(f"MYOS:{self.info.settings.os}!!") + self.output.info(f"TARGET:{self.settings_target.os}!!") + """) + c.save({"dep/conanfile.py": dep, + "tool/conanfile.py": tool}) + c.run("create dep --build-require --version=0.1 -s:b os=Windows -s:h os=Linux") + assert "dep/0.1: MYOS:Windows!!" in c.out + assert "dep/0.1: TARGET:Linux!!" in c.out + + # The lockfile should contain dep in build-requires + c.run("lock create tool --build-require -s:b os=Windows -s:h os=Linux") + assert "dep/0.1: MYOS:Windows!!" in c.out + assert "dep/0.1: TARGET:Linux!!" in c.out + lock = json.loads(c.load("tool/conan.lock")) + assert "dep/0.1" in lock["build_requires"][0] + + # Now try to apply it in graph info, even if a new 0.2 verion si there + c.run("create dep --build-require --version=0.2 -s:b os=Windows -s:h os=Linux") + assert "dep/0.2: MYOS:Windows!!" in c.out + assert "dep/0.2: TARGET:Linux!!" in c.out + + c.run("graph info tool --build-require -s:b os=Windows -s:h os=Linux") + c.assert_listed_require({"dep/0.1": "Cache"}, build=True) + assert "dep/0.1: MYOS:Windows!!" in c.out + assert "dep/0.1: TARGET:Linux!!" in c.out + assert "conanfile.py (tool/0.1): MYOS:Windows!!" in c.out + assert "conanfile.py (tool/0.1): TARGET:Linux!!" in c.out + assert "context: build" in c.out + assert "context: host" not in c.out + + c.run("install tool --build-require -s:b os=Windows -s:h os=Linux") + c.assert_listed_require({"dep/0.1": "Cache"}, build=True) + assert "dep/0.1: MYOS:Windows!!" in c.out + assert "dep/0.1: TARGET:Linux!!" in c.out + assert "conanfile.py (tool/0.1): MYOS:Windows!!" in c.out + assert "conanfile.py (tool/0.1): TARGET:Linux!!" in c.out + assert "conanfile.py (tool/0.1): MYOS-GEN:Windows!!" in c.out + assert "conanfile.py (tool/0.1): TARGET-GEN:Linux!!" in c.out