Skip to content
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

Fix grpc static in Windows with shared protobuf dependency #26556

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 2 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
93 changes: 32 additions & 61 deletions recipes/grpc/all/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from conan.tools.microsoft import check_min_vs, is_msvc
from conan.tools.scm import Version

required_conan_version = ">=1.60.0 <2 || >=2.0.5"
required_conan_version = ">=2.0.5"


class GrpcConan(ConanFile):
Expand Down Expand Up @@ -58,7 +58,6 @@ class GrpcConan(ConanFile):
"with_libsystemd": True
}

short_paths = True
_target_info = None

@property
Expand All @@ -69,10 +68,6 @@ def _grpc_plugin_template(self):
def _cxxstd_required(self):
return 14 if Version(self.version) >= "1.47" else 11

@property
def _is_legacy_one_profile(self):
return not hasattr(self, "settings_build")

@property
def _supports_libsystemd(self):
return self.settings.os in ["Linux", "FreeBSD"] and Version(self.version) >= "1.52"
Expand Down Expand Up @@ -147,8 +142,8 @@ def validate(self):
)

def build_requirements(self):
if not self._is_legacy_one_profile:
self.tool_requires("protobuf/<host_version>")
self.tool_requires("cmake/[>=3.25 <4")
memsharded marked this conversation as resolved.
Show resolved Hide resolved
self.tool_requires("protobuf/<host_version>")
if cross_building(self):
# when cross compiling we need pre compiled grpc plugins for protoc
self.tool_requires(f"grpc/{self.version}")
Expand All @@ -157,11 +152,6 @@ def source(self):
get(self, **self.conan_data["sources"][self.version], strip_root=True)

def generate(self):
# Set up environment so that we can run grpc-cpp-plugin at build time
VirtualBuildEnv(self).generate()
if self._is_legacy_one_profile:
VirtualRunEnv(self).generate(scope="build")

# This doesn't work yet as one would expect, because the install target builds everything
# and we need the install target because of the generated CMake files
#
Expand Down Expand Up @@ -220,25 +210,30 @@ def generate(self):
def _patch_sources(self):
apply_conandata_patches(self)

# On macOS if all the following are true:
# - protoc from protobuf has shared library dependencies
# - grpc_cpp_plugin has shared library deps (when crossbuilding)
# - using `make` as the cmake generator
# Make will run commands via `/bin/sh` which will strip all env vars that start with `DYLD*`
# This workaround wraps the protoc command to be invoked by CMake with a modified environment
# Management of shared libs when grpc has shared dependencies (like protobuf)
# As the grpc_cpp_plugin that executes during the build will need those packages shared libs
cmakelists = os.path.join(self.source_folder, "CMakeLists.txt")
settings_build = getattr(self, "settings_build", self.settings)
if settings_build.os == "Macos":
variable, repl = None, None
if self.settings_build.os == "Macos":
# On macOS if all the following are true:
# - protoc from protobuf has shared library dependencies
# - grpc_cpp_plugin has shared library deps (when crossbuilding)
# - using `make` as the cmake generator
# Make will run commands via `/bin/sh` which will strip all env vars that start with `DYLD*`
# This workaround wraps the protoc command to be invoked by CMake with a modified environment
variable, repl = "DYLD_LIBRARY_PATH", "$ENV{DYLD_LIBRARY_PATH}" # to bypass OSX restrictions
elif not cross_building(self) and self.settings_build.os == "Linux":
# CMAKE_LIBRARY_PATH is defined by conan_toolchain.cmake, in Linux it is "lib" dir of .so dependencies
variable, repl = "LD_LIBRARY_PATH", "$<JOIN:${CMAKE_LIBRARY_PATH},:>" # to allow using protobuf/abseil as shared deps
elif not cross_building(self) and self.settings_build.os == "Windows":
# CONAN_RUNTIME_LIB_DIRS defined by conan_toolchain.cmake points to the "bin" folder in Linux, containing the DLLs
variable, repl = "PATH", "$<JOIN:${CONAN_RUNTIME_LIB_DIRS},;>" # to allow using protobuf/abseil as shared deps

