diff --git a/conan/tools/cmake/cmakedeps.py b/conan/tools/cmake/cmakedeps.py index 27608c8b97e..304cf62644b 100644 --- a/conan/tools/cmake/cmakedeps.py +++ b/conan/tools/cmake/cmakedeps.py @@ -5,6 +5,7 @@ from conans.errors import ConanException from conans.model.build_info import CppInfo, merge_dicts +from conans.util.conan_v2_mode import conan_v2_error from conans.util.files import save COMPONENT_SCOPE = "::" @@ -560,12 +561,40 @@ def _check_component_in_requirements(require): for pkg_require in cpp_info.requires: _check_component_in_requirements(pkg_require) + def _get_name(self, cpp_info, pkg_name): + # FIXME: This is a workaround to be able to use existing recipes that declare + # FIXME: cpp_info.names["cmake_find_package_multi"] = "xxxxx" + name = cpp_info.names.get(self.name) + if name is not None: + return name + find_name = cpp_info.names.get("cmake_find_package_multi") + if find_name is not None: + # Not displaying a warning, too noisy as this is called many times + conan_v2_error("'{}' defines information for 'cmake_find_package_multi', " + "but not 'CMakeDeps'".format(pkg_name)) + return find_name + return cpp_info._name + + def _get_filename(self, cpp_info, pkg_name): + # FIXME: This is a workaround to be able to use existing recipes that declare + # FIXME: cpp_info.filenames["cmake_find_package_multi"] = "xxxxx" + name = cpp_info.filenames.get(self.name) + if name is not None: + return name + find_name = cpp_info.filenames.get("cmake_find_package_multi") + if find_name is not None: + # Not displaying a warning, too noisy as this is called many times + conan_v2_error("'{}' defines information for 'cmake_find_package_multi', " + "but not 'CMakeDeps'".format(pkg_name)) + return find_name + return cpp_info._name + def _get_require_name(self, pkg_name, req): pkg, cmp = req.split(COMPONENT_SCOPE) if COMPONENT_SCOPE in req else (pkg_name, req) pkg_cpp_info = self._conanfile.deps_cpp_info[pkg] - pkg_name = pkg_cpp_info.get_name(self.name) + pkg_name = self._get_name(pkg_cpp_info, pkg_name) if cmp in pkg_cpp_info.components: - cmp_name = pkg_cpp_info.components[cmp].get_name(self.name) + cmp_name = self._get_name(pkg_cpp_info.components[cmp], pkg_name) else: cmp_name = pkg_name return pkg_name, cmp_name @@ -575,7 +604,7 @@ def _get_components(self, pkg_name, cpp_info): sorted_comps = cpp_info._get_sorted_components() for comp_name, comp in sorted_comps.items(): - comp_genname = cpp_info.components[comp_name].get_name(self.name) + comp_genname = self._get_name(cpp_info.components[comp_name], pkg_name) comp_requires_gennames = [] for require in comp.requires: comp_requires_gennames.append(self._get_require_name(pkg_name, require)) @@ -617,8 +646,8 @@ def content(self): for pkg_name, cpp_info in self._conanfile.deps_cpp_info.dependencies: self._validate_components(cpp_info) - pkg_filename = cpp_info.get_filename(self.name) - pkg_findname = cpp_info.get_name(self.name) + pkg_filename = self._get_filename(cpp_info, pkg_name) + pkg_findname = self._get_name(cpp_info, pkg_name) pkg_version = cpp_info.version public_deps = self.get_public_deps(cpp_info) @@ -628,7 +657,8 @@ def content(self): if name not in deps_names: deps_names.append(name) deps_names = ';'.join(deps_names) - pkg_public_deps_filenames = [self._conanfile.deps_cpp_info[it[0]].get_filename(self.name) + pkg_public_deps_filenames = [self._get_filename(self._conanfile.deps_cpp_info[it[0]], + pkg_name) for it in public_deps] config_version = self.config_version_template.format(version=pkg_version) ret[self._config_version_filename(pkg_filename)] = config_version diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py index 98ed75b5ff9..36d43bb1685 100644 --- a/conans/client/migrations_settings.py +++ b/conans/client/migrations_settings.py @@ -2058,5 +2058,6 @@ """ settings_1_34_0 = settings_1_33_1 +settings_1_34_1 = settings_1_34_0 -settings_1_35_0 = settings_1_34_0 +settings_1_35_0 = settings_1_34_1 diff --git a/conans/model/lock_bundle.py b/conans/model/lock_bundle.py index 54bbe0497a0..fa0815e4d15 100644 --- a/conans/model/lock_bundle.py +++ b/conans/model/lock_bundle.py @@ -14,11 +14,12 @@ class LockBundle(object): products. The format is a json with: For every reference, for each "package_id" for that reference, list the lockfiles that contain such reference:package_id and the node indexes inside that lockfile - + lock_bundle": { "app1/0.1@#584778f98ba1d0eb7c80a5ae1fe12fe2": { - "package_id": { - "3bcd6800847f779e0883ee91b411aad9ddd8e83c": { + "packages": [ + { + "package_id": "3bcd6800847f779e0883ee91b411aad9ddd8e83c", "lockfiles": { "app1_windows.lock": [ "1" @@ -27,9 +28,7 @@ class LockBundle(object): "prev": null, "modified": null }, - "60fbb0a22359b4888f7ecad69bcdfcd6e70e2784": { - } - }, + ], "requires": [ "pkgb/0.1@#cd8f22d6f264f65398d8c534046e8e20", "tool/0.1@#f096d7d54098b7ad7012f9435d9c33f3" @@ -38,9 +37,6 @@ class LockBundle(object): """ def __init__(self): - """ The structure is - " - """ self._nodes = {} @staticmethod @@ -64,8 +60,16 @@ def ref_convert(r): ref_str = node.ref.full_str() ref_str = ref_convert(ref_str) ref_node = result._nodes.setdefault(ref_str, {}) - pids_node = ref_node.setdefault("package_id", {}) - pid_node = pids_node.setdefault(node.package_id, {}) + packages_node = ref_node.setdefault("packages", []) + # Find existing package_id in the list of packages + # This is the equivalent of a setdefault over a dict, but on a list + for pkg in packages_node: + if pkg["package_id"] == node.package_id: + pid_node = pkg + break + else: + pid_node = {"package_id": node.package_id} + packages_node.append(pid_node) ids = pid_node.setdefault("lockfiles", {}) # TODO: Add check that this prev is always the same pid_node["prev"] = node.prev @@ -95,16 +99,21 @@ def build_order(self): opened = list(self._nodes.keys()) while opened: current_level = [] + closed = [] for o in opened: node = self._nodes[o] requires = node.get("requires", []) - if not any(n in opened for n in requires): - current_level.append(o) - - current_level.sort() - levels.append(current_level) + if not any(n in opened for n in requires): # Doesn't have an open requires + # iterate all packages to see if some has prev=null + if any(pkg["prev"] is None for pkg in node["packages"]): + current_level.append(o) + closed.append(o) + + if current_level: + current_level.sort() + levels.append(current_level) # now initialize new level - opened = set(opened).difference(current_level) + opened = set(opened).difference(closed) return levels @@ -118,12 +127,12 @@ def update_bundle(bundle_path, revisions_enabled): bundle.loads(load(bundle_path)) for node in bundle._nodes.values(): # Each node in bundle belongs to a "ref", and contains lockinfo for every package_id - for bundle_package_ids in node["package_id"].values(): + for pkg in node["packages"]: # Each package_id contains information of multiple lockfiles # First, compute the modified PREV from all lockfiles prev = modified = prev_lockfile = None - for lockfile, nodes_ids in bundle_package_ids["lockfiles"].items(): + for lockfile, nodes_ids in pkg["lockfiles"].items(): graph_lock_conf = GraphLockFile.load(lockfile, revisions_enabled) graph_lock = graph_lock_conf.graph_lock @@ -139,11 +148,11 @@ def update_bundle(bundle_path, revisions_enabled): msg = "Lock mismatch for {} prev: {}:{} != {}:{}".format( ref, prev_lockfile, prev, lockfile, lock_prev) raise ConanException(msg) - bundle_package_ids["prev"] = prev - bundle_package_ids["modified"] = modified + pkg["prev"] = prev + pkg["modified"] = modified # Then, update all prev of all config lockfiles - for lockfile, nodes_ids in bundle_package_ids["lockfiles"].items(): + for lockfile, nodes_ids in pkg["lockfiles"].items(): graph_lock_conf = GraphLockFile.load(lockfile, revisions_enabled) graph_lock = graph_lock_conf.graph_lock for node_id in nodes_ids: diff --git a/conans/test/integration/graph_lock/test_lock_bundle.py b/conans/test/integration/graph_lock/test_lock_bundle.py index df40d2e63c3..8f524e4b390 100644 --- a/conans/test/integration/graph_lock/test_lock_bundle.py +++ b/conans/test/integration/graph_lock/test_lock_bundle.py @@ -58,9 +58,9 @@ def test_basic(): for level in order: for ref in level: # Now get the package_id, lockfile - pkg_ids = bundle[ref]["package_id"] - for pkg_id, lockfile_info in pkg_ids.items(): - lockfiles = lockfile_info["lockfiles"] + packages = bundle[ref]["packages"] + for pkg in packages: + lockfiles = pkg["lockfiles"] lockfile = next(iter(sorted(lockfiles))) client.run("install {ref} --build={ref} --lockfile={lockfile} " @@ -166,9 +166,9 @@ def test_build_requires(): for level in order: for ref in level: # Now get the package_id, lockfile - pkg_ids = bundle[ref]["package_id"] - for pkg_id, lockfile_info in pkg_ids.items(): - lockfiles = lockfile_info["lockfiles"] + packages = bundle[ref]["packages"] + for pkg in packages: + lockfiles = pkg["lockfiles"] lockfile = next(iter(sorted(lockfiles))) client.run("install {ref} --build={ref} --lockfile={lockfile} " @@ -222,3 +222,35 @@ def test_build_requires(): assert nodes[n].ref.full_str() == "tool/0.1#f096d7d54098b7ad7012f9435d9c33f3" assert nodes[n].package_id == "cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31" assert nodes[n].prev == "9e99cfd92d0d7df79d687b01512ce844" + + +def test_build_requires_error(): + # https://github.com/conan-io/conan/issues/8577 + client = TestClient() + # TODO: This is hardcoded + client.run("config set general.revisions_enabled=1") + client.save({"tool/conanfile.py": GenConanfile().with_settings("os"), + "pkga/conanfile.py": GenConanfile().with_settings("os"), + "app1/conanfile.py": GenConanfile().with_settings("os").with_requires("pkga/0.1"), + "profile": "[build_requires]\ntool/0.1"}) + client.run("create tool tool/0.1@ -s os=Windows") + client.run("create tool tool/0.1@ -s os=Linux") + client.run("export pkga pkga/0.1@") + client.run("export app1 app1/0.1@") + + client.run("lock create --ref=app1/0.1 -pr=profile -s os=Windows " + "--lockfile-out=app1_windows.lock --build=missing") + assert "tool/0.1:3475bd55b91ae904ac96fde0f106a136ab951a5e - Cache" in client.out + client.run("lock create --ref=app1/0.1 -pr=profile -s os=Linux " + "--lockfile-out=app1_linux.lock --build=missing") + assert "tool/0.1:cb054d0b3e1ca595dc66bc2339d40f1f8f04ab31 - Cache" in client.out + + client.run("lock bundle create app1_windows.lock app1_linux.lock --bundle-out=lock1.bundle") + client.run("lock bundle build-order lock1.bundle --json=bo.json") + order = client.load("bo.json") + print(order) + order = json.loads(order) + assert order == [ + ["pkga/0.1@#f096d7d54098b7ad7012f9435d9c33f3"], + ["app1/0.1@#5af607abc205b47375f485a98abc3b38"] + ] diff --git a/conans/test/unittests/tools/cmake/test_cmakedeps.py b/conans/test/unittests/tools/cmake/test_cmakedeps.py new file mode 100644 index 00000000000..99b96cc1ef9 --- /dev/null +++ b/conans/test/unittests/tools/cmake/test_cmakedeps.py @@ -0,0 +1,57 @@ +import pytest +from mock import Mock + +from conan.tools.cmake import CMakeDeps +from conans import ConanFile, Settings +from conans.client.tools import environment_append +from conans.errors import ConanException +from conans.model.build_info import CppInfo, DepCppInfo +from conans.model.env_info import EnvValues +from conans.util.conan_v2_mode import CONAN_V2_MODE_ENVVAR + + +def test_cpp_info_name_cmakedeps(): + conanfile = ConanFile(Mock(), None) + conanfile.settings = "os", "compiler", "build_type", "arch" + conanfile.initialize(Settings({"os": ["Windows"], + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["x86"]}), EnvValues()) + + cpp_info = CppInfo("mypkg", "dummy_root_folder1") + cpp_info.names["cmake_find_package_multi"] = "MySuperPkg1" + cpp_info.filenames["cmake_find_package_multi"] = "ComplexFileName1" + conanfile.deps_cpp_info.add("mypkg", cpp_info) + + cmakedeps = CMakeDeps(conanfile) + files = cmakedeps.content + assert "TARGET MySuperPkg1::MySuperPkg1" in files["ComplexFileName1Config.cmake"] + + with pytest.raises(ConanException, + match="'mypkg' defines information for 'cmake_find_package_multi'"): + with environment_append({CONAN_V2_MODE_ENVVAR: "1"}): + _ = cmakedeps.content + + +def test_cpp_info_name_cmakedeps_components(): + conanfile = ConanFile(Mock(), None) + conanfile.settings = "os", "compiler", "build_type", "arch" + conanfile.initialize(Settings({"os": ["Windows"], + "compiler": ["gcc"], + "build_type": ["Release"], + "arch": ["x86"]}), EnvValues()) + + cpp_info = CppInfo("mypkg", "dummy_root_folder1") + cpp_info.names["cmake_find_package_multi"] = "GlobakPkgName1" + cpp_info.components["mycomp"].names["cmake_find_package_multi"] = "MySuperPkg1" + cpp_info.filenames["cmake_find_package_multi"] = "ComplexFileName1" + conanfile.deps_cpp_info.add("mypkg", DepCppInfo(cpp_info)) + + cmakedeps = CMakeDeps(conanfile) + files = cmakedeps.content + assert "TARGET GlobakPkgName1::MySuperPkg1" in files["ComplexFileName1Config.cmake"] + + with pytest.raises(ConanException, + match="'mypkg' defines information for 'cmake_find_package_multi'"): + with environment_append({CONAN_V2_MODE_ENVVAR: "1"}): + _ = cmakedeps.content