From d5faf01aae90c829333d56474808b52394808d47 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 3 Apr 2024 20:26:12 +0200 Subject: [PATCH 01/19] wip --- conan/tools/cps/__init__.py | 1 + conan/tools/cps/cps_deps.py | 98 +++++++++++++++++++++++++ conans/client/generators/__init__.py | 3 +- conans/test/integration/cps/__init__.py | 0 conans/test/integration/cps/test_cps.py | 10 +++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 conan/tools/cps/__init__.py create mode 100644 conan/tools/cps/cps_deps.py create mode 100644 conans/test/integration/cps/__init__.py create mode 100644 conans/test/integration/cps/test_cps.py diff --git a/conan/tools/cps/__init__.py b/conan/tools/cps/__init__.py new file mode 100644 index 00000000000..64674e37160 --- /dev/null +++ b/conan/tools/cps/__init__.py @@ -0,0 +1 @@ +from conan.tools.cps.cps_deps import CPSDeps diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py new file mode 100644 index 00000000000..c14cdf83cdd --- /dev/null +++ b/conan/tools/cps/cps_deps.py @@ -0,0 +1,98 @@ +from conan.tools.apple import is_apple_os +from conan.tools.files import save + +import glob +import json +import os + + +class CPSDeps: + def __init__(self, conanfile): + self.conanfile = conanfile + + _package_type_map = { + "shared-library": "dylib", + "static-library": "archive", + "header-library": "interface" + } + + def find_library(self, libdirs, bindirs, name, shared, dll): + libdirs = [x.replace("\\", "/") for x in libdirs] + bindirs = [x.replace("\\", "/") for x in bindirs] + libname = name[0] # assume one library per component + if shared and not dll: + patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", + f"lib{libname}.*dylib", f"{libname}.lib"] + elif shared and dll: + patterns = [f"{libname}.dll"] + else: + patterns = [f"lib{libname}.a", f"{libname}.lib"] + + matches = set() + search_dirs = bindirs if self.conanfile.settings.os == "Windows" and shared and dll else libdirs + for folder in search_dirs: + for pattern in patterns: + glob_expr = f"{folder}/{pattern}" + matches.update(glob.glob(glob_expr)) + + if len(matches) == 1: + return next(iter(matches)) + elif len(matches) >= 1: + # assume at least one is not a symlink + return [x for x in list(matches) if not os.path.islink(x)][0] + else: + self.conanfile.output.error(f"[CPSDeps] Could not locate library: {libname}") + return None + + def generate(self): + self.conanfile.output.info(f"[CPSDeps] generators folder {self.conanfile.generators_folder}") + deps = self.conanfile.dependencies.host.items() + for _, dep in deps: + self.conanfile.output.info(f"[CPSDeps]: dep {dep.ref.name}") + + cps = {"Cps-Version": "0.8.1", + "Name": dep.ref.name, + "Version": str(dep.ref.version)} + """ + "Platform": { + "Isa": "arm64" if self.conanfile.settings.arch == "armv8" else self.conanfile.settings.arch, + "Kernel": "darwin" if is_apple_os(self.conanfile) else str( + self.conanfile.settings.os).lower() + }} + """ + build_type = str(self.conanfile.settings.build_type).lower() + cps["Configurations"] = [build_type] + + if not dep.cpp_info.has_components: + # single component, called same as library + component = {} + component["Type"] = self._package_type_map.get(str(dep.package_type), "unknown") + component["Definitions"] = dep.cpp_info.defines + component["Includes"] = [x.replace("\\", "/") for x in dep.cpp_info.includedirs] + + """is_shared = dep.package_type == "shared-library" + if is_shared and self.conanfile.settings.os == "Windows": + dll_location = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, + dep.cpp_info.libs, is_shared, dll=True) + import_library = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, + dep.cpp_info.libs, is_shared, dll=False) + component["Configurations"] = { + build_type: {'Location': dll_location, 'Link-Location': import_library}} + else: + library_location = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, + dep.cpp_info.libs, is_shared, dll=False) + component["Configurations"] = {build_type: {'Location': library_location}} + + if dep.dependencies: + for transitive_dep in dep.dependencies.items(): + self.conanfile.output.info( + f"[CPSGEN] {dep} has a dependency on: {transitive_dep[0].ref}") + dep_name = transitive_dep[0].ref.name + component["Requires"] = [f"{dep_name}:{dep_name}"] +""" + cps["Default-Components"] = [f"{dep.ref.name}"] + cps["Components"] = {f"{dep.ref.name}": component} + + output_file = os.path.join(self.conanfile.generators_folder, f"{dep.ref.name}.cps") + cps_json = json.dumps(cps, indent=4) + save(self.conanfile, output_file, cps_json) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 749e9ec2c71..02e600f78a9 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -23,7 +23,8 @@ "XcodeDeps": "conan.tools.apple", "XcodeToolchain": "conan.tools.apple", "PremakeDeps": "conan.tools.premake", "MakeDeps": "conan.tools.gnu", - "SConsDeps": "conan.tools.scons" + "SConsDeps": "conan.tools.scons", + "CPSDeps": "conan.tools.cps" } diff --git a/conans/test/integration/cps/__init__.py b/conans/test/integration/cps/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/conans/test/integration/cps/test_cps.py b/conans/test/integration/cps/test_cps.py new file mode 100644 index 00000000000..3a314863b50 --- /dev/null +++ b/conans/test/integration/cps/test_cps.py @@ -0,0 +1,10 @@ +from conans.test.assets.genconanfile import GenConanfile +from conans.test.utils.tools import TestClient + + +def test_cps(): + c = TestClient() + c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1")}) + c.run("create pkg") + c.run("install --requires=pkg/0.1 -g CPSDeps") + print(c.out) From 773122bbd59859db2f0218eb49eaaec53f67153f Mon Sep 17 00:00:00 2001 From: memsharded Date: Thu, 4 Apr 2024 12:02:46 +0200 Subject: [PATCH 02/19] wip --- conan/tools/cps/cps_deps.py | 62 +++++++++++++------------ conans/test/integration/cps/test_cps.py | 12 ++++- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index c14cdf83cdd..e406575e49d 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -1,4 +1,3 @@ -from conan.tools.apple import is_apple_os from conan.tools.files import save import glob @@ -44,6 +43,30 @@ def find_library(self, libdirs, bindirs, name, shared, dll): self.conanfile.output.error(f"[CPSDeps] Could not locate library: {libname}") return None + def _component(self, package_type, cpp_info, build_type): + component = {} + component["Type"] = self._package_type_map.get(str(package_type), "unknown") + component["Definitions"] = cpp_info.defines + component["Includes"] = [x.replace("\\", "/") for x in cpp_info.includedirs] + + if not cpp_info.libs: # No compiled libraries, header-only + return component + is_shared = package_type == "shared-library" + if is_shared and self.conanfile.settings.os == "Windows": + dll_location = self.find_library(cpp_info.libdirs, cpp_info.bindirs, + cpp_info.libs, is_shared, dll=True) + import_library = self.find_library(cpp_info.libdirs, cpp_info.bindirs, + cpp_info.libs, is_shared, dll=False) + locations = {'Location': dll_location, + 'Link-Location': import_library} + component["Configurations"] = {build_type: locations} # noqa + elif package_type == "static-library": + library_location = self.find_library(cpp_info.libdirs, cpp_info.bindirs, + cpp_info.libs, is_shared, dll=False) + component["Configurations"] = {build_type: {'Location': library_location}} + + return component + def generate(self): self.conanfile.output.info(f"[CPSDeps] generators folder {self.conanfile.generators_folder}") deps = self.conanfile.dependencies.host.items() @@ -53,45 +76,26 @@ def generate(self): cps = {"Cps-Version": "0.8.1", "Name": dep.ref.name, "Version": str(dep.ref.version)} - """ - "Platform": { - "Isa": "arm64" if self.conanfile.settings.arch == "armv8" else self.conanfile.settings.arch, - "Kernel": "darwin" if is_apple_os(self.conanfile) else str( - self.conanfile.settings.os).lower() - }} - """ + build_type = str(self.conanfile.settings.build_type).lower() cps["Configurations"] = [build_type] if not dep.cpp_info.has_components: # single component, called same as library - component = {} - component["Type"] = self._package_type_map.get(str(dep.package_type), "unknown") - component["Definitions"] = dep.cpp_info.defines - component["Includes"] = [x.replace("\\", "/") for x in dep.cpp_info.includedirs] - - """is_shared = dep.package_type == "shared-library" - if is_shared and self.conanfile.settings.os == "Windows": - dll_location = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, - dep.cpp_info.libs, is_shared, dll=True) - import_library = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, - dep.cpp_info.libs, is_shared, dll=False) - component["Configurations"] = { - build_type: {'Location': dll_location, 'Link-Location': import_library}} - else: - library_location = self.find_library(dep.cpp_info.libdirs, dep.cpp_info.bindirs, - dep.cpp_info.libs, is_shared, dll=False) - component["Configurations"] = {build_type: {'Location': library_location}} - + component = self._component(dep.package_type, dep.cpp_info, build_type) if dep.dependencies: for transitive_dep in dep.dependencies.items(): - self.conanfile.output.info( - f"[CPSGEN] {dep} has a dependency on: {transitive_dep[0].ref}") dep_name = transitive_dep[0].ref.name component["Requires"] = [f"{dep_name}:{dep_name}"] -""" + cps["Default-Components"] = [f"{dep.ref.name}"] cps["Components"] = {f"{dep.ref.name}": component} + else: + sorted_comps = dep.cpp_info.get_sorted_components() + for comp_name, comp in sorted_comps.items(): + component = self._component(dep.package_type, comp, build_type) + cps.setdefault("Components", {})[comp_name] = component + cps["Default-Components"] = [comp_name for comp_name in sorted_comps] output_file = os.path.join(self.conanfile.generators_folder, f"{dep.ref.name}.cps") cps_json = json.dumps(cps, indent=4) diff --git a/conans/test/integration/cps/test_cps.py b/conans/test/integration/cps/test_cps.py index 3a314863b50..0a7c02d4350 100644 --- a/conans/test/integration/cps/test_cps.py +++ b/conans/test/integration/cps/test_cps.py @@ -1,3 +1,5 @@ +import json + from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient @@ -6,5 +8,13 @@ def test_cps(): c = TestClient() c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1")}) c.run("create pkg") + c.run("install --requires=pkg/0.1 -g CPSDeps") - print(c.out) + pkg = json.loads(c.load("pkg.cps")) + print(json.dumps(pkg, indent=2)) + assert pkg["Name"] == "pkg" + assert pkg["Version"] == "0.1" + assert pkg["Configurations"] == ["release"] + assert pkg["Name"] == "pkg" + assert pkg["Default-Components"] == ["pkg"] + pkg_comp = pkg["Components"]["pkg"] From b5c89c1b1b6781b764a8ad459a89f091af91d7a5 Mon Sep 17 00:00:00 2001 From: memsharded Date: Fri, 26 Apr 2024 12:22:05 +0200 Subject: [PATCH 03/19] wip --- conan/tools/cps/cps_deps.py | 15 ++++++++ conans/test/integration/cps/test_cps.py | 50 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index e406575e49d..9dcf170c2c2 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -70,9 +70,15 @@ def _component(self, package_type, cpp_info, build_type): def generate(self): self.conanfile.output.info(f"[CPSDeps] generators folder {self.conanfile.generators_folder}") deps = self.conanfile.dependencies.host.items() + mapping = {} for _, dep in deps: self.conanfile.output.info(f"[CPSDeps]: dep {dep.ref.name}") + cps_in_package = os.path.join(dep.package_folder, f"{dep.ref.name}.cps") + if os.path.exists(cps_in_package): + mapping[dep.ref.name] = cps_in_package + continue + cps = {"Cps-Version": "0.8.1", "Name": dep.ref.name, "Version": str(dep.ref.version)} @@ -100,3 +106,12 @@ def generate(self): output_file = os.path.join(self.conanfile.generators_folder, f"{dep.ref.name}.cps") cps_json = json.dumps(cps, indent=4) save(self.conanfile, output_file, cps_json) + mapping[dep.ref.name] = output_file + + name = ["cpsmap", + self.conanfile.settings.get_safe("arch"), + self.conanfile.settings.get_safe("build_type"), + self.conanfile.options.get_safe("shared")] + name = "-".join([f for f in name if f]) + ".json" + self.conanfile.output.info(f"Generating CPS mapping file: {name}") + save(self.conanfile, name, json.dumps(mapping, indent=2)) diff --git a/conans/test/integration/cps/test_cps.py b/conans/test/integration/cps/test_cps.py index 0a7c02d4350..77ecedc04e5 100644 --- a/conans/test/integration/cps/test_cps.py +++ b/conans/test/integration/cps/test_cps.py @@ -1,4 +1,6 @@ import json +import os +import textwrap from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient @@ -9,7 +11,7 @@ def test_cps(): c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1")}) c.run("create pkg") - c.run("install --requires=pkg/0.1 -g CPSDeps") + c.run("install --requires=pkg/0.1 -s arch=x86_64 -g CPSDeps") pkg = json.loads(c.load("pkg.cps")) print(json.dumps(pkg, indent=2)) assert pkg["Name"] == "pkg" @@ -18,3 +20,49 @@ def test_cps(): assert pkg["Name"] == "pkg" assert pkg["Default-Components"] == ["pkg"] pkg_comp = pkg["Components"]["pkg"] + mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) + for _, path_cps in mapping.items(): + assert os.path.exists(path_cps) + + +def test_cps_in_pkg(): + c = TestClient() + cps = textwrap.dedent(""" + { + "Cps-Version": "0.8.1", + "Name": "pkg", + "Version": "0.1", + "Configurations": ["release"], + "Default-Components": ["pkg"], + "Components": { + "pkg": { "Type": "unknown", "Definitions": [], + "Includes": ["include"]} + } + } + """) + cps = "".join(cps.splitlines()) + conanfile = textwrap.dedent(f""" + import os + from conan.tools.files import save + from conan import ConanFile + class Pkg(ConanFile): + name = "mypkg" + version = "0.1" + + def package(self): + cps = '{cps}' + cps_path = os.path.join(self.package_folder, "mypkg.cps") + save(self, cps_path, cps) + """) + c.save({"pkg/conanfile.py": conanfile}) + c.run("create pkg") + + c.run("install --requires=mypkg/0.1 -s arch=x86_64 -g CPSDeps") + print(c.out) + + mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) + print(json.dumps(mapping, indent=2)) + for _, path_cps in mapping.items(): + assert os.path.exists(path_cps) + + assert not os.path.exists(os.path.join(c.current_folder, "mypkg.cps")) From 1c864e623b906756710a46adc448b897b2741b64 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 22 Jul 2024 01:15:17 +0200 Subject: [PATCH 04/19] wip --- conan/cps/cps.py | 125 +++++++++++++++++++++++++++---- conan/tools/cps/cps_deps.py | 26 ------- conans/client/installer.py | 3 - conans/model/build_info.py | 12 ++- test/integration/cps/test_cps.py | 23 +++++- 5 files changed, 141 insertions(+), 48 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index c206e30eac3..0d12b62ab58 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -1,7 +1,10 @@ +import glob import json import os from enum import Enum +from conan.api.output import ConanOutput +from conans.model.pkg_type import PackageType from conans.util.files import save @@ -30,29 +33,104 @@ def from_conan(pkg_type): return _package_type_map.get(str(pkg_type), "unknown") +def deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type): + if full_lib.get("type") is not None: + assert "location" in full_lib, f"If 'type' is specified in library {libname}, 'location' too" + return + + # Recipe didn't specify things, need to auto deduce + libdirs = [x.replace("\\", "/") for x in cpp_info.libdirs] + bindirs = [x.replace("\\", "/") for x in cpp_info.bindirs] + + static_patterns = [f"{libname}.lib", f"{libname}.a"] + shared_patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", + f"lib{libname}.*dylib"] + dll_patterns = [f"{libname}.dll"] + + def _find_matching(patterns, dirs): + matches = set() + for pattern in patterns: + for d in dirs: + matches.update(glob.glob(f"{d}/{pattern}")) + matches = [m for m in matches if not os.path.islink(m)] + if len(matches) == 1: + return next(iter(matches)) + + static_location = _find_matching(static_patterns, libdirs) + shared_location = _find_matching(shared_patterns, libdirs) + dll_location = _find_matching(dll_patterns, bindirs) + if static_location: + if shared_location: + ConanOutput().warning(f"Lib {libname} has both static {static_location} and " + f"shared {shared_location} in the same package") + if pkg_type is PackageType.STATIC: + full_lib["location"] = static_location + full_lib["type"] = PackageType.STATIC + else: + full_lib["location"] = shared_location + full_lib["type"] = PackageType.SHARED + elif dll_location: + full_lib["location"] = dll_location + full_lib["link_location"] = static_location + full_lib["type"] = PackageType.SHARED + else: + full_lib["location"] = static_location + full_lib["type"] = PackageType.STATIC + elif shared_location: + full_lib["location"] = shared_location + full_lib["type"] = PackageType.SHARED + elif dll_location: + # Only .dll but no link library + full_lib["location"] = dll_location + full_lib["type"] = PackageType.SHARED + if full_lib["type"] != pkg_type: + ConanOutput().warning(f"Lib {libname} deduced as '{full_lib['type']}, " + f"but 'package_type={pkg_type}'") + + class CPSComponent: def __init__(self, component_type=None): self.includes = [] self.type = component_type or "unknown" self.definitions = [] self.requires = [] + self.location = None + self.link_location = None def serialize(self): component = {"type": self.type, "includes": [x.replace("\\", "/") for x in self.includes]} if self.definitions: component["definitions"] = self.definitions + if self.location: # TODO: @prefix@ + component["location"] = self.location + if self.link_location: + component["link_location"] = self.link_location return component def deserialize(self): pass @staticmethod - def from_cpp_info(cpp_info): + def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp = CPSComponent() - cps_comp.type = CPSComponentType.from_conan(str(cpp_info.type)) - cps_comp.definitions = cpp_info.defines - cps_comp.includes = [x.replace("\\", "/") for x in cpp_info.includedirs] + if not libname: + cps_comp.definitions = cpp_info.defines + # TODO: @prefix@ + cps_comp.includes = [x.replace("\\", "/") for x in cpp_info.includedirs] + + if not cpp_info.libs: + return cps_comp + + if len(cpp_info.libs) > 1 and not libname: # Multi-lib pkg without components defined + return cps_comp + + libname = libname or cpp_info.libs[0] + full_lib = cpp_info.full_libs[libname] + deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type) + cps_comp.type = CPSComponentType.from_conan(full_lib.get("type")) + cps_comp.location = full_lib.get("location") + cps_comp.link_location = full_lib.get("link_location") return cps_comp @@ -96,25 +174,40 @@ def deserialize(self): @staticmethod def from_conan(dep): cps = CPS(dep.ref.name, str(dep.ref.version)) + # supplemental + cps.license = dep.license + cps.description = dep.description + cps.website = dep.homepage if dep.settings.get_safe("build_type"): cps.configurations = [str(dep.settings.build_type).lower()] if not dep.cpp_info.has_components: - # single component, called same as library - component = CPSComponent.from_cpp_info(dep.cpp_info) - # self._component(dep.package_type, dep.cpp_info, build_type) - if dep.dependencies: - for transitive_dep in dep.dependencies.items(): - dep_name = transitive_dep[0].ref.name - component.requires = [f"{dep_name}:{dep_name}"] - - # the component will be just the package name - cps.default_components = [f"{dep.ref.name}"] - cps.components[f"{dep.ref.name}"] = component + if dep.cpp_info.libs and len(dep.cpp_info.libs) > 1: + comp = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type) # base + base_name = cps.name + cps.components[base_name] = comp + for lib in dep.cpp_info.libs: + comp = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type, lib) + comp.requires.insert(0, base_name) # dep to the common one + cps.components[lib] = comp + cps.default_components = [dep.cpp_info.libs] + # FIXME: What if one lib is named equal to the package? + else: + # single component, called same as library + component = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type) + # self._component(dep.package_type, dep.cpp_info, build_type) + if dep.dependencies: + for transitive_dep in dep.dependencies.items(): + dep_name = transitive_dep[0].ref.name + component.requires = [f"{dep_name}:{dep_name}"] + + # the component will be just the package name + cps.default_components = [f"{dep.ref.name}"] + cps.components[f"{dep.ref.name}"] = component else: sorted_comps = dep.cpp_info.get_sorted_components() for comp_name, comp in sorted_comps.items(): - component = CPSComponent.from_cpp_info(comp) + component = CPSComponent.from_cpp_info(comp, dep.package_type) cps.components[comp_name] = component # Now by default all are default_components cps.default_components = [comp_name for comp_name in sorted_comps] diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index 1f537c05256..f28a44023f5 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -10,33 +10,7 @@ class CPSDeps: def __init__(self, conanfile): self.conanfile = conanfile - def find_library(self, libdirs, bindirs, name, shared, dll): - libdirs = [x.replace("\\", "/") for x in libdirs] - bindirs = [x.replace("\\", "/") for x in bindirs] - libname = name[0] # assume one library per component - if shared and not dll: - patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", - f"lib{libname}.*dylib", f"{libname}.lib"] - elif shared and dll: - patterns = [f"{libname}.dll"] - else: - patterns = [f"lib{libname}.a", f"{libname}.lib"] - matches = set() - search_dirs = bindirs if self.conanfile.settings.os == "Windows" and shared and dll else libdirs - for folder in search_dirs: - for pattern in patterns: - glob_expr = f"{folder}/{pattern}" - matches.update(glob.glob(glob_expr)) - - if len(matches) == 1: - return next(iter(matches)) - elif len(matches) >= 1: - # assume at least one is not a symlink - return [x for x in list(matches) if not os.path.islink(x)][0] - else: - self.conanfile.output.error(f"[CPSDeps] Could not locate library: {libname}") - return None def _component(self, package_type, cpp_info, build_type): component = {} diff --git a/conans/client/installer.py b/conans/client/installer.py index 74a4db6dd4a..ea679f44e56 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -413,9 +413,6 @@ def _call_package_info(self, conanfile, package_folder, is_editable): MockInfoProperty.package = str(conanfile) conanfile.package_info() - # CPS - conanfile.cpp.package.type = conanfile.package_type - # TODO: Check this package_folder usage for editable when not defined conanfile.cpp.package.set_relative_base_folder(package_folder) diff --git a/conans/model/build_info.py b/conans/model/build_info.py index 90d61869e3f..bb6d6a307f0 100644 --- a/conans/model/build_info.py +++ b/conans/model/build_info.py @@ -91,8 +91,6 @@ def __init__(self, set_defaults=False): self.libdirs = ["lib"] self.bindirs = ["bin"] - self.type = None # By default it will be the package_type - def serialize(self): return { "includedirs": self._includedirs, @@ -238,6 +236,16 @@ def frameworks(self, value): def libs(self): if self._libs is None: self._libs = [] + if isinstance(self._libs, dict): + return [self._libs.keys()] # Return a list to not break any interface + return self._libs + + @property + def full_libs(self): + if self._libs is None: + self._libs = [] + if isinstance(self._libs, list): + return {k: {} for k in self._libs} return self._libs @libs.setter diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index 0c04989dbb0..cd28728a5eb 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -8,7 +8,8 @@ def test_cps(): c = TestClient() - c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type")}) + c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_settings("build_type") + .with_class_attribute("license='MIT'")}) c.run("create pkg") c.run("install --requires=pkg/0.1 -s arch=x86_64 -g CPSDeps") @@ -16,6 +17,7 @@ def test_cps(): print(json.dumps(pkg, indent=2)) assert pkg["name"] == "pkg" assert pkg["version"] == "0.1" + assert pkg["license"] == "MIT" assert pkg["configurations"] == ["release"] assert pkg["default_components"] == ["pkg"] pkg_comp = pkg["components"]["pkg"] @@ -25,6 +27,25 @@ def test_cps(): assert os.path.exists(path_cps) +def test_cps_static_lib(): + c = TestClient() + c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_package_file("lib/pkg.a", "-") + .with_settings("build_type") + .with_package_info(cpp_info={"libs": ["pkg"]}, env_info={})}) + c.run("create pkg") + + c.run("install --requires=pkg/0.1 -s arch=x86_64 -g CPSDeps") + pkg = json.loads(c.load("pkg.cps")) + print(json.dumps(pkg, indent=2)) + assert pkg["name"] == "pkg" + assert pkg["version"] == "0.1" + assert pkg["configurations"] == ["release"] + assert pkg["default_components"] == ["pkg"] + pkg_comp = pkg["components"]["pkg"] + assert pkg_comp["type"] == "archive" + assert pkg_comp["location"] is not None + + def test_cps_in_pkg(): c = TestClient() cps = textwrap.dedent(""" From f97170cccdee63a304c918c747490ffc877f1ec3 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 22 Jul 2024 02:05:36 +0200 Subject: [PATCH 05/19] CPS->cpp_info --- conan/cps/cps.py | 52 +++++++++++++++++++++++++++----- conan/tools/cps/cps_deps.py | 24 --------------- conans/client/installer.py | 6 ++++ test/integration/cps/test_cps.py | 23 +++++++++----- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 0d12b62ab58..0922595dfa3 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -4,8 +4,9 @@ from enum import Enum from conan.api.output import ConanOutput +from conans.model.build_info import CppInfo from conans.model.pkg_type import PackageType -from conans.util.files import save +from conans.util.files import save, load class CPSComponentType(Enum): @@ -14,6 +15,7 @@ class CPSComponentType(Enum): INTERFACE = "interface" EXE = "exe" JAR = "jar" + UNKNOWN = "unknown" def __str__(self): return self.value @@ -108,8 +110,15 @@ def serialize(self): component["link_location"] = self.link_location return component - def deserialize(self): - pass + @staticmethod + def deserialize(data): + comp = CPSComponent() + comp.type = CPSComponentType(data.get("type")) + comp.includes = data.get("includes") + comp.definitions = data.get("definitions") + comp.location = data.get("location") + comp.link_location = data.get("link_location") + return comp @staticmethod def from_cpp_info(cpp_info, pkg_type, libname=None): @@ -151,9 +160,7 @@ def __init__(self, name=None, version=None): def serialize(self): cps = {"cps_version": "0.12.0", "name": self.name, - "version": self.version, - "default_components": self.default_components, - "components": {}} + "version": self.version} # Supplemental for data in "license", "description", "website": @@ -163,13 +170,26 @@ def serialize(self): if self.configurations: cps["configurations"] = self.configurations + cps["default_components"] = self.default_components + cps["components"] = {} for name, comp in self.components.items(): cps["components"][name] = comp.serialize() return cps - def deserialize(self): - pass + @staticmethod + def deserialize(data): + cps = CPS() + cps.name = data.get("name") + cps.version = data.get("version") + cps.license = data.get("license") + cps.description = data.get("description") + cps.website = data.get("website") + cps.configurations = data.get("configurations") + cps.default_components = data.get("default_components") + cps.components = {k: CPSComponent.deserialize(v) + for k, v in data.get("components", {}).items()} + return cps @staticmethod def from_conan(dep): @@ -214,7 +234,23 @@ def from_conan(dep): return cps + def to_conan(self): + cpp_info = CppInfo() + if len(self.components) == 1: + comp = next(iter(self.components.values())) + cpp_info.includedirs = comp.includes + cpp_info.defines = comp.definitions + else: + for comp_name, comp in self.components.items(): + cpp_info.components[comp_name] = comp.to_conan() + return cpp_info + def save(self, folder): filename = os.path.join(folder, f"{self.name}.cps") save(filename, json.dumps(self.serialize(), indent=4)) return filename + + @staticmethod + def load(file): + contents = load(file) + return CPS.deserialize(json.loads(contents)) diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index f28a44023f5..3e8dc23f67e 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -1,7 +1,6 @@ from conan.cps.cps import CPS from conan.tools.files import save -import glob import json import os @@ -10,29 +9,6 @@ class CPSDeps: def __init__(self, conanfile): self.conanfile = conanfile - - - def _component(self, package_type, cpp_info, build_type): - component = {} - - if not cpp_info.libs: # No compiled libraries, header-only - return component - is_shared = package_type == "shared-library" - if is_shared and self.conanfile.settings.os == "Windows": - dll_location = self.find_library(cpp_info.libdirs, cpp_info.bindirs, - cpp_info.libs, is_shared, dll=True) - import_library = self.find_library(cpp_info.libdirs, cpp_info.bindirs, - cpp_info.libs, is_shared, dll=False) - locations = {'Location': dll_location, - 'Link-Location': import_library} - component["Configurations"] = {build_type: locations} # noqa - elif package_type == "static-library": - library_location = self.find_library(cpp_info.libdirs, cpp_info.bindirs, - cpp_info.libs, is_shared, dll=False) - component["Configurations"] = {build_type: {'Location': library_location}} - - return component - def generate(self): self.conanfile.output.info(f"[CPSDeps] generators folder {self.conanfile.generators_folder}") deps = self.conanfile.dependencies.host.items() diff --git a/conans/client/installer.py b/conans/client/installer.py index ea679f44e56..db9f2289094 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -1,8 +1,10 @@ +import json import os import shutil from multiprocessing.pool import ThreadPool from conan.api.output import ConanOutput +from conan.cps.cps import CPS from conans.client.conanfile.build import run_build_method from conans.client.conanfile.package import run_package_method from conan.internal.api.install.generators import write_generators @@ -407,6 +409,10 @@ def _call_package_info(self, conanfile, package_folder, is_editable): with conanfile_exception_formatter(conanfile, "package_info"): self._hook_manager.execute("pre_package_info", conanfile=conanfile) + cps_file = os.path.join(package_folder, f"{conanfile.name}.cps") + if os.path.exists(cps_file): + cps = CPS.load(cps_file) + conanfile.cpp_info = cps.to_conan() if hasattr(conanfile, "package_info"): with conanfile_remove_attr(conanfile, ['info', "source_folder", "build_folder"], "package_info"): diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index cd28728a5eb..379e085e9ac 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -50,14 +50,14 @@ def test_cps_in_pkg(): c = TestClient() cps = textwrap.dedent(""" { - "Cps-Version": "0.8.1", - "Name": "pkg", - "Version": "0.1", - "Configurations": ["release"], - "Default-Components": ["pkg"], - "Components": { - "pkg": { "Type": "unknown", "Definitions": [], - "Includes": ["include"]} + "cps_version": "0.8.1", + "name": "pkg", + "version": "0.1", + "configurations": ["release"], + "default-Components": ["pkg"], + "components": { + "pkg": { "type": "unknown", "definitions": ["mydefine"], + "includes": ["myinc"]} } } """) @@ -87,3 +87,10 @@ def package(self): assert os.path.exists(path_cps) assert not os.path.exists(os.path.join(c.current_folder, "mypkg.cps")) + + c.run("install --requires=mypkg/0.1 -s arch=x86_64 -g CMakeDeps") + print(c.out) + cmake = c.load("mypkg-release-x86_64-data.cmake") + print(cmake) + assert 'set(mypkg_INCLUDE_DIRS_RELEASE "${mypkg_PACKAGE_FOLDER_RELEASE}/myinc")' in cmake + assert 'set(mypkg_COMPILE_DEFINITIONS_RELEASE "mydefine")' in cmake From 8779e4697bfaaef286a0d3153830953284aca9ad Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 22 Jul 2024 19:47:09 +0200 Subject: [PATCH 06/19] wip --- conans/client/installer.py | 6 ---- test/integration/cps/test_cps.py | 52 ++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/conans/client/installer.py b/conans/client/installer.py index db9f2289094..ea679f44e56 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -1,10 +1,8 @@ -import json import os import shutil from multiprocessing.pool import ThreadPool from conan.api.output import ConanOutput -from conan.cps.cps import CPS from conans.client.conanfile.build import run_build_method from conans.client.conanfile.package import run_package_method from conan.internal.api.install.generators import write_generators @@ -409,10 +407,6 @@ def _call_package_info(self, conanfile, package_folder, is_editable): with conanfile_exception_formatter(conanfile, "package_info"): self._hook_manager.execute("pre_package_info", conanfile=conanfile) - cps_file = os.path.join(package_folder, f"{conanfile.name}.cps") - if os.path.exists(cps_file): - cps = CPS.load(cps_file) - conanfile.cpp_info = cps.to_conan() if hasattr(conanfile, "package_info"): with conanfile_remove_attr(conanfile, ['info', "source_folder", "build_folder"], "package_info"): diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index 379e085e9ac..ec4129ae20a 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -48,17 +48,26 @@ def test_cps_static_lib(): def test_cps_in_pkg(): c = TestClient() - cps = textwrap.dedent(""" + cps = textwrap.dedent("""\ { - "cps_version": "0.8.1", - "name": "pkg", - "version": "0.1", - "configurations": ["release"], - "default-Components": ["pkg"], - "components": { - "pkg": { "type": "unknown", "definitions": ["mydefine"], - "includes": ["myinc"]} - } + "cps_version": "0.12.0", + "name": "zlib", + "version": "1.3.1", + "configurations": [ + "release" + ], + "default_components": [ + "zlib" + ], + "components": { + "zlib": { + "type": "archive", + "includes": [ + "@prefix@/include" + ], + "location": "@prefix@/lib/pkg.a" + } + } } """) cps = "".join(cps.splitlines()) @@ -67,18 +76,22 @@ def test_cps_in_pkg(): from conan.tools.files import save from conan import ConanFile class Pkg(ConanFile): - name = "mypkg" - version = "0.1" + name = "zlib" + version = "1.3.1" def package(self): cps = '{cps}' - cps_path = os.path.join(self.package_folder, "mypkg.cps") + cps_path = os.path.join(self.package_folder, "zlib.cps") save(self, cps_path, cps) + + def package_info(self): + from conan.cps.cps import CPS + self.cpp_info = CPS.load("zlib.cps").to_conan() """) c.save({"pkg/conanfile.py": conanfile}) c.run("create pkg") - c.run("install --requires=mypkg/0.1 -s arch=x86_64 -g CPSDeps") + c.run("install --requires=zlib/1.3.1 -s arch=x86_64 -g CPSDeps") print(c.out) mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) @@ -86,11 +99,12 @@ def package(self): for _, path_cps in mapping.items(): assert os.path.exists(path_cps) - assert not os.path.exists(os.path.join(c.current_folder, "mypkg.cps")) + assert not os.path.exists(os.path.join(c.current_folder, "zlib.cps")) - c.run("install --requires=mypkg/0.1 -s arch=x86_64 -g CMakeDeps") + c.run("install --requires=zlib/1.3.1 -s arch=x86_64 -g CMakeDeps") print(c.out) - cmake = c.load("mypkg-release-x86_64-data.cmake") + cmake = c.load("zlib-release-x86_64-data.cmake") print(cmake) - assert 'set(mypkg_INCLUDE_DIRS_RELEASE "${mypkg_PACKAGE_FOLDER_RELEASE}/myinc")' in cmake - assert 'set(mypkg_COMPILE_DEFINITIONS_RELEASE "mydefine")' in cmake + assert 'set(zlib_INCLUDE_DIRS_RELEASE "${zlib_PACKAGE_FOLDER_RELEASE}/include")' in cmake + assert 'set(zlib_LIB_DIRS_RELEASE "${zlib_PACKAGE_FOLDER_RELEASE}/lib")' + assert 'set(zlib_LIBS_RELEASE zlib)' in cmake From 69fa0cbf0bbf2c920a041dd091b770ec8eb1c041 Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 23 Jul 2024 10:04:43 +0200 Subject: [PATCH 07/19] wip --- conan/cps/cps.py | 12 +++++++++++- test/integration/cps/test_cps.py | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 0922595dfa3..048a3de770c 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -235,11 +235,21 @@ def from_conan(dep): return cps def to_conan(self): + def strip_prefix(dirs): + return [d.replace("@prefix@/", "") for d in dirs] + cpp_info = CppInfo() if len(self.components) == 1: comp = next(iter(self.components.values())) - cpp_info.includedirs = comp.includes + cpp_info.includedirs = strip_prefix(comp.includes) cpp_info.defines = comp.definitions + if comp.type is CPSComponentType.ARCHIVE: + location = comp.location + location = location.replace("@prefix@/", "") + cpp_info.libdirs = [os.path.dirname(location)] + filename = os.path.basename(location) + basefile = os.path.splitext(filename)[0] + cpp_info.libs = [basefile] else: for comp_name, comp in self.components.items(): cpp_info.components[comp_name] = comp.to_conan() diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index ec4129ae20a..c30cec15194 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -65,7 +65,7 @@ def test_cps_in_pkg(): "includes": [ "@prefix@/include" ], - "location": "@prefix@/lib/pkg.a" + "location": "@prefix@/lib/zlib.a" } } } From fca259c4f3156bd2f8d4267b9e7367fbcfe69edc Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 23 Jul 2024 13:46:52 +0200 Subject: [PATCH 08/19] requires --- conan/cps/cps.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 048a3de770c..e05cb42194b 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -98,26 +98,34 @@ def __init__(self, component_type=None): self.requires = [] self.location = None self.link_location = None + self.link_libraries = None # system libraries def serialize(self): - component = {"type": self.type, - "includes": [x.replace("\\", "/") for x in self.includes]} + component = {"type": self.type} + if self.requires: + component["requires"] = self.requires + if self.includes: + component["includes"] = [x.replace("\\", "/") for x in self.includes] if self.definitions: component["definitions"] = self.definitions if self.location: # TODO: @prefix@ component["location"] = self.location if self.link_location: component["link_location"] = self.link_location + if self.link_libraries: + component["link_libraries"] = self.link_libraries return component @staticmethod def deserialize(data): comp = CPSComponent() comp.type = CPSComponentType(data.get("type")) + comp.requires = data.get("requires") comp.includes = data.get("includes") comp.definitions = data.get("definitions") comp.location = data.get("location") comp.link_location = data.get("link_location") + comp.link_libraries = data.get("link_libraries") return comp @staticmethod @@ -140,6 +148,9 @@ def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp.type = CPSComponentType.from_conan(full_lib.get("type")) cps_comp.location = full_lib.get("location") cps_comp.link_location = full_lib.get("link_location") + cps_comp.link_libraries = cpp_info.system_libs + required = cpp_info.requires + cps_comp.requires = [f":{c}" if "::" not in c else c for c in required] return cps_comp @@ -152,6 +163,7 @@ def __init__(self, name=None, version=None): self.default_components = [] self.components = {} self.configurations = [] + self.requires = [] # Supplemental self.description = None self.license = None @@ -167,6 +179,9 @@ def serialize(self): if getattr(self, data, None): cps[data] = getattr(self, data) + if self.requires: + cps["requires"] = self.requires + if self.configurations: cps["configurations"] = self.configurations @@ -185,6 +200,7 @@ def deserialize(data): cps.license = data.get("license") cps.description = data.get("description") cps.website = data.get("website") + cps.requires = data.get("requires") cps.configurations = data.get("configurations") cps.default_components = data.get("default_components") cps.components = {k: CPSComponent.deserialize(v) @@ -198,6 +214,8 @@ def from_conan(dep): cps.license = dep.license cps.description = dep.description cps.website = dep.homepage + + cps.requires = {d.ref.name: None for d in dep.dependencies.host.values()} if dep.settings.get_safe("build_type"): cps.configurations = [str(dep.settings.build_type).lower()] @@ -250,9 +268,24 @@ def strip_prefix(dirs): filename = os.path.basename(location) basefile = os.path.splitext(filename)[0] cpp_info.libs = [basefile] + # FIXME: Missing requires + cpp_info.system_libs = comp.link_libraries else: for comp_name, comp in self.components.items(): - cpp_info.components[comp_name] = comp.to_conan() + cpp_comp = cpp_info.components[comp_name] + cpp_comp.includedirs = strip_prefix(comp.includes) + cpp_comp.defines = comp.definitions + if comp.type is CPSComponentType.ARCHIVE: + location = comp.location + location = location.replace("@prefix@/", "") + cpp_comp.libdirs = [os.path.dirname(location)] + filename = os.path.basename(location) + basefile = os.path.splitext(filename)[0] + cpp_comp.libs = [basefile] + for r in comp.requires: + cpp_comp.requires.append(r[1:] if r.startswith(":") else r) + cpp_comp.system_libs = comp.link_libraries + return cpp_info def save(self, folder): From 944a586aa66aee9890604545ae7ae45d9f3e1787 Mon Sep 17 00:00:00 2001 From: memsharded Date: Tue, 23 Jul 2024 18:58:54 +0200 Subject: [PATCH 09/19] wip --- conan/cps/cps.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index e05cb42194b..5e5eee7b20f 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -44,7 +44,7 @@ def deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type): libdirs = [x.replace("\\", "/") for x in cpp_info.libdirs] bindirs = [x.replace("\\", "/") for x in cpp_info.bindirs] - static_patterns = [f"{libname}.lib", f"{libname}.a"] + static_patterns = [f"{libname}.lib", f"{libname}.a", f"lib{libname}.a"] shared_patterns = [f"lib{libname}.so", f"lib{libname}.so.*", f"lib{libname}.dylib", f"lib{libname}.*dylib"] dll_patterns = [f"{libname}.dll"] @@ -85,8 +85,8 @@ def _find_matching(patterns, dirs): # Only .dll but no link library full_lib["location"] = dll_location full_lib["type"] = PackageType.SHARED - if full_lib["type"] != pkg_type: - ConanOutput().warning(f"Lib {libname} deduced as '{full_lib['type']}, " + if full_lib.get("type") != pkg_type: + ConanOutput().warning(f"Lib {libname} deduced as '{full_lib.get('type')}, " f"but 'package_type={pkg_type}'") @@ -267,6 +267,8 @@ def strip_prefix(dirs): cpp_info.libdirs = [os.path.dirname(location)] filename = os.path.basename(location) basefile = os.path.splitext(filename)[0] + if basefile.startswith("lib"): + basefile = basefile[3:] cpp_info.libs = [basefile] # FIXME: Missing requires cpp_info.system_libs = comp.link_libraries @@ -281,6 +283,8 @@ def strip_prefix(dirs): cpp_comp.libdirs = [os.path.dirname(location)] filename = os.path.basename(location) basefile = os.path.splitext(filename)[0] + if basefile.startswith("lib"): + basefile = basefile[3:] cpp_comp.libs = [basefile] for r in comp.requires: cpp_comp.requires.append(r[1:] if r.startswith(":") else r) From 0e19364237e22575371d87a710bb8bb3a7bfa727 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 11:11:44 +0200 Subject: [PATCH 10/19] wip --- conan/cps/cps.py | 9 +++++---- test/integration/cps/test_cps.py | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 5e5eee7b20f..26115f6f85e 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -32,7 +32,7 @@ def from_conan(pkg_type): "header-library": "interface", "application": "executable" } - return _package_type_map.get(str(pkg_type), "unknown") + return CPSComponentType(_package_type_map.get(str(pkg_type), "unknown")) def deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type): @@ -101,7 +101,7 @@ def __init__(self, component_type=None): self.link_libraries = None # system libraries def serialize(self): - component = {"type": self.type} + component = {"type": str(self.type)} if self.requires: component["requires"] = self.requires if self.includes: @@ -133,13 +133,14 @@ def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp = CPSComponent() if not libname: cps_comp.definitions = cpp_info.defines - # TODO: @prefix@ cps_comp.includes = [x.replace("\\", "/") for x in cpp_info.includedirs] if not cpp_info.libs: + cps_comp.type = CPSComponentType.INTERFACE return cps_comp if len(cpp_info.libs) > 1 and not libname: # Multi-lib pkg without components defined + cps_comp.type = CPSComponentType.INTERFACE return cps_comp libname = libname or cpp_info.libs[0] @@ -150,7 +151,7 @@ def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp.link_location = full_lib.get("link_location") cps_comp.link_libraries = cpp_info.system_libs required = cpp_info.requires - cps_comp.requires = [f":{c}" if "::" not in c else c for c in required] + cps_comp.requires = [f":{c}" if "::" not in c else c.replace("::", ":") for c in required] return cps_comp diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index c30cec15194..c9b018181b7 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -21,7 +21,7 @@ def test_cps(): assert pkg["configurations"] == ["release"] assert pkg["default_components"] == ["pkg"] pkg_comp = pkg["components"]["pkg"] - assert pkg_comp["type"] == "unknown" + assert pkg_comp["type"] == "interface" mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) for _, path_cps in mapping.items(): assert os.path.exists(path_cps) From a180115f444ec46487ffae95fe135e4773647f76 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 12:02:23 +0200 Subject: [PATCH 11/19] wip --- conan/cps/cps.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 26115f6f85e..6ad94b2503c 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -267,8 +267,8 @@ def strip_prefix(dirs): location = location.replace("@prefix@/", "") cpp_info.libdirs = [os.path.dirname(location)] filename = os.path.basename(location) - basefile = os.path.splitext(filename)[0] - if basefile.startswith("lib"): + basefile, ext = os.path.splitext(filename) + if basefile.startswith("lib") and ext != ".lib": basefile = basefile[3:] cpp_info.libs = [basefile] # FIXME: Missing requires @@ -283,12 +283,12 @@ def strip_prefix(dirs): location = location.replace("@prefix@/", "") cpp_comp.libdirs = [os.path.dirname(location)] filename = os.path.basename(location) - basefile = os.path.splitext(filename)[0] - if basefile.startswith("lib"): + basefile, ext = os.path.splitext(filename) + if basefile.startswith("lib") and ext != ".lib": basefile = basefile[3:] cpp_comp.libs = [basefile] for r in comp.requires: - cpp_comp.requires.append(r[1:] if r.startswith(":") else r) + cpp_comp.requires.append(r[1:] if r.startswith(":") else r.replace(":", "::")) cpp_comp.system_libs = comp.link_libraries return cpp_info From 3a9d932e13cf2e59332c76bcfb35c88c21988ebf Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 17:45:34 +0200 Subject: [PATCH 12/19] wip --- conan/cps/cps.py | 1 - conan/tools/cps/cps_deps.py | 43 ++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 6ad94b2503c..82da64a51fe 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -54,7 +54,6 @@ def _find_matching(patterns, dirs): for pattern in patterns: for d in dirs: matches.update(glob.glob(f"{d}/{pattern}")) - matches = [m for m in matches if not os.path.islink(m)] if len(matches) == 1: return next(iter(matches)) diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index 3e8dc23f67e..acb4b7feb8b 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -7,14 +7,37 @@ class CPSDeps: def __init__(self, conanfile): - self.conanfile = conanfile + self._conanfile = conanfile + + def _config_name(self): + build_vars = ["settings.compiler", "settings.compiler.version", "settings.arch", + "settings.compiler.cppstd", "settings.build_type", "options.shared"] + ret = [] + for s in build_vars: + group, var = s.split(".", 1) + tmp = None + if group == "settings": + tmp = self._conanfile.settings.get_safe(var) + elif group == "options": + value = self._conanfile.options.get_safe(var) + if value is not None: + if var == "shared": + tmp = "shared" if value else "static" + else: + tmp = "{}_{}".format(var, value) + if tmp: + ret.append(tmp.lower()) + return "-".join(ret) def generate(self): - self.conanfile.output.info(f"[CPSDeps] generators folder {self.conanfile.generators_folder}") - deps = self.conanfile.dependencies.host.items() + cps_folder = os.path.join(self._conanfile.recipe_folder, "build", "cps") + config_name = self._config_name() + folder = os.path.join(cps_folder, config_name) + self._conanfile.output.info(f"[CPSDeps] folder {cps_folder}") + deps = self._conanfile.dependencies.host.items() mapping = {} for _, dep in deps: - self.conanfile.output.info(f"[CPSDeps]: dep {dep.ref.name}") + self._conanfile.output.info(f"[CPSDeps]: dep {dep.ref.name}") cps_in_package = os.path.join(dep.package_folder, f"{dep.ref.name}.cps") if os.path.exists(cps_in_package): @@ -22,13 +45,9 @@ def generate(self): continue cps = CPS.from_conan(dep) - output_file = cps.save(self.conanfile.generators_folder) + output_file = cps.save(folder) mapping[dep.ref.name] = output_file - name = ["cpsmap", - self.conanfile.settings.get_safe("arch"), - self.conanfile.settings.get_safe("build_type"), - self.conanfile.options.get_safe("shared")] - name = "-".join([f for f in name if f]) + ".json" - self.conanfile.output.info(f"Generating CPS mapping file: {name}") - save(self.conanfile, name, json.dumps(mapping, indent=2)) + name = f"cpsmap-{config_name}.json" + self._conanfile.output.info(f"Generating CPS mapping file: {name}") + save(self._conanfile, os.path.join(cps_folder, name), json.dumps(mapping, indent=2)) From 1c67c7c55df2920eeac307e45b45ac4088689fff Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 20:32:12 +0200 Subject: [PATCH 13/19] fix default_components --- conan/cps/cps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 82da64a51fe..24bfb3b679f 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -228,7 +228,7 @@ def from_conan(dep): comp = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type, lib) comp.requires.insert(0, base_name) # dep to the common one cps.components[lib] = comp - cps.default_components = [dep.cpp_info.libs] + cps.default_components = dep.cpp_info.libs # FIXME: What if one lib is named equal to the package? else: # single component, called same as library @@ -294,7 +294,7 @@ def strip_prefix(dirs): def save(self, folder): filename = os.path.join(folder, f"{self.name}.cps") - save(filename, json.dumps(self.serialize(), indent=4)) + save(filename, json.dumps(self.serialize(), indent=2)) return filename @staticmethod From 3d6454ab82b54b262e36d04d59b7cf3ce4097ad5 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 21:00:00 +0200 Subject: [PATCH 14/19] fix tests --- conan/tools/cps/cps_deps.py | 2 +- test/integration/cps/test_cps.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/conan/tools/cps/cps_deps.py b/conan/tools/cps/cps_deps.py index acb4b7feb8b..578a87c0162 100644 --- a/conan/tools/cps/cps_deps.py +++ b/conan/tools/cps/cps_deps.py @@ -30,7 +30,7 @@ def _config_name(self): return "-".join(ret) def generate(self): - cps_folder = os.path.join(self._conanfile.recipe_folder, "build", "cps") + cps_folder = os.path.join(self._conanfile.folders.base_build, "build", "cps") config_name = self._config_name() folder = os.path.join(cps_folder, config_name) self._conanfile.output.info(f"[CPSDeps] folder {cps_folder}") diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index c9b018181b7..897fd769aa0 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -12,9 +12,9 @@ def test_cps(): .with_class_attribute("license='MIT'")}) c.run("create pkg") - c.run("install --requires=pkg/0.1 -s arch=x86_64 -g CPSDeps") - pkg = json.loads(c.load("pkg.cps")) - print(json.dumps(pkg, indent=2)) + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") + pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) assert pkg["name"] == "pkg" assert pkg["version"] == "0.1" assert pkg["license"] == "MIT" @@ -22,7 +22,7 @@ def test_cps(): assert pkg["default_components"] == ["pkg"] pkg_comp = pkg["components"]["pkg"] assert pkg_comp["type"] == "interface" - mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) + mapping = json.loads(c.load("build/cps/cpsmap-msvc-191-x86_64-release.json")) for _, path_cps in mapping.items(): assert os.path.exists(path_cps) @@ -34,9 +34,9 @@ def test_cps_static_lib(): .with_package_info(cpp_info={"libs": ["pkg"]}, env_info={})}) c.run("create pkg") - c.run("install --requires=pkg/0.1 -s arch=x86_64 -g CPSDeps") - pkg = json.loads(c.load("pkg.cps")) - print(json.dumps(pkg, indent=2)) + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") + pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) assert pkg["name"] == "pkg" assert pkg["version"] == "0.1" assert pkg["configurations"] == ["release"] @@ -91,17 +91,18 @@ def package_info(self): c.save({"pkg/conanfile.py": conanfile}) c.run("create pkg") - c.run("install --requires=zlib/1.3.1 -s arch=x86_64 -g CPSDeps") + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=zlib/1.3.1 {settings} -g CPSDeps") print(c.out) - mapping = json.loads(c.load("cpsmap-x86_64-Release.json")) + mapping = json.loads(c.load("build/cps/cpsmap-msvc-191-x86_64-release.json")) print(json.dumps(mapping, indent=2)) for _, path_cps in mapping.items(): assert os.path.exists(path_cps) assert not os.path.exists(os.path.join(c.current_folder, "zlib.cps")) - c.run("install --requires=zlib/1.3.1 -s arch=x86_64 -g CMakeDeps") + c.run(f"install --requires=zlib/1.3.1 {settings} -g CMakeDeps") print(c.out) cmake = c.load("zlib-release-x86_64-data.cmake") print(cmake) From 0e11959c0abb3afcbd2d09584da40acba552d1c1 Mon Sep 17 00:00:00 2001 From: memsharded Date: Wed, 31 Jul 2024 23:44:22 +0200 Subject: [PATCH 15/19] wip --- conan/cps/cps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 24bfb3b679f..e7d00225225 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -222,11 +222,11 @@ def from_conan(dep): if not dep.cpp_info.has_components: if dep.cpp_info.libs and len(dep.cpp_info.libs) > 1: comp = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type) # base - base_name = cps.name + base_name = f"_{cps.name}" cps.components[base_name] = comp for lib in dep.cpp_info.libs: comp = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type, lib) - comp.requires.insert(0, base_name) # dep to the common one + comp.requires.insert(0, f":{base_name}") # dep to the common one cps.components[lib] = comp cps.default_components = dep.cpp_info.libs # FIXME: What if one lib is named equal to the package? From 87ae71a937619b37da3b56f39421fc3d433f7ad1 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 12 Aug 2024 16:43:08 +0200 Subject: [PATCH 16/19] wip --- conan/cps/cps.py | 2 +- test/integration/cps/test_cps.py | 17 +++++++++++ .../integration/cps/test_extended_cpp_info.py | 29 +++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 test/integration/cps/test_extended_cpp_info.py diff --git a/conan/cps/cps.py b/conan/cps/cps.py index e7d00225225..948a09f7975 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -142,7 +142,7 @@ def from_cpp_info(cpp_info, pkg_type, libname=None): cps_comp.type = CPSComponentType.INTERFACE return cps_comp - libname = libname or cpp_info.libs[0] + libname = libname or next(iter(cpp_info.full_libs)) full_lib = cpp_info.full_libs[libname] deduce_full_lib_info(libname, full_lib, cpp_info, pkg_type) cps_comp.type = CPSComponentType.from_conan(full_lib.get("type")) diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index 897fd769aa0..8c6b8413e34 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -46,6 +46,23 @@ def test_cps_static_lib(): assert pkg_comp["location"] is not None +def test_cps_header(): + c = TestClient() + c.save({"pkg/conanfile.py": GenConanfile("pkg", "0.1").with_package_type("header-library")}) + c.run("create pkg") + + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") + pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) + assert pkg["name"] == "pkg" + assert pkg["version"] == "0.1" + assert "configurations" not in "pkg" + assert pkg["default_components"] == ["pkg"] + pkg_comp = pkg["components"]["pkg"] + assert pkg_comp["type"] == "interface" + assert "location" not in pkg_comp + + def test_cps_in_pkg(): c = TestClient() cps = textwrap.dedent("""\ diff --git a/test/integration/cps/test_extended_cpp_info.py b/test/integration/cps/test_extended_cpp_info.py new file mode 100644 index 00000000000..4308fd5b6e1 --- /dev/null +++ b/test/integration/cps/test_extended_cpp_info.py @@ -0,0 +1,29 @@ +import json +import textwrap + +from conan.test.utils.tools import TestClient + + +def test_extended_cpp_info(): + c = TestClient() + conanfile = textwrap.dedent(""" + from conan import ConanFile + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + def package_info(self): + self.cpp_info.libs = {"mylib": {"location": "my_custom_location", + "type": "static-library"}} + """) + c.save({"conanfile.py": conanfile}) + c.run("create .") + + settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" + c.run(f"install --requires=pkg/0.1 {settings} -g CPSDeps") + pkg = json.loads(c.load("build/cps/msvc-191-x86_64-release/pkg.cps")) + assert pkg["name"] == "pkg" + assert pkg["version"] == "0.1" + assert pkg["default_components"] == ["pkg"] + pkg_comp = pkg["components"]["pkg"] + assert pkg_comp["type"] == "archive" + assert pkg_comp["location"] == "my_custom_location" From e86a0d636a216549ae807699c28fa9dda9e88141 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 12 Aug 2024 19:06:53 +0200 Subject: [PATCH 17/19] wip --- conan/cps/cps.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 948a09f7975..78d3c5a44be 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -233,11 +233,10 @@ def from_conan(dep): else: # single component, called same as library component = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type) - # self._component(dep.package_type, dep.cpp_info, build_type) if dep.dependencies: - for transitive_dep in dep.dependencies.items(): + for transitive_dep in dep.dependencies.host.items(): dep_name = transitive_dep[0].ref.name - component.requires = [f"{dep_name}:{dep_name}"] + component.requires.append(f"{dep_name}:{dep_name}") # the component will be just the package name cps.default_components = [f"{dep.ref.name}"] From 3685216332f830f3389ece3a562da33706267ac7 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 12 Aug 2024 19:35:45 +0200 Subject: [PATCH 18/19] fix --- conan/cps/cps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conan/cps/cps.py b/conan/cps/cps.py index 78d3c5a44be..cec264e6543 100644 --- a/conan/cps/cps.py +++ b/conan/cps/cps.py @@ -233,7 +233,7 @@ def from_conan(dep): else: # single component, called same as library component = CPSComponent.from_cpp_info(dep.cpp_info, dep.package_type) - if dep.dependencies: + if not component.requires and dep.dependencies: for transitive_dep in dep.dependencies.host.items(): dep_name = transitive_dep[0].ref.name component.requires.append(f"{dep_name}:{dep_name}") From 577104d1becab1f9b6ea42d3546d2e65050e01f7 Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 26 Aug 2024 00:18:50 +0200 Subject: [PATCH 19/19] wip --- conan/cps/__init__.py | 1 + test/integration/cps/test_cps.py | 19 +++++-------------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/conan/cps/__init__.py b/conan/cps/__init__.py index e69de29bb2d..d2efed3aefe 100644 --- a/conan/cps/__init__.py +++ b/conan/cps/__init__.py @@ -0,0 +1 @@ +from conan.cps.cps import CPS diff --git a/test/integration/cps/test_cps.py b/test/integration/cps/test_cps.py index 8c6b8413e34..f1ce24f6c19 100644 --- a/test/integration/cps/test_cps.py +++ b/test/integration/cps/test_cps.py @@ -70,18 +70,12 @@ def test_cps_in_pkg(): "cps_version": "0.12.0", "name": "zlib", "version": "1.3.1", - "configurations": [ - "release" - ], - "default_components": [ - "zlib" - ], + "configurations": ["release"], + "default_components": ["zlib"], "components": { "zlib": { "type": "archive", - "includes": [ - "@prefix@/include" - ], + "includes": ["@prefix@/include"], "location": "@prefix@/lib/zlib.a" } } @@ -102,7 +96,7 @@ def package(self): save(self, cps_path, cps) def package_info(self): - from conan.cps.cps import CPS + from conan.cps import CPS self.cpp_info = CPS.load("zlib.cps").to_conan() """) c.save({"pkg/conanfile.py": conanfile}) @@ -110,19 +104,16 @@ def package_info(self): settings = "-s os=Windows -s compiler=msvc -s compiler.version=191 -s arch=x86_64" c.run(f"install --requires=zlib/1.3.1 {settings} -g CPSDeps") - print(c.out) mapping = json.loads(c.load("build/cps/cpsmap-msvc-191-x86_64-release.json")) - print(json.dumps(mapping, indent=2)) for _, path_cps in mapping.items(): assert os.path.exists(path_cps) assert not os.path.exists(os.path.join(c.current_folder, "zlib.cps")) + assert not os.path.exists(os.path.join(c.current_folder, "build", "cps", "zlib.cps")) c.run(f"install --requires=zlib/1.3.1 {settings} -g CMakeDeps") - print(c.out) cmake = c.load("zlib-release-x86_64-data.cmake") - print(cmake) assert 'set(zlib_INCLUDE_DIRS_RELEASE "${zlib_PACKAGE_FOLDER_RELEASE}/include")' in cmake assert 'set(zlib_LIB_DIRS_RELEASE "${zlib_PACKAGE_FOLDER_RELEASE}/lib")' assert 'set(zlib_LIBS_RELEASE zlib)' in cmake