Skip to content

[conan-center] New KB-H077 - test whether shared libs on macOS are relocatable #477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions hooks/conan-center.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
import glob
import inspect
import os
import platform
import re
import sys
import subprocess

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

import yaml
from conan.tools.apple import is_apple_os
from conans import tools
from conans.client.graph.python_requires import ConanPythonRequire
from conans.client.loader import parse_conanfile
from conans.util.runners import check_output_runner
try:
from conans import Settings
except ImportError:
Expand Down Expand Up @@ -87,6 +90,7 @@
"KB-H074": "STATIC ARTIFACTS",
"KB-H075": "REQUIREMENT OVERRIDE PARAMETER",
"KB-H076": "EITHER STATIC OR SHARED OF EACH LIB",
"KB-H077": "APPLE RELOCATABLE SHARED LIBS",
}


Expand Down Expand Up @@ -1240,6 +1244,14 @@ def test(out):
for lib in libs:
out.warn("Library '{}' links to system library '{}' but it is not in cpp_info.{}.".format(lib, missing_system_lib, attribute))

@run_test("KB-H077", output)
def test(out):
if not is_apple_os(conanfile):
return
not_relocatable_libs = _get_non_relocatable_shared_libs(conanfile)
if not_relocatable_libs:
out.error(f"install_name dir of these shared libs is not @rpath: {', '.join(not_relocatable_libs)}")


@raise_if_error_output
def post_package_info(output, conanfile, reference, **kwargs):
Expand Down Expand Up @@ -1605,6 +1617,25 @@ def _deplibs_from_shlibs(conanfile, out):
return deplibs


def _get_non_relocatable_shared_libs(conanfile):
if platform.system() != "Darwin":
return None

bad_shared_libs = []

libdirs = [os.path.join(conanfile.package_folder, libdir)
for libdir in getattr(conanfile.cpp.package, "libdirs")]
for libdir in libdirs:
for dylib_path in glob.glob(os.path.join(libdir, "*.dylib")):
command = f"otool -D {dylib_path}"
install_name = check_output_runner(command).strip().split(":")[1].strip()
install_name_dir = os.path.dirname(install_name)
if install_name_dir != "@rpath":
bad_shared_libs.append(os.path.basename(dylib_path))

return bad_shared_libs


_GLIBC_LIBS = {
"anl", "BrokenLocale", "crypt", "dl", "g", "m", "mvec", "nsl", "nss_compat", "nss_db", "nss_dns",
"nss_files", "nss_hesiod", "pthread", "resolv", "rt", "thread_db", "util",
Expand Down
126 changes: 126 additions & 0 deletions tests/test_hooks/conan-center/test_apple_relocatable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import os
import platform
import textwrap

from parameterized import parameterized

from conans import tools

from tests.utils.test_cases.conan_client import ConanClientTestCase


class TestAppleRelocatableSharedLibs(ConanClientTestCase):
def _get_environ(self, **kwargs):
kwargs = super(TestAppleRelocatableSharedLibs, 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

conanfile = textwrap.dedent("""\
from conan import ConanFile
from conan.tools.cmake import CMake, cmake_layout

class FooConan(ConanFile):
name = "foo"
url = "fake_url.com"
license = "fake_license"
description = "whatever"
homepage = "homepage.com"
topics = ("fake_topic", "another_fake_topic")

settings = "os", "arch", "compiler", "build_type"
options = {"shared": [True, False], "fPIC": [True, False]}
default_options = {"shared": False, "fPIC": True}

exports_sources = "CMakeLists.txt", "foo.h", "foo.c"
generators = "CMakeToolchain"

def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC

def configure(self):
if self.options.shared:
self.options.rm_safe("fPIC")
self.settings.rm_safe("compiler.cppstd")
self.settings.rm_safe("compiler.libcxx")

def layout(self):
cmake_layout(self)

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()

def package_info(self):
self.cpp_info.libs = ["foo"]
""")

foo_h = textwrap.dedent("""\
#pragma once
#include "foo_export.h"
FOO_API int foo_test();
""")

foo_c = textwrap.dedent("""\
#include "foo.h"
int foo_test() {return 1;}
""")

@staticmethod
def cmakelists(install_name_dir="@rpath"):
if install_name_dir == "@rpath":
cmake_install_name_dir = ""
else:
cmake_install_name_dir = f"set(CMAKE_INSTALL_NAME_DIR \"{install_name_dir}\")"

return textwrap.dedent(f"""\
cmake_minimum_required(VERSION 3.15)
project(test_relocatable LANGUAGES C)

include(GenerateExportHeader)
include(GNUInstallDirs)

{cmake_install_name_dir}

add_library(foo foo.c)
generate_export_header(foo EXPORT_MACRO_NAME FOO_API)
set_target_properties(foo PROPERTIES C_VISIBILITY_PRESET hidden)
target_include_directories(foo PUBLIC
$<BUILD_INTERFACE:${{PROJECT_SOURCE_DIR}}>
$<BUILD_INTERFACE:${{PROJECT_BINARY_DIR}}>
)

install(FILES ${{PROJECT_SOURCE_DIR}}/foo.h ${{PROJECT_BINARY_DIR}}/foo_export.h
DESTINATION ${{CMAKE_INSTALL_INCLUDEDIR}})
install(TARGETS foo
RUNTIME DESTINATION ${{CMAKE_INSTALL_BINDIR}}
ARCHIVE DESTINATION ${{CMAKE_INSTALL_LIBDIR}}
LIBRARY DESTINATION ${{CMAKE_INSTALL_LIBDIR}})
""")

@parameterized.expand([
(False, "@rpath"),
(False, ""),
(False, "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"),
(True, "@rpath"),
(True, ""),
(True, "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}"),
])
def test_relocatable(self, shared, install_name_dir):
tools.save("conanfile.py", content=self.conanfile)
tools.save("CMakeLists.txt", content=self.cmakelists(install_name_dir))
tools.save("foo.h", content=self.foo_h)
tools.save("foo.c", content=self.foo_c)
output = self.conan(["create", ".", "foo/1.0@user/test", "-o", f"foo:shared={shared}"])
if platform.system() == "Darwin" and shared and install_name_dir != "@rpath":
self.assertIn("ERROR: [APPLE RELOCATABLE SHARED LIBS (KB-H077)] install_name dir of these shared libs is not @rpath: libfoo.dylib", output)
else:
self.assertIn("[APPLE RELOCATABLE SHARED LIBS (KB-H077)] OK", output)