Compare commits

..

84 Commits

Author SHA1 Message Date
Max Kellermann
b5bd294e5c release v0.23.16 2024-12-03 12:56:57 +01:00
Max Kellermann
ac60bd47f0 thread/WindowsFuture: add missing include for std::error_category 2024-12-03 12:54:12 +01:00
Max Kellermann
b7248f0333 filter/ffmpeg: fill AVFrame::pts
Some libavfilter plugins don't produce any output if `pts` is never
set, e.g. the `lowpass` plugin.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/2114
2024-12-03 12:52:15 +01:00
Marius Feraru
8a5b5378e6 db/update/Walk:FindAncestorLoop: uint64_t inode & device
Previously, inode numbers were truncated to 32 bits, which could lead
to problems on XFS where inodes are 64 bit; this could lead to bogus
"recursive directory found" errors during database update.

[mk: added commit description and NEWS line]

Closes https://github.com/MusicPlayerDaemon/MPD/issues/2000
2024-12-03 12:49:02 +01:00
Michael Cho
4a49f75799 meson.build: support building with ICU 76
ICU 76 decided to reduce overlinking[^1] thus `icu-i18n` will no longer
add `icu-uc` when linking to shared libraries. This results in failure:
```
src/lib/icu/libicu.a.p/Converter.cxx.o: undefined reference to symbol 'ucnv_fromUnicode_76'
```

[^1]: 199bc82702

Closes https://github.com/MusicPlayerDaemon/MPD/issues/2151
2024-12-03 12:47:55 +01:00
Max Kellermann
9d2666f293 subprojects: update sqlite3 to 3.47.1-1 2024-12-03 12:46:33 +01:00
Max Kellermann
e63fcc5982 python/build/libs: update libnfs to 5.0.3 2024-12-03 12:45:05 +01:00
Max Kellermann
4c37c17f2e python/build/libs.py: update WildMidi to 0.4.6 2024-12-03 12:44:33 +01:00
Max Kellermann
722820a375 python/build/libs: update zlib to 1.3.1 2024-12-03 12:44:14 +01:00
Max Kellermann
688023eb9e python/build/libs.py: update libopenmpt to 0.7.9 2024-12-03 12:43:34 +01:00
Max Kellermann
5771aeaddd subprojects: update fmt to 11.0.2-1 2024-12-03 12:42:02 +01:00
Rudi Heitbaum
a42da90042 lib/fmt: support build with libfmt-11.0.0
Upstream libfmt commit fmtlib/fmt@d707292
now requires the format function to be const.

Adjust the function prototype so it is const and can compile.

Signed-off-by: Rudi Heitbaum <rudi@heitbaum.com>

Closes https://github.com/MusicPlayerDaemon/MPD/issues/2141
2024-12-03 12:42:02 +01:00
Max Kellermann
d7d32ed6fc meson.build: suppress -Wnan-infinity-disabled (clang 18) due to libfmt 2024-12-03 12:40:21 +01:00
Max Kellermann
4715acf27e Log: add missing include for std::back_inserter()
Closes https://github.com/MusicPlayerDaemon/MPD/issues/2071
2024-12-03 12:38:00 +01:00
Max Kellermann
8780db5ee8 increment version number to 0.23.16 2024-12-03 12:34:01 +01:00
Max Kellermann
b8bfc98618 release v0.23.15 2023-12-20 16:21:57 +01:00
Max Kellermann
6e6f72a521 win32/HResult: convert assert() to runtime check to work around -Walloc-size-larger-than 2023-12-20 16:15:58 +01:00
Max Kellermann
a654c5d643 Revert "android: Fix MPD shutdown from settings UI"
This reverts commit 94b5b9f370.  It was
not necessary for branch v0.23.x because there, Break() is
thread-safe; this was only changed later by commit
a3b32819b1
2023-12-20 16:15:58 +01:00
Max Kellermann
c5d6aa169f lib/curl/patches: refresh no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch for 7.85.0 2023-12-20 13:43:20 +01:00
Max Kellermann
c1c67286d3 python/build/libs.py: update CURL to 8.5.0 2023-12-20 13:28:54 +01:00
borine
2fb34697c7 input/plugins/Alsa: catch all exceptions
snd_pcm_poll_descriptors_revents() may return any error code; the
ALSA docs do not constrain the permitted values. A 'hw' device
will only ever return an error if the pfd array passed in is
invalid (-EINVAL), but other I/O plugins may return arbitary
errors. For example a network-based device may return -EPIPE etc.
The resulting exception thrown by
AlsaNonBlockPcm::DispatchSockets() must be caught to prevent the
mpd process from being aborted.
2023-12-20 13:27:25 +01:00
Colin Edwards
94b5b9f370 android: Fix MPD shutdown from settings UI 2023-12-20 13:27:16 +01:00
Max Kellermann
a9467513e1 doc/developer.rst: add missing return type to code style sample 2023-12-20 13:26:48 +01:00
borine
17d944f6ce input/plugins/Alsa: limit ALSA buffer time to 2 seconds maximum
Some ALSA capture devices can have very large buffers, holding 10
seconds or more audio. Using the maximum buffer size with such
devices leads to unacceptably large, and unnecessary, latency.
Also, some ALSA drivers (e.g. HDA Intel PCH) report an invalid
maximum period size, and the period size that mpd calculates from
the maximum buffer size results in "Invalid argument" error when
applying the hw_params. Note that the "default" capture device on
many cards includes the "dsnoop" plugin which imposes a buffer
size of 16384 frames, so that "alsa://" works OK but
"alsa://plughw" or "alsa://hw" both fail.

Limit the maximum buffer time for ALSA input devices to a more useable
2 seconds, thereby avoiding both the above problems.
2023-12-20 13:26:24 +01:00
Max Kellermann
0f82f18652 python/build/libs.py: update CURL to 8.4.0 2023-12-20 13:25:08 +01:00
Max Kellermann
3db3e577f1 python/build/libs.py: update OpenSSL to 3.1.4 2023-12-20 13:25:04 +01:00
Max Kellermann
37ee821947 python/build/libs.py: update FFmpeg to 6.1 2023-12-20 13:25:00 +01:00
Max Kellermann
916ab9a7e6 python/build/libs.py: update openmpt to 0.7.3 2023-12-20 13:24:57 +01:00
Max Kellermann
1802cf9fd1 python/build/cmake.py: add CMAKE_FIND_ROOT_PATH on Windows
Works around CURL build failure because cmake insists on using
/usr/include/zlib.h.
2023-12-20 13:24:53 +01:00
Max Kellermann
1bf7d30623 subprojects: update fmt to 10.1.1-1 2023-12-20 13:24:14 +01:00
naglis
b2d89253a6 doc/protocol.rst: mention song id lifetime 2023-12-20 13:23:03 +01:00
skidoo23
50c1e3738a decoder/ffmpeg: Fix build error with ffmpeg 6.1
Closes https://github.com/MusicPlayerDaemon/MPD/issues/1898
2023-11-21 20:36:37 +01:00
Max Kellermann
7a939746ae increment version number to 0.23.15 2023-11-21 20:32:45 +01:00
Max Kellermann
feac1a3f56 release v0.23.14 2023-10-08 10:11:05 +02:00
Max Kellermann
f3c37e484e tag/Mask: add static_assert on the size of the mask 2023-10-08 10:01:00 +02:00
Max Kellermann
49130c2018 python/build/openssl: remove obsolete variable 2023-09-26 15:32:49 +02:00
Max Kellermann
94af199c49 python/build/openssl: add --cross-compile-prefix=... 2023-09-26 15:31:21 +02:00
Max Kellermann
2d25f6f57f python/build/openssl: pass arch only if cross-compiling 2023-09-26 15:31:11 +02:00
Max Kellermann
cf179ec294 python/build/openssl: pass --libdir=lib to Configure
Without this, the AMD64 build installs to "lib64/" which then cannot
be found by CURL.
2023-09-26 15:30:51 +02:00
Max Kellermann
4d6f220a2f python/build/cmake: write toolchain file only if cross-compiling 2023-09-26 15:29:45 +02:00
Max Kellermann
0ffbe5b5ea python/build/autotools: add --host=... only if cross-compiling 2023-09-26 15:27:05 +02:00
Max Kellermann
5b83c834ac python/build/meson: write cross-file only if cross-compiling 2023-09-26 15:26:17 +02:00
Max Kellermann
da7f32bddb python/build/toolchain: rename arch to host_triplet 2023-09-26 15:25:23 +02:00
Max Kellermann
9a5eac4ea9 python/build/toolchain: set arch=llvm_triple 2023-09-26 15:13:28 +02:00
Max Kellermann
6571b5d118 python/build/openssl: add option "no-makedepend"
We do not need "make" dependencies for one-time builds.
2023-09-26 15:05:14 +02:00
Max Kellermann
12dff8e382 python/build/openssl: use no-asm only on Windows 2023-09-26 15:03:50 +02:00
Max Kellermann
c4da87a0cb python/build/openssl: add configure variable 2023-09-26 15:03:24 +02:00
Max Kellermann
446f8f29d3 python/build/openssl: add Darwin archs 2023-09-26 15:02:02 +02:00
Max Kellermann
48cc76f114 python/build/toolchain: add is_android, is_darwin 2023-09-26 14:49:39 +02:00
Max Kellermann
a0892b852e build/python/autotools: add option per_arch_cflags 2023-09-26 14:33:24 +02:00
Max Kellermann
485c7805eb python/build/autotools: use list.extend() to append configure_args 2023-09-26 14:31:30 +02:00
Max Kellermann
23802f4489 python/build/tarball: Python type hints 2023-09-26 14:28:23 +02:00
Max Kellermann
3fedd978a2 python/build/meson: disable ccache because Meson detects it automatically 2023-09-26 14:27:43 +02:00
Max Kellermann
a9f1bed922 build/python/cmake: add cast to fix mypy warning 2023-09-26 14:25:51 +02:00
Max Kellermann
eb23788fec python/build: add support for fallback download URLs 2023-09-26 14:25:51 +02:00
Max Kellermann
f6d73555a6 python/build/libs: update OpenSSL toi 3.1.3 2023-09-26 14:21:43 +02:00
Max Kellermann
a56a709406 python/build/download: relative imports 2023-09-26 14:15:01 +02:00
Max Kellermann
5f253e66f6 python/build/toolchain.py: add AnyToolchain for type hints 2023-09-26 12:54:58 +02:00
Max Kellermann
4669f7e2b9 {android,win32}/build.py: move Toolchain classes to python/build/toolchain.py 2023-09-26 12:47:02 +02:00
Max Kellermann
4c90f88704 win32: rename CrossGccToolchain to MingwToolchain 2023-09-26 12:46:10 +02:00
Max Kellermann
a7213b78d6 win32/build.py: move code to class CrossGccToolchain 2023-09-26 12:20:53 +02:00
Max Kellermann
719333e16e android/build.py: move code to class AndroidNdkToolchain 2023-09-26 12:18:18 +02:00
Max Kellermann
100e471b49 android/build.py: remove duplicate import 2023-09-26 12:12:00 +02:00
Max Kellermann
3f2016e552 python: add type hints 2023-09-26 12:04:08 +02:00
Max Kellermann
dd89ea4505 android/AndroidManifest.xml: raise minSdkVersion to 24
This is needed to build libFLAC which uses ftello().
2023-09-26 12:04:05 +02:00
Max Kellermann
101e12cf9a modplug: add patch to remove the deprecated register keyword 2023-09-26 11:48:44 +02:00
Max Kellermann
f382808450 python/build/libs.py: update CURL to 8.2.1 2023-09-26 11:48:16 +02:00
Max Kellermann
0cbe3c2a93 python/build/libs.py: update OpenSSL to 3.1.2 2023-09-26 11:48:13 +02:00
Max Kellermann
4f0ae28359 python/build/libs.py: update zlib to 1.3 2023-09-26 11:48:09 +02:00
Max Kellermann
6a4250f485 python/build/libs.py: update Opus to 1.4 2023-09-26 11:48:06 +02:00
Max Kellermann
3322b29e6a python/build/libs.py: update FLAC to 1.4.3 2023-09-26 11:48:02 +02:00
naglis
33ac472601 doc/plugins.rst: change command to list PipeWire targets
The `dump` command was dropped[1] in favor of other tools.

[1]: 50bdebe4e8
2023-09-26 11:41:37 +02:00
Simon Arlott
561d6fd478 meson: Use correct prefix for systemd_system_unit_dir
systemd uses "rootprefix", not "prefix" for this value

