Skip to content

Commit 9e7ddbd

Browse files
committed
add KB-H077 in conan-center to test whether shared libs on macOS are relocatable
1 parent dfa1dc1 commit 9e7ddbd

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

hooks/conan-center.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@
22
import collections
33

44
import fnmatch
5+
import glob
56
import inspect
67
import os
8+
import platform
79
import re
810
import sys
911
import subprocess
1012

1113
from logging import WARNING, ERROR, INFO, DEBUG, NOTSET
1214

1315
import yaml
16+
from conan.tools.apple import is_apple_os
1417
from conans import tools
1518
from conans.client.graph.python_requires import ConanPythonRequire
1619
from conans.client.loader import parse_conanfile
20+
from conans.util.runners import check_output_runner
1721
try:
1822
from conans import Settings
1923
except ImportError:
@@ -85,6 +89,7 @@
8589
"KB-H073": "TEST V1 PACKAGE FOLDER",
8690
"KB-H074": "STATIC ARTIFACTS",
8791
"KB-H075": "REQUIREMENT OVERRIDE PARAMETER",
92+
"KB-H077": "APPLE RELOCATABLE SHARED LIBS",
8893
}
8994

9095

@@ -1231,6 +1236,14 @@ def test(out):
12311236
for lib in libs:
12321237
out.warn("Library '{}' links to system library '{}' but it is not in cpp_info.{}.".format(lib, missing_system_lib, attribute))
12331238

1239+
@run_test("KB-H077", output)
1240+
def test(out):
1241+
if not is_apple_os(conanfile):
1242+
return
1243+
not_relocatable_libs = _get_non_relocatable_shared_libs(conanfile)
1244+
if not_relocatable_libs:
1245+
out.error(f"install_name dir of these shared libs is not @rpath: {', '.join(not_relocatable_libs)}")
1246+
12341247

12351248
@raise_if_error_output
12361249
def post_package_info(output, conanfile, reference, **kwargs):
@@ -1565,6 +1578,25 @@ def _deplibs_from_shlibs(conanfile, out):
15651578
return deplibs
15661579

15671580

