1{
2 lib,
3 stdenv,
4 src,
5 version,
6 fetchpatch,
7 bashInteractive,
8 diffPlugins,
9 gobject-introspection,
10 gst_all_1,
11 python3Packages,
12 sphinxHook,
13 writableTmpDirAsHomeHook,
14 runtimeShell,
15 writeScript,
16
17 # plugin deps, used indirectly by the @inputs when we `import ./builtin-plugins.nix`
18 aacgain,
19 chromaprint,
20 essentia-extractor,
21 ffmpeg,
22 flac,
23 imagemagick,
24 keyfinder-cli,
25 mp3gain,
26 mp3val,
27
28 extraPatches ? [ ],
29 pluginOverrides ? { },
30 disableAllPlugins ? false,
31 disabledTests ? [ ],
32 extraNativeBuildInputs ? [ ],
33
34 # tests
35 runCommand,
36 beets,
37}@inputs:
38let
39 inherit (lib) attrNames attrValues concatMap;
40
41 mkPlugin =
42 {
43 name,
44 enable ? !disableAllPlugins,
45 builtin ? false,
46 propagatedBuildInputs ? [ ],
47 testPaths ? [
48 "test/plugins/test_${name}.py"
49 ],
50 wrapperBins ? [ ],
51 }:
52 {
53 inherit
54 name
55 enable
56 builtin
57 propagatedBuildInputs
58 testPaths
59 wrapperBins
60 ;
61 };
62
63 basePlugins = lib.mapAttrs (_: a: { builtin = true; } // a) (import ./builtin-plugins.nix inputs);
64 pluginOverrides' = lib.mapAttrs (
65 plugName:
66 lib.throwIf (basePlugins.${plugName}.deprecated or false)
67 "beets evaluation error: Plugin ${plugName} was enabled in pluginOverrides, but it has been removed. Remove the override to fix evaluation."
68 ) pluginOverrides;
69
70 allPlugins = lib.mapAttrs (n: a: mkPlugin { name = n; } // a) (
71 lib.recursiveUpdate basePlugins pluginOverrides'
72 );
73 builtinPlugins = lib.filterAttrs (_: p: p.builtin) allPlugins;
74 enabledPlugins = lib.filterAttrs (_: p: p.enable) allPlugins;
75 disabledPlugins = lib.filterAttrs (_: p: !p.enable) allPlugins;
76 disabledTestPaths = lib.flatten (attrValues (lib.mapAttrs (_: v: v.testPaths) disabledPlugins));
77
78 pluginWrapperBins = concatMap (p: p.wrapperBins) (attrValues enabledPlugins);
79in
80python3Packages.buildPythonApplication {
81 pname = "beets";
82 inherit src version;
83 pyproject = true;
84
85 patches = [
86 ]
87 ++ extraPatches;
88
89 build-system = [
90 python3Packages.poetry-core
91 ];
92
93 dependencies =
94 with python3Packages;
95 [
96 confuse
97 gst-python
98 jellyfish
99 mediafile
100 munkres
101 musicbrainzngs
102 platformdirs
103 pyyaml
104 unidecode
105 typing-extensions
106 lap
107 ]
108 ++ (concatMap (p: p.propagatedBuildInputs) (attrValues enabledPlugins));
109
110 nativeBuildInputs = [
111 gobject-introspection
112 sphinxHook
113 python3Packages.pydata-sphinx-theme
114 ]
115 ++ extraNativeBuildInputs;
116
117 buildInputs = [
118 ]
119 ++ (with gst_all_1; [
120 gst-plugins-base
121 gst-plugins-good
122 gst-plugins-ugly
123 ]);
124
125 outputs = [
126 "out"
127 "doc"
128 "man"
129 ];
130 sphinxBuilders = [
131 "html"
132 "man"
133 ];
134
135 postInstall = ''
136 mkdir -p $out/share/zsh/site-functions
137 cp extra/_beet $out/share/zsh/site-functions/
138 '';
139
140 makeWrapperArgs = [
141 "--set GI_TYPELIB_PATH \"$GI_TYPELIB_PATH\""
142 "--set GST_PLUGIN_SYSTEM_PATH_1_0 \"$GST_PLUGIN_SYSTEM_PATH_1_0\""
143 "--prefix PATH : ${lib.makeBinPath pluginWrapperBins}"
144 ];
145
146 nativeCheckInputs =
147 with python3Packages;
148 [
149 pytestCheckHook
150 pytest-cov-stub
151 mock
152 rarfile
153 responses
154 requests-mock
155 ]
156 ++ [
157 writableTmpDirAsHomeHook
158 ]
159 ++ pluginWrapperBins;
160
161 __darwinAllowLocalNetworking = true;
162
163 disabledTestPaths =
164 disabledTestPaths
165 ++ [
166 # touches network
167 "test/plugins/test_aura.py"
168 ]
169 ++ lib.optionals stdenv.hostPlatform.isDarwin [
170 # Flaky: several tests fail randomly with:
171 # if not self._poll(timeout):
172 # raise Empty
173 # _queue.Empty
174 "test/plugins/test_player.py"
175 ];
176 disabledTests = disabledTests ++ [
177 # https://github.com/beetbox/beets/issues/5880
178 "test_reject_different_art"
179 ];
180
181 # Perform extra "sanity checks", before running pytest tests.
182 preCheck = ''
183 # Check for undefined plugins
184 find beetsplug -mindepth 1 \
185 \! -path 'beetsplug/__init__.py' -a \
186 \( -name '*.py' -o -path 'beetsplug/*/__init__.py' \) -print \
187 | sed -n -re 's|^beetsplug/([^/.]+).*|\1|p' \
188 | sort -u > plugins_available
189 ${diffPlugins (attrNames builtinPlugins) "plugins_available"}
190
191 export BEETS_TEST_SHELL="${lib.getExe bashInteractive} --norc"
192
193 env EDITOR="${writeScript "beetconfig.sh" ''
194 #!${runtimeShell}
195 cat > "$1" <<CFG
196 plugins: ${lib.concatStringsSep " " (attrNames enabledPlugins)}
197 CFG
198 ''}" "$out/bin/beet" config -e
199 env EDITOR=true "$out/bin/beet" config -e
200 '';
201
202 passthru.plugins = allPlugins;
203
204 passthru.tests.gstreamer =
205 runCommand "beets-gstreamer-test"
206 {
207 meta.timeout = 60;
208 }
209 ''
210 set -euo pipefail
211 export HOME=$(mktemp -d)
212 mkdir $out
213
214 cat << EOF > $out/config.yaml
215 replaygain:
216 backend: gstreamer
217 EOF
218
219 ${beets}/bin/beet -c $out/config.yaml > /dev/null
220 '';
221
222 meta = {
223 description = "Music tagger and library organizer";
224 homepage = "https://beets.io";
225 license = lib.licenses.mit;
226 maintainers = with lib.maintainers; [
227 aszlig
228 doronbehar
229 lovesegfault
230 montchr
231 pjones
232 ];
233 platforms = lib.platforms.linux ++ lib.platforms.darwin;
234 mainProgram = "beet";
235 };
236}