059b1b31ad/src/core/systemd.pc.in (L23)
2023-06-02 14:33:13 +02:00
Naïm Favier
42a01822bf meson: use correct prefix for systemd dirs
See https://www.bassi.io/articles/2018/03/15/pkg-config-and-paths/

Fixes the build in nixpkgs
2023-06-02 14:33:09 +02:00
Shen-Ta Hsieh
38f1237d49 output/wasapi: cast to const char * for fmt 10 compatible 2023-06-02 14:29:43 +02:00
Shen-Ta Hsieh
8df77122e5 python/build/libs.py: use right cmake variable to disable SDL 2023-06-02 14:28:22 +02:00
Shen-Ta Hsieh
fef6b9df80 flac: Try InputStream interface if flac failed to read through a wchar_t path 2023-06-02 14:28:22 +02:00
Shen-Ta Hsieh
d52eac66db doc/mpdconf.example: add hardware mixer example config for wasapi 2023-06-02 14:27:24 +02:00
Shen-Ta Hsieh
70879f0abc thread/WindowsFuture: remove wrong address_of operator 2023-06-02 14:27:24 +02:00
Shen-Ta Hsieh
bcb393628e win32/ComWorker: rename variable name to prevent ambiguous 2023-06-02 14:22:11 +02:00
Max Kellermann
18d3a5c12b decoder/flac: add noexcept and inline 2023-06-02 14:22:11 +02:00
Simon Arlott
6ee3d0102b decoder/mad: Fix decode of LAME peak value
6d91b5c7b2 ("fix double promotions") changed
how LAME peak values are decoded, producing large incorrect values that
cause some MP3 files to play silently.

Restore the original decode from MAD fixed-point format to double and
document what it's doing.

Fixes 
2023-06-02 14:15:48 +02:00
Max Kellermann
fc9626e2f4 increment version number to 0.23.14 2023-06-02 14:15:14 +02:00
Simon Arlott
3bedd94fc8 doc: Fix syntax error
With sphinx-build 5.0.0:
doc/user.rst:728: ERROR: Unexpected indentation.
doc/user.rst:731: ERROR: Unexpected indentation.
2023-06-02 14:10:38 +02:00
54 changed files with 690 additions and 445 deletions

25
NEWS

@@ -1,3 +1,28 @@
ver 0.23.16 (2024/12/03)
* database
- fix integer overflows with 64-bit inode numbers
* filter
- ffmpeg: fix for filters producing no output
* support libfmt 11
* support ICU 76
ver 0.23.15 (2023/12/20)
* decoder
- ffmpeg: fix build failure with FFmpeg 6.1
* output
- alsa: limit buffer time to 2 seconds
ver 0.23.14 (2023/10/08)
* decoder
- flac: fix scanning files with non-ASCII names on Windows
- mad: fix calculation of LAME peak values
* mixer
- wasapi: fix problem setting volume
* more libfmt 10 fixes
* fix auto-detected systemd unit directory
* Android
- require Android 7 or newer
ver 0.23.13 (2023/05/22) ver 0.23.13 (2023/05/22)
* input * input
- curl: fix busy loop after connection failed - curl: fix busy loop after connection failed

@@ -2,10 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd" package="org.musicpd"
android:installLocation="auto" android:installLocation="auto"
android:versionCode="71" android:versionCode="74"
android:versionName="0.23.12"> android:versionName="0.23.16">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/> <uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30"/>
<uses-feature android:name="android.software.leanback" <uses-feature android:name="android.software.leanback"
android:required="false" /> android:required="false" />

