From 16e01e9e3b59f42c6c763b2b6dded1d87608b9b4 Mon Sep 17 00:00:00 2001 From: SpaceIm <30052553+SpaceIm@users.noreply.github.com> Date: Sun, 15 Jan 2023 16:39:55 +0100 Subject: [PATCH 1/2] add KB-H076 in conan-center to test that static & shared are not packaged together --- hooks/conan-center.py | 33 ++++++ .../test_packaging_static_shared.py | 104 ++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 tests/test_hooks/conan-center/test_packaging_static_shared.py diff --git a/hooks/conan-center.py b/hooks/conan-center.py index caeee326..55a3580a 100644 --- a/hooks/conan-center.py +++ b/hooks/conan-center.py @@ -2,6 +2,7 @@ import collections import fnmatch +import glob import inspect import os import re @@ -85,6 +86,7 @@ "KB-H073": "TEST V1 PACKAGE FOLDER", "KB-H074": "STATIC ARTIFACTS", "KB-H075": "REQUIREMENT OVERRIDE PARAMETER", + "KB-H076": "EITHER STATIC OR SHARED OF EACH LIB", } @@ -1154,6 +1156,13 @@ def test(out): if not _static_files_well_managed(conanfile, conanfile.package_folder): out.error("Package with 'shared=False' option did not contain any static artifact") + @run_test("KB-H076", output) + def test(out): + libs_both_static_shared = _get_libs_if_static_and_shared(conanfile) + if len(libs_both_static_shared): + out.error("Package contains both shared and static flavors of these " + f"libraries: {', '.join(libs_both_static_shared)}") + @run_test("KB-H020", output) def test(out): if conanfile.name in ["cmake", "msys2", "strawberryperl", "android-ndk", "emsdk"]: @@ -1367,6 +1376,30 @@ def _static_files_well_managed(conanfile, folder): return True +def _get_libs_if_static_and_shared(conanfile): + # TODO: to improve. We only check whether we can find the same lib name with a static or + # shared extension. Therefore: + # - it can't check anything useful on Windows for the moment + # - it can't detect a bad packaging if static & shared flavors have different names + static_extensions = ["a"] + shared_extensions = ["so", "dylib"] + + static_libs = set() + shared_libs = set() + + libdirs = [os.path.join(conanfile.package_folder, libdir) + for libdir in getattr(conanfile.cpp.package, "libdirs")] + for libdir in libdirs: + for ext in static_extensions: + static_libs.update([re.sub(fr"\.{ext}$", "", os.path.basename(p)) + for p in glob.glob(os.path.join(libdir, f"*.{ext}"))]) + for ext in shared_extensions: + shared_libs.update([re.sub(fr"\.{ext}$", "", os.path.basename(p)) + for p in glob.glob(os.path.join(libdir, f"*.{ext}"))]) + + return static_libs.intersection(shared_libs) + + def _files_match_settings(conanfile, folder, output): header_extensions = ["h", "h++", "hh", "hxx", "hpp"] visual_extensions = ["lib", "dll", "exe", "bat"] diff --git a/tests/test_hooks/conan-center/test_packaging_static_shared.py b/tests/test_hooks/conan-center/test_packaging_static_shared.py new file mode 100644 index 00000000..ba0d93d5 --- /dev/null +++ b/tests/test_hooks/conan-center/test_packaging_static_shared.py @@ -0,0 +1,104 @@ +import os +import textwrap + +from parameterized import parameterized + +from conans import tools + +from tests.utils.test_cases.conan_client import ConanClientTestCase + + +class TestPackagingStaticSharedLibraries(ConanClientTestCase): + conanfile_test_artifacts = textwrap.dedent("""\ + from conan import ConanFile + import os + + class AConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + options = {"shared": [True, False], "status": [True, False]} + default_options = {"shared": False, "status": True} + + def package(self): + libdir = os.path.join(self.package_folder, "lib") + os.makedirs(libdir) + if self.options.status: + if self.options.shared: + open(os.path.join(libdir, "libfoo.so"), "w") + else: + open(os.path.join(libdir, "libfoo.a"), "w") + else: + if self.options.shared: + open(os.path.join(libdir, "libfoo.a"), "w") + else: + open(os.path.join(libdir, "libfoo.so"), "w") + + def package_info(self): + self.cpp_info.libs = ["foo"] + """) + + conanfile_test_static_or_shared = textwrap.dedent("""\ + from conan import ConanFile + import os + + class AConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + options = {"shared": [True, False], "status": [True, False]} + default_options = {"shared": False, "status": True} + + def package(self): + libdir = os.path.join(self.package_folder, "lib") + os.makedirs(libdir) + if self.options.status: + if self.options.shared: + open(os.path.join(libdir, "libfoo.so"), "w") + open(os.path.join(libdir, "libbar.so"), "w") + else: + open(os.path.join(libdir, "libfoo.a"), "w") + open(os.path.join(libdir, "libbar.a"), "w") + else: + open(os.path.join(libdir, "libfoo.a"), "w") + open(os.path.join(libdir, "libfoo.so"), "w") + open(os.path.join(libdir, "libbar.a"), "w") + + def package_info(self): + self.cpp_info.libs = ["foo"] + """) + + def _get_environ(self, **kwargs): + kwargs = super(TestPackagingStaticSharedLibraries, self)._get_environ(**kwargs) + kwargs.update({ + "CONAN_HOOKS": os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, + os.pardir, "hooks", "conan-center") + }) + return kwargs + + @parameterized.expand([(True, True), (True, False), (False, True), (False, False)]) + def test_artifacts(self, shared, hook_ok): + tools.save("conanfile.py", content=self.conanfile_test_artifacts) + output = self.conan([ + "create", ".", "name/version@user/test", + "-o", f"name:shared={shared}", + "-o", f"name:status={hook_ok}", + ]) + if hook_ok: + self.assertIn("[SHARED ARTIFACTS (KB-H015)] OK", output) + self.assertIn("[STATIC ARTIFACTS (KB-H074)] OK", output) + elif shared: + self.assertIn("ERROR: [SHARED ARTIFACTS (KB-H015)] Package with 'shared=True' option did not contain any shared artifact", output) + self.assertIn("[STATIC ARTIFACTS (KB-H074)] OK", output) + else: + self.assertIn("[SHARED ARTIFACTS (KB-H015)] OK", output) + self.assertIn("ERROR: [STATIC ARTIFACTS (KB-H074)] Package with 'shared=False' option did not contain any static artifact", output) + + @parameterized.expand([(True, True), (True, False), (False, True), (False, False)]) + def test_either_shared_or_static(self, shared, hook_ok): + tools.save("conanfile.py", content=self.conanfile_test_static_or_shared) + output = self.conan([ + "create", ".", "name/version@user/test", + "-o", f"name:shared={shared}", + "-o", f"name:status={hook_ok}", + ]) + if hook_ok: + self.assertIn("[EITHER STATIC OR SHARED OF EACH LIB (KB-H076)] OK", output) + else: + self.assertIn("ERROR: [EITHER STATIC OR SHARED OF EACH LIB (KB-H076)] Package contains both shared and static flavors of these libraries: libfoo", output) From 184123bbba373c61973a0d6731f685dd0c5c7672 Mon Sep 17 00:00:00 2001 From: SpaceIm <30052553+SpaceIm@users.noreply.github.com> Date: Thu, 19 Jan 2023 11:49:30 +0100 Subject: [PATCH 2/2] handle MinGW libs --- hooks/conan-center.py | 21 ++++++++++++------- .../test_packaging_static_shared.py | 6 +++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/hooks/conan-center.py b/hooks/conan-center.py index 55a3580a..78e31097 100644 --- a/hooks/conan-center.py +++ b/hooks/conan-center.py @@ -1379,9 +1379,10 @@ def _static_files_well_managed(conanfile, folder): def _get_libs_if_static_and_shared(conanfile): # TODO: to improve. We only check whether we can find the same lib name with a static or # shared extension. Therefore: - # - it can't check anything useful on Windows for the moment + # - it can't check anything useful for cl like compilers (Visual Studio, clang-cl, Intel-cc) for the moment # - it can't detect a bad packaging if static & shared flavors have different names - static_extensions = ["a"] + static_extension = "a" + import_lib_extension = "dll.a" shared_extensions = ["so", "dylib"] static_libs = set() @@ -1390,14 +1391,20 @@ def _get_libs_if_static_and_shared(conanfile): libdirs = [os.path.join(conanfile.package_folder, libdir) for libdir in getattr(conanfile.cpp.package, "libdirs")] for libdir in libdirs: - for ext in static_extensions: - static_libs.update([re.sub(fr"\.{ext}$", "", os.path.basename(p)) - for p in glob.glob(os.path.join(libdir, f"*.{ext}"))]) - for ext in shared_extensions: + # Collect statib libs. + # Pay attention to not pick up import libs while collecting static libs ! + static_libs.update([re.sub(fr"\.{static_extension}$", "", os.path.basename(p)) + for p in glob.glob(os.path.join(libdir, f"*.{static_extension}")) + if not p.endswith(f".{import_lib_extension}")]) + + # Collect shared libs and import libs + for ext in shared_extensions + [import_lib_extension]: shared_libs.update([re.sub(fr"\.{ext}$", "", os.path.basename(p)) for p in glob.glob(os.path.join(libdir, f"*.{ext}"))]) - return static_libs.intersection(shared_libs) + result = list(static_libs.intersection(shared_libs)) + result.sort() + return result def _files_match_settings(conanfile, folder, output): diff --git a/tests/test_hooks/conan-center/test_packaging_static_shared.py b/tests/test_hooks/conan-center/test_packaging_static_shared.py index ba0d93d5..bebf9a39 100644 --- a/tests/test_hooks/conan-center/test_packaging_static_shared.py +++ b/tests/test_hooks/conan-center/test_packaging_static_shared.py @@ -52,13 +52,17 @@ def package(self): if self.options.shared: open(os.path.join(libdir, "libfoo.so"), "w") open(os.path.join(libdir, "libbar.so"), "w") + open(os.path.join(libdir, "libfoobar.dll.a"), "w") else: open(os.path.join(libdir, "libfoo.a"), "w") open(os.path.join(libdir, "libbar.a"), "w") + open(os.path.join(libdir, "libfoobar.a"), "w") else: open(os.path.join(libdir, "libfoo.a"), "w") open(os.path.join(libdir, "libfoo.so"), "w") open(os.path.join(libdir, "libbar.a"), "w") + open(os.path.join(libdir, "libfoobar.a"), "w") + open(os.path.join(libdir, "libfoobar.dll.a"), "w") def package_info(self): self.cpp_info.libs = ["foo"] @@ -101,4 +105,4 @@ def test_either_shared_or_static(self, shared, hook_ok): if hook_ok: self.assertIn("[EITHER STATIC OR SHARED OF EACH LIB (KB-H076)] OK", output) else: - self.assertIn("ERROR: [EITHER STATIC OR SHARED OF EACH LIB (KB-H076)] Package contains both shared and static flavors of these libraries: libfoo", output) + self.assertIn("ERROR: [EITHER STATIC OR SHARED OF EACH LIB (KB-H076)] Package contains both shared and static flavors of these libraries: libfoo, libfoobar", output)