1581+
def _get_non_relocatable_shared_libs(conanfile):
1582+
if platform.system() != "Darwin":
1583+
return None
1584+
1585+
bad_shared_libs = []
1586+
1587+
libdirs = [os.path.join(conanfile.package_folder, libdir)
1588+
for libdir in getattr(conanfile.cpp.package, "libdirs")]
1589+
for libdir in libdirs:
1590+
for dylib_path in glob.glob(os.path.join(libdir, "*.dylib")):
1591+
command = f"otool -D {dylib_path}"
1592+
install_name = check_output_runner(command).strip().split(":")[1].strip()
1593+
base_install_name = install_name.rsplit("/", 1)[0]
1594+
if base_install_name != "@rpath":
1595+
bad_shared_libs.append(os.path.basename(dylib_path))
1596+
1597+
return bad_shared_libs
1598+
1599+
15681600
_GLIBC_LIBS = {
15691601
"anl", "BrokenLocale", "crypt", "dl", "g", "m", "mvec", "nsl", "nss_compat", "nss_db", "nss_dns",
15701602
"nss_files", "nss_hesiod", "pthread", "resolv", "rt", "thread_db", "util",
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
import platform
3+
import textwrap
4+
5+
from parameterized import parameterized
6+
7+
from conans import tools
8+
9+
from tests.utils.test_cases.conan_client import ConanClientTestCase
10+
11+
12+
class TestAppleRelocatableSharedLibs(ConanClientTestCase):
13+
def _get_environ(self, **kwargs):
14+
kwargs = super(TestAppleRelocatableSharedLibs, self)._get_environ(**kwargs)
15+
kwargs.update({
16+
"CONAN_HOOKS": os.path.join(os.path.dirname(__file__), os.pardir,
17+
os.pardir, os.pardir, "hooks", "conan-center")
18+
})
19+
return kwargs
20+
21+
conanfile = textwrap.dedent("""\
22+
from conan import ConanFile
23+
from conan.tools.cmake import CMake, cmake_layout
24+
25+
class FooConan(ConanFile):
26+
name = "foo"
27+
url = "fake_url.com"
28+
license = "fake_license"
29+
description = "whatever"
30+
homepage = "homepage.com"
31+
topics = ("fake_topic", "another_fake_topic")
32+
33+
settings = "os", "arch", "compiler", "build_type"
34+
options = {"shared": [True, False], "fPIC": [True, False]}
35+
default_options = {"shared": False, "fPIC": True}
36+
37+
exports_sources = "CMakeLists.txt", "foo.h", "foo.c"
38+
generators = "CMakeToolchain"
39+
40+
def config_options(self):
41+
if self.settings.os == "Windows":
42+
del self.options.fPIC
43+
44+
def configure(self):
45+
if self.options.shared:
46+
self.options.rm_safe("fPIC")
47+
self.settings.rm_safe("compiler.cppstd")
48+
self.settings.rm_safe("compiler.libcxx")
49+
50+
def layout(self):
51+
cmake_layout(self)
52+
53+
def build(self):
54+
cmake = CMake(self)
55+
cmake.configure()
56+
cmake.build()
57+
58+
def package(self):
59+
cmake = CMake(self)
60+
cmake.install()
61+
62+
def package_info(self):
63+
self.cpp_info.libs = ["foo"]
64+
""")
65+
66+
foo_h = textwrap.dedent("""\
67+
#pragma once
68+
#include "foo_export.h"
69+
FOO_API int foo_test();
70+
""")
71+
72+
foo_c = textwrap.dedent("""\
73+
#include "foo.h"
74+
int foo_test() {return 1;}
75+
""")
76+
77+
@staticmethod
78+
def cmakelists(install_name_dir="@rpath"):
79+
if install_name_dir == "@rpath":
80+
cmake_install_name_dir = ""
81+
else:
82+
cmake_install_name_dir = f"set(CMAKE_INSTALL_NAME_DIR \"{install_name_dir}\")"
83+
84+
return textwrap.dedent(f"""\
85+
cmake_minimum_required(VERSION 3.15)
86+
project(test_relocatable LANGUAGES C)
87+
88+
include(GenerateExportHeader)
89+
include(GNUInstallDirs)
90+
91+
{cmake_install_name_dir}
92+
93+
add_library(foo foo.c)
94+
generate_export_header(foo EXPORT_MACRO_NAME FOO_API)
95+
set_target_properties(foo PROPERTIES C_VISIBILITY_PRESET hidden)
96+
target_include_directories(foo PUBLIC
97+
$<BUILD_INTERFACE:${{PROJECT_SOURCE_DIR}}>
98+
$<BUILD_INTERFACE:${{PROJECT_BINARY_DIR}}>
99+
)
100+
101+
install(FILES ${{PROJECT_SOURCE_DIR}}/foo.h ${{PROJECT_BINARY_DIR}}/foo_export.h
102+
DESTINATION ${{CMAKE_INSTALL_INCLUDEDIR}})
103+
install(TARGETS foo
104+
RUNTIME DESTINATION ${{CMAKE_INSTALL_BINDIR}}
105+
ARCHIVE DESTINATION ${{CMAKE_INSTALL_LIBDIR}}
106+
LIBRARY DESTINATION ${{CMAKE_INSTALL_LIBDIR}})
107+
""")
108+
109+
@parameterized.expand([
110+
(False, "@rpath"),
111+
(False, ""),
112+
(False, "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"),
113+
(True, "@rpath"),
114+
(True, ""),
115+
(True, "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"),
116+
])
117+
def test_relocatable(self, shared, install_name_dir):
118+
tools.save("conanfile.py", content=self.conanfile)
119+
tools.save("CMakeLists.txt", content=self.cmakelists(install_name_dir))
120+
tools.save("foo.h", content=self.foo_h)
121+
tools.save("foo.c", content=self.foo_c)
122+
output = self.conan(["create", ".", "foo/1.0@user/test", "-o", f"foo:shared={shared}"])
123+
if platform.system() == "Darwin" and shared and install_name_dir != "@rpath":
124+
self.assertIn("ERROR: [APPLE RELOCATABLE SHARED LIBS (KB-H077)] install_name dir of these shared libs is not @rpath: libfoo.dylib", output)
125+
else:
126+
self.assertIn("[APPLE RELOCATABLE SHARED LIBS (KB-H077)] OK", output)

0 commit comments

Comments
 (0)