@@ -20,131 +20,13 @@ if not os.path.isdir(ndk_path):
print("NDK not found in", ndk_path, file=sys.stderr) print("NDK not found in", ndk_path, file=sys.stderr)
sys.exit(1) sys.exit(1)
android_abis = {
'armeabi-v7a': {
'arch': 'arm-linux-androideabi',
'ndk_arch': 'arm',
'llvm_triple': 'armv7-linux-androideabi',
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
},
'arm64-v8a': {
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'llvm_triple': 'aarch64-linux-android',
'cflags': '-fpic',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'llvm_triple': 'i686-linux-android',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64',
'llvm_triple': 'x86_64-linux-android',
'cflags': '-fPIC -m64',
},
}
# select the NDK target
abi_info = android_abis[android_abi]
arch = abi_info['arch']
# the path to the MPD sources # the path to the MPD sources
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..')) mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
sys.path[0] = os.path.join(mpd_path, 'python') sys.path[0] = os.path.join(mpd_path, 'python')
# output directories # output directories
from build.dirs import lib_path, tarball_path, src_path from build.dirs import lib_path, tarball_path, src_path
from build.meson import configure as run_meson from build.toolchain import AndroidNdkToolchain
arch_path = os.path.join(lib_path, arch)
build_path = os.path.join(arch_path, 'build')
# build host configuration
build_arch = 'linux-x86_64'
# set up the NDK toolchain
class AndroidNdkToolchain:
def __init__(self, tarball_path, src_path, build_path,
use_cxx):
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = build_path
ndk_arch = abi_info['ndk_arch']
android_api_level = '21'
install_prefix = os.path.join(arch_path, 'root')
self.arch = arch
self.actual_arch = arch
self.install_prefix = install_prefix
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = abi_info['llvm_triple'] + android_api_level
common_flags = '-Os -g'
common_flags += ' ' + abi_info['cflags']
llvm_bin = os.path.join(llvm_path, 'bin')
self.cc = os.path.join(llvm_bin, 'clang')
self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(llvm_bin, 'llvm-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.strip = os.path.join(llvm_bin, 'llvm-strip')
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
' -Wl,--exclude-libs=ALL' + \
' ' + common_flags
self.ldflags = common_flags
self.libs = ''
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
libstdcxx_flags = ''
libstdcxx_cxxflags = ''
libstdcxx_ldflags = ''
libstdcxx_libs = '-static-libstdc++'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.libs += ' ' + libstdcxx_libs
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
import shutil
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
# a list of third-party libraries to be used by MPD on Android # a list of third-party libraries to be used by MPD on Android
from build.libs import * from build.libs import *
@@ -166,13 +48,17 @@ thirdparty_libs = [
# build the third-party libraries # build the third-party libraries
for x in thirdparty_libs: for x in thirdparty_libs:
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path, toolchain = AndroidNdkToolchain(mpd_path, lib_path,
tarball_path, src_path,
ndk_path, android_abi,
use_cxx=x.use_cxx) use_cxx=x.use_cxx)
if not x.is_installed(toolchain): if not x.is_installed(toolchain):
x.build(toolchain) x.build(toolchain)
# configure and build MPD # configure and build MPD
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path, toolchain = AndroidNdkToolchain(mpd_path, lib_path,
tarball_path, src_path,
ndk_path, android_abi,
use_cxx=True) use_cxx=True)
configure_args += [ configure_args += [

@@ -20,6 +20,7 @@ Some example code:
.. code-block:: c .. code-block:: c
int
Foo(const char *abc, int xyz) Foo(const char *abc, int xyz)
{ {
if (abc == nullptr) { if (abc == nullptr) {

@@ -314,6 +314,7 @@ input {
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional ## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
# or # or
## device "0" # optional ## device "0" # optional
## mixer_type "hardware" # optional
## Exclusive mode blocks all other audio source, and get best audio quality without resampling. ## Exclusive mode blocks all other audio source, and get best audio quality without resampling.
## exclusive "no" # optional ## exclusive "no" # optional
## Enumerate all devices in log. ## Enumerate all devices in log.

@@ -1115,7 +1115,7 @@ Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
* - **target NAME** * - **target NAME**
- Link to the given target. If not specified, let the PipeWire - Link to the given target. If not specified, let the PipeWire
manager select a target. To get a list of available targets, manager select a target. To get a list of available targets,
type ``pw-cli dump short Node`` type ``pw-cli ls Node``
* - **remote NAME** * - **remote NAME**
- The name of the remote to connect to. The default is - The name of the remote to connect to. The default is
``pipewire-0``. ``pipewire-0``.

@@ -691,7 +691,8 @@ Song ids on the other hand are stable: an id is assigned to a song
when it is added, and will stay the same, no matter how much it is when it is added, and will stay the same, no matter how much it is
moved around. Adding the same song twice will assign different ids to moved around. Adding the same song twice will assign different ids to
them, and a deleted-and-readded song will have a new id. This way, a them, and a deleted-and-readded song will have a new id. This way, a
client can always be sure the correct song is being used. client can always be sure the correct song is being used. Song ids are not
preserved across :program:`MPD` restarts.
Many commands come in two flavors, one for each address type. Many commands come in two flavors, one for each address type.
Whenever possible, ids should be used. Whenever possible, ids should be used.

@@ -661,9 +661,11 @@ MPD enables MixRamp if:
- Cross-fade is enabled - Cross-fade is enabled
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive - :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
value, e.g.:: value, e.g.::
mpc mixrampdelay 1 mpc mixrampdelay 1
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value, - :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
e.g.:: e.g.::
mpc mixrampdb -17 mpc mixrampdb -17
- both songs have MixRamp tags - both songs have MixRamp tags
- both songs have the same audio format (or :ref:`audio_output_format` - both songs have the same audio format (or :ref:`audio_output_format`

@@ -1,7 +1,7 @@
project( project(
'mpd', 'mpd',
['c', 'cpp'], ['c', 'cpp'],
version: '0.23.13', version: '0.23.16',
meson_version: '>= 0.56.0', meson_version: '>= 0.56.0',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',
@@ -76,6 +76,9 @@ test_common_flags = [
# suppress bogus GCC12 warnings in libfmt headers # suppress bogus GCC12 warnings in libfmt headers
'-Wno-stringop-overflow', '-Wno-stringop-overflow',
# libfmt causes this warning due to -ffast-math
'-Wno-nan-infinity-disabled',
] ]
test_global_cxxflags = test_global_common_flags + [ test_global_cxxflags = test_global_common_flags + [

@@ -1,26 +1,32 @@
import os.path, subprocess, sys import os.path, subprocess, sys
from typing import Collection, Iterable, Optional, Sequence, Union
from collections.abc import Mapping
from build.makeproject import MakeProject from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class AutotoolsProject(MakeProject): class AutotoolsProject(MakeProject):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
autogen=False, configure_args: Iterable[str]=[],
autoreconf=False, autogen: bool=False,
cppflags='', autoreconf: bool=False,
ldflags='', per_arch_cflags: Optional[Mapping[str, str]]=None,
libs='', cppflags: str='',
subdirs=None, ldflags: str='',
libs: str='',
subdirs: Optional[Collection[str]]=None,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, **kwargs) MakeProject.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.autogen = autogen self.autogen = autogen
self.autoreconf = autoreconf self.autoreconf = autoreconf
self.per_arch_cflags = per_arch_cflags
self.cppflags = cppflags self.cppflags = cppflags
self.ldflags = ldflags self.ldflags = ldflags
self.libs = libs self.libs = libs
self.subdirs = subdirs self.subdirs = subdirs
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
if self.autogen: if self.autogen:
if sys.platform == 'darwin': if sys.platform == 'darwin':
@@ -35,12 +41,16 @@ class AutotoolsProject(MakeProject):
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
arch_cflags = ''
if self.per_arch_cflags is not None and toolchain.host_triplet is not None:
arch_cflags = self.per_arch_cflags.get(toolchain.host_triplet, '')
configure = [ configure = [
os.path.join(src, 'configure'), os.path.join(src, 'configure'),
'CC=' + toolchain.cc, 'CC=' + toolchain.cc,
'CXX=' + toolchain.cxx, 'CXX=' + toolchain.cxx,
'CFLAGS=' + toolchain.cflags, 'CFLAGS=' + toolchain.cflags + ' ' + arch_cflags,
'CXXFLAGS=' + toolchain.cxxflags, 'CXXFLAGS=' + toolchain.cxxflags + ' ' + arch_cflags,
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags, 'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags, 'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
'LIBS=' + toolchain.libs + ' ' + self.libs, 'LIBS=' + toolchain.libs + ' ' + self.libs,
@@ -48,10 +58,14 @@ class AutotoolsProject(MakeProject):
'ARFLAGS=' + toolchain.arflags, 'ARFLAGS=' + toolchain.arflags,
'RANLIB=' + toolchain.ranlib, 'RANLIB=' + toolchain.ranlib,
'STRIP=' + toolchain.strip, 'STRIP=' + toolchain.strip,
'--host=' + toolchain.arch,
'--prefix=' + toolchain.install_prefix, '--prefix=' + toolchain.install_prefix,
'--disable-silent-rules', '--disable-silent-rules',
] + self.configure_args ]
if toolchain.host_triplet is not None:
configure.append('--host=' + toolchain.host_triplet)
configure.extend(self.configure_args)
try: try:
print(configure) print(configure)
@@ -68,7 +82,7 @@ class AutotoolsProject(MakeProject):
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
if self.subdirs is not None: if self.subdirs is not None:
for subdir in self.subdirs: for subdir in self.subdirs:

@@ -1,18 +1,21 @@
import os import os
import re import re
import subprocess import subprocess
from typing import cast, Optional, Sequence, TextIO, Union
from collections.abc import Mapping
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
def __write_cmake_compiler(f, language, compiler): def __write_cmake_compiler(f: TextIO, language: str, compiler: str) -> None:
s = compiler.split(' ', 1) s = compiler.split(' ', 1)
if len(s) == 2: if len(s) == 2:
print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f) print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f)
compiler = s[1] compiler = s[1]
print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f) print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f)
def __write_cmake_toolchain_file(f, toolchain): def __write_cmake_toolchain_file(f: TextIO, toolchain: AnyToolchain) -> None:
if '-darwin' in toolchain.actual_arch: if toolchain.is_darwin:
cmake_system_name = 'Darwin' cmake_system_name = 'Darwin'
elif toolchain.is_windows: elif toolchain.is_windows:
cmake_system_name = 'Windows' cmake_system_name = 'Windows'
@@ -21,10 +24,10 @@ def __write_cmake_toolchain_file(f, toolchain):
f.write(f""" f.write(f"""
set(CMAKE_SYSTEM_NAME {cmake_system_name}) set(CMAKE_SYSTEM_NAME {cmake_system_name})
set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]}) set(CMAKE_SYSTEM_PROCESSOR {toolchain.host_triplet.split('-', 1)[0]})
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch}) set(CMAKE_C_COMPILER_TARGET {toolchain.host_triplet})
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch}) set(CMAKE_CXX_COMPILER_TARGET {toolchain.host_triplet})
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}") set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}") set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
@@ -50,38 +53,49 @@ set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}") set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
""")
elif cmake_system_name == 'Windows':
# search libraries and headers only in the sysroot, not on
# the build host
f.write(f"""
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix}")
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
""") """)
def configure(toolchain, src, build, args=(), env=None): def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[], env: Optional[Mapping[str, str]]=None) -> None:
cross_args = [] cross_args: list[str] = []
if toolchain.is_windows: if toolchain.is_windows:
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres) cross_args.append('-DCMAKE_RC_COMPILER=' + cast(str, toolchain.windres))
# Several targets need a sysroot to prevent pkg-config from
# looking for libraries on the build host (TODO: fix this
# properly); but we must not do that on Android because the NDK
# has a sysroot already
if '-android' not in toolchain.actual_arch and '-darwin' not in toolchain.actual_arch:
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
os.makedirs(build, exist_ok=True)
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
with open(cmake_toolchain_file, 'w') as f:
__write_cmake_toolchain_file(f, toolchain)
configure = [ configure = [
'cmake', 'cmake',
src, src,
'-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file,
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix, '-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
'-DCMAKE_BUILD_TYPE=release', '-DCMAKE_BUILD_TYPE=release',
'-GNinja', '-GNinja',
] + cross_args + args ] + cross_args + args
if toolchain.host_triplet is not None:
# cross-compiling: write a toolchain file
os.makedirs(build, exist_ok=True)
# Several targets need a sysroot to prevent pkg-config from
# looking for libraries on the build host (TODO: fix this
# properly); but we must not do that on Android because the NDK
# has a sysroot already
if not toolchain.is_android and not toolchain.is_darwin:
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
with open(cmake_toolchain_file, 'w') as f:
__write_cmake_toolchain_file(f, toolchain)
configure.append('-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file)
if env is None: if env is None:
env = toolchain.env env = toolchain.env
else: else:
@@ -91,16 +105,17 @@ def configure(toolchain, src, build, args=(), env=None):
subprocess.check_call(configure, env=env, cwd=build) subprocess.check_call(configure, env=env, cwd=build)
class CmakeProject(Project): class CmakeProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
windows_configure_args=[], configure_args: list[str]=[],
env=None, windows_configure_args: list[str]=[],
env: Optional[Mapping[str, str]]=None,
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
self.windows_configure_args = windows_configure_args self.windows_configure_args = windows_configure_args
self.env = env self.env = env
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
configure_args = self.configure_args configure_args = self.configure_args
@@ -109,7 +124,7 @@ class CmakeProject(Project):
configure(toolchain, src, build, configure_args, self.env) configure(toolchain, src, build, configure_args, self.env)
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', '-v', 'install'], subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)

@@ -1,12 +1,50 @@
from build.verify import verify_file_digest from typing import Sequence, Union
import os import os
import sys
import urllib.request import urllib.request
def download_and_verify(url, md5, parent_path): from .verify import verify_file_digest
def __to_string_sequence(x: Union[str, Sequence[str]]) -> Sequence[str]:
if isinstance(x, str):
return (x,)
else:
return x
def __get_any(x: Union[str, Sequence[str]]) -> str:
if isinstance(x, str):
return x
else:
return x[0]
def __download_one(url: str, path: str) -> None:
print("download", url)
urllib.request.urlretrieve(url, path)
def __download(urls: Sequence[str], path: str) -> None:
for url in urls[:-1]:
try:
__download_one(url, path)
return
except:
print("download error:", sys.exc_info()[0])
__download_one(urls[-1], path)
def __download_and_verify_to(urls: Sequence[str], md5: str, path: str) -> None:
__download(urls, path)
if not verify_file_digest(path, md5):
raise RuntimeError("Digest mismatch")
def download_basename(urls: Union[str, Sequence[str]]) -> str:
return os.path.basename(__get_any(urls))
def download_and_verify(urls: Union[str, Sequence[str]], md5: str, parent_path: str) -> str:
"""Download a file, verify its MD5 checksum and return the local path.""" """Download a file, verify its MD5 checksum and return the local path."""
base = download_basename(urls)
os.makedirs(parent_path, exist_ok=True) os.makedirs(parent_path, exist_ok=True)
path = os.path.join(parent_path, os.path.basename(url)) path = os.path.join(parent_path, base)
try: try:
if verify_file_digest(path, md5): return path if verify_file_digest(path, md5): return path
@@ -16,11 +54,6 @@ def download_and_verify(url, md5, parent_path):
tmp_path = path + '.tmp' tmp_path = path + '.tmp'
print("download", url) __download_and_verify_to(__to_string_sequence(urls), md5, tmp_path)
urllib.request.urlretrieve(url, tmp_path)
if not verify_file_digest(tmp_path, md5):
os.unlink(tmp_path)
raise RuntimeError("Digest mismatch")
os.rename(tmp_path, path) os.rename(tmp_path, path)
return path return path

@@ -29,8 +29,8 @@ libogg = CmakeProject(
) )
opus = AutotoolsProject( opus = AutotoolsProject(
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz', 'https://downloads.xiph.org/releases/opus/opus-1.4.tar.gz',
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d', 'c9b32b4253be5ae63d1ff16eea06b94b5f0f2951b7a02aceef58e3a3ce49c51f',
'lib/libopus.a', 'lib/libopus.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -43,8 +43,8 @@ opus = AutotoolsProject(
) )
flac = AutotoolsProject( flac = AutotoolsProject(
'http://downloads.xiph.org/releases/flac/flac-1.4.2.tar.xz', 'http://downloads.xiph.org/releases/flac/flac-1.4.3.tar.xz',
'e322d58a1f48d23d9dd38f432672865f6f79e73a6f9cc5a5f57fcaa83eb5a8e4', '6c58e69cd22348f441b861092b825e591d0b822e106de6eb0ee4d05d27205b70',
'lib/libFLAC.a', 'lib/libFLAC.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -57,8 +57,9 @@ flac = AutotoolsProject(
) )
zlib = ZlibProject( zlib = ZlibProject(
'http://zlib.net/zlib-1.2.13.tar.xz', ('http://zlib.net/zlib-1.3.1.tar.xz',
'd14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98', 'https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.xz'),
'38ef96b8dfe510d42707d9c781877914792541133e1870841463bfa73f883e32',
'lib/libz.a', 'lib/libz.a',
) )
@@ -111,11 +112,12 @@ libmodplug = AutotoolsProject(
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
], ],
patches='src/lib/modplug/patches',
) )
libopenmpt = AutotoolsProject( libopenmpt = AutotoolsProject(
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz', 'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.7.9+release.autotools.tar.gz',
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7', '0386e918d75d797e79d5b14edd0847165d8b359e9811ef57652c0a356a2dfcf4',
'lib/libopenmpt.a', 'lib/libopenmpt.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -127,12 +129,12 @@ libopenmpt = AutotoolsProject(
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile', '--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
'--without-flac', '--without-flac',
], ],
base='libopenmpt-0.6.6+release.autotools', base='libopenmpt-0.7.9+release.autotools',
) )
wildmidi = CmakeProject( wildmidi = CmakeProject(
'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz', 'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.6/wildmidi-0.4.6.tar.gz',
'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99', '24ca992639ce76efa3737029fceb3672385d56e2ac0a15d50b40cc12d26e60de',
'lib/libWildMidi.a', 'lib/libWildMidi.a',
[ [
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
@@ -149,13 +151,13 @@ gme = CmakeProject(
'-DBUILD_SHARED_LIBS=OFF', '-DBUILD_SHARED_LIBS=OFF',
'-DENABLE_UBSAN=OFF', '-DENABLE_UBSAN=OFF',
'-DZLIB_INCLUDE_DIR=OFF', '-DZLIB_INCLUDE_DIR=OFF',
'-DSDL2_DIR=OFF', '-DCMAKE_DISABLE_FIND_PACKAGE_SDL2=ON',
], ],
) )
ffmpeg = FfmpegProject( ffmpeg = FfmpegProject(
'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz', 'http://ffmpeg.org/releases/ffmpeg-6.1.tar.xz',
'57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082', '488c76e57dd9b3bee901f71d5c95eaf1db4a5a31fe46a28654e837144207c270',
'lib/libavcodec.a', 'lib/libavcodec.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -462,6 +464,8 @@ ffmpeg = FfmpegProject(
'--disable-decoder=pam', '--disable-decoder=pam',
'--disable-decoder=pbm', '--disable-decoder=pbm',
'--disable-decoder=pcx', '--disable-decoder=pcx',
'--disable-decoder=pdv',
'--disable-decoder=pfm',
'--disable-decoder=pgm', '--disable-decoder=pgm',
'--disable-decoder=pgmyuv', '--disable-decoder=pgmyuv',
'--disable-decoder=pgssub', '--disable-decoder=pgssub',
@@ -598,14 +602,16 @@ ffmpeg = FfmpegProject(
) )
openssl = OpenSSLProject( openssl = OpenSSLProject(
'https://www.openssl.org/source/openssl-3.1.0.tar.gz', ('https://www.openssl.org/source/openssl-3.1.4.tar.gz',
'aaa925ad9828745c4cad9d9efeb273deca820f2cdcf2c3ac7d7c1212b7c497b4', 'https://artfiles.org/openssl.org/source/openssl-3.1.4.tar.gz'),
'840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3',
'include/openssl/ossl_typ.h', 'include/openssl/ossl_typ.h',
) )
curl = CmakeProject( curl = CmakeProject(
'https://curl.se/download/curl-8.0.1.tar.xz', ('https://curl.se/download/curl-8.5.0.tar.xz',
'0a381cd82f4d00a9a334438b8ca239afea5bfefcfa9a1025f2bf118e79e0b5f0', 'https://github.com/curl/curl/releases/download/curl-8_5_0/curl-8.5.0.tar.xz'),
'42ab8db9e20d8290a3b633e7fbb3cec15db34df65fd1015ef8ac1e4723750eeb',
'lib/libcurl.a', 'lib/libcurl.a',
[ [
'-DBUILD_CURL_EXE=OFF', '-DBUILD_CURL_EXE=OFF',
@@ -638,8 +644,8 @@ curl = CmakeProject(
) )
libnfs = AutotoolsProject( libnfs = AutotoolsProject(
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz', 'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.3.tar.gz',
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3', 'd945cb4f4c8f82ee1f3640893a168810f794a28e1010bb007ec5add345e9df3e',
'lib/libnfs.a', 'lib/libnfs.a',
[ [
'--disable-shared', '--enable-static', '--disable-shared', '--enable-static',
@@ -650,7 +656,7 @@ libnfs = AutotoolsProject(
'--disable-utils', '--disable-examples', '--disable-utils', '--disable-examples',
], ],
base='libnfs-libnfs-5.0.2', base='libnfs-libnfs-5.0.3',
autoreconf=True, autoreconf=True,
) )

@@ -1,15 +1,17 @@
import subprocess, multiprocessing import subprocess, multiprocessing
from typing import Optional, Sequence, Union
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
class MakeProject(Project): class MakeProject(Project):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
install_target='install', install_target: str='install',
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.install_target = install_target self.install_target = install_target
def get_simultaneous_jobs(self): def get_simultaneous_jobs(self) -> int:
try: try:
# use twice as many simultaneous jobs as we have CPU cores # use twice as many simultaneous jobs as we have CPU cores
return multiprocessing.cpu_count() * 2 return multiprocessing.cpu_count() * 2
@@ -17,17 +19,17 @@ class MakeProject(Project):
# default to 12, if multiprocessing.cpu_count() is not implemented # default to 12, if multiprocessing.cpu_count() is not implemented
return 12 return 12
def get_make_args(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())] return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
def get_make_install_args(self, toolchain): def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
return ['--quiet', self.install_target] return ['--quiet', self.install_target]
def make(self, toolchain, wd, args): def make(self, toolchain: AnyToolchain, wd: str, args: list[str]) -> None:
subprocess.check_call(['make'] + args, subprocess.check_call(['make'] + args,
cwd=wd, env=toolchain.env) cwd=wd, env=toolchain.env)
def build_make(self, toolchain, wd, install=True): def build_make(self, toolchain: AnyToolchain, wd: str, install: bool=True) -> None:
self.make(toolchain, wd, self.get_make_args(toolchain)) self.make(toolchain, wd, self.get_make_args(toolchain))
if install: if install:
self.make(toolchain, wd, self.get_make_install_args(toolchain)) self.make(toolchain, wd, self.get_make_install_args(toolchain))

@@ -1,10 +1,17 @@
import os import os
import subprocess import subprocess
import platform import platform
from typing import Optional, Sequence, Union
from build.project import Project from build.project import Project
from .toolchain import AnyToolchain
def make_cross_file(toolchain): def __no_ccache(cmd: str) -> str:
if cmd.startswith('ccache '):
cmd = cmd[7:]
return cmd
def make_cross_file(toolchain: AnyToolchain) -> str:
if toolchain.is_windows: if toolchain.is_windows:
system = 'windows' system = 'windows'
windres = "windres = '%s'" % toolchain.windres windres = "windres = '%s'" % toolchain.windres
@@ -23,7 +30,7 @@ def make_cross_file(toolchain):
cpu = 'arm64-v8a' cpu = 'arm64-v8a'
else: else:
cpu_family = 'x86' cpu_family = 'x86'
if 'x86_64' in toolchain.arch: if 'x86_64' in toolchain.host_triplet:
cpu = 'x86_64' cpu = 'x86_64'
else: else:
cpu = 'i686' cpu = 'i686'
@@ -38,8 +45,8 @@ def make_cross_file(toolchain):
with open(path, 'w') as f: with open(path, 'w') as f:
f.write(f""" f.write(f"""
[binaries] [binaries]
c = '{toolchain.cc}' c = '{__no_ccache(toolchain.cc)}'
cpp = '{toolchain.cxx}' cpp = '{__no_ccache(toolchain.cxx)}'
ar = '{toolchain.ar}' ar = '{toolchain.ar}'
strip = '{toolchain.strip}' strip = '{toolchain.strip}'
pkgconfig = '{toolchain.pkg_config}' pkgconfig = '{toolchain.pkg_config}'
@@ -56,7 +63,7 @@ pkgconfig = '{toolchain.pkg_config}'
root = '{toolchain.install_prefix}' root = '{toolchain.install_prefix}'
""") """)
if 'android' in toolchain.arch: if toolchain.is_android:
f.write(""" f.write("""
# Keep Meson from executing Android-x86 test binariees # Keep Meson from executing Android-x86 test binariees
needs_exe_wrapper = true needs_exe_wrapper = true
@@ -80,8 +87,7 @@ endian = '{endian}'
""") """)
return path return path
def configure(toolchain, src, build, args=()): def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[]) -> None:
cross_file = make_cross_file(toolchain)
configure = [ configure = [
'meson', 'setup', 'meson', 'setup',
build, src, build, src,
@@ -91,10 +97,13 @@ def configure(toolchain, src, build, args=()):
'--buildtype', 'plain', '--buildtype', 'plain',
'--default-library=static', '--default-library=static',
'--cross-file', cross_file,
] + args ] + args
if toolchain.host_triplet is not None:
# cross-compiling: write a cross-file
cross_file = make_cross_file(toolchain)
configure.append(f'--cross-file={cross_file}')
env = toolchain.env.copy() env = toolchain.env.copy()
# Meson 0.54 requires the BOOST_ROOT environment variable # Meson 0.54 requires the BOOST_ROOT environment variable
@@ -103,18 +112,19 @@ def configure(toolchain, src, build, args=()):
subprocess.check_call(configure, env=env) subprocess.check_call(configure, env=env)
class MesonProject(Project): class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[], def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
configure_args: list[str]=[],
**kwargs): **kwargs):
Project.__init__(self, url, md5, installed, **kwargs) Project.__init__(self, url, md5, installed, **kwargs)
self.configure_args = configure_args self.configure_args = configure_args
def configure(self, toolchain): def configure(self, toolchain: AnyToolchain) -> str:
src = self.unpack(toolchain) src = self.unpack(toolchain)
build = self.make_build_path(toolchain) build = self.make_build_path(toolchain)
configure(toolchain, src, build, self.configure_args) configure(toolchain, src, build, self.configure_args)
return build return build
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
build = self.configure(toolchain) build = self.configure(toolchain)
subprocess.check_call(['ninja', '-v', 'install'], subprocess.check_call(['ninja', '-v', 'install'],
cwd=build, env=toolchain.env) cwd=build, env=toolchain.env)

@@ -1,13 +1,15 @@
import subprocess import subprocess
from typing import Optional, Sequence, Union
from build.makeproject import MakeProject from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class OpenSSLProject(MakeProject): class OpenSSLProject(MakeProject):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs) MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
def get_make_args(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return MakeProject.get_make_args(self, toolchain) + [ return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc, 'CC=' + toolchain.cc,
'CFLAGS=' + toolchain.cflags, 'CFLAGS=' + toolchain.cflags,
@@ -17,45 +19,60 @@ class OpenSSLProject(MakeProject):
'build_libs', 'build_libs',
] ]
def get_make_install_args(self, toolchain): def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
# OpenSSL's Makefile runs "ranlib" during installation # OpenSSL's Makefile runs "ranlib" during installation
return MakeProject.get_make_install_args(self, toolchain) + [ return MakeProject.get_make_install_args(self, toolchain) + [
'RANLIB=' + toolchain.ranlib, 'RANLIB=' + toolchain.ranlib,
] ]
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
src = self.unpack(toolchain, out_of_tree=False) src = self.unpack(toolchain, out_of_tree=False)
# OpenSSL has a weird target architecture scheme with lots of # OpenSSL has a weird target architecture scheme with lots of
# hard-coded architectures; this table translates between our # hard-coded architectures; this table translates between our
# "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target # host triplet and the OpenSSL target
openssl_archs = { openssl_archs = {
# not using "android-*" because those OpenSSL targets want # not using "android-*" because those OpenSSL targets want
# to know where the SDK is, but our own build scripts # to know where the SDK is, but our own build scripts
# prepared everything already to look like a regular Linux # prepared everything already to look like a regular Linux
# build # build
'arm-linux-androideabi': 'linux-generic32', 'armv7a-linux-androideabi': 'linux-generic32',
'aarch64-linux-android': 'linux-aarch64', 'aarch64-linux-android': 'linux-aarch64',
'i686-linux-android': 'linux-x86-clang', 'i686-linux-android': 'linux-x86-clang',
'x86_64-linux-android': 'linux-x86_64-clang', 'x86_64-linux-android': 'linux-x86_64-clang',
# Kobo # generic Linux
'arm-linux-gnueabihf': 'linux-generic32', 'arm-linux-gnueabihf': 'linux-generic32',
# Windows # Windows
'i686-w64-mingw32': 'mingw', 'i686-w64-mingw32': 'mingw',
'x86_64-w64-mingw32': 'mingw64', 'x86_64-w64-mingw32': 'mingw64',
# Apple
'x86_64-apple-darwin': 'darwin64-x86_64-cc',
'aarch64-apple-darwin': 'darwin64-arm64-cc',
} }
openssl_arch = openssl_archs[toolchain.arch] configure = [
'./Configure',
'no-shared',
'no-module',
'no-engine',
'no-static-engine',
'no-async',
'no-tests',
'no-makedepend',
'--libdir=lib', # no "lib64" on amd64, please
'--prefix=' + toolchain.install_prefix,
]
subprocess.check_call(['./Configure', if toolchain.is_windows:
'no-shared', # workaround for build failures
'no-module', 'no-engine', 'no-static-engine', configure.append('no-asm')
'no-async',
'no-tests', if toolchain.host_triplet is not None:
'no-asm', # "asm" causes build failures on Windows configure.append(openssl_archs[toolchain.host_triplet])
openssl_arch, configure.append(f'--cross-compile-prefix={toolchain.host_triplet}-')
'--prefix=' + toolchain.install_prefix],
cwd=src, env=toolchain.env) subprocess.check_call(configure, cwd=src, env=toolchain.env)
self.build_make(toolchain, src) self.build_make(toolchain, src)

@@ -1,18 +1,21 @@
import os, shutil import os, shutil
import re import re
from typing import cast, BinaryIO, Optional, Sequence, Union
from build.download import download_and_verify from build.download import download_basename, download_and_verify
from build.tar import untar from build.tar import untar
from build.quilt import push_all from build.quilt import push_all
from .toolchain import AnyToolchain
class Project: class Project:
def __init__(self, url, md5, installed, name=None, version=None, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
base=None, name: Optional[str]=None, version: Optional[str]=None,
patches=None, base: Optional[str]=None,
patches: Optional[str]=None,
edits=None, edits=None,
use_cxx=False): use_cxx: bool=False):
if base is None: if base is None:
basename = os.path.basename(url) basename = download_basename(url)
m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename) m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
if not m: raise RuntimeError('Could not identify tarball name: ' + basename) if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
self.base = m.group(1) self.base = m.group(1)
@@ -39,10 +42,10 @@ class Project:
self.edits = edits self.edits = edits
self.use_cxx = use_cxx self.use_cxx = use_cxx
def download(self, toolchain): def download(self, toolchain: AnyToolchain) -> str:
return download_and_verify(self.url, self.md5, toolchain.tarball_path) return download_and_verify(self.url, self.md5, toolchain.tarball_path)
def is_installed(self, toolchain): def is_installed(self, toolchain: AnyToolchain) -> bool:
tarball = self.download(toolchain) tarball = self.download(toolchain)
installed = os.path.join(toolchain.install_prefix, self.installed) installed = os.path.join(toolchain.install_prefix, self.installed)
tarball_mtime = os.path.getmtime(tarball) tarball_mtime = os.path.getmtime(tarball)
@@ -51,7 +54,7 @@ class Project:
except FileNotFoundError: except FileNotFoundError:
return False return False
def unpack(self, toolchain, out_of_tree=True): def unpack(self, toolchain: AnyToolchain, out_of_tree: bool=True) -> str:
if out_of_tree: if out_of_tree:
parent_path = toolchain.src_path parent_path = toolchain.src_path
else: else:
@@ -72,7 +75,7 @@ class Project:
return path return path
def make_build_path(self, toolchain, lazy=False): def make_build_path(self, toolchain: AnyToolchain, lazy: bool=False) -> str:
path = os.path.join(toolchain.build_path, self.base) path = os.path.join(toolchain.build_path, self.base)
if lazy and os.path.isdir(path): if lazy and os.path.isdir(path):
return path return path
@@ -83,5 +86,5 @@ class Project:
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
return path return path
def build(self, toolchain): def build(self, toolchain: AnyToolchain) -> None:
self._build(toolchain) self._build(toolchain)

