forked from sony/nmos-cpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconanfile.py
More file actions
222 lines (196 loc) · 12.4 KB
/
conanfile.py
File metadata and controls
222 lines (196 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import json
import os
import re
from conans import ConanFile, CMake, tools
required_conan_version = ">=1.33.0"
class NmosCppConan(ConanFile):
name = "nmos-cpp"
description = "An NMOS C++ Implementation"
license = "Apache-2.0"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://github.com/sony/nmos-cpp"
topics = ("amwa", "nmos", "is-04", "is-05", "is-07", "is-08", "is-09", "broadcasting", "network", "media")
settings = "os", "compiler", "build_type", "arch"
# for now, no "shared" option support
options = {
"fPIC": [True, False],
}
# "fPIC" is handled automatically by Conan, injecting CMAKE_POSITION_INDEPENDENT_CODE
default_options = {
"fPIC": True,
}
# wrapper CMakeLists.txt to call conan_basic_setup()
exports_sources = ["CMakeLists.txt"]
# use cmake_find_package_multi and prefer config-file packages
generators = "cmake", "cmake_find_package_multi"
_cmake = None
# for out-of-source build, cf. wrapper CMakeLists.txt
@property
def _source_subfolder(self):
return "source_subfolder"
@property
def _build_subfolder(self):
return "build_subfolder"
def config_options(self):
if self.settings.os == "Windows":
del self.options.fPIC
def requirements(self):
# for now, consistent with project's conanfile.txt
self.requires("boost/1.78.0")
self.requires("cpprestsdk/2.10.18")
self.requires("websocketpp/0.8.2")
self.requires("openssl/1.1.1n")
self.requires("json-schema-validator/2.1.0")
def build_requirements(self):
self.build_requires("cmake/[>3.17]")
def validate(self):
if self.settings.compiler.get_safe("cppstd"):
tools.check_min_cppstd(self, 11)
def source(self):
tools.get(**self.conan_data["sources"][self.version],
destination=self._source_subfolder, strip_root=True)
def _configure_cmake(self):
if self._cmake:
return self._cmake
self._cmake = CMake(self)
# prefer config-file packages created by cmake_find_package_multi
# over any system-installed find-module packages
self._cmake.definitions["CMAKE_FIND_PACKAGE_PREFER_CONFIG"] = True
# no need to build unit tests
self._cmake.definitions["NMOS_CPP_BUILD_TESTS"] = False
# the examples (nmos-cpp-registry and nmos-cpp-node) are useful utilities for users
self._cmake.definitions["NMOS_CPP_BUILD_EXAMPLES"] = True
# out-of-source build
self._cmake.configure(build_folder=self._build_subfolder)
return self._cmake
def build(self):
cmake = self._configure_cmake()
cmake.build()
def package(self):
self.copy("LICENSE", dst="licenses", src=self._source_subfolder)
cmake = self._configure_cmake()
cmake.install()
cmake_folder = os.path.join(self.package_folder, "lib", "cmake")
self._create_components_file_from_cmake_target_file(os.path.join(cmake_folder, "nmos-cpp", "nmos-cpp-targets.cmake"))
# remove the project's own generated config-file package
tools.rmdir(cmake_folder)
# based on abseil recipe
# see https://github.com/conan-io/conan-center-index/blob/master/recipes/abseil/all/conanfile.py
def _create_components_file_from_cmake_target_file(self, target_file_path):
components = {}
target_content = tools.load(target_file_path)
cmake_functions = re.findall(r"(?P<func>add_library|set_target_properties)[\n|\s]*\([\n|\s]*(?P<args>[^)]*)\)", target_content)
for (cmake_function_name, cmake_function_args) in cmake_functions:
cmake_function_args = re.split(r"[\s|\n]+", cmake_function_args, maxsplit=2)
cmake_imported_target_name = cmake_function_args[0]
cmake_target_nonamespace = cmake_imported_target_name.replace("nmos-cpp::", "")
component_name = cmake_target_nonamespace.lower()
# Conan component name cannot be the same as the package name
if component_name == "nmos-cpp":
component_name = "nmos-cpp-lib"
components.setdefault(component_name, {"cmake_target": cmake_target_nonamespace})
if cmake_function_name == "add_library":
cmake_imported_target_type = cmake_function_args[1]
if cmake_imported_target_type in ["STATIC", "SHARED"]:
# library filenames are based on the target name by default
lib_name = cmake_target_nonamespace
# the filename may be changed by a straightforward command:
# set_property(TARGET Bonjour PROPERTY OUTPUT_NAME dnssd)
# but we'd have to read the nmos-cpp-targets-<config>.cmake files
# and parse the IMPORTED_LOCATION_<CONFIG> values
if lib_name == "Bonjour":
lib_name = "dnssd"
components[component_name]["libs"] = [lib_name]
elif cmake_function_name == "set_target_properties":
target_properties = re.findall(r"(?P<property>INTERFACE_[A-Z_]+)[\n|\s]+\"(?P<values>.+)\"", cmake_function_args[2])
for target_property in target_properties:
property_type = target_property[0]
# '\', '$' and '"' are escaped; '$' especially is important here
# see https://github.com/conan-io/conan/blob/release/1.39/conans/client/generators/cmake_common.py#L43-L48
property_values = re.sub(r"\\(.)", r"\1", target_property[1]).split(";")
if property_type == "INTERFACE_LINK_LIBRARIES":
for dependency in property_values:
match_private = re.fullmatch(r"\$<LINK_ONLY:(.+)>", dependency)
if match_private:
dependency = match_private.group(1)
if "::" in dependency:
dependency = dependency.replace("nmos-cpp::", "")
# Conan component name cannot be the same as the package name
if dependency == "nmos-cpp":
dependency = "nmos-cpp-lib"
# Conan packages for Boost, cpprestsdk, websocketpp and OpenSSL have component names that (except for being lowercase) match the CMake targets
# json-schema-validator overrides cmake_find_package[_multi] names
elif dependency == "nlohmann_json_schema_validator::nlohmann_json_schema_validator":
dependency = "json-schema-validator::json-schema-validator"
components[component_name].setdefault("requires" if not match_private else "requires_private", []).append(dependency.lower())
elif "${_IMPORT_PREFIX}/lib/" in dependency:
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, dependency))
else:
components[component_name].setdefault("system_libs", []).append(dependency)
elif property_type == "INTERFACE_COMPILE_DEFINITIONS":
for property_value in property_values:
components[component_name].setdefault("defines", []).append(property_value)
elif property_type == "INTERFACE_COMPILE_FEATURES":
for property_value in property_values:
if property_value not in ["cxx_std_11"]:
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, property_value))
elif property_type == "INTERFACE_COMPILE_OPTIONS":
for property_value in property_values:
# handle forced include (Visual Studio /FI, gcc -include) by relying on includedirs containing "include"
property_value = property_value.replace("${_IMPORT_PREFIX}/include/", "")
components[component_name].setdefault("cxxflags", []).append(property_value)
elif property_type == "INTERFACE_INCLUDE_DIRECTORIES":
for property_value in property_values:
if property_value not in ["${_IMPORT_PREFIX}/include"]:
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, property_value))
elif property_type == "INTERFACE_LINK_OPTIONS":
for property_value in property_values:
# workaround required because otherwise "/ignore:4099" gets converted to "\ignore:4099.obj"
# thankfully the MSVC linker accepts both '/' and '-' for the option specifier and Visual Studio
# handles link options appearing in Link/AdditionalDependencies rather than Link/AdditionalOptions
# because the CMake generators put them in INTERFACE_LINK_LIBRARIES rather than INTERFACE_LINK_OPTIONS
# see https://github.com/conan-io/conan/pull/8812
# and https://docs.microsoft.com/en-us/cpp/build/reference/linking?view=msvc-160#command-line
property_value = re.sub(r"^/", r"-", property_value)
components[component_name].setdefault("linkflags", []).append(property_value)
else:
self.output.warn("{} recipe does not handle {} (yet)".format(self.name, property_type))
# Save components informations in json file
with open(self._components_helper_filepath, "w") as json_file:
json.dump(components, json_file, indent=4)
@property
def _components_helper_filepath(self):
return os.path.join(self.package_folder, "lib", "components.json")
def package_info(self):
bindir = "bin"
libdir = "lib"
# on Windows, cmake_install() puts the binaries in a config-specific sub-folder
if self.settings.os == "Windows":
config_install_dir = "Debug" if self.settings.build_type == "Debug" else "Release"
bindir = os.path.join(bindir, config_install_dir)
libdir = os.path.join(libdir, config_install_dir)
def _register_components():
components_json_file = tools.load(self._components_helper_filepath)
components = json.loads(components_json_file)
for component_name, values in components.items():
cmake_target = values["cmake_target"]
self.cpp_info.components[component_name].names["cmake_find_package"] = cmake_target
self.cpp_info.components[component_name].names["cmake_find_package_multi"] = cmake_target
self.cpp_info.components[component_name].libs = values.get("libs", [])
self.cpp_info.components[component_name].libdirs = [libdir]
self.cpp_info.components[component_name].defines = values.get("defines", [])
self.cpp_info.components[component_name].cxxflags = values.get("cxxflags", [])
linkflags = values.get("linkflags", [])
self.cpp_info.components[component_name].sharedlinkflags = linkflags
self.cpp_info.components[component_name].exelinkflags = linkflags
self.cpp_info.components[component_name].system_libs = values.get("system_libs", [])
self.cpp_info.components[component_name].frameworks = values.get("frameworks", [])
self.cpp_info.components[component_name].requires = values.get("requires", [])
# hmm, how should private requirements be indicated? this results in a string format error...
#self.cpp_info.components[component_name].requires.extend([(r, "private") for r in values.get("requires_private", [])])
self.cpp_info.components[component_name].requires.extend(values.get("requires_private", []))
_register_components()
# add nmos-cpp-registry and nmos-cpp-node to the path
bin_path = os.path.join(self.package_folder, bindir)
self.output.info("Appending PATH environment variable: {}".format(bin_path))
self.env_info.PATH.append(bin_path)