Am I doing it right in creating a package?

I’m a nixos newbie. Today I tried to create (or wrap?) a package using nix for my first time.

The package that I tried to create is logisim-ita, a fork of logisim, for educational purposes.
After reading some blogs on nix packaging, and after going through some trials and errors, I finally made it.

  1. I first cloned nur-package-template to my machine
  2. and according to its readme, I added those codes to default.nix
  logisim-ita = pkgs.callPackage ./pkgs/logisim-ita { inherit (pkgs) fetchurl jre makeWrapper copyDesktopItems makeDesktopItem unzip ; };
  1. There’s is already a derivation for logisim in nixpkgs and logisim-ita should be similar. So I did some tiny tweaks to logisim’s derivation and put the codes into pkgs/logisim-ita/default.nix
{ lib, stdenv, fetchurl, jre, makeWrapper, copyDesktopItems, makeDesktopItem, unzip }:

stdenv.mkDerivation rec {
  pname = "logisim-ita";
  version = "2.16.1.4";

  src = fetchurl {
    url = "https://github.com/Logisim-Ita/Logisim/releases/download/v${version}/Logisim-ITA.jar";
    sha256 = "d27b92e38188309be935e6355faef8689594537b8e280d79b84372e1f85a38d7";
  };

  dontUnpack = true;

  nativeBuildInputs = [ makeWrapper copyDesktopItems unzip ];

  desktopItems = [
    (makeDesktopItem {
      name = pname;
      desktopName = "Logisim-ITA";
      exec = "logisim-ita";
      icon = "logisim-ita";
      comment = meta.description;
      categories = [ "Education" ];
    })
  ];

  installPhase = ''
    runHook preInstall

    mkdir -p $out/bin
    makeWrapper ${jre}/bin/java $out/bin/${pname} --add-flags "-jar $src"

    # Create icons
    unzip $src "resources/logisim/img/*"
    for size in 16 20 24 48 64 128
    do
      install -D "./resources/logisim/img/logisim-icon-$size.png" "$out/share/icons/hicolor/''${size}x''${size}/apps/logisim-ita.png"
    done

    runHook postInstall
  '';

  meta = with lib; {
    homepage = "logisim.altervista.org";
    description = "Logisim Italian Fork";
    maintainers = with maintainers; [ A7R7 ];
    sourceProvenance = with sourceTypes; [ binaryBytecode ];
    license = licenses.gpl3Plus;
    platforms = platforms.unix;
  };
}

  1. run nix build '.#logisim-ita'
    then nix profile install '.#logisim-ita' for a temporary install.
    And boom! logisim-ita is avaliable on my machine

Although managed to create the package, I still felt somewhat strange and curious, so I just raise my questions here.

  1. what is exactly ‘A’ ‘#’ and ‘B’ in the format A#B?
    When I do nix profile install nixpkgs#XXX, why does my system know that it should reference the nixpkgs repo from github, but not a local dir called ‘nixpkgs’?
    And why it knows to reference the local dir when it is ‘.#XXX’?
    And most importantly, where should I find the manual page that describes this in detail?

  2. the default.nix has an argument set

{ lib, stdenv, fetchurl, jre, makeWrapper, copyDesktopItems, makeDesktopItem, unzip }

but I call default.nix with

{ inherit (pkgs) fetchurl jre makeWrapper copyDesktopItems makeDesktopItem unzip ; };

I did not pass lib and stdenv. How does default.nix know their value?
Also, what is the best practice to call default.nix when the argument set is as long as this?

You may check my nur repo if you’re interested. currently there’s only one package logisim-ita

1 Like

While not harmful, passing the arguments is superfluous. callPackage literally just does that.

That’s a flake reference. The manual explains it in detail, but it’s basically (flake to use)(separator)(desired output).

nixpkgs is a default entry of the flake registry. If you use a flake without a protocol prefix, it is looked up in the registry instead. In the case of nixpkgs, by default it resolves to github:NixOS/nixpkgs/master iirc, but you can change that.

The flake registry is useful when you want to just use whatever the system is using, to avoid having many different versions of nixpkgs locally. I.e. it’s great for nix shell and such, but I don’t recommend using it for projects you intend to share, and you probably should pin it to what your running system depends on to avoid downloads.

If you wanted the local directory nixpkgs, you’d need path:nixpkgs#XXX or ./nixpkgs#XXX.

. is explicitly part of the path-like syntax instead, so it’s unambigously the current directory. Anything that starts with . or / is treated as a path.

See links to the nix reference manual :wink:

As I mentioned earlier, callPackage introspects the argument list for the function it is passed, and automatically supplies the arguments from the pkgs set it is part of.

The nix pills explain in detail how this works, but it’s basically just a neat language feature that saves a lot of typing if it is used as it is in the nixpkgs library.

Given the above, not to specify any arguments that are in pkgs, and use callPackage instead of a manual function call. If you need to add args not in pkgs, and you call multiple packages, callPackageWith can be used to create a function that inserts your own arguments into the set.

I use this feature a lot, it’s very nice: https://github.com/TLATER/dotfiles/blob/8374dc285e057622859ce4168e28277c854974b4/pkgs/default.nix#L7

1 Like

Thank you, your answer is incredibly helpful to me!

Btw, when I do nix profile install .#logisim-ita, how does it know exactly to install from the output packages.x86_64-linux.logisim-ita? what if there’s another output, say legacyPackages.x86_64-linux.logisim-ita?

I think the priority is packages > legacyPackages. nix profile install will only look at packages and legacyPackages, no other outputs.

So, it doesn’t “know”, there’s just a defined order if you happen to have both. This is an edge case, don’t think many commands can be used on multiple outputs like this, and you really shouldn’t have both legacy and non-legacy packages.

1 Like