@@ -1,9 +1,12 @@
import subprocess import subprocess
from typing import Union
def run_quilt(toolchain, cwd, patches_path, *args): from .toolchain import AnyToolchain
def run_quilt(toolchain: AnyToolchain, cwd: str, patches_path: str, *args: str) -> None:
env = dict(toolchain.env) env = dict(toolchain.env)
env['QUILT_PATCHES'] = patches_path env['QUILT_PATCHES'] = patches_path
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env) subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
def push_all(toolchain, src_path, patches_path): def push_all(toolchain: AnyToolchain, src_path: str, patches_path: str) -> None:
run_quilt(toolchain, src_path, patches_path, 'push', '-a') run_quilt(toolchain, src_path, patches_path, 'push', '-a')

@@ -1,6 +1,7 @@
import os, shutil, subprocess import os, shutil, subprocess
def untar(tarball_path, parent_path, base, lazy=False): def untar(tarball_path: str, parent_path: str, base: str,
lazy: bool=False) -> str:
path = os.path.join(parent_path, base) path = os.path.join(parent_path, base)
if lazy and os.path.isdir(path): if lazy and os.path.isdir(path):
return path return path

175
python/build/toolchain.py Normal file

@@ -0,0 +1,175 @@
import os.path
import shutil
from typing import Union
android_abis = {
'armeabi-v7a': {
'arch': 'armv7a-linux-androideabi',
'ndk_arch': 'arm',
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
},
'arm64-v8a': {
'arch': 'aarch64-linux-android',
'ndk_arch': 'arm64',
'cflags': '-fpic',
},
'x86': {
'arch': 'i686-linux-android',
'ndk_arch': 'x86',
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
},
'x86_64': {
'arch': 'x86_64-linux-android',
'ndk_arch': 'x86_64',
'cflags': '-fPIC -m64',
},
}
class AndroidNdkToolchain:
def __init__(self, top_path: str, lib_path: str,
tarball_path: str, src_path: str,
ndk_path: str, android_abi: str,
use_cxx):
# build host configuration
build_arch = 'linux-x86_64'
# select the NDK target
abi_info = android_abis[android_abi]
host_triplet = abi_info['arch']
arch_path = os.path.join(lib_path, host_triplet)
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = os.path.join(arch_path, 'build')
ndk_arch = abi_info['ndk_arch']
android_api_level = '24'
install_prefix = os.path.join(arch_path, 'root')
self.host_triplet = host_triplet
self.install_prefix = install_prefix
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
llvm_triple = host_triplet + android_api_level
common_flags = '-Os -g'
common_flags += ' ' + abi_info['cflags']
llvm_bin = os.path.join(llvm_path, 'bin')
self.cc = os.path.join(llvm_bin, 'clang')
self.cxx = os.path.join(llvm_bin, 'clang++')
common_flags += ' -target ' + llvm_triple
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
self.ar = os.path.join(llvm_bin, 'llvm-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
self.nm = os.path.join(llvm_bin, 'llvm-nm')
self.strip = os.path.join(llvm_bin, 'llvm-strip')
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
' -Wl,--exclude-libs=ALL' + \
' ' + common_flags
self.ldflags = common_flags
self.libs = ''
self.is_arm = ndk_arch == 'arm'
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = ndk_arch == 'arm64'
self.is_windows = False
self.is_android = True
self.is_darwin = False
libstdcxx_flags = ''
libstdcxx_cxxflags = ''
libstdcxx_ldflags = ''
libstdcxx_libs = '-static-libstdc++'
if self.is_armv7:
# On 32 bit ARM, clang generates no ".eh_frame" section;
# instead, the LLVM unwinder library is used for unwinding
# the stack after a C++ exception was thrown
libstdcxx_libs += ' -lunwind'
if use_cxx:
self.cxxflags += ' ' + libstdcxx_cxxflags
self.ldflags += ' ' + libstdcxx_ldflags
self.libs += ' ' + libstdcxx_libs
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
class MingwToolchain:
def __init__(self, top_path: str,
toolchain_path, host_triplet, x64: bool,
tarball_path, src_path, build_path, install_prefix):
self.host_triplet = host_triplet
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = build_path
self.install_prefix = install_prefix
toolchain_bin = os.path.join(toolchain_path, 'bin')
self.cc = os.path.join(toolchain_bin, host_triplet + '-gcc')
self.cxx = os.path.join(toolchain_bin, host_triplet + '-g++')
self.ar = os.path.join(toolchain_bin, host_triplet + '-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(toolchain_bin, host_triplet + '-ranlib')
self.nm = os.path.join(toolchain_bin, host_triplet + '-nm')
self.strip = os.path.join(toolchain_bin, host_triplet + '-strip')
self.windres = os.path.join(toolchain_bin, host_triplet + '-windres')
common_flags = '-O2 -g'
if not x64:
# enable SSE support which is required for LAME
common_flags += ' -march=pentium3'
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
' -static-libstdc++ -static-libgcc'
self.libs = ''
# Explicitly disable _FORTIFY_SOURCE because it is broken with
# mingw. This prevents some libraries such as libFLAC to
# enable it.
self.cppflags += ' -D_FORTIFY_SOURCE=0'
self.is_arm = host_triplet.startswith('arm')
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = host_triplet == 'aarch64'
self.is_windows = 'mingw32' in host_triplet
self.is_android = False
self.is_darwin = False
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
import shutil
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
AnyToolchain = Union[AndroidNdkToolchain, MingwToolchain]

@@ -1,6 +1,7 @@
import hashlib import hashlib
from typing import cast, Any, BinaryIO
def feed_file(h, f): def feed_file(h: Any, f: BinaryIO) -> None:
"""Feed data read from an open file into the hashlib instance.""" """Feed data read from an open file into the hashlib instance."""
while True: while True:
@@ -10,20 +11,20 @@ def feed_file(h, f):
break break
h.update(data) h.update(data)
def feed_file_path(h, path): def feed_file_path(h: Any, path: str) -> None:
"""Feed data read from a file (to be opened by this function) into the hashlib instance.""" """Feed data read from a file (to be opened by this function) into the hashlib instance."""
with open(path, 'rb') as f: with open(path, 'rb') as f:
feed_file(h, f) feed_file(h, f)
def file_digest(algorithm, path): def file_digest(algorithm: Any, path: str) -> str:
"""Calculate the digest of a file and return it in hexadecimal notation.""" """Calculate the digest of a file and return it in hexadecimal notation."""
h = algorithm() h = algorithm()
feed_file_path(h, path) feed_file_path(h, path)
return h.hexdigest() return cast(str, h.hexdigest())
def guess_digest_algorithm(digest): def guess_digest_algorithm(digest: str) -> Any:
l = len(digest) l = len(digest)
if l == 32: if l == 32:
return hashlib.md5 return hashlib.md5
@@ -36,7 +37,7 @@ def guess_digest_algorithm(digest):
else: else:
return None return None
def verify_file_digest(path, expected_digest): def verify_file_digest(path: str, expected_digest: str) -> bool:
"""Verify the digest of a file, and return True if the digest matches with the given expected digest.""" """Verify the digest of a file, and return True if the digest matches with the given expected digest."""
algorithm = guess_digest_algorithm(expected_digest) algorithm = guess_digest_algorithm(expected_digest)

@@ -1,13 +1,15 @@
import subprocess import subprocess
from typing import Optional, Sequence, Union
from build.makeproject import MakeProject from build.makeproject import MakeProject
from .toolchain import AnyToolchain
class ZlibProject(MakeProject): class ZlibProject(MakeProject):
def __init__(self, url, md5, installed, def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
**kwargs): **kwargs):
MakeProject.__init__(self, url, md5, installed, **kwargs) MakeProject.__init__(self, url, md5, installed, **kwargs)
def get_make_args(self, toolchain): def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
return MakeProject.get_make_args(self, toolchain) + [ return MakeProject.get_make_args(self, toolchain) + [
'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags, 'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags, 'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
@@ -18,13 +20,13 @@ class ZlibProject(MakeProject):
'libz.a' 'libz.a'
] ]
def get_make_install_args(self, toolchain): def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
return [ return [
'RANLIB=' + toolchain.ranlib, 'RANLIB=' + toolchain.ranlib,
self.install_target self.install_target
] ]
def _build(self, toolchain): def _build(self, toolchain: AnyToolchain) -> None:
src = self.unpack(toolchain, out_of_tree=False) src = self.unpack(toolchain, out_of_tree=False)
subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'], subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],

@@ -23,6 +23,8 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <iterator> // for std::back_inserter()
static constexpr Domain exception_domain("exception"); static constexpr Domain exception_domain("exception");
void void

@@ -153,7 +153,7 @@ update_directory_stat(Storage &storage, Directory &directory) noexcept
*/ */
static int static int
FindAncestorLoop(Storage &storage, Directory *parent, FindAncestorLoop(Storage &storage, Directory *parent,
unsigned inode, unsigned device) noexcept uint64_t inode, uint64_t device) noexcept
{ {
#ifndef _WIN32 #ifndef _WIN32
if (device == 0 && inode == 0) if (device == 0 && inode == 0)

@@ -26,6 +26,9 @@
extern "C" { extern "C" {
#include <libavutil/mem.h> #include <libavutil/mem.h>
#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 29, 100)
#include <libavutil/error.h>
#endif
} }
AvioStream::~AvioStream() AvioStream::~AvioStream()

@@ -30,7 +30,7 @@
bool bool
FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample, FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels, FLAC__uint64 total_frames) unsigned channels, FLAC__uint64 total_frames) noexcept
{ {
assert(!initialized); assert(!initialized);
assert(!unsupported); assert(!unsupported);
@@ -60,7 +60,7 @@ FlacDecoder::Initialize(unsigned sample_rate, unsigned bits_per_sample,
} }
inline void inline void
FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept
{ {
if (initialized) if (initialized)
return; return;
@@ -72,7 +72,7 @@ FlacDecoder::OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info)
} }
inline void inline void
FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept
{ {
ReplayGainInfo rgi; ReplayGainInfo rgi;
if (flac_parse_replay_gain(rgi, vc)) if (flac_parse_replay_gain(rgi, vc))
@@ -86,7 +86,7 @@ FlacDecoder::OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc)
} }
void void
FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata) FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata) noexcept
{ {
if (unsupported) if (unsupported)
return; return;
@@ -106,7 +106,7 @@ FlacDecoder::OnMetadata(const FLAC__StreamMetadata &metadata)
} }
inline bool inline bool
FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header) FlacDecoder::OnFirstFrame(const FLAC__FrameHeader &header) noexcept
{ {
if (unsupported) if (unsupported)
return false; return false;
@@ -139,7 +139,7 @@ FlacDecoder::GetDeltaPosition(const FLAC__StreamDecoder &sd)
FLAC__StreamDecoderWriteStatus FLAC__StreamDecoderWriteStatus
FlacDecoder::OnWrite(const FLAC__Frame &frame, FlacDecoder::OnWrite(const FLAC__Frame &frame,
const FLAC__int32 *const buf[], const FLAC__int32 *const buf[],
FLAC__uint64 nbytes) FLAC__uint64 nbytes) noexcept
{ {
if (!initialized && !OnFirstFrame(frame.header)) if (!initialized && !OnFirstFrame(frame.header))
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;

@@ -65,20 +65,21 @@ struct FlacDecoder : public FlacInput {
*/ */
ConstBuffer<void> chunk = nullptr; ConstBuffer<void> chunk = nullptr;
FlacDecoder(DecoderClient &_client, InputStream &_input_stream) FlacDecoder(DecoderClient &_client,
InputStream &_input_stream) noexcept
:FlacInput(_input_stream, &_client) {} :FlacInput(_input_stream, &_client) {}
/** /**
* Wrapper for DecoderClient::Ready(). * Wrapper for DecoderClient::Ready().
*/ */
bool Initialize(unsigned sample_rate, unsigned bits_per_sample, bool Initialize(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels, FLAC__uint64 total_frames); unsigned channels, FLAC__uint64 total_frames) noexcept;
void OnMetadata(const FLAC__StreamMetadata &metadata); void OnMetadata(const FLAC__StreamMetadata &metadata) noexcept;
FLAC__StreamDecoderWriteStatus OnWrite(const FLAC__Frame &frame, FLAC__StreamDecoderWriteStatus OnWrite(const FLAC__Frame &frame,
const FLAC__int32 *const buf[], const FLAC__int32 *const buf[],
FLAC__uint64 nbytes); FLAC__uint64 nbytes) noexcept;
/** /**
* Calculate the delta (in bytes) between the last frame and * Calculate the delta (in bytes) between the last frame and
@@ -87,8 +88,8 @@ struct FlacDecoder : public FlacInput {
FLAC__uint64 GetDeltaPosition(const FLAC__StreamDecoder &sd); FLAC__uint64 GetDeltaPosition(const FLAC__StreamDecoder &sd);
private: private:
void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info); void OnStreamInfo(const FLAC__StreamMetadata_StreamInfo &stream_info) noexcept;
void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc); void OnVorbisComment(const FLAC__StreamMetadata_VorbisComment &vc) noexcept;
/** /**
* This function attempts to call DecoderClient::Ready() in case there * This function attempts to call DecoderClient::Ready() in case there
@@ -97,7 +98,7 @@ private:
* providing the STREAMINFO block from the beginning of the file * providing the STREAMINFO block from the beginning of the file
* (e.g. when seeking with SqueezeBox Server). * (e.g. when seeking with SqueezeBox Server).
*/ */
bool OnFirstFrame(const FLAC__FrameHeader &header); bool OnFirstFrame(const FLAC__FrameHeader &header) noexcept;
}; };
#endif /* _FLAC_COMMON_H */ #endif /* _FLAC_COMMON_H */

@@ -24,6 +24,7 @@
#include "lib/xiph/FlacMetadataChain.hxx" #include "lib/xiph/FlacMetadataChain.hxx"
#include "OggCodec.hxx" #include "OggCodec.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "input/LocalOpen.hxx"
#include "fs/Path.hxx" #include "fs/Path.hxx"
#include "fs/NarrowPath.hxx" #include "fs/NarrowPath.hxx"
#include "Log.hxx" #include "Log.hxx"
@@ -32,7 +33,8 @@
#error libFLAC is too old #error libFLAC is too old
#endif #endif
static void flacPrintErroredState(FLAC__StreamDecoderState state) static void
flacPrintErroredState(FLAC__StreamDecoderState state) noexcept
{ {
switch (state) { switch (state) {
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
@@ -53,8 +55,9 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
LogError(flac_domain, FLAC__StreamDecoderStateString[state]); LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
} }
static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec, static void
const FLAC__StreamMetadata * block, void *vdata) flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
const FLAC__StreamMetadata * block, void *vdata) noexcept
{ {
auto &fd = *(FlacDecoder *)vdata; auto &fd = *(FlacDecoder *)vdata;
fd.OnMetadata(*block); fd.OnMetadata(*block);
@@ -62,29 +65,45 @@ static void flacMetadata([[maybe_unused]] const FLAC__StreamDecoder * dec,
static FLAC__StreamDecoderWriteStatus static FLAC__StreamDecoderWriteStatus
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const buf[], void *vdata) const FLAC__int32 *const buf[], void *vdata) noexcept
{ {
auto &fd = *(FlacDecoder *)vdata; auto &fd = *(FlacDecoder *)vdata;
return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec)); return fd.OnWrite(*frame, buf, fd.GetDeltaPosition(*dec));
} }
static bool static bool
flac_scan_file(Path path_fs, TagHandler &handler) flac_scan_file(Path path_fs, TagHandler &handler) noexcept {
{
FlacMetadataChain chain; FlacMetadataChain chain;
if (!chain.Read(NarrowPath(path_fs))) { const bool succeed = [&chain, &path_fs]() noexcept {
// read by NarrowPath
if (chain.Read(NarrowPath(path_fs))) {
return true;
}
if (std::is_same_v<Path::value_type, char> ||
chain.GetStatus() != FLAC__METADATA_CHAIN_STATUS_ERROR_OPENING_FILE) {
return false;
}
// read by InputStream
Mutex mutex;
auto is = OpenLocalInputStream(path_fs, mutex);
if (is && chain.Read(*is)) {
return true;
}
return false;
}();
if (!succeed) {
FmtDebug(flac_domain, FmtDebug(flac_domain,
"Failed to read FLAC tags: {}", "Failed to read FLAC tags: {}",
chain.GetStatusString()); chain.GetStatusString());
return false; return false;
} }
chain.Scan(handler); chain.Scan(handler);
return true; return true;
} }
static bool static bool
flac_scan_stream(InputStream &is, TagHandler &handler) flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
{ {
FlacMetadataChain chain; FlacMetadataChain chain;
if (!chain.Read(is)) { if (!chain.Read(is)) {
@@ -102,7 +121,7 @@ flac_scan_stream(InputStream &is, TagHandler &handler)
* Some glue code around FLAC__stream_decoder_new(). * Some glue code around FLAC__stream_decoder_new().
*/ */
static FlacStreamDecoder static FlacStreamDecoder
flac_decoder_new() flac_decoder_new() noexcept
{ {
FlacStreamDecoder sd; FlacStreamDecoder sd;
if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT)) if(!FLAC__stream_decoder_set_metadata_respond(sd.get(), FLAC__METADATA_TYPE_VORBIS_COMMENT))
@@ -113,7 +132,7 @@ flac_decoder_new()
} }
static bool static bool
flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) flac_decoder_initialize(FlacDecoder *data, FLAC__StreamDecoder *sd) noexcept
{ {
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM) if (FLAC__stream_decoder_get_state(sd) != FLAC__STREAM_DECODER_END_OF_STREAM)
@@ -231,7 +250,7 @@ flac_decoder_loop(FlacDecoder *data, FLAC__StreamDecoder *flac_dec)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
{ {
return FLAC__stream_decoder_init_ogg_stream(flac_dec, return FLAC__stream_decoder_init_ogg_stream(flac_dec,
FlacInput::Read, FlacInput::Read,
@@ -246,7 +265,7 @@ stream_init_oggflac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data) noexcept
{ {
return FLAC__stream_decoder_init_stream(flac_dec, return FLAC__stream_decoder_init_stream(flac_dec,
FlacInput::Read, FlacInput::Read,
@@ -261,7 +280,8 @@ stream_init_flac(FLAC__StreamDecoder *flac_dec, FlacDecoder *data)
} }
static FLAC__StreamDecoderInitStatus static FLAC__StreamDecoderInitStatus
stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data, bool is_ogg) stream_init(FLAC__StreamDecoder *flac_dec, FlacDecoder *data,
bool is_ogg) noexcept
{ {
return is_ogg return is_ogg
? stream_init_oggflac(flac_dec, data) ? stream_init_oggflac(flac_dec, data)
@@ -307,7 +327,7 @@ flac_decode(DecoderClient &client, InputStream &input_stream)
} }
static bool static bool
oggflac_init([[maybe_unused]] const ConfigBlock &block) oggflac_init([[maybe_unused]] const ConfigBlock &block) noexcept
{ {
return !!FLAC_API_SUPPORTS_OGG_FLAC; return !!FLAC_API_SUPPORTS_OGG_FLAC;
} }

@@ -22,12 +22,11 @@
#include "../DecoderAPI.hxx" #include "../DecoderAPI.hxx"
#include "input/InputStream.hxx" #include "input/InputStream.hxx"
#include "Log.hxx" #include "Log.hxx"
#include "util/Compiler.h"
#include <exception> #include <exception>
FLAC__StreamDecoderReadStatus inline FLAC__StreamDecoderReadStatus
FlacInput::Read(FLAC__byte buffer[], size_t *bytes) FlacInput::Read(FLAC__byte buffer[], size_t *bytes) noexcept
{ {
size_t r = decoder_read(client, input_stream, (void *)buffer, *bytes); size_t r = decoder_read(client, input_stream, (void *)buffer, *bytes);
*bytes = r; *bytes = r;
@@ -44,8 +43,8 @@ FlacInput::Read(FLAC__byte buffer[], size_t *bytes)
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
} }
FLAC__StreamDecoderSeekStatus inline FLAC__StreamDecoderSeekStatus
FlacInput::Seek(FLAC__uint64 absolute_byte_offset) FlacInput::Seek(FLAC__uint64 absolute_byte_offset) noexcept
{ {
if (!input_stream.IsSeekable()) if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
@@ -59,8 +58,8 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
} }
} }
FLAC__StreamDecoderTellStatus inline FLAC__StreamDecoderTellStatus
FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) FlacInput::Tell(FLAC__uint64 *absolute_byte_offset) noexcept
{ {
if (!input_stream.IsSeekable()) if (!input_stream.IsSeekable())
return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
@@ -69,8 +68,8 @@ FlacInput::Tell(FLAC__uint64 *absolute_byte_offset)
return FLAC__STREAM_DECODER_TELL_STATUS_OK; return FLAC__STREAM_DECODER_TELL_STATUS_OK;
} }
FLAC__StreamDecoderLengthStatus inline FLAC__StreamDecoderLengthStatus
FlacInput::Length(FLAC__uint64 *stream_length) FlacInput::Length(FLAC__uint64 *stream_length) noexcept
{ {
if (!input_stream.KnownSize()) if (!input_stream.KnownSize())
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
@@ -79,8 +78,8 @@ FlacInput::Length(FLAC__uint64 *stream_length)
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
} }
FLAC__bool inline FLAC__bool
FlacInput::Eof() FlacInput::Eof() noexcept
{ {
return (client != nullptr && return (client != nullptr &&
client->GetCommand() != DecoderCommand::NONE && client->GetCommand() != DecoderCommand::NONE &&
@@ -88,8 +87,8 @@ FlacInput::Eof()
input_stream.LockIsEOF(); input_stream.LockIsEOF();
} }
void inline void
FlacInput::Error(FLAC__StreamDecoderErrorStatus status) FlacInput::Error(FLAC__StreamDecoderErrorStatus status) noexcept
{ {
if (client == nullptr || if (client == nullptr ||
client->GetCommand() != DecoderCommand::STOP) client->GetCommand() != DecoderCommand::STOP)
@@ -100,7 +99,7 @@ FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
FLAC__StreamDecoderReadStatus FLAC__StreamDecoderReadStatus
FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__byte buffer[], size_t *bytes, FLAC__byte buffer[], size_t *bytes,
void *client_data) void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -109,7 +108,7 @@ FlacInput::Read([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderSeekStatus FLAC__StreamDecoderSeekStatus
FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 absolute_byte_offset, void *client_data) FLAC__uint64 absolute_byte_offset, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -118,7 +117,7 @@ FlacInput::Seek([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderTellStatus FLAC__StreamDecoderTellStatus
FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *absolute_byte_offset, void *client_data) FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -127,7 +126,7 @@ FlacInput::Tell([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__StreamDecoderLengthStatus FLAC__StreamDecoderLengthStatus
FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *stream_length, void *client_data) FLAC__uint64 *stream_length, void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -136,7 +135,7 @@ FlacInput::Length([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
FLAC__bool FLAC__bool
FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder, FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
void *client_data) void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;
@@ -145,7 +144,8 @@ FlacInput::Eof([[maybe_unused]] const FLAC__StreamDecoder *flac_decoder,
void void
FlacInput::Error([[maybe_unused]] const FLAC__StreamDecoder *decoder, FlacInput::Error([[maybe_unused]] const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data) FLAC__StreamDecoderErrorStatus status,
void *client_data) noexcept
{ {
auto *i = (FlacInput *)client_data; auto *i = (FlacInput *)client_data;

@@ -48,36 +48,38 @@ public:
} }
protected: protected:
FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes); FLAC__StreamDecoderReadStatus Read(FLAC__byte buffer[], size_t *bytes) noexcept;
FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset); FLAC__StreamDecoderSeekStatus Seek(FLAC__uint64 absolute_byte_offset) noexcept;
FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset); FLAC__StreamDecoderTellStatus Tell(FLAC__uint64 *absolute_byte_offset) noexcept;
FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length); FLAC__StreamDecoderLengthStatus Length(FLAC__uint64 *stream_length) noexcept;
FLAC__bool Eof(); FLAC__bool Eof() noexcept;
void Error(FLAC__StreamDecoderErrorStatus status); void Error(FLAC__StreamDecoderErrorStatus status) noexcept;
public: public:
static FLAC__StreamDecoderReadStatus static FLAC__StreamDecoderReadStatus
Read(const FLAC__StreamDecoder *flac_decoder, Read(const FLAC__StreamDecoder *flac_decoder,
FLAC__byte buffer[], size_t *bytes, void *client_data); FLAC__byte buffer[], size_t *bytes, void *client_data) noexcept;
static FLAC__StreamDecoderSeekStatus static FLAC__StreamDecoderSeekStatus
Seek(const FLAC__StreamDecoder *flac_decoder, Seek(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 absolute_byte_offset, void *client_data); FLAC__uint64 absolute_byte_offset, void *client_data) noexcept;
static FLAC__StreamDecoderTellStatus static FLAC__StreamDecoderTellStatus
Tell(const FLAC__StreamDecoder *flac_decoder, Tell(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *absolute_byte_offset, void *client_data); FLAC__uint64 *absolute_byte_offset, void *client_data) noexcept;
static FLAC__StreamDecoderLengthStatus static FLAC__StreamDecoderLengthStatus
Length(const FLAC__StreamDecoder *flac_decoder, Length(const FLAC__StreamDecoder *flac_decoder,
FLAC__uint64 *stream_length, void *client_data); FLAC__uint64 *stream_length, void *client_data) noexcept;
static FLAC__bool static FLAC__bool
Eof(const FLAC__StreamDecoder *flac_decoder, void *client_data); Eof(const FLAC__StreamDecoder *flac_decoder,
void *client_data) noexcept;
static void static void
Error(const FLAC__StreamDecoder *decoder, Error(const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data); FLAC__StreamDecoderErrorStatus status,
void *client_data) noexcept;
}; };
#endif #endif

@@ -39,7 +39,8 @@ FlacPcmImport::Open(unsigned sample_rate, unsigned bits_per_sample,
template<typename T> template<typename T>
static void static void
FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames) FlacImportStereo(T *dest, const FLAC__int32 *const src[],
size_t n_frames) noexcept
{ {
for (size_t i = 0; i != n_frames; ++i) { for (size_t i = 0; i != n_frames; ++i) {
*dest++ = (T)src[0][i]; *dest++ = (T)src[0][i];
@@ -50,7 +51,7 @@ FlacImportStereo(T *dest, const FLAC__int32 *const src[], size_t n_frames)
template<typename T> template<typename T>
static void static void
FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames, FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
for (size_t i = 0; i != n_frames; ++i) for (size_t i = 0; i != n_frames; ++i)
for (unsigned c = 0; c != n_channels; ++c) for (unsigned c = 0; c != n_channels; ++c)
@@ -60,7 +61,7 @@ FlacImportAny(T *dest, const FLAC__int32 *const src[], size_t n_frames,
template<typename T> template<typename T>
static void static void
FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames, FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
if (n_channels == 2) if (n_channels == 2)
FlacImportStereo(dest, src, n_frames); FlacImportStereo(dest, src, n_frames);
@@ -71,7 +72,7 @@ FlacImport(T *dest, const FLAC__int32 *const src[], size_t n_frames,
template<typename T> template<typename T>
static ConstBuffer<void> static ConstBuffer<void>
FlacImport(PcmBuffer &buffer, const FLAC__int32 *const src[], size_t n_frames, FlacImport(PcmBuffer &buffer, const FLAC__int32 *const src[], size_t n_frames,
unsigned n_channels) unsigned n_channels) noexcept
{ {
size_t n_samples = n_frames * n_channels; size_t n_samples = n_frames * n_channels;
size_t dest_size = n_samples * sizeof(T); size_t dest_size = n_samples * sizeof(T);

@@ -43,7 +43,7 @@ public:
void Open(unsigned sample_rate, unsigned bits_per_sample, void Open(unsigned sample_rate, unsigned bits_per_sample,
unsigned channels); unsigned channels);
const AudioFormat &GetAudioFormat() const { const AudioFormat &GetAudioFormat() const noexcept {
return audio_format; return audio_format;
} }

@@ -562,7 +562,21 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) noexcept
mad_bit_skip(ptr, 16); mad_bit_skip(ptr, 16);
lame->peak = MAD_F(mad_bit_read(ptr, 32) << 5); /* peak */ /* The lame peak value is a float multiplied by 2^23 and stored as an
* unsigned integer (it is always positive). MAD's fixed-point format uses
* 28 bits for the fractional part, so shift the 23 bit fraction up before
* converting to a float.
*/
unsigned long peak_int = mad_bit_read(ptr, 32);
#define LAME_PEAK_FRACBITS 23
#if MAD_F_FRACBITS > LAME_PEAK_FRACBITS
peak_int <<= (MAD_F_FRACBITS - LAME_PEAK_FRACBITS);
#elif LAME_PEAK_FRACBITS > MAD_F_FRACBITS
peak_int >>= (LAME_PEAK_FRACBITS - MAD_F_FRACBITS);
#endif
lame->peak = mad_f_todouble(peak_int); /* peak */
FmtDebug(mad_domain, "LAME peak found: {}", lame->peak); FmtDebug(mad_domain, "LAME peak found: {}", lame->peak);
lame->track_gain = 0; lame->track_gain = 0;

@@ -66,6 +66,9 @@ FfmpegFilter::FilterPCM(ConstBuffer<void> src)
#endif #endif
frame->nb_samples = src.size / in_audio_frame_size; frame->nb_samples = src.size / in_audio_frame_size;
frame->pts = pts;
pts += frame->nb_samples;
frame.GetBuffer(); frame.GetBuffer();
memcpy(frame.GetData(0), src.data, src.size); memcpy(frame.GetData(0), src.data, src.size);

@@ -17,14 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#ifndef MPD_FFMPEG_FILTER__HXX #pragma once
#define MPD_FFMPEG_FILTER__HXX
#include "filter/Filter.hxx" #include "filter/Filter.hxx"
#include "lib/ffmpeg/Buffer.hxx" #include "lib/ffmpeg/Buffer.hxx"
#include "lib/ffmpeg/Filter.hxx" #include "lib/ffmpeg/Filter.hxx"
#include "lib/ffmpeg/Frame.hxx" #include "lib/ffmpeg/Frame.hxx"
#include <cstdint>
/** /**
* A #Filter implementation using FFmpeg's libavfilter. * A #Filter implementation using FFmpeg's libavfilter.
*/ */
@@ -46,6 +47,11 @@ class FfmpegFilter final : public Filter {
const size_t in_audio_frame_size; const size_t in_audio_frame_size;
const size_t out_audio_frame_size; const size_t out_audio_frame_size;
/**
* Presentation timestamp. A counter for `AVFrame::pts`.
*/
int_least64_t pts = 0;
public: public:
/** /**
* @param _graph a checked and configured AVFilterGraph * @param _graph a checked and configured AVFilterGraph
@@ -63,5 +69,3 @@ public:
/* virtual methods from class Filter */ /* virtual methods from class Filter */
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override; ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
}; };
#endif

@@ -234,7 +234,7 @@ AlsaInputStream::PrepareSockets() noexcept
void void
AlsaInputStream::DispatchSockets() noexcept AlsaInputStream::DispatchSockets() noexcept
{ try {
non_block.DispatchSockets(*this, capture_handle); non_block.DispatchSockets(*this, capture_handle);
const std::scoped_lock<Mutex> protect(mutex); const std::scoped_lock<Mutex> protect(mutex);
@@ -253,16 +253,17 @@ AlsaInputStream::DispatchSockets() noexcept
if (n_frames == -EAGAIN) if (n_frames == -EAGAIN)
return; return;
if (Recover(n_frames) < 0) { if (Recover(n_frames) < 0)
postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted")); throw std::runtime_error("PCM error - stream aborted");
InvokeOnAvailable();
return;
}
} }
size_t nbytes = n_frames * frame_size; size_t nbytes = n_frames * frame_size;
CommitWriteBuffer(nbytes); CommitWriteBuffer(nbytes);
} }
catch (...) {
postponed_exception = std::current_exception();
InvokeOnAvailable();
}
inline int inline int
AlsaInputStream::Recover(int err) AlsaInputStream::Recover(int err)
@@ -369,9 +370,14 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
period_size_min, period_size_max, period_size_min, period_size_max,
period_time_min, period_time_max); period_time_min, period_time_max);
/* choose the maximum possible buffer_size ... */ /* choose the maximum buffer_time up to limit of 2 seconds ... */
snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params, unsigned buffer_time = buffer_time_max;
buffer_size_max); if (buffer_time > 2000000U)
buffer_time = 2000000U;
int direction = -1;
if ((err = snd_pcm_hw_params_set_buffer_time_near(capture_handle,
hw_params, &buffer_time, &direction)) < 0)
throw Alsa::MakeError(err, "Cannot set buffer time");
/* ... and calculate the period_size to have four periods in /* ... and calculate the period_size to have four periods in
one buffer; this way, we get woken up often enough to avoid one buffer; this way, we get woken up often enough to avoid
@@ -379,7 +385,7 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
snd_pcm_uframes_t buffer_size; snd_pcm_uframes_t buffer_size;
if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) { if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
snd_pcm_uframes_t period_size = buffer_size / 4; snd_pcm_uframes_t period_size = buffer_size / 4;
int direction = -1; direction = -1;
if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
hw_params, &period_size, &direction)) < 0) hw_params, &period_size, &direction)) < 0)
throw Alsa::MakeError(err, "Cannot set period size"); throw Alsa::MakeError(err, "Cannot set period size");

@@ -1,13 +1,13 @@
Index: curl-7.84.0/CMakeLists.txt Index: curl-7.85.0/CMakeLists.txt
=================================================================== ===================================================================
--- curl-7.84.0.orig/CMakeLists.txt --- curl-7.85.0.orig/CMakeLists.txt
+++ curl-7.84.0/CMakeLists.txt +++ curl-7.85.0/CMakeLists.txt
@@ -1536,7 +1536,7 @@ set(includedir "\${prefix}/ @@ -1655,7 +1655,7 @@
set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}") set(LDFLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
set(LIBCURL_LIBS "") set(LIBCURL_LIBS "")
set(libdir "${CMAKE_INSTALL_PREFIX}/lib") set(libdir "${CMAKE_INSTALL_PREFIX}/lib")
-foreach(_lib ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${CURL_LIBS}) - foreach(_lib ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${CURL_LIBS})
+foreach(_lib ${CURL_LIBS}) + foreach(_lib ${CURL_LIBS})
if(TARGET "${_lib}") if(TARGET "${_lib}")
set(_libname "${_lib}") set(_libname "${_lib}")
get_target_property(_imported "${_libname}" IMPORTED) get_target_property(_imported "${_libname}" IMPORTED)

@@ -29,7 +29,7 @@ template<>
struct fmt::formatter<AVSampleFormat> : formatter<string_view> struct fmt::formatter<AVSampleFormat> : formatter<string_view>
{ {
template<typename FormatContext> template<typename FormatContext>
auto format(const AVSampleFormat format, FormatContext &ctx) { auto format(const AVSampleFormat format, FormatContext &ctx) const {
const char *name = av_get_sample_fmt_name(format); const char *name = av_get_sample_fmt_name(format);
if (name == nullptr) if (name == nullptr)
name = "?"; name = "?";

@@ -39,7 +39,7 @@ template<>
struct fmt::formatter<SampleFormat> : formatter<string_view> struct fmt::formatter<SampleFormat> : formatter<string_view>
{ {
template<typename FormatContext> template<typename FormatContext>
auto format(const SampleFormat format, FormatContext &ctx) { auto format(const SampleFormat format, FormatContext &ctx) const {
return formatter<string_view>::format(sample_format_to_string(format), return formatter<string_view>::format(sample_format_to_string(format),
ctx); ctx);
} }
@@ -49,7 +49,7 @@ template<>
struct fmt::formatter<AudioFormat> : formatter<string_view> struct fmt::formatter<AudioFormat> : formatter<string_view>
{ {
template<typename FormatContext> template<typename FormatContext>
auto format(const AudioFormat &af, FormatContext &ctx) { auto format(const AudioFormat &af, FormatContext &ctx) const {
return formatter<string_view>::format(ToString(af).c_str(), return formatter<string_view>::format(ToString(af).c_str(),
ctx); ctx);
} }

@@ -38,7 +38,7 @@ template<>
struct fmt::formatter<std::exception_ptr> : formatter<string_view> struct fmt::formatter<std::exception_ptr> : formatter<string_view>
{ {
template<typename FormatContext> template<typename FormatContext>
auto format(std::exception_ptr e, FormatContext &ctx) { auto format(std::exception_ptr e, FormatContext &ctx) const {
return formatter<string_view>::format(GetFullMessage(e), ctx); return formatter<string_view>::format(GetFullMessage(e), ctx);
} }
}; };

@@ -29,7 +29,7 @@ template<>
struct fmt::formatter<Path> : formatter<string_view> struct fmt::formatter<Path> : formatter<string_view>
{ {
template<typename FormatContext> template<typename FormatContext>
auto format(Path path, FormatContext &ctx) { auto format(Path path, FormatContext &ctx) const {
return formatter<string_view>::format(path.ToUTF8(), ctx); return formatter<string_view>::format(path.ToUTF8(), ctx);
} }
}; };

@@ -1,5 +1,7 @@
icu_dep = dependency('icu-i18n', version: '>= 50', required: get_option('icu')) icu_i18n_dep = dependency('icu-i18n', version: '>= 50', required: get_option('icu'))
conf.set('HAVE_ICU', icu_dep.found()) icu_uc_dep = dependency('icu-uc', version: '>= 50', required: get_option('icu'))
have_icu = icu_i18n_dep.found() and icu_uc_dep.found()
conf.set('HAVE_ICU', have_icu)
icu_sources = [ icu_sources = [
'CaseFold.cxx', 'CaseFold.cxx',
@@ -13,7 +15,7 @@ if is_windows
endif endif
iconv_dep = [] iconv_dep = []
if icu_dep.found() if have_icu
icu_sources += [ icu_sources += [
'Util.cxx', 'Util.cxx',
'Init.cxx', 'Init.cxx',
@@ -44,7 +46,8 @@ icu = static_library(
icu_sources, icu_sources,
include_directories: inc, include_directories: inc,
dependencies: [ dependencies: [
icu_dep, icu_i18n_dep,
icu_uc_dep,
iconv_dep, iconv_dep,
fmt_dep, fmt_dep,
], ],

@@ -0,0 +1,22 @@
Index: libmodplug-0.8.9.0/src/fastmix.cpp
===================================================================
--- libmodplug-0.8.9.0.orig/src/fastmix.cpp
+++ libmodplug-0.8.9.0/src/fastmix.cpp
@@ -288,7 +288,7 @@ CzWINDOWEDFIR sfir;
// MIXING MACROS
// ----------------------------------------------------------------------------
#define SNDMIX_BEGINSAMPLELOOP8\
- register MODCHANNEL * const pChn = pChannel;\
+ MODCHANNEL * const pChn = pChannel;\
nPos = pChn->nPosLo;\
const signed char *p = (signed char *)(pChn->pCurrentSample+pChn->nPos);\
if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\
@@ -296,7 +296,7 @@ CzWINDOWEDFIR sfir;
do {
#define SNDMIX_BEGINSAMPLELOOP16\
- register MODCHANNEL * const pChn = pChannel;\
+ MODCHANNEL * const pChn = pChannel;\
nPos = pChn->nPosLo;\
const signed short *p = (signed short *)(pChn->pCurrentSample+(pChn->nPos*2));\
if (pChn->dwFlags & CHN_STEREO) p += pChn->nPos;\

@@ -0,0 +1 @@
no_register

@@ -1026,7 +1026,7 @@ WasapiOutput::EnumerateDevices(IMMDeviceEnumerator &enumerator)
continue; continue;
FmtNotice(wasapi_output_domain, FmtNotice(wasapi_output_domain,
"Device \"{}\" \"{}\"", i, name); "Device \"{}\" \"{}\"", i, name.c_str());
} }
} }

@@ -26,6 +26,11 @@
class TagMask { class TagMask {
typedef uint_least32_t mask_t; typedef uint_least32_t mask_t;
/* the mask must have enough bits to represent all tags
supported by MPD */
static_assert(TAG_NUM_OF_ITEM_TYPES <= sizeof(mask_t) * 8);
mask_t value; mask_t value;
explicit constexpr TagMask(uint_least32_t _value) noexcept explicit constexpr TagMask(uint_least32_t _value) noexcept

@@ -24,6 +24,7 @@
#include "WindowsCond.hxx" #include "WindowsCond.hxx"
#include <memory> #include <memory>
#include <system_error> // for std::error_category
#include <variant> #include <variant>
enum class WinFutureErrc : int { enum class WinFutureErrc : int {
@@ -130,7 +131,7 @@ public:
void set_value(const T &value) { void set_value(const T &value) {
std::unique_lock<CriticalSection> lock(mutex); std::unique_lock<CriticalSection> lock(mutex);
if (!std::holds_alternative<std::monostate>(&result)) { if (!std::holds_alternative<std::monostate>(result)) {
throw WinFutureError(WinFutureErrc::promise_already_satisfied); throw WinFutureError(WinFutureErrc::promise_already_satisfied);
} }
result.template emplace<T>(value); result.template emplace<T>(value);

@@ -52,17 +52,17 @@ public:
using R = std::invoke_result_t<std::decay_t<Function>>; using R = std::invoke_result_t<std::decay_t<Function>>;
auto promise = std::make_shared<Promise<R>>(); auto promise = std::make_shared<Promise<R>>();
auto future = promise->get_future(); auto future = promise->get_future();
Push([function = std::forward<Function>(function), Push([func = std::forward<Function>(function),
promise = std::move(promise)]() mutable { prom = std::move(promise)]() mutable {
try { try {
if constexpr (std::is_void_v<R>) { if constexpr (std::is_void_v<R>) {
std::invoke(std::forward<Function>(function)); std::invoke(std::forward<Function>(func));
promise->set_value(); prom->set_value();
} else { } else {
promise->set_value(std::invoke(std::forward<Function>(function))); prom->set_value(std::invoke(std::forward<Function>(func)));
} }
} catch (...) { } catch (...) {
promise->set_exception(std::current_exception()); prom->set_exception(std::current_exception());
} }
}); });
return future; return future;

@@ -100,9 +100,11 @@ FormatHResultError(HRESULT result, const char *fmt, ...) noexcept
va_start(args1, fmt); va_start(args1, fmt);
va_copy(args2, args1); va_copy(args2, args1);
const int size = vsnprintf(nullptr, 0, fmt, args1); int size = vsnprintf(nullptr, 0, fmt, args1);
va_end(args1); va_end(args1);
assert(size >= 0);
if (size < 0)
size = 0;
auto buffer = std::make_unique<char[]>(size + 1); auto buffer = std::make_unique<char[]>(size + 1);
vsprintf(buffer.get(), fmt, args2); vsprintf(buffer.get(), fmt, args2);

@@ -1,12 +1,13 @@
[wrap-file] [wrap-file]
directory = fmt-9.1.0 directory = fmt-11.0.2
source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz source_url = https://github.com/fmtlib/fmt/archive/11.0.2.tar.gz
source_filename = fmt-9.1.0.tar.gz source_filename = fmt-11.0.2.tar.gz
source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 source_hash = 6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f
patch_filename = fmt_9.1.0-1_patch.zip patch_filename = fmt_11.0.2-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-1/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.2-1/get_patch
patch_hash = 4557b9ba87b3eb63694ed9b21d1a2117d4a97ca56b91085b10288e9a5294adf8 patch_hash = 90c9e3b8e8f29713d40ca949f6f93ad115d78d7fb921064112bc6179e6427c5e
wrapdb_version = 9.1.0-1 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.2-1/fmt-11.0.2.tar.gz
wrapdb_version = 11.0.2-1
[provide] [provide]
fmt = fmt_dep fmt = fmt_dep

@@ -1,13 +1,13 @@
[wrap-file] [wrap-file]
directory = sqlite-amalgamation-3410200 directory = sqlite-amalgamation-3470100
source_url = https://www.sqlite.org/2023/sqlite-amalgamation-3410200.zip source_url = https://www.sqlite.org/2024/sqlite-amalgamation-3470100.zip
source_filename = sqlite-amalgamation-3410200.zip source_filename = sqlite-amalgamation-3470100.zip
source_hash = 01df06a84803c1ab4d62c64e995b151b2dbcf5dbc93bbc5eee213cb18225d987 source_hash = 9da21e6b14ef6a943cdc30f973df259fb390bb4483f77e7f171b9b6e977e5458
patch_filename = sqlite3_3.41.2-2_patch.zip patch_filename = sqlite3_3.47.1-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.41.2-2/get_patch patch_url = https://wrapdb.mesonbuild.com/v2/sqlite3_3.47.1-1/get_patch
patch_hash = 246681dfb731a14bfa61bcde651d5581a7e1c7d14851bfb57a941fac540a6810 patch_hash = 7a298e69c663abfccd2d3632c6897b4f90627d36fd7fa137240c1d97c9a86466
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sqlite3_3.41.2-2/sqlite-amalgamation-3410200.zip source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sqlite3_3.47.1-1/sqlite-amalgamation-3470100.zip
wrapdb_version = 3.41.2-2 wrapdb_version = 3.47.1-1
[provide] [provide]
sqlite3 = sqlite3_dep sqlite3 = sqlite3_dep

@@ -2,7 +2,10 @@ systemd_system_unit_dir = get_option('systemd_system_unit_dir')
if systemd_system_unit_dir == '' if systemd_system_unit_dir == ''
systemd = dependency('systemd', required: false) systemd = dependency('systemd', required: false)
if systemd.found() if systemd.found()
systemd_system_unit_dir = systemd.get_variable(pkgconfig: 'systemdsystemunitdir') systemd_system_unit_dir = systemd.get_variable(
pkgconfig: 'systemdsystemunitdir',
pkgconfig_define: ['rootprefix', get_option('prefix')],
)
endif endif
endif endif
if systemd_system_unit_dir == '' if systemd_system_unit_dir == ''

@@ -2,7 +2,10 @@ systemd_user_unit_dir = get_option('systemd_user_unit_dir')
if systemd_user_unit_dir == '' if systemd_user_unit_dir == ''
systemd = dependency('systemd', required: false) systemd = dependency('systemd', required: false)
if systemd.found() if systemd.found()
systemd_user_unit_dir = systemd.get_variable(pkgconfig: 'systemduserunitdir') systemd_user_unit_dir = systemd.get_variable(
pkgconfig: 'systemduserunitdir',
pkgconfig_define: ['prefix', get_option('prefix')],
)
endif endif
endif endif
if systemd_user_unit_dir == '' if systemd_user_unit_dir == ''

@@ -29,66 +29,12 @@ sys.path[0] = os.path.join(mpd_path, 'python')
# output directories # output directories
from build.dirs import lib_path, tarball_path, src_path from build.dirs import lib_path, tarball_path, src_path
from build.toolchain import MingwToolchain
arch_path = os.path.join(lib_path, host_arch) arch_path = os.path.join(lib_path, host_arch)
build_path = os.path.join(arch_path, 'build') build_path = os.path.join(arch_path, 'build')
root_path = os.path.join(arch_path, 'root') root_path = os.path.join(arch_path, 'root')
class CrossGccToolchain:
def __init__(self, toolchain_path, arch,
tarball_path, src_path, build_path, install_prefix):
self.arch = arch
self.actual_arch = arch
self.tarball_path = tarball_path
self.src_path = src_path
self.build_path = build_path
self.install_prefix = install_prefix
toolchain_bin = os.path.join(toolchain_path, 'bin')
self.cc = os.path.join(toolchain_bin, arch + '-gcc')
self.cxx = os.path.join(toolchain_bin, arch + '-g++')
self.ar = os.path.join(toolchain_bin, arch + '-ar')
self.arflags = 'rcs'
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
self.nm = os.path.join(toolchain_bin, arch + '-nm')
self.strip = os.path.join(toolchain_bin, arch + '-strip')
self.windres = os.path.join(toolchain_bin, arch + '-windres')
common_flags = '-O2 -g'
if not x64:
# enable SSE support which is required for LAME
common_flags += ' -march=pentium3'
self.cflags = common_flags
self.cxxflags = common_flags
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
' -static-libstdc++ -static-libgcc'
self.libs = ''
# Explicitly disable _FORTIFY_SOURCE because it is broken with
# mingw. This prevents some libraries such as libFLAC to
# enable it.
self.cppflags += ' -D_FORTIFY_SOURCE=0'
self.is_arm = arch.startswith('arm')
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
self.is_aarch64 = arch == 'aarch64'
self.is_windows = 'mingw32' in arch
self.env = dict(os.environ)
# redirect pkg-config to use our root directory instead of the
# default one on the build host
import shutil
bin_dir = os.path.join(install_prefix, 'bin')
os.makedirs(bin_dir, exist_ok=True)
self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
os.path.join(bin_dir, 'pkg-config'))
self.env['PKG_CONFIG'] = self.pkg_config
# a list of third-party libraries to be used by MPD on Android # a list of third-party libraries to be used by MPD on Android
from build.libs import * from build.libs import *
thirdparty_libs = [ thirdparty_libs = [
@@ -111,8 +57,9 @@ thirdparty_libs = [
] ]
# build the third-party libraries # build the third-party libraries
toolchain = CrossGccToolchain('/usr', host_arch, toolchain = MingwToolchain(mpd_path,
tarball_path, src_path, build_path, root_path) '/usr', host_arch, x64,
tarball_path, src_path, build_path, root_path)
for x in thirdparty_libs: for x in thirdparty_libs:
if not x.is_installed(toolchain): if not x.is_installed(toolchain):