# -*- Mode: Python -*- vi:si:et:sw=4:sts=4:ts=4:syntax=python from custom import GStreamer from cerbero.utils import messages as m from cerbero.utils import shell, determine_num_cargo_jobs from pathlib import Path import re import shutil import subprocess import tempfile class Recipe(recipe.Recipe): name = 'gst-plugins-rs' version = '0.12.0-alpha.1' stype = SourceType.GIT remotes = {'origin': 'https://gitlab.freedesktop.org/gstreamer/%(name)s.git'} if GStreamer.tagged_for_release: commit = f'gstreamer-{GStreamer.version}' else: commit = 'origin/main' # Each plugin uses one or more of these licenses. licenses = [{ License.Apachev2: ['LICENSE-APACHE'], License.MIT: ['LICENSE-MIT'], License.LGPLv2_1Plus: None, }] btype = BuildType.CARGO_C cargoc_packages = [ 'audiofx', 'aws', 'cdg', 'claxon', 'closedcaption', 'dav1d', 'fallbackswitch', 'ffv1', 'fmp4', 'gif', 'hlssink3', 'hsv', 'json', 'livesync', 'lewton', 'mp4', 'ndi', 'onvif', 'rav1e', 'regex', 'reqwest', 'raptorq', 'png', 'rtp', 'textahead', 'textwrap', 'threadshare', 'togglerecord', 'tracers', 'uriplaylistbin', 'videofx', 'webrtc', 'webrtchttp', ] # If the system has less than 8GB ram or less than 4 cores, # block parallelism at both recipe and architectural level. if determine_num_cargo_jobs() == 1: allow_parallel_build = False allow_universal_parallel_build = False # Needed for openssl use_system_libs = True deps = ['gstreamer-1.0', 'gst-plugins-base-1.0', 'pango', 'cairo', 'gst-plugins-bad-1.0', 'dav1d'] def enable_plugin(self, name, category): if LibraryType.SHARED in self.library_type: attr = f'files_plugins_{category}' if not hasattr(self, attr): setattr(self, attr, []) self.update_categories() f = getattr(self, attr) f += [f'%(libdir)s/gstreamer-1.0/libgst{name}%(mext)s'] if LibraryType.STATIC in self.library_type: attr = f'files_plugins_{category}_devel' if not hasattr(self, attr): setattr(self, attr, []) d = getattr(self, attr) d += [ f'%(libdir)s/gstreamer-1.0/libgst{name}.a', f'%(libdir)s/gstreamer-1.0/libgst{name}.la', ] def prepare(self): # See "static plugins" bullet point in phase 2 at # https://gitlab.freedesktop.org/gstreamer/cerbero/-/issues/381 if self.config.target_platform in (Platform.IOS, Platform.ANDROID): self.library_type = LibraryType.STATIC else: self.library_type = LibraryType.SHARED if self.config.target_platform != Platform.LINUX or self.config.cross_compiling(): self.deps.append('openssl') plugin_files = { 'core': ['fallbackswitch', 'livesync', 'togglerecord', 'rstracers'], 'net': ['aws', 'hlssink3', 'ndi', 'rsonvif', 'raptorq', 'rsrtp', 'reqwest', 'webrtchttp', 'rswebrtc'], 'effects': ['rsaudiofx', 'rsvideofx'], 'codecs': ['cdg', 'claxon', 'dav1d', 'rsclosedcaption', 'ffv1', 'fmp4', 'mp4', 'gif', 'hsv', 'lewton', 'rav1e', 'json', 'rspng', 'regex', 'textwrap', 'textahead'], 'playback': ['uriplaylistbin'], } # https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/326 if self.library_type != LibraryType.STATIC: plugin_files['core'].append('threadshare') for category, names in plugin_files.items(): for name in names: self.enable_plugin(name, category) self.cargoc_packages = [f'gst-plugin-{pkg}' for pkg in self.cargoc_packages] # Build with Cerbero's latest glib version as minimum version self.cargo_features += ['glib/v2_74', 'gio/v2_74'] # Enable assembly optimizations via nasm self.cargo_features.append('gst-plugin-rav1e/asm') # Build with the current GStreamer version as minimum version components = ('', '-app', '-audio', '-base', '-check', '-net', '-pbutils', '-plugin-tracers', '-rtp', '-sdp', '-utils', '-video', '-webrtc') for each in components: self.cargo_features.append(f'gst{each}/v1_22') self.cargo_features.append('gst-plugin-webrtc/v1_22') async def configure(self): # Check that the Cargo.toml version matches the recipe version toml_version = self.get_cargo_toml_version() if toml_version != self.version: msg = f'{self.name} version {self.version} doesn\'t match Cargo.toml version {toml_version}' if GStreamer.tagged_for_release: raise FatalError(msg) else: m.warning(msg) await super().configure() def post_install(self): # Cargo-C currently can't install pc files into custom dirs, so we need # to move these plugin pkgconfig files to the right place. for f in self.files_list_by_category(self.DEVEL_CAT): if not f.endswith('.pc') or not 'gstreamer-1.0' in f: continue name = os.path.basename(f) src = os.path.join(self.config.libdir, 'pkgconfig', name) dst = os.path.join(self.config.prefix, f) if os.path.exists(src): shutil.copy(src, dst) # Cargo-C names MinGW DLLs as foo.dll instead of libfoo.dll # https://github.com/lu-zero/cargo-c/issues/280 # It also follows MSVC convention for static/import libraries if self.using_msvc(): for f in self.files_list_by_category(self.DEVEL_CAT): if not f.endswith('.a'): continue dst = Path(self.config.prefix) / f src = dst.with_name(f'{dst.stem[3:]}.lib') if src.exists(): shutil.copy(src, dst) else: for f in self.dist_files_list(): if not f.endswith('.dll'): continue name = os.path.basename(f) d = os.path.dirname(f) src = os.path.join(self.config.prefix, d, f'{name[3:]}') dst = os.path.join(self.config.prefix, f) if os.path.exists(src): shutil.copy(src, dst) # if there's a .dll.a it also needs renaming if os.path.exists(src + '.a'): shutil.copy(src + '.a', dst + '.a') libraries = [f for f in self.devel_files_list() if f.endswith('.a')] for f in libraries: if self.config.target_platform in (Platform.ANDROID, Platform.LINUX): shell.new_call([self.env['STRIP'], '--wildcard', '--strip-all', '--keep-symbol=gst_plugin_*', f], self.config.prefix, env=self.env) elif self.config.target_platform == Platform.IOS: # Unlike ELF, for MachO Apple wants you to do # Single-Object Prelink source = Path(f) # Only global symbols # Only symbol names # Use portable output format # Skip undefined symbols # Write pathname of the object file manifest = subprocess.check_output([self.get_llvm_tool('nm'), '-gjPUA', source.absolute()], universal_newlines=True, text=True) # Now we need to match the symbols to the pattern # Here's the catch: Apple strip is silly enough to be unable to # -undefined suppress a .o because of the -two_level_namespace being # the default post-10.1. So we need to determine which objects have # matching symbols. The rest can be safely stripped. # The symbol listing format is as follows: #  ./libgstrswebrtc.a[gstrswebrtc-3a8116aacab254c2.2u9b7sba8k2fvc9v.rcgu.o]: _gst_plugin_rswebrtc_get_desc T 500 0 # Field 1 has the object name between brackets. # Field 2 is the symbol name. symbol_pattern = re.compile('_gst_plugin_*') with tempfile.TemporaryDirectory(prefix='cerbero', dir=self.config.home_dir) as tmp: # List those symbols that will be kept symbols_to_keep = set() for line in manifest.stdout.splitlines(): data = line.split(' ') symbol = data[1] if symbol_pattern.match(symbol): symbols_to_keep.add(symbol) module = ( Path(tmp) / source.name).with_suffix('.symbols') with module.open('w', encoding='utf-8') as f: f.write('# Stripped by Cerbero\n') for symbol in symbols_to_keep: f.write(f'{symbol}\n') m.log(f'Symbols to preserve in {source.absolute()}:') for symbol in symbols_to_keep: m.log(f'\t{symbol}') # Unpack archive m.log(f"Unpacking {source.absolute()} with ar") shell.new_call( [shutil.which('ar'), 'xv', source.absolute()], cmd_dir=tmp, logfile=self.logfile) # Now everything is flat in the pwd print('Performing Single-Object Prelinking') prelinked_obj = ( Path(tmp) / source.name).with_suffix('.prelinked.o') cmd = ' '.join([ shutil.which('ld'), '-r', '-exported_symbols_list', module, '-o', prelinked_obj, '*.o', ]) shell.new_call(cmd, cmd_dir=tmp, shell=True, logfile=self.logfile) # With the stripping done, all files now need to be rearchived dest = Path(tmp) / source.name print(f'Repacking library to {dest.absolute()}') shell.new_call([shutil.which('libtool'), '-static', '-o', dest.absolute(), prelinked_obj.absolute()], logfile=self.logfile) # And now we paper over os.replace(dest.absolute(), f) super().post_install()