if variable and repl:
replace_in_file(self, cmakelists,
"COMMAND ${_gRPC_PROTOBUF_PROTOC_EXECUTABLE}",
'COMMAND ${CMAKE_COMMAND} -E env "DYLD_LIBRARY_PATH=$ENV{DYLD_LIBRARY_PATH}" ${_gRPC_PROTOBUF_PROTOC_EXECUTABLE}')
elif not cross_building(self) and settings_build.os == "Linux":
# we are not cross-building, but protobuf or abseil may be shared
# so we need to set LD_LIBRARY_PATH to find them
# Note: if protobuf used RPATH instead of RUNPATH this is not needed
replace_in_file(self, cmakelists,
"COMMAND ${_gRPC_PROTOBUF_PROTOC_EXECUTABLE}",
'COMMAND ${CMAKE_COMMAND} -E env "LD_LIBRARY_PATH=$<JOIN:${CMAKE_LIBRARY_PATH},:>:$ENV{LD_LIBRARY_PATH}" ${_gRPC_PROTOBUF_PROTOC_EXECUTABLE}')
f'COMMAND ${{CMAKE_COMMAND}} -E env --modify "{variable}=path_list_prepend:{repl}" ${{_gRPC_PROTOBUF_PROTOC_EXECUTABLE}}')

if self.settings.os == "Macos" and Version(self.version) >= "1.64":
# See https://github.com/grpc/grpc/issues/36654#issuecomment-2228569158
replace_in_file(self, cmakelists, "target_compile_features(upb_textformat_lib PUBLIC cxx_std_14)",
Expand Down Expand Up @@ -306,24 +301,13 @@ def _module_path(self):

@property
def _grpc_components(self):
system_libs = []
if self.settings.os == "Windows":
system_libs = ["crypt32", "ws2_32", "wsock32"]
elif self.settings.os in ["Linux", "FreeBSD"]:
system_libs = ["m", "pthread"]

def libsystemd():
return ["libsystemd::libsystemd"] if self._supports_libsystemd and self.options.with_libsystemd else []

def libm():
return ["m"] if self.settings.os in ["Linux", "FreeBSD"] else []

def pthread():
return ["pthread"] if self.settings.os in ["Linux", "FreeBSD"] else []

def crypt32():
return ["crypt32"] if self.settings.os == "Windows" else []

def ws2_32():
return ["ws2_32"] if self.settings.os == "Windows" else []

def wsock32():
return ["wsock32"] if self.settings.os == "Windows" else []
libsystemd = ["libsystemd::libsystemd"] if self._supports_libsystemd and self.options.with_libsystemd else []

targets = self.target_info['grpc_targets']
components = {}
Expand All @@ -334,8 +318,8 @@ def wsock32():
continue
components[target['name']] = {
"lib": target['lib'],
"requires": target.get('requires', []) + libsystemd(),
"system_libs": libm() + pthread() + crypt32() + ws2_32() + wsock32(),
"requires": target.get('requires', []) + libsystemd,
"system_libs": system_libs,
"frameworks": target.get('frameworks', []),
}

Expand All @@ -358,10 +342,6 @@ def package_info(self):
self.cpp_info.components[component].system_libs = values.get("system_libs", [])
self.cpp_info.components[component].frameworks = values.get("frameworks", [])

# TODO: to remove in conan v2 once cmake_find_package_* generators removed
self.cpp_info.components[component].names["cmake_find_package"] = target
self.cpp_info.components[component].names["cmake_find_package_multi"] = target

# Executable imported targets are added through custom CMake module files,
# since conan generators don't know how to emulate these kind of targets.
grpc_modules = []
Expand All @@ -372,12 +352,3 @@ def package_info(self):
grpc_module_filename = "{}.cmake".format(executable)
grpc_modules.append(os.path.join(self._module_path, grpc_module_filename))
self.cpp_info.set_property("cmake_build_modules", grpc_modules)

# TODO: to remove once conan v1 not supported anymore
self.cpp_info.names["cmake_find_package"] = "gRPC"
self.cpp_info.names["cmake_find_package_multi"] = "gRPC"
self.env_info.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH = ssl_roots_file_path
if grpc_modules:
self.cpp_info.components["grpc_execs"].build_modules["cmake_find_package"] = grpc_modules
self.cpp_info.components["grpc_execs"].build_modules["cmake_find_package_multi"] = grpc_modules
self.env_info.PATH.append(os.path.join(self.package_folder, "bin"))