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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
|
# -*- Mode: Python -*- vi:si:et:sw=4:sts=4:ts=4:syntax=python
from custom import GStreamer, running_on_cerbero_ci
from cerbero.enums import DistroVersion, Symbolication
from cerbero.errors import FatalError
from cerbero.tools.pkgconfig import PkgConfigWriter
from cerbero.utils import messages as m
from cerbero.utils import shell
from cerbero.utils.rust import Prelink
import os
from pathlib import Path
import re
import shutil
class Recipe(recipe.Recipe):
name = 'gst-plugins-rs'
version = '0.16.0-alpha'
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
cargo_packages = [
'analytics',
'audiofx',
'audioparsers',
'aws',
'burn',
'cdg',
'claxon',
'closedcaption',
'colorlut',
'dav1d',
'demucs',
'elevenlabs',
'fallbackswitch',
'ffv1',
# 'file',
# 'flavors',
'gif',
'gopbuffer',
'hlsmultivariantsink',
'hlssink3',
'hsv',
'icecast',
'inter',
'isobmff',
'json',
'livesync',
'lewton',
'mpegtslive',
'ndi',
'onvif',
'originalbuffer',
'png',
'quinn',
'raptorq',
'rav1e',
'regex',
'reqwest',
'rtp',
'rtsp',
# 'sodium',
'speechmatics',
# 'spotify',
'streamgrouper',
'textahead',
'textwrap',
'threadshare',
'togglerecord',
'tracers',
'uriplaylistbin',
'videofx',
# 'vvdec': added conditionally below
# 'webp',
'webrtc',
'webrtcbin2',
'webrtchttp',
]
deps = ['openssl', 'gstreamer-1.0', 'gst-plugins-base-1.0', 'gst-plugins-bad-1.0', 'rice-proto']
files_libs = []
symbolication_patterns = {
# dragonfire generated library, static only
Symbolication.SKIP: ['rsworkspace'],
Symbolication.MANUAL: ['gstreamer-1.0/libgst']
}
def enable_plugin(self, name, category):
if self.library_type in (LibraryType.SHARED, LibraryType.BOTH):
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']
attr = f'files_plugins_{category}_devel'
if not hasattr(self, attr):
setattr(self, attr, [])
d = getattr(self, attr)
d.append(f'%(libdir)s/gstreamer-1.0/pkgconfig/gst{name}.pc')
if self.library_type in (LibraryType.STATIC, LibraryType.BOTH):
d += [
f'%(libdir)s/gstreamer-1.0/libgst{name}.a',
f'%(libdir)s/gstreamer-1.0/libgst{name}.la',
]
# MSVC import library for the gst<name>.dll built in the
# shared half of library_type=BOTH (Linux/Windows default).
# cargo-c installs it as <name>.lib (MSVC naming); ship it
# so link.exe can resolve cargo:rustc-link-lib=<name> from
# Rust consumers using the bundle (e.g. system-deps' binary
# feature). Not the static archive — static linking goes
# through libgstrsworkspace.a (the dragonfire meld) below.
if (self.config.target_platform == Platform.WINDOWS
and self.config.variants.visualstudio):
d.append(f'%(libdir)s/gstreamer-1.0/gst{name}.lib')
def requires_melding(self):
if Platform.is_apple(self.config.target_platform):
return False
return self.library_type in (LibraryType.STATIC, LibraryType.BOTH)
def prepare(self):
if Platform.is_app_platform(self.config.target_platform):
self.library_type = LibraryType.STATIC
else:
self.library_type = LibraryType.BOTH
plugin_files = {
'analytics': [
'rsanalytics',
'burn',
],
'core': [
'fallbackswitch',
'gopbuffer',
'livesync',
'originalbuffer',
'rsinter',
'rstracers',
'streamgrouper',
'threadshare',
'togglerecord',
],
'net': [
'aws',
'elevenlabs',
'hlssink3',
'hlsmultivariantsink',
'icecast',
'mpegtslive',
'ndi',
'quinn',
'rsonvif',
'raptorq',
'reqwest',
'rsrtp',
'rsrtsp',
'speechmatics', # in the audio subdir, but net seems most fitting
'rswebrtcbin2',
'webrtchttp',
'rswebrtc',
],
'effects': [
'colorlut',
'demucs',
'rsaudiofx',
'rsvideofx',
],
'codecs': [
'rsaudioparsers',
'cdg',
'claxon',
'dav1d',
'rsclosedcaption',
'ffv1',
'gif',
'hsv',
'isobmff',
'lewton',
'rav1e',
'json',
'rspng',
'regex',
'textwrap',
'textahead',
# 'rswebp',
],
'codecs_restricted': [
'vvdec',
],
'playback': [
'uriplaylistbin',
],
}
if self.config.variants.gtk:
self.deps.append('gtk')
plugin_files['gtk4'] = ['gtk4']
self.cargo_packages += ['gtk4']
for category, names in plugin_files.items():
for name in names:
if Subsystem.is_apple_simulator(self.config.target_subsystem):
if name == 'vvdec':
# FIXME: bindgen needs to be upgraded in vvdec-sys
# See:
# - https://github.com/cadubentzen/vvdec-rs/blob/main/vvdec-sys/Cargo.toml#L13
# - https://github.com/aws/aws-lc-rs/issues/761
continue
package_name = name if name[0:2] != 'rs' else name[2:]
if package_name in self.cargo_packages:
self.enable_plugin(name, category)
if 'dav1d' in self.cargo_packages:
self.deps.append('dav1d')
if 'onvif' in self.cargo_packages or 'closedcaption' in self.cargo_packages:
self.deps.append('pango')
self.deps.append('cairo')
if 'vvdec' in self.cargo_packages:
self.deps.append('vvdec')
self.cargo_packages = [f'gst-plugin-{pkg}' for pkg in self.cargo_packages]
# Build with Cerbero's latest glib version as minimum version
if 'gst-plugin-gtk4' in self.cargo_packages:
self.cargo_features += ['glib/v2_74', 'gio/v2_74']
if 'gst-plugin-rav1e' in self.cargo_packages:
# 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',
'-rtp', '-sdp', '-utils', '-video', '-webrtc')
for each in components:
self.cargo_features.append(f'gst{each}/v1_22')
if 'gst-plugin-tracers' in self.cargo_packages:
self.cargo_features.append('gst-plugin-tracers/v1_22')
if 'gst-plugin-webrtc' in self.cargo_packages:
self.cargo_features.append('gst-plugin-webrtc/aws')
self.cargo_features.append('gst-plugin-webrtc/livekit')
# The LIBRARY_PATH setting affects all invocations of GCC underneath
# Cargo, in particular those that link build scripts (that are meant to
# run on the host machine, not through wine).
# When creating these, a value for LIBRARY_PATH present will cause
# cross-mingw GCC to prefer libpthread.a for MinGW-w64 over the system
# libpthread, and hell breaks loose.
if self.config.target_platform == Platform.WINDOWS and \
self.config.platform != self.config.target_platform:
self.set_env('LDFLAGS')
self.set_env('LIBRARY_PATH')
if self.requires_melding():
self.workspace_lib = Path(self.config.libdir) / 'libgstrsworkspace.a'
# gstrsworkspace describes the dragonfire-melded workspace
# meta-library, not a GStreamer plugin (no gst_plugin_*_register
# entrypoint). Install its .pc under lib/pkgconfig/ rather than
# alongside the plugin .pc files in lib/gstreamer-1.0/pkgconfig/:
# consumers like gstreamer-sys's wildcard_plugins iterate that
# directory to populate gst_init_static_plugins and would try to
# register the meta-library, failing to link with LNK2019.
# Requires.private references injected into the plugin .pc files
# keep resolving via PKG_CONFIG_PATH, which carries both
# lib/pkgconfig/ and lib/gstreamer-1.0/pkgconfig/ by default.
self.workspace_pc = Path(self.config.libdir) / 'pkgconfig' / 'gstrsworkspace.pc'
# Append the melded library
self.files_devel.append('%(libdir)s/libgstrsworkspace.a')
self.files_devel.append('%(libdir)s/pkgconfig/gstrsworkspace.pc')
self.skip_pdb_files = ['%(libdir)s/libgstrsworkspace.a']
if self.config.target_platform == Platform.WINDOWS:
if self.config.platform == Platform.WINDOWS:
# For bindgen if it's needed
self.set_env('LIBCLANG_PATH', os.path.join(self.config.build_tools_prefix, 'bin', 'libclang.dll'))
# For aws-lc-rs -- uses CMake in the background
if self.using_msvc():
# MSVC is already set up
self.set_env('CMAKE_GENERATOR', 'Ninja')
else:
bindgen_args = [
# Extracted in this order from mingw gcc
f'--sysroot={self.config.toolchain_prefix}/x86_64-w64-mingw32/sysroot/usr/{self.config.target}',
# stdbool.h and sha512intrin.h
f'-I{self.config.toolchain_prefix}/lib/x86_64-linux-gnu/gcc/{self.config.target}/14.2.0/include'
# # c++
# f'-I{self.config.toolchain_prefix}/x86_64-w64-mingw32/include',
# # stdarg.h valist etc.
f'-I/{self.config.toolchain_prefix}/x86_64-w64-mingw32/sysroot/usr/{self.config.target}/include'
]
self.set_env('BINDGEN_EXTRA_CLANG_ARGS', ' '.join(bindgen_args))
elif self.config.target_platform == Platform.ANDROID:
# For aws-lc-rs
self.set_env('ANDROID_NDK_ROOT', self.config.env['ANDROID_NDK_HOME'])
# https://github.com/rust-lang/cmake-rs/issues/140
self.set_env("CMAKE_TOOLCHAIN_FILE", f"{self.config_src_dir}/toolchain.cmake")
self.set_env('BINDGEN_EXTRA_CLANG_ARGS', f'--sysroot={self.config.sysroot}')
self.set_env('AWS_LC_SYS_EXTERNAL_BINDGEN', '1')
self.set_env('CMAKE_GENERATOR', 'Ninja')
elif Subsystem.is_apple_simulator(self.config.target_subsystem):
self.set_env('AWS_LC_SYS_EXTERNAL_BINDGEN', '1')
self.set_env('BINDGEN_EXTRA_CLANG_ARGS', f'--sysroot={self.config.sysroot}')
# for openssl-sys to be able to find openssl
self.set_env('OPENSSL_DIR', self.config.prefix)
self.set_env('OPENSSL_NO_VENDOR', '1')
self.set_env('OPENSSL_STATIC', '0')
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 and running_on_cerbero_ci():
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)
# For aws-lc-rs/bindgen -- libclang does no introspection when using
# external compilers to build
if self.config.target_platform == Platform.ANDROID:
# For aws-lc-rs
# https://github.com/rust-lang/cmake-rs/issues/140
arch = self.config.target_arch
if self.config.target_arch == Architecture.ARMv7:
arch = 'armeabi-v7a'
elif self.config.target_arch == Architecture.ARM64:
arch = 'arm64-v8a'
with open(f'{self.config_src_dir}/toolchain.cmake', 'w') as f:
f.write(f'set(ANDROID_ABI {arch})\n')
f.write(f'set(ANDROID_PLATFORM android-{DistroVersion.get_android_api_version(self.config.target_distro_version)})\n')
f.write(f"include({self.config.env['ANDROID_NDK_HOME']}/build/cmake/android.toolchain.cmake)")
await super().configure()
async def install(self):
await super(recipe.Recipe, self).install()
# 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, only_existing=False):
if not f.endswith('.pc') or 'gstreamer-1.0' not in f or 'gstrsworkspace' in f:
continue
dst = Path(self.config.prefix, f)
src = Path(self.config.libdir, 'pkgconfig', dst.name)
with src.open('r', encoding='utf-8') as f:
pc_file = f.read()
if self.config.target_platform == Platform.WINDOWS:
# Patch up Rust's leakage of windows-sys prebuilt lib,
# the import files are already inserted inside the static libs
# https://github.com/rust-lang/cargo/issues/15748
pc_file = re.sub(r'-lwindows[0-9.]+', '', pc_file)
# winapi crate imports under MinGW
pc_file = re.sub(r'-lwinapi_', '-l', pc_file)
if self.requires_melding():
# Inject our substitute Rust workspace library
if re.search(r'Requires.private:\s+\w', pc_file):
pc_file = pc_file.replace('Requires.private:',
'Requires.private: gstrsworkspace,')
else:
pc_file = pc_file.replace('Requires.private:', 'Requires.private: gstrsworkspace')
with dst.open('w', encoding='utf-8') as f:
f.write(pc_file)
src.unlink()
# 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.config.target_platform == Platform.WINDOWS:
if self.config.variants.visualstudio:
for f in self.files_list_by_category(self.DEVEL_CAT, only_existing=False):
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(only_existing=False):
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')
else:
for f in self.dist_files_list(only_existing=False):
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')
if self.requires_melding():
libs = '-L${libdir}/gstreamer-1.0 -lgstrsworkspace'
if self.config.target_platform == Platform.WINDOWS:
# Inject default libraries to cover inclusions from stdlib.
# The linker will seamlessly remove any unused ones.
# (May need to be altered in the future!)
# Note: I'm sure GCC complains if these are missing, adding
# them to MSVC is placebo because of the below flag
libs += ' -lws2_32 -lntdll -lsecur32 -lcrypt32 -lncrypt -luserenv '
if self.config.variants.visualstudio:
# MSVC first resolves functions, then prunes the COMDATs.
# However, with melded libraries rsworkspace will introduce
# unneeded imports. This will guarantee that the prune passes.
libs += ' /FORCE:UNRESOLVED '
PkgConfigWriter(
'gstrsworkspace',
'gst-plugins-rs workspace library',
self.version,
self.config.prefix,
libs=libs,
rel_libdir=self.config.rel_libdir
).save('gstrsworkspace', str(self.workspace_pc.parent))
self.workspace_lib.unlink(missing_ok=True)
files = [f for f in self.files_list_by_category(self.DEVEL_CAT, only_existing=False) if f.endswith('.a') and 'gstrsworkspace' not in f]
dragonfire = os.path.join(self.config.build_tools_prefix, 'bin', 'dragonfire')
if self.config.target_platform == Platform.WINDOWS:
fmt = 'coff'
elif Platform.is_apple(self.config.target_platform):
fmt = 'darwin'
else:
fmt = 'gnu'
await shell.async_call(
[dragonfire, '-o', self.workspace_lib, '-f', fmt, *files], cmd_dir=self.config.prefix, logfile=self.logfile, env=self.env
)
# These end up cached, remove them
for f in files:
src = Path(self.config.prefix, f)
bak = src.with_suffix('.bak')
bak.unlink(missing_ok=True)
elif self.config.target_platform == Platform.DARWIN and \
self.config.target_subsystem == Subsystem.MACOS:
libraries = [f for f in self.devel_files_list(only_existing=False)
if f.endswith('.a')]
prelinker = Prelink(self.config, self.env, self.logfile)
for f in libraries:
prelinker.prelink(f, '_gst_plugin_*')
pc_files = [
f for f in self.files_list_by_category(self.DEVEL_CAT, only_existing=False)
if f.endswith('.pc') and 'gstreamer-1.0' in f
]
for f in pc_files:
prelinker.patch_pkgconfig(f)
|