Skip to content

Commit fd8e6fd

Browse files
garethsbmadebruilianries
authored
(#6891) Add nmos-cpp
* nmos-cpp recipe files copied from sony/nmos-cpp#195 * Add config.yml for current tip of garethsb:library-cmake * Attempt to install Avahi Apple Bonjour compatibility library on Linux * Provide with_dnssd options based on #6928, #6930, #6937 * Fix typo in prev commit * Simplify selection of appropriate requirements for DNS-SD * For now, keep default option as mdnsresponder on Windows and Linux * Leave most exceptions uncaught as they really aren't expected in the test_package (cf. the best practice shown in nmos-cpp-node example app) but do catch web::http::http_exception since the most likely cause of that is a port being in use or, on Windows with cpprestsdk built with its default settings, that the test_package wasn't run as administrator, which WinHTTP enforces to listen on the wildcard hostname. Uncaught exceptions, via std::terminate, end up as STATUS_STACK_BUFFER_OVERRUN on Windows (see https://devblogs.microsoft.com/oldnewthing/20190108-00/?p=100655), which Conan reports as "ConanException: Error 3221226505 while executing bin\test_package", which might take one a while to understand, if one didn't already know all of this... * Update to head revision of garethsb:library-cmake and latest mdnsresponder (#6930) and avahi (#7049) from-source recipes, since those seem more likely to be approved on CCI * Better comment * Switch to upstream sony/nmos-cpp (sony/nmos-cpp#195 was merged to master) * self.options.get_safe("with_dnssd") * For now, to build on CCI, don't require the opt-in patches * Build requires CMake 3.17 * Hopefully using short paths will resolve "fatal error C1083: Cannot open compiler generated file: '': Invalid argument" on some Windows builds on CCI * Test package requires C++11 * int64_t should work everywhere... I think * Test proposed upstream fix to make relocatable package per https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#creating-relocatable-packages * Fix handling of ${_IMPORT_PREFIX} * Fix handling of ${_IMPORT_PREFIX} and add tools.check_min_cppstd * No version ranges * Log all exceptions to debug intermittent test_package failure on arch=x86_64, build_type=Release, compiler=Visual Studio, compiler.runtime=MT, compiler.version=14, os=Windows * Additional warning * Take upstream latest which merged the relocatable package fix (cf. bf1388a) * Update conanfile.py * Apply suggestions from code review (properly) Co-authored-by: Anonymous Maarten <[email protected]> * Apply suggestions from code review Co-authored-by: Anonymous Maarten <[email protected]> * Apply suggestions from code review Co-authored-by: Anonymous Maarten <[email protected]> * self.options["mdnsresponder"].with_opt_patches = True * Apply suggestions from code review Co-authored-by: Uilian Ries <[email protected]> * cmake/3.21.1 conflicts with openssl/1.1.1l * Update topics * Try bump openssl/1.1.1l and cmake/3.21.2 * Take upstream latest, now built and tested with OpenSSL v1.1.1L Co-authored-by: Anonymous Maarten <[email protected]> Co-authored-by: Uilian Ries <[email protected]>
1 parent b214537 commit fd8e6fd

File tree

7 files changed

+362
-0
lines changed

7 files changed

+362
-0
lines changed

recipes/nmos-cpp/all/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# see https://github.com/conan-io/cmake-conan/issues/249#issuecomment-737011732
2+
cmake_minimum_required(VERSION 3.17)
3+
project(cmake_wrapper)
4+
5+
include(conanbuildinfo.cmake)
6+
conan_basic_setup()
7+
8+
# conanfile.py source() method extracts to source_subfolder
9+
# nmos-cpp top-level CMakeLists.txt is in Development
10+
add_subdirectory("source_subfolder/Development")

recipes/nmos-cpp/all/conandata.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sources:
2+
# see https://github.com/conan-io/conan-center-index/blob/master/docs/faqs.md#what-version-should-packages-use-for-libraries-without-official-releases
3+
"cci.20210902":
4+
url: "https://github.com/sony/nmos-cpp/archive/d3a8c7935f80ce5de6d4727c307ddcef667b5d57.tar.gz"
5+
sha256: "f5bd0b96542810ac1ed3ebaf7237d7471f3c42e68d966ae8b2d39db2601a38f9"

recipes/nmos-cpp/all/conanfile.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import json
2+
import os
3+
import re
4+
from conans import ConanFile, CMake, tools
5+
6+
required_conan_version = ">=1.33.0"
7+
8+
class NmosCppConan(ConanFile):
9+
name = "nmos-cpp"
10+
description = "An NMOS C++ Implementation"
11+
license = "Apache-2.0"
12+
url = "https://github.com/conan-io/conan-center-index"
13+
homepage = "https://github.com/sony/nmos-cpp"
14+
topics = ("amwa", "nmos", "is-04", "is-05", "is-07", "is-08", "is-09", "broadcasting", "network", "media")
15+
16+
settings = "os", "compiler", "build_type", "arch"
17+
# for now, no "shared" option support
18+
options = {
19+
"fPIC": [True, False],
20+
"with_dnssd": ["mdnsresponder", "avahi"],
21+
}
22+
# "fPIC" is handled automatically by Conan, injecting CMAKE_POSITION_INDEPENDENT_CODE
23+
default_options = {
24+
"fPIC": True,
25+
"with_dnssd": "mdnsresponder",
26+
}
27+
28+
# wrapper CMakeLists.txt to call conan_basic_setup()
29+
exports_sources = ["CMakeLists.txt"]
30+
# use cmake_find_package_multi and prefer config-file packages
31+
generators = "cmake", "cmake_find_package_multi"
32+
33+
short_paths = True
34+
35+
_cmake = None
36+
37+
# for out-of-source build, cf. wrapper CMakeLists.txt
38+
@property
39+
def _source_subfolder(self):
40+
return "source_subfolder"
41+
42+
@property
43+
def _build_subfolder(self):
44+
return "build_subfolder"
45+
46+
def config_options(self):
47+
if self.settings.os == "Windows":
48+
del self.options.fPIC
49+
50+
if self.settings.os == "Macos":
51+
del self.options.with_dnssd
52+
elif self.settings.os == "Linux":
53+
self.options.with_dnssd = "avahi"
54+
elif self.settings.os == "Windows":
55+
self.options.with_dnssd = "mdnsresponder"
56+
57+
def requirements(self):
58+
# for now, consistent with project's conanfile.txt
59+
self.requires("boost/1.76.0")
60+
self.requires("cpprestsdk/2.10.18")
61+
self.requires("websocketpp/0.8.2")
62+
self.requires("openssl/1.1.1l")
63+
self.requires("json-schema-validator/2.1.0")
64+
65+
if self.options.get_safe("with_dnssd") == "mdnsresponder":
66+
self.requires("mdnsresponder/878.200.35")
67+
# The option mdnsresponder:with_opt_patches=True is recommended in order to permit the
68+
# over-long service type _nmos-registration._tcp used in IS-04 v1.2, and also to enable
69+
# support for unicast DNS-SD on Linux, since NMOS recommends this in preference to mDNS.
70+
# See https://specs.amwa.tv/is-04/releases/v1.3.1/docs/3.1._Discovery_-_Registered_Operation.html#dns-sd-advertisement
71+
elif self.options.get_safe("with_dnssd") == "avahi":
72+
self.requires("avahi/0.8")
73+
74+
def build_requirements(self):
75+
# nmos-cpp needs CMake 3.17 or higher but CCI doesn't allow version ranges
76+
self.build_requires("cmake/3.21.2")
77+
78+
def validate(self):
79+
if self.settings.compiler.get_safe("cppstd"):
80+
tools.check_min_cppstd(self, 11)
81+
82+
def source(self):
83+
tools.get(**self.conan_data["sources"][self.version],
84+
destination=self._source_subfolder, strip_root=True)
85+
86+
def _configure_cmake(self):
87+
if self._cmake:
88+
return self._cmake
89+
self._cmake = CMake(self)
90+
# prefer config-file packages created by cmake_find_package_multi
91+
# over any system-installed find-module packages
92+
self._cmake.definitions["CMAKE_FIND_PACKAGE_PREFER_CONFIG"] = True
93+
# (on Linux) select Avahi or mDNSResponder
94+
self._cmake.definitions["NMOS_CPP_USE_AVAHI"] = self.options.get_safe("with_dnssd") == "avahi"
95+
# (on Windows) use the Conan package for DNSSD (mdnsresponder), not the project's own DLL stub library
96+
self._cmake.definitions["NMOS_CPP_USE_BONJOUR_SDK"] = True
97+
# no need to build unit tests
98+
self._cmake.definitions["NMOS_CPP_BUILD_TESTS"] = False
99+
# the examples (nmos-cpp-registry and nmos-cpp-node) are useful utilities for users
100+
self._cmake.definitions["NMOS_CPP_BUILD_EXAMPLES"] = True
101+
# out-of-source build
102+
self._cmake.configure(build_folder=self._build_subfolder)
103+
return self._cmake
104+
105+
def build(self):
106+
cmake = self._configure_cmake()
107+
cmake.build()
108+
109+
def package(self):
110+
self.copy("LICENSE", dst="licenses", src=self._source_subfolder)
111+
cmake = self._configure_cmake()
112+
cmake.install()
113+
cmake_folder = os.path.join(self.package_folder, "lib", "cmake")
114+
self._create_components_file_from_cmake_target_file(os.path.join(cmake_folder, "nmos-cpp", "nmos-cpp-targets.cmake"))
115+
# remove the project's own generated config-file package
116+
tools.rmdir(cmake_folder)
117+
118+
# based on abseil recipe
119+
# see https://github.com/conan-io/conan-center-index/blob/master/recipes/abseil/all/conanfile.py
120+
def _create_components_file_from_cmake_target_file(self, target_file_path):
121+
components = {}
122+
123+
target_content = tools.load(target_file_path)
124+
125+
cmake_functions = re.findall(r"(?P<func>add_library|set_target_properties)[\n|\s]*\([\n|\s]*(?P<args>[^)]*)\)", target_content)
126+
for (cmake_function_name, cmake_function_args) in cmake_functions:
127+
cmake_function_args = re.split(r"[\s|\n]+", cmake_function_args, maxsplit=2)
128+
129+
cmake_imported_target_name = cmake_function_args[0]
130+
cmake_target_nonamespace = cmake_imported_target_name.replace("nmos-cpp::", "")
131+
component_name = cmake_target_nonamespace.lower()
132+
# Conan component name cannot be the same as the package name
133+
if component_name == "nmos-cpp":
134+
component_name = "nmos-cpp-lib"
135+
136+
components.setdefault(component_name, {"cmake_target": cmake_target_nonamespace})
137+
138+
if cmake_function_name == "add_library":
139+
cmake_imported_target_type = cmake_function_args[1]
140+
if cmake_imported_target_type in ["STATIC", "SHARED"]:
141+
# library filenames are based on the target name by default
142+
lib_name = cmake_target_nonamespace
143+
# the filename may be changed by a straightforward command:
144+
# set_property(TARGET Bonjour PROPERTY OUTPUT_NAME dnssd)
145+
# but we'd have to read the nmos-cpp-targets-<config>.cmake files
146+
# and parse the IMPORTED_LOCATION_<CONFIG> values
147+
if lib_name == "Bonjour":
148+
lib_name = "dnssd"
149+
components[component_name]["libs"] = [lib_name]
150+
elif cmake_function_name == "set_target_properties":
151+
target_properties = re.findall(r"(?P<property>INTERFACE_[A-Z_]+)[\n|\s]+\"(?P<values>.+)\"", cmake_function_args[2])
152+
for target_property in target_properties:
153+
property_type = target_property[0]
154+
# '\', '$' and '"' are escaped; '$' especially is important here
155+
# see https://github.com/conan-io/conan/blob/release/1.39/conans/client/generators/cmake_common.py#L43-L48
156+
property_values = re.sub(r"\\(.)", r"\1", target_property[1]).split(";")
157+
if property_type == "INTERFACE_LINK_LIBRARIES":
158+
for dependency in property_values:
159+
match_private = re.fullmatch(r"\$<LINK_ONLY:(.+)>", dependency)
160+
if match_private:
161+
dependency = match_private.group(1)
162+
if "::" in dependency:
163+
dependency = dependency.replace("nmos-cpp::", "")
164+
# Conan component name cannot be the same as the package name
165+
if dependency == "nmos-cpp":
166+
dependency = "nmos-cpp-lib"
167+
# Conan packages for Boost, cpprestsdk, websocketpp, OpenSSL and Avahi have component names that (except for being lowercase) match the CMake targets
168+
# json-schema-validator overrides cmake_find_package[_multi] names
169+
elif dependency == "nlohmann_json_schema_validator::nlohmann_json_schema_validator":
170+
dependency = "json-schema-validator::json-schema-validator"
171+
# mdnsresponder overrides cmake_find_package[_multi] names
172+
elif dependency == "DNSSD::DNSSD":
173+
dependency = "mdnsresponder::mdnsresponder"
174+
components[component_name].setdefault("requires" if not match_private else "requires_private", []).append(dependency.lower())
175+
elif "${_IMPORT_PREFIX}/lib/" in dependency:
176+
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, dependency))
177+
else:
178+
components[component_name].setdefault("system_libs", []).append(dependency)
179+
elif property_type == "INTERFACE_COMPILE_DEFINITIONS":
180+
for property_value in property_values:
181+
components[component_name].setdefault("defines", []).append(property_value)
182+
elif property_type == "INTERFACE_COMPILE_FEATURES":
183+
for property_value in property_values:
184+
if property_value not in ["cxx_std_11"]:
185+
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, property_value))
186+
elif property_type == "INTERFACE_COMPILE_OPTIONS":
187+
for property_value in property_values:
188+
# handle forced include (Visual Studio /FI, gcc -include) by relying on includedirs containing "include"
189+
property_value = property_value.replace("${_IMPORT_PREFIX}/include/", "")
190+
components[component_name].setdefault("cxxflags", []).append(property_value)
191+
elif property_type == "INTERFACE_INCLUDE_DIRECTORIES":
192+
for property_value in property_values:
193+
if property_value not in ["${_IMPORT_PREFIX}/include"]:
194+
self.output.warn("{} recipe does not handle {} {} (yet)".format(self.name, property_type, property_value))
195+
elif property_type == "INTERFACE_LINK_OPTIONS":
196+
for property_value in property_values:
197+
# workaround required because otherwise "/ignore:4099" gets converted to "\ignore:4099.obj"
198+
# thankfully the MSVC linker accepts both '/' and '-' for the option specifier and Visual Studio
199+
# handles link options appearing in Link/AdditionalDependencies rather than Link/AdditionalOptions
200+
# because the CMake generators put them in INTERFACE_LINK_LIBRARIES rather than INTERFACE_LINK_OPTIONS
201+
# see https://github.com/conan-io/conan/pull/8812
202+
# and https://docs.microsoft.com/en-us/cpp/build/reference/linking?view=msvc-160#command-line
203+
property_value = re.sub(r"^/", r"-", property_value)
204+
components[component_name].setdefault("linkflags", []).append(property_value)
205+
else:
206+
self.output.warn("{} recipe does not handle {} (yet)".format(self.name, property_type))
207+
208+
# Save components informations in json file
209+
with open(self._components_helper_filepath, "w") as json_file:
210+
json.dump(components, json_file, indent=4)
211+
212+
@property
213+
def _components_helper_filepath(self):
214+
return os.path.join(self.package_folder, "lib", "components.json")
215+
216+
def package_info(self):
217+
bindir = "bin"
218+
libdir = "lib"
219+
# on Windows, cmake_install() puts the binaries in a config-specific sub-folder
220+
if self.settings.os == "Windows":
221+
config_install_dir = "Debug" if self.settings.build_type == "Debug" else "Release"
222+
bindir = os.path.join(bindir, config_install_dir)
223+
libdir = os.path.join(libdir, config_install_dir)
224+
225+
def _register_components():
226+
components_json_file = tools.load(self._components_helper_filepath)
227+
components = json.loads(components_json_file)
228+
for component_name, values in components.items():
229+
cmake_target = values["cmake_target"]
230+
self.cpp_info.components[component_name].names["cmake_find_package"] = cmake_target
231+
self.cpp_info.components[component_name].names["cmake_find_package_multi"] = cmake_target
232+
self.cpp_info.components[component_name].libs = values.get("libs", [])
233+
self.cpp_info.components[component_name].libdirs = [libdir]
234+
self.cpp_info.components[component_name].defines = values.get("defines", [])
235+
self.cpp_info.components[component_name].cxxflags = values.get("cxxflags", [])
236+
linkflags = values.get("linkflags", [])
237+
self.cpp_info.components[component_name].sharedlinkflags = linkflags
238+
self.cpp_info.components[component_name].exelinkflags = linkflags
239+
self.cpp_info.components[component_name].system_libs = values.get("system_libs", [])
240+
self.cpp_info.components[component_name].frameworks = values.get("frameworks", [])
241+
self.cpp_info.components[component_name].requires = values.get("requires", [])
242+
# hmm, how should private requirements be indicated? this results in a string format error...
243+
#self.cpp_info.components[component_name].requires.extend([(r, "private") for r in values.get("requires_private", [])])
244+
self.cpp_info.components[component_name].requires.extend(values.get("requires_private", []))
245+
_register_components()
246+
247+
# add nmos-cpp-registry and nmos-cpp-node to the path
248+
bin_path = os.path.join(self.package_folder, bindir)
249+
self.output.info("Appending PATH environment variable: {}".format(bin_path))
250+
self.env_info.PATH.append(bin_path)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cmake_minimum_required(VERSION 3.1)
2+
project(NmosCppTestPackage CXX)
3+
4+
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
5+
conan_basic_setup(TARGETS)
6+
7+
find_package(nmos-cpp REQUIRED)
8+
9+
add_executable(test_package main.cpp)
10+
target_link_libraries(test_package nmos-cpp::compile-settings nmos-cpp::nmos-cpp)
11+
set_target_properties(test_package PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
import subprocess
3+
from six import StringIO
4+
from conans import ConanFile, CMake, tools
5+
6+
class NmosCppTestPackageConan(ConanFile):
7+
settings = "os", "compiler", "build_type", "arch"
8+
# use cmake_find_package_multi because the project installs a config-file package
9+
generators = "cmake", "cmake_find_package_multi"
10+
11+
def build(self):
12+
cmake = CMake(self)
13+
cmake.configure()
14+
cmake.build()
15+
16+
def test(self):
17+
if not tools.cross_building(self):
18+
with open("registry-config.json", "w") as config:
19+
config.write('{"http_port": 10000, "domain": "local.", "pri": 51967}')
20+
with open("node-config.json", "w") as config:
21+
config.write('{"http_port": 20000, "domain": "local.", "highest_pri": 51967, "lowest_pri": 51967}')
22+
23+
# start up the installed nmos-cpp-registry to check it works
24+
registry = subprocess.Popen(["nmos-cpp-registry", "registry-config.json"],
25+
stdout=subprocess.PIPE,
26+
stderr=subprocess.STDOUT,
27+
universal_newlines=True)
28+
29+
# run the test_package node which should have time to register and then exit
30+
node_out = StringIO()
31+
try:
32+
bin_path = os.path.join("bin", "test_package")
33+
self.run(bin_path + " node-config.json", run_environment=True, output=node_out)
34+
finally:
35+
registry.terminate()
36+
if "Adopting registered operation" not in node_out.getvalue():
37+
self.output.warn("test_package node failed to register with nmos-cpp-registry\n"
38+
"\n"
39+
"nmos-cpp-registry log:\n"
40+
"{}\n"
41+
"test_package log:\n"
42+
"{}"
43+
.format(registry.communicate()[0], node_out.getvalue()))
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <fstream>
2+
#include <thread>
3+
#include "cpprest/json_utils.h"
4+
#include "nmos/id.h"
5+
#include "nmos/log_gate.h"
6+
#include "nmos/model.h"
7+
#include "nmos/node_resource.h"
8+
#include "nmos/node_server.h"
9+
#include "nmos/server.h"
10+
11+
const web::json::field_with_default<int64_t> how_long{ U("how_long"), 2000 };
12+
13+
int main(int argc, char* argv[])
14+
{
15+
nmos::node_model node_model;
16+
nmos::experimental::log_model log_model;
17+
nmos::experimental::log_gate gate(std::cerr, std::cout, log_model);
18+
nmos::experimental::node_implementation node_implementation;
19+
20+
if (argc > 1)
21+
{
22+
std::ifstream file(argv[1]);
23+
node_model.settings = web::json::value::parse(file);
24+
}
25+
nmos::insert_node_default_settings(node_model.settings);
26+
27+
auto node_server = nmos::experimental::make_node_server(node_model, node_implementation, log_model, gate);
28+
nmos::insert_resource(node_model.node_resources, nmos::make_node(nmos::make_id(), node_model.settings));
29+
30+
try
31+
{
32+
nmos::server_guard node_server_guard(node_server);
33+
std::this_thread::sleep_for(std::chrono::milliseconds(how_long(node_model.settings)));
34+
}
35+
catch (const std::exception& e)
36+
{
37+
slog::log<slog::severities::error>(gate, SLOG_FLF) << "Exception: " << e.what();
38+
}
39+
return 0;
40+
}

recipes/nmos-cpp/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
versions:
2+
"cci.20210902":
3+
folder: all

0 commit comments

Comments
 (0)