First package attempt, cherrymusic

Hi,

So I’m trying to learn how to make my own packages in Nix. There’s still lots of magic that I don’t understand, but I’ve pieced this together after a lot of trial and error when reading the youtube-dl package:

{ stdenv, lib, pkgs
, python3Packages
, fetchPypi
, opusSupport ? true
, vorbisSupport ? true
, flacSupport ? true
, lameSupport ? false
, faad2Support ? false
, imageMagickSupport ? false
, makeWrapper }:

# buildPythonPackage rec {
python3Packages.buildPythonApplication rec {

  pname = "CherryMusic";
  version = "0.41.1";

  src = fetchPypi {
    inherit pname version;
    sha256 = "bbd826c05919b76bc72ecd892ff07557ca7b8fa5df240750518d3ada3ff5f843";
  };

  doCheck = false;

  nativeBuildInputs = [ makeWrapper ];
  propagatedBuildInputs = with python3Packages; [ cherrypy unidecode ];

  makeWrapperArgs = let
    packagesToBinPath = with pkgs;
      []
      ++ lib.optional opusSupport lame
      ++ lib.optional vorbisSupport vorbis-tools
      ++ lib.optional lameSupport lame
      ++ lib.optional flacSupport flac
      ++ lib.optional faad2Support faad2
      ++ lib.optional imageMagickSupport imagemagick_light;
    in [ ''--prefix PATH : "${lib.makeBinPath packagesToBinPath}"'' ];

  meta = with stdenv.lib; {
    description = "Stream your own music collection to all your devices! The easy to use free and open-source music streaming server.";
    longDescription = ''
      CherryMusic is a music streaming server based on CherryPy and jPlayer. It
      plays the music inside your PC, smartphone, tablet, toaster or whatever
      device has a HTML5 compliant browser installed.
    '';
    homepage = http://www.fomori.org/cherrymusic;
    downloadPage = https://github.com/devsnd/cherrymusic;
    license = licenses.gpl3;
    maintainers = [ maintainers.monotux ];
    platforms = platforms.all;
  };
}

So…it builds, but doesn’t run:

./result/bin/cherrymusic
Traceback (most recent call last):
  File "/nix/store/yb1q870pr0ss6rfll90zapa624k867v1-CherryMusic-0.41.1/bin/.cherrymusic-wrapped", line 34, in <module>
    import cherrymusicserver
  File "/nix/store/yb1q870pr0ss6rfll90zapa624k867v1-CherryMusic-0.41.1/lib/python3.7/site-packages/cherrymusicserver/__init__.py", line 64, in <module>
    gettext.install('default', localedir=pathprovider.getResourcePath('res/i18n'))
  File "/nix/store/yb1q870pr0ss6rfll90zapa624k867v1-CherryMusic-0.41.1/lib/python3.7/site-packages/cherrymusicserver/pathprovider.py", line 166, in getResourcePath
    "Couldn't locate {path!r} in any {res!r}!".format(path=path, res=RESOURCE_PATHS)
cherrymusicserver.pathprovider.ResourceNotFound: "Couldn't locate 'res/i18n' in any ['/nix/store/yv4pzx3lxk3lscq0pw3hqzs7k4x76xsm-python3-3.7.2/share/cherrymusic', '/nix/store/yv4pzx3lxk3lscq0pw3hqzs7k4x76xsm-python3-3.7.2/local/share/cherrymusic', '/nix/store/yb1q870pr0ss6rfll90zapa624k867v1-CherryMusic-0.41.1/lib/python3.7/site-packages', '/home/oscar/.local/share/cherrymusic']!"

If I look in the relevant folder, the files are there:

ls result/share/cherrymusic/res/i18n/
cherrymusic.pot  de  es  __pycache__  update_translations.py

…so what is going on here?

And I’d love some other critique on my current attempt. Next step is to create a service for this package as well.

On first glance it looks good. :slight_smile:

It seems like it looks for the share folder only in the python package path. The path it looks in that you have control over is ‘/nix/store/yb1q870pr0ss6rfll90zapa624k867v1-CherryMusic-0.41.1/lib/python3.7/site-packages’.

