project('gst-plugins-rs', 'rust', 'c', version: '0.12.9', meson_version : '>= 1.1') # dependencies.py needs a toml parsing module python = import('python').find_installation(modules: ['tomllib'], required: false) if not python.found() python = import('python').find_installation(modules: ['tomli']) endif fs = import('fs') host_system = host_machine.system() if get_option('debug') target = 'debug' else target = 'release' endif cargo = find_program('cargo', version:'>=1.40') cargo_wrapper = find_program('cargo_wrapper.py') cargo_c = find_program('cargo-cbuild', version:'>=0.9.21', required: false) rustc = meson.get_compiler('rust') if not cargo_c.found() error('cargo-c missing, install it with: \'cargo install cargo-c\'') endif system = host_machine.system() exe_suffix = '' if system == 'windows' exe_suffix = '.exe' ext_dynamic = 'dll' ext_static = 'lib' elif system == 'darwin' ext_dynamic = 'dylib' ext_static = 'a' else ext_dynamic = 'so' ext_static = 'a' endif # Extra env to pass to cargo extra_env = {} # Used to not lookup the same dependency multiple times which clutters logs deps_cache = {} # Need to depends on all gstreamer-rs deps to ensure they are built # before gstreamer-rs when building with gst-build. # Custom targets can't depend on dependency() objects so we have to depend # on the library variable from the subproject instead. glib_req = '>=2.62' gst_req = '>=1.20.0' depends = [] deps = [ # name, subproject name, subproject dep, library object ['gstreamer-1.0', 'gstreamer', 'gst_dep', 'libgst'], ['gstreamer-app-1.0', 'gst-plugins-base', 'app_dep', 'gstapp'], ['gstreamer-audio-1.0', 'gst-plugins-base', 'audio_dep', 'gstaudio'], ['gstreamer-base-1.0', 'gstreamer', 'gst_base_dep', 'gst_base'], ['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo'], ] webrtc_option = get_option('webrtc') rtp_option = get_option('rtp').enable_if(webrtc_option.enabled(), error_message: 'webrtc option needs rtp') rtsp_option = get_option('rtsp') if get_option('threadshare').allowed() \ or get_option('onvif').allowed() \ or get_option('raptorq').allowed() \ or rtp_option.allowed() \ or webrtc_option.allowed() deps += [['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp']] endif if get_option('webrtc').allowed() \ or get_option('webrtchttp').allowed() deps += [['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc']] deps += [['gstreamer-sdp-1.0', 'gst-plugins-base', 'sdp_dep', 'gstsdp']] endif if get_option('tests').allowed() deps += [['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check']] endif if get_option('gtk4').allowed() deps += [['gstreamer-gl-1.0', 'gst-plugins-base', 'gstgl_dep', 'gstgl', get_option('gtk4')]] endif if get_option('threadshare').allowed() or get_option('rtsp').allowed() deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']] endif glib_dep = dependency('glib-2.0', version: glib_req) deps_cache += {'glib-2.0': glib_dep} foreach d: deps dep = dependency(d[0], version: gst_req, fallback : [d[1], d[2]], required: d.get(4, true)) set_variable(d[2], dep) deps_cache += {d[0]: dep} if dep.type_name() == 'internal' lib = subproject(d[1]).get_variable(d[3]) depends += lib endif endforeach # kept in the same order as the `members` list in Cargo.toml plugins = { 'audiofx': { 'library': 'libgstrsaudiofx', 'examples': ['hrtfrender'], }, 'claxon': {'library': 'libgstclaxon'}, # csound has a non-trivial external dependency, see below 'lewton': {'library': 'libgstlewton'}, 'spotify': {'library': 'libgstspotify'}, 'file': {'library': 'libgstrsfile'}, # sodium can have an external dependency, see below 'threadshare': { 'library': 'libgstthreadshare', 'examples': [ 'ts-benchmark', 'udpsrc-benchmark-sender', 'tcpclientsrc-benchmark-sender', 'ts-standalone', ], }, 'inter': {'library': 'libgstrsinter'}, 'mp4': {'library': 'libgstmp4'}, 'fmp4': { 'library': 'libgstfmp4', 'examples': [ 'dash_vod', 'hls_live', 'hls_vod', ], }, 'aws': { 'library': 'libgstaws', 'extra-deps': {'openssl': ['>=1.1']}, }, 'hlssink3': {'library': 'libgsthlssink3'}, 'ndi': {'library': 'libgstndi'}, 'onvif': { 'library': 'libgstrsonvif', 'extra-deps': {'pangocairo': []}, }, 'raptorq': {'library': 'libgstraptorq'}, 'reqwest': {'library': 'libgstreqwest'}, 'rtsp': {'library': 'libgstrsrtsp'}, 'rtp': {'library': 'libgstrsrtp'}, 'webrtchttp': {'library': 'libgstwebrtchttp'}, 'webrtc': { 'library': 'libgstrswebrtc', 'examples': ['webrtcsink-stats-server'], }, 'textahead': {'library': 'libgsttextahead'}, 'json': {'library': 'libgstjson'}, 'regex': {'library': 'libgstregex'}, 'textwrap': {'library': 'libgsttextwrap'}, 'tracers': {'library': 'libgstrstracers'}, 'uriplaylistbin': { 'library': 'libgsturiplaylistbin', 'examples': ['playlist'], 'features': ['clap'], }, 'cdg': {'library': 'libgstcdg'}, 'closedcaption': { 'library': 'libgstrsclosedcaption', 'extra-deps': { 'pango': [], 'pangocairo': [], 'cairo-gobject': [], } }, 'dav1d': { 'library': 'libgstdav1d', 'extra-deps': {'dav1d': ['>=1.0', '<1.3']}, }, 'ffv1': {'library': 'libgstffv1'}, 'flavors': {'library': 'libgstrsflv'}, 'gif': { 'library': 'libgstgif', 'examples': ['testvideosrc2gif'], }, # gtk4 is added below 'hsv': {'library': 'libgsthsv'}, 'png': { 'library': 'libgstrspng', 'examples': ['pngenc'], }, 'rav1e': {'library': 'libgstrav1e'}, 'videofx': { 'library': 'libgstrsvideofx', 'extra-deps': {'cairo-gobject': []}, }, } # Won't build on platforms where it bundles the sources because of: # https://github.com/qnighy/libwebp-sys2-rs/issues/12 # the fix is: # https://github.com/qnighy/libwebp-sys2-rs/pull/13 if host_system not in ['windows', 'darwin'] # FIXME: libwebp-sys2 will build its bundled version on msvc and apple platforms # https://github.com/qnighy/libwebp-sys2-rs/issues/4 plugins += {'webp': { 'library': 'libgstrswebp', 'extra-deps': {'libwebpdemux': []}, }} endif sodium_opt = get_option('sodium') if sodium_opt.allowed() sodium_plugin = {'sodium': { 'library': 'libgstsodium', 'examples': ['generate-keys', 'encrypt-example', 'decrypt-example'], 'features': ['serde', 'serde_json', 'clap'], }} if get_option('sodium-source') == 'system' sodium_dep = dependency('libsodium', required: sodium_opt.enabled()) extra_env += {'SODIUM_USE_PKG_CONFIG': '1'} if sodium_dep.found() plugins += sodium_plugin endif else plugins += sodium_plugin endif endif cc = meson.get_compiler('c') csound_option = get_option('csound') if csound_option.allowed() # if csound isn't distributed with pkg-config then user needs to define CSOUND_LIB_DIR with its location res = run_command(python, '-c', 'import os; print(os.environ["CSOUND_LIB_DIR"])', check: false) if res.returncode() == 0 csound_libdir = res.stdout().strip() csound_dep = cc.find_library('csound64', dirs: csound_libdir, required: false) if csound_dep.found() plugins += {'csound': { 'library': 'libgstcsound', 'examples': ['csound-effect'], }} extra_env += {'CSOUND_LIB_DIR': csound_libdir} elif csound_option.enabled() error('csound option is enabled, but csound64 library could not be found and CSOUND_LIB_DIR was not set') endif endif endif if get_option('gtk4').allowed() gtk4_features = [] gl_winsys = gstgl_dep.get_variable('gl_winsys').split() gl_platforms = gstgl_dep.get_variable('gl_platforms').split() if 'wayland' in gl_winsys gtk4_features += 'wayland' endif if 'x11' in gl_winsys if 'egl' in gl_platforms gtk4_features += 'x11egl' endif if 'glx' in gl_platforms gtk4_features += 'x11glx' endif elif host_system == 'windows' if 'egl' in gl_platforms gtk4_features += 'winegl' endif endif gst_allocators_dep = dependency('gstreamer-allocators-1.0', version: '>=1.24', required: false) gtk_dep = dependency('gtk4', version: '>=4.6', required: get_option('gtk4')) if gtk_dep.found() if host_system == 'linux' and gtk_dep.version().version_compare('>=4.14') and \ gst_allocators_dep.found() and 'wayland' in gtk4_features gtk4_features += 'dmabuf' endif if gtk_dep.version().version_compare('>=4.14') gtk4_features += 'gtk_v4_14' elif gtk_dep.version().version_compare('>=4.12') gtk4_features += 'gtk_v4_12' elif gtk_dep.version().version_compare('>=4.10') gtk4_features += 'gtk_v4_10' endif plugins += { 'gtk4': { 'library': 'libgstgtk4', 'examples': ['gtksink'], 'extra-deps': {'gtk4': ['>=4.6']}, 'features': gtk4_features, }, } endif endif examples_opt = get_option('examples') if examples_opt.allowed() and 'gtk4' in plugins plugins += { 'fallbackswitch': { 'library': 'libgstfallbackswitch', 'examples_features': { 'gtk-fallbackswitch': ['gtk', 'gio', 'gst-plugin-gtk4'], }, }, 'livesync': { 'library': 'libgstlivesync', 'examples_features': { 'gtk-livesync': ['gtk', 'gio', 'gst-plugin-gtk4'], } }, 'togglerecord': { 'library': 'libgsttogglerecord', 'examples_features': { 'gtk-recording': ['gtk', 'gio', 'gst-plugin-gtk4'], } }, } else plugins += { 'fallbackswitch': { 'library': 'libgstfallbackswitch'}, 'livesync': { 'library': 'libgstlivesync'}, 'togglerecord': { 'library': 'libgsttogglerecord'}, } endif # Process plugins list default_library = get_option('default_library') library_suffixes = [] if default_library in ['shared', 'both'] library_suffixes += [ext_dynamic] endif if default_library in ['static', 'both'] library_suffixes += [ext_static] endif # cargo packages (plugins) to build packages = [] # cargo features features = [] # examples to build examples = [] # Add the plugin library files as output output = [] if get_option('gtk4').allowed() if glib_dep.version().version_compare('>=2.74') features += ['glib/v2_74', 'gio/v2_74'] elif glib_dep.version().version_compare('>=2.72') features += ['glib/v2_72', 'gio/v2_72'] elif glib_dep.version().version_compare('>=2.70') features += ['glib/v2_70', 'gio/v2_70'] elif glib_dep.version().version_compare('>=2.68') features += ['glib/v2_68', 'gio/v2_68'] elif glib_dep.version().version_compare('>=2.66') features += ['glib/v2_66', 'gio/v2_66'] elif glib_dep.version().version_compare('>=2.64') features += ['glib/v2_64', 'gio/v2_64'] elif glib_dep.version().version_compare('>=2.62') features += ['glib/v2_62', 'gio/v2_62'] elif glib_dep.version().version_compare('>=2.60') features += ['glib/v2_60', 'gio/v2_60'] elif glib_dep.version().version_compare('>=2.58') features += ['glib/v2_58', 'gio/v2_58'] endif endif if get_option('rav1e').allowed() nasm = find_program('nasm', required: false) if nasm.found() features += 'gst-plugin-rav1e/asm' extra_env += {'NASM': nasm.full_path()} endif endif if get_option('default_library') == 'static' extra_env += { # Tell the pkg-config crate to think of all libraries as static 'PKG_CONFIG_ALL_STATIC': '1', # Tell the system-deps crate to process linker flag for static deps 'SYSTEM_DEPS_LINK': 'static' } endif foreach plugin_name, details: plugins plugin_opt = get_variable(f'@plugin_name@_option', get_option(plugin_name)) if not plugin_opt.allowed() debug(f'@plugin_name@ is disabled') continue endif plugin_deps_found = true # Check whether we have all needed deps foreach dep_name, dep_ver: details.get('extra-deps', {}) if dep_ver.length() != 0 dep = dependency(dep_name, version: dep_ver, required: plugin_opt) else dep = dependency(dep_name, required: plugin_opt) endif deps_cache += {dep_name: dep} if not dep.found() if dep_ver.length() != 0 dep_ver_msg = ' '.join(dep_ver) debug(f'@plugin_name@ dependency @dep_name@ @dep_ver_msg@ not found, skipping') else debug(f'@plugin_name@ dependency @dep_name@ not found, skipping') endif plugin_deps_found = false break endif endforeach if not plugin_deps_found continue endif # Validate gst-plugin features plugin_features = details.get('features', []) foreach feature: plugin_features if feature.startswith('gst-plugin') and not packages.contains(feature) msg = f'@plugin_name@ required feature @feature@ not found' if plugin_opt.enabled() error(msg) endif message(msg + ', skipping') plugin_deps_found = false break endif endforeach if not plugin_deps_found continue endif # Parse and enable examples plugin_examples = details.get('examples', []) foreach example: plugin_examples examples += example endforeach plugin_examples_features = details.get('examples_features', {}) foreach example, examples_features: plugin_examples_features example_deps_found = true foreach feature: examples_features if feature.startswith('gst-plugin') and not packages.contains(feature) msg = f'@plugin_name@ example @example@ required feature @feature@ not found' if plugin_opt.enabled() and examples_opt.enabled() error(msg) endif message(msg + ', skipping') example_deps_found = false break endif endforeach features += examples_features if example_deps_found examples += example endif endforeach packages += f'gst-plugin-@plugin_name@' features += plugin_features extra_features = run_command('dependencies.py', meson.current_source_dir(), plugin_name, '--feature', '--gst-version', gst_dep.version(), capture: true, check: true).stdout().strip() if extra_features != '' features += extra_features.split(',') endif lib = details.get('library') # No 'lib' suffix with MSVC if cc.get_argument_syntax() == 'msvc' lib = lib.substring(3) endif if default_library in ['shared', 'both'] output += [lib + '.' + ext_dynamic] endif if default_library in ['static', 'both'] output += [lib + '.' + ext_static] endif endforeach feature_args = [] if features.length() > 0 feature_args += ['--features', features] endif plugins_install_dir = get_option('libdir') / 'gstreamer-1.0' pkgconfig_install_dir = get_option('libdir') / 'pkgconfig' extra_args = [] if get_option('doc').disabled() extra_args += ['--disable-doc'] endif # 'pkgconfig' is the entry in the machine file, if specified pkg_config = find_program('pkgconfig', 'pkg-config') if pkg_config.found() extra_env += {'PKG_CONFIG': pkg_config.full_path()} endif pkg_config_path = get_option('pkg_config_path') if pkg_config_path.length() > 0 pathsep = ':' if host_system == 'windows' pathsep = ';' endif extra_env += {'PKG_CONFIG_PATH': pathsep.join(pkg_config_path)} endif # get cmdline for rust extra_env += {'RUSTC': ' '.join(rustc.cmd_array())} plugins = [] if output.length() > 0 rs_plugins = custom_target('gst-plugins-rs', build_by_default: true, output: output, console: true, install: true, install_dir: plugins_install_dir, depends: depends, depfile: 'gst-plugins-rs.dep', env: extra_env, command: [cargo_wrapper, 'build', meson.current_build_dir(), meson.current_source_dir(), meson.global_build_root(), target, get_option('prefix'), get_option('libdir'), '--packages', packages, '--depfile', '@DEPFILE@', '--lib-suffixes', library_suffixes, ] + feature_args + extra_args) plugins = rs_plugins.to_list() endif # This is used by GStreamer to static link Rust plugins into gst-full gst_plugins = [] pc_files = [] plugin_names = [] foreach plugin : plugins plugin_name = fs.stem(plugin.full_path()) # skip the 'lib' prefix from plugin path when not building with MSVC if cc.get_argument_syntax() != 'msvc' plugin_name = plugin_name.substring(3) endif plugin_display_name = plugin_name if plugin_name.startswith('gst') plugin_display_name = plugin_name.substring(3) endif if plugin_display_name in plugin_names # When default_library=both plugins are duplicated. continue endif plugin_names += plugin_display_name option_name = plugin_name.substring(3) if option_name.startswith('rs') option_name = option_name.substring(2) endif if option_name == 'flv' option_name = 'flavors' endif if not get_option(option_name).allowed() continue endif # Extract plugin dependencies from their Cargo.toml file plugin_deps = [] p = run_command('dependencies.py', meson.current_source_dir(), plugin_name, capture: true, check: true) foreach dep_name : p.stdout().strip().split(',') dep_name_version = dep_name.split('|') dep_name = dep_name_version.get(0).strip() if dep_name_version.length() > 1 extras = {'version': dep_name_version.get(1).strip()} else extras = {} endif if deps_cache.has_key(dep_name) plugin_deps += deps_cache[dep_name] else dep = dependency(dep_name, required: false, kwargs: extras) plugin_deps += dep deps_cache += {dep_name: dep} endif endforeach dep = declare_dependency( link_with: plugin, dependencies: plugin_deps, variables: {'full_path': plugin.full_path()}, ) meson.override_dependency(plugin_name, dep) if default_library == 'static' and plugin_name in ['gstcsound', 'gstthreadshare', 'gstgtk4'] warning('Static plugin @0@ is known to fail. It will not be included in libgstreamer-full.'.format(plugin_name)) else gst_plugins += dep pc_files += [plugin_name + '.pc'] endif endforeach subdir('docs') # We don't need to pass a command as we depends on the target above # but it is currently mandatory ( https://github.com/mesonbuild/meson/issues/8059 ) # so use python as it's guaranteed to be present on any setup if pc_files.length() > 0 custom_target('gst-plugins-rs-pc-files', build_by_default: true, output: pc_files, console: true, install: true, install_dir: pkgconfig_install_dir, depends: rs_plugins, command: [python, '-c', '""']) endif if get_option('webrtc').allowed() custom_target('gst-webrtc-signalling-server', build_by_default: true, output: 'gst-webrtc-signalling-server' + exe_suffix, console: true, install: true, install_dir: get_option('bindir'), depfile: 'gst-webrtc-signalling-server.dep', env: extra_env, command: [cargo_wrapper, 'build', meson.current_build_dir(), meson.current_source_dir(), meson.global_build_root(), target, get_option('prefix'), get_option('libdir'), '--depfile', '@DEPFILE@', '--bin', 'gst-webrtc-signalling-server', '--exe-suffix', exe_suffix, ]) endif if get_option('examples').allowed() and examples.length() > 0 outputs = [] foreach example: examples outputs += [example + exe_suffix] endforeach custom_target('gst-plugins-rs-examples', build_by_default: true, output: outputs, console: true, install: false, depfile: 'gst-plugins-rs-examples.dep', env: extra_env, command: [cargo_wrapper, 'build', meson.current_build_dir(), meson.current_source_dir(), meson.global_build_root(), target, get_option('prefix'), get_option('libdir'), '--depfile', '@DEPFILE@', '--packages', packages, '--examples', examples, '--exe-suffix', exe_suffix, ] + feature_args) endif if get_option('tests').allowed() test('tests', cargo_wrapper, env: extra_env, args: ['test', meson.current_build_dir(), meson.current_source_dir(), meson.global_build_root(), target, get_option('prefix'), get_option('libdir'), '--packages', packages], timeout: 600) endif summary({ 'Plugins': plugin_names, }, list_sep: ', ')