Does it work when you copy/move the ‘res’ folder to $out/lib/python3.7/site-packages e.g. in postInstall.

Maybe there is an option to add another search path to that list, or you could patch that line in the source code.

Sorry, I am on mobile and can’t try it myself ATM.

One thing I noticed though, is that you take the packages from pkgs. For package derivations you typically pass them as function arguments, so you would list them all at the top.

You also don’t use stdenv, so you can leave that out at the top.

In many derivations with optional dependencies with flags for them those are set as optional function arguments. In your case e.g. , vorbisSupport ? true, vorbis-tools ? null,. You should be able to easily find an example in nixpkgs, but if I you want I can look it up later.

Is

correct?

Thank you very much! I’ve updated my setup and this is the current file:

{ lib, pkgs
, python3Packages
, python
, fetchPypi
, lame ? null
, opus ? null
, vorbis ? null
, flac ? null
, faad2 ? null
, imagemagick ? null
, makeWrapper }:

python3Packages.buildPythonApplication rec {

  pname = "CherryMusic";
  version = "0.41.1";

  src = fetchPypi {
    inherit pname version;
    sha256 = "bbd826c05919b76bc72ecd892ff07557ca7b8fa5df240750518d3ada3ff5f843";
  };

  doCheck = false;

  nativeBuildInputs = [ makeWrapper ];
  propagatedBuildInputs = with python3Packages; [ cherrypy unidecode ];

  makeWrapperArgs = let
    packagesToBinPath = with pkgs;
      []
      ++ (lib.optional (opus != null) opusTools)
      ++ (lib.optional (vorbis != null) vorbis-tools)
      ++ (lib.optional (lame != null) lame)
      ++ (lib.optional (flac != null) flac)
      ++ (lib.optional (faad2 != null) faad2)
      ++ (lib.optional (imagemagick != null) imagemagick_light);
    in [ ''--prefix PATH : "${lib.makeBinPath packagesToBinPath}"'' ];

  postInstall = ''
    cp -r res $out/${python.sitePackages}
  '';

  meta = with lib; {
    description = "Stream your own music collection to all your devices! The easy to use free and open-source music streaming server.";
    longDescription = ''
      CherryMusic is a music streaming server based on CherryPy and jPlayer. It
      plays the music inside your PC, smartphone, tablet, toaster or whatever
      device has a HTML5 compliant browser installed.
    '';
    homepage = http://www.fomori.org/cherrymusic;
    downloadPage = https://github.com/devsnd/cherrymusic;
    license = licenses.gpl3;
    maintainers = [ maintainers.monotux ];
    platforms = platforms.all;
  };
}

Now it’s time for a service definition :slight_smile:

Oh sorry I might have been a bit unclear, you still would use the enable flags.

I.e.

{ lib
, python3Packages
, fetchPypi
, opusSupport ? true, opusTools ? null
, vorbisSupport ? true, vorbis-tools ? null
, flacSupport ? true, flac ? null
, lameSupport ? false, lame ? null
, faad2Support ? false, faad2 ? null
, imageMagickSupport ? false, imagemagick_light ? null
, makeWrapper }:

And makeWrapperArgs as in your first version. Package derivations normally are called with every package in pkg available, so your second version would just have everything enabled on most platforms.

An example would be https://github.com/NixOS/nixpkgs/blob/c9d5546635071a65a205b5237dcc39b69d50d135/pkgs/applications/misc/audio/sox/default.nix

Some packages also check whether the package arguments are not null via asserts when the corresponding flag is enabled, but it isn’t done everywhere and I’m not sure if this is recommended for new packages. A package that does it this way is https://github.com/NixOS/nixpkgs/blob/c9d5546635071a65a205b5237dcc39b69d50d135/pkgs/applications/networking/browsers/elinks/default.nix.

I also think that you might run into issues with passing python in the function argument. If this is a different version than python3Packages, the path to the res folder would be wrong. You can use cp -r res ${python3Packages.python.sitePackages} for this instead.

Otherwise it looks good! :slight_smile:

@monotux How is your work going? :slight_smile:

Oh hi! I got distracted (hello parenting), but I think that the above instructions were fine (thanks!) and that the final step was to setup users and other similar parameters.

1 Like