Packaging a Printer Driver

Hi all

Can anyone help please? I have a Brother MFC-J5340DW printer which I have been trying to build a driver for from the .deb

It’s the last obstacle to me using NixOS as my daily-driver as without the .deb package, I’m missing a lot of options.

I’ve copied a driver from a different printer and made some changes but still can’t get it to work, can anyone help please?

{
  pkgsi686Linux,
  stdenv,
  fetchurl,
  dpkg,
  makeWrapper,
  coreutils,
  ghostscript,
  gnugrep,
  gnused,
  which,
  perl,
  lib,
}:

let
  model = "mfcj5340dw";
  version = "3.5.0-1";
  src = fetchurl {
    url = "https://download.brother.com/welcome/dlf105455/${model}pdrv-${version}.i386.deb";
    sha256 = "sha256-VVq5Urcdaag3rAfsWDtr8/HhLSxOiXScawhxmyS4GEM=";
  };
  reldir = "opt/brother/Printers/${model}";

in
rec {
  driver = pkgsi686Linux.stdenv.mkDerivation rec {
    inherit src version;
    name = "${model}drv-${version}";

    nativeBuildInputs = [
      dpkg
      makeWrapper
    ];

    unpackPhase = "dpkg-deb -x $src $out";

    installPhase = ''
        dir="$out/${reldir}"
        substituteInPlace $dir/lpd/filter_${model} \
          --replace /usr/bin/perl ${perl}/bin/perl \
          --replace "BR_PRT_PATH =~" "BR_PRT_PATH = \"$dir\"; #" \
          --replace "PRINTER =~" "PRINTER = \"${model}\"; #"
        wrapProgram $dir/lpd/filter_${model} \
          --prefix PATH : ${
            lib.makeBinPath [
              coreutils
              ghostscript
              gnugrep
              gnused
              which
            ]
          }
      # need to use i686 glibc here, these are 32bit proprietary binaries
      patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
        $dir/lpd/filter_mfcj5340dw
    '';

  };

  cupswrapper = stdenv.mkDerivation rec {
    inherit version src;
    name = "${model}cupswrapper-${version}";

    nativeBuildInputs = [
      dpkg
      makeWrapper
    ];

    unpackPhase = "dpkg-deb -x $src $out";

    installPhase = ''
      basedir=${driver}/${reldir}
      dir=$out/${reldir}
      substituteInPlace $dir/cupswrapper/brother_lpdwrapper_${model} \
        --replace /usr/bin/perl ${perl}/bin/perl \
        --replace "basedir =~" "basedir = \"$basedir\"; #" \
        --replace "PRINTER =~" "PRINTER = \"${model}\"; #"
      wrapProgram $dir/cupswrapper/brother_lpdwrapper_${model} \
        --prefix PATH : ${
          lib.makeBinPath [
            coreutils
            gnugrep
            gnused
          ]
        }
      mkdir -p $out/lib/cups/filter
      mkdir -p $out/share/cups/model
      ln $dir/cupswrapper/brother_lpdwrapper_${model} $out/lib/cups/filter
      ln $dir/cupswrapper/brother_${model}_printer_en.ppd $out/share/cups/model
    '';

  };
}

and tried to build with this command:

nix-build -E 'with import <nixpkgs> { }; callPackage ./tryme.nix { }'

but got the following error:

these 2 derivations will be built:
  /nix/store/i1dqdqz74klb01jdyc12ca706avpps5d-mfcj5340dwdrv-3.5.0-1.drv
  /nix/store/lqi73h86blj1j34x5494al0wkk61v049-mfcj5340dwcupswrapper-3.5.0-1.drv
building '/nix/store/i1dqdqz74klb01jdyc12ca706avpps5d-mfcj5340dwdrv-3.5.0-1.drv'...
Running phase: unpackPhase
Running phase: patchPhase
Running phase: updateAutotoolsGnuConfigScriptsPhase
Running phase: configurePhase
no configure script, doing nothing
Running phase: buildPhase
no Makefile or custom buildPhase, doing nothing
Running phase: installPhase
substituteStream() in derivation mfcj5340dwdrv-3.5.0-1: WARNING: '--replace' is deprecated, use --replace-{fail,warn,quiet}. (file '/nix/store/wf2xa21782vsjsvqmcmb7cd5y32lkn58-mfcj5340dwdrv-3.5.0-1/opt/brother/Printers/mfcj5340dw/lpd/filter_mfcj5340dw')
patchelf: not an ELF executable
error: builder for '/nix/store/i1dqdqz74klb01jdyc12ca706avpps5d-mfcj5340dwdrv-3.5.0-1.drv' failed with exit code 1;
       last 10 log lines:
       > Running phase: unpackPhase
       > Running phase: patchPhase
       > Running phase: updateAutotoolsGnuConfigScriptsPhase
       > Running phase: configurePhase
       > no configure script, doing nothing
       > Running phase: buildPhase
       > no Makefile or custom buildPhase, doing nothing
       > Running phase: installPhase
       > substituteStream() in derivation mfcj5340dwdrv-3.5.0-1: WARNING: '--replace' is deprecated, use --replace-{fail,warn,quiet}. (file '/nix/store/wf2xa21782vsjsvqmcmb7cd5y32lkn58-mfcj5340dwdrv-3.5.0-1/opt/brother/Printers/mfcj5340dw/lpd/filter_mfcj5340dw')
       > patchelf: not an ELF executable
       For full logs, run:
         nix-store -l /nix/store/i1dqdqz74klb01jdyc12ca706avpps5d-mfcj5340dwdrv-3.5.0-1.drv
error: build of '/nix/store/i1dqdqz74klb01jdyc12ca706avpps5d-mfcj5340dwdrv-3.5.0-1.drv', '/nix/store/lqi73h86blj1j34x5494al0wkk61v049-mfcj5340dwcupswrapper-3.5.0-1.drv' failed

can someone please help?

Can’t say that I know a lot about printer drivers or .deb, but just reading the output, it seems like patchelf is complaining that filter_mfcj5340dw is not an ELF file. If you extract the .deb somewhere, can you check if that file is an ELF file? I think file should tell you that.

Thanks for your response, I’ve tried everything I can think of, I’ve even tried [God help me] ChatGPT. I give up, looks like I’m going back to OpenSUSE Tumbleweed.

Please don’t give up. I recommend you to look for a similar printer driver in nixpkgs and adopt from that. I did something similar for a dcpl3550cdw which is similar to a cups-brother-dcpt310 which I found in nixpkgs (it took me several hours to get this right but in the end it worked nicely). I do not know your printer and therefore my work may not help you but anyway here is my repo and maybe you can get some inspiration from it: https://git.sr.ht/~bwolf/cups-brother-dcpl3550cdw.nix/tree/master/item/dcpl3550cdw.nix

Thank you for your kind words. I believe I have fixed it:
I have made a new file called mfc-j5340dw.nix with the following contents

{
  pkgsi686Linux,
  stdenv,
  fetchurl,
  dpkg,
  makeWrapper,
  coreutils,
  ghostscript,
  gnugrep,
  gnused,
  which,
  perl,
  lib,
}:

let
  model = "mfcj5340dw";
  version = "3.5.0-1";
  src = fetchurl {
    url = "https://download.brother.com/welcome/dlf105455/${model}pdrv-${version}.i386.deb";
    sha256 = "sha256-VVq5Urcdaag3rAfsWDtr8/HhLSxOiXScawhxmyS4GEM=";
  };
  reldir = "opt/brother/Printers/${model}";

in
rec {
  driver = pkgsi686Linux.stdenv.mkDerivation rec {
    inherit src version;
    name = "${model}drv-${version}";

    nativeBuildInputs = [
      dpkg
      makeWrapper
    ];

    unpackPhase = "dpkg-deb -x $src $out";

    installPhase = ''
        dir="$out/${reldir}"
        substituteInPlace $dir/lpd/filter_${model} \
          --replace /usr/bin/perl ${perl}/bin/perl \
          --replace "BR_PRT_PATH =~" "BR_PRT_PATH = \"$dir\"; #" \
          --replace "PRINTER =~" "PRINTER = \"${model}\"; #"
        wrapProgram $dir/lpd/filter_${model} \
          --prefix PATH : ${
            lib.makeBinPath [
              coreutils
              ghostscript
              gnugrep
              gnused
              which
            ]
          }
      # need to use i686 glibc here, these are 32bit proprietary binaries
      patchelf --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \
        $dir/lpd/x86_64/brmfcj5340dwfilter
    '';

  };

  cupswrapper = stdenv.mkDerivation rec {
    inherit version src;
    name = "${model}cupswrapper-${version}";

    nativeBuildInputs = [
      dpkg
      makeWrapper
    ];

    unpackPhase = "dpkg-deb -x $src $out";

    installPhase = ''
      basedir=${driver}/${reldir}
      dir=$out/${reldir}
      substituteInPlace $dir/cupswrapper/brother_lpdwrapper_${model} \
        --replace /usr/bin/perl ${perl}/bin/perl \
        --replace "basedir =~" "basedir = \"$basedir\"; #" \
        --replace "PRINTER =~" "PRINTER = \"${model}\"; #"
      wrapProgram $dir/cupswrapper/brother_lpdwrapper_${model} \
        --prefix PATH : ${
          lib.makeBinPath [
            coreutils
            gnugrep
            gnused
          ]
        }
      mkdir -p $out/lib/cups/filter
      mkdir -p $out/share/cups/model
      ln $dir/cupswrapper/brother_lpdwrapper_${model} $out/lib/cups/filter
      ln $dir/cupswrapper/brother_${model}_printer_en.ppd $out/share/cups/model
    '';

  };
}

I have to run nix-build -E 'with import <nixpkgs> { }; callPackage ./mfc-j5340dw.nix { }' which then allows nixos to detect the printer after a nixos-rebuild switch. Is there a way to upload this package so it can just be included in (in my case) packages.nix?

1 Like

Well done! Upstreaming would be cool, but will be even more work. You can always just add the expression (i.e. callPackage ./mfc-j5340dw.nix {}) to your configuration (services.printing.drivers), and it will incorporated into your system just as if it were included in nixpkgs.

2 Likes

oh, so I don’t have to build it with that long command?

Right, you can just have it build as part of your build. Just add

services.printing.drivers = [ (pkgs.callPackage ./mfc-j5340dw.nix {}) ];

to some module and as long as your nix file is in the same folder this will work just like any other driver (assuming a variable in scope called pkgs which is nixpkgs).

This is brilliant, thank you.

I’m afraid it didn’t work, I copied the file to /etc/nixos/ but when trying to build, got an error. I’ll add the error and printer.nix files here:

uilding the system configuration...
error:
       … while calling the 'head' builtin
         at /nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/lib/attrsets.nix:1701:13:
         1700|           if length values == 1 || pred here (elemAt values 1) (head values) then
         1701|             head values
             |             ^
         1702|           else

       … while evaluating the attribute 'value'
         at /nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/lib/modules.nix:1118:7:
         1117|     // {
         1118|       value = addErrorContext "while evaluating the option `${showOption loc}':" value;
             |       ^
         1119|       inherit (res.defsFinal') highestPrio;

       … while evaluating the option `system.build.toplevel':

       … while evaluating definitions from `/nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/nixos/modules/system/activation/top-level.nix':

       … while evaluating the option `warnings':

       … while evaluating definitions from `/nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/nixos/modules/system/boot/systemd.nix':

       … while evaluating the option `systemd.services.cups.serviceConfig':

       … while evaluating definitions from `/nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/nixos/modules/system/boot/systemd.nix':

       … while evaluating the option `systemd.services.cups.preStart':

       … while evaluating definitions from `/nix/store/qhpqw694xpsjp5is8vrmlk1674sk8d5n-nixos/nixos/nixos/modules/services/printing/cupsd.nix':

       (stack trace truncated; use '--show-trace' to show the full, detailed trace)

       error: A definition for option `services.printing.drivers."[definition 1-entry 1]"' is not of type `absolute path'. Definition values:
       - In `/etc/nixos/printer.nix':
           {
             cupswrapper = <derivation mfcj5340dwcupswrapper-3.5.0-1>;
             driver = <derivation mfcj5340dwdrv-3.5.0-1>;
             override = <function, args: {coreutils, dpkg, fetchurl, ghostscript, gnugrep, gnused, lib, makeWrapper, perl, pkgsi686Linux, stdenv, which}>;
             overrideDerivation = <function>;
           ...
Command 'nix-build '<nixpkgs/nixos>' --attr config.system.build.toplevel --no-out-link' returned non-zero exit status 1.

printer.nix:

{ config, lib, pkgs, modulesPath, ... }:

{
  imports =
    [ (modulesPath + "/installer/scan/not-detected.nix")
<nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan5.nix>
    ];


# Enable Printer
hardware.printers = {
  ensureDefaultPrinter = "Brother";
  ensurePrinters = [{
    name = "Brother";
    location = "office";
    model = "everywhere";
ppdOptions = {
      pageSize = "A4";
    };
    description = "Brother MFC-J5340DW";
    deviceUri = "ipp://192.168.1.21/ipp";
  }];
};

services.printing.drivers = [ (pkgs.callPackage ./mfc-j5340dw.nix {}) ];

# Enable Scanner
hardware = {
sane = {
enable = true;
brscan5 = {
enable = true;
netDevices = {
home = { model = "MFC-J5340DW"; ip = "192.168.1.21"; };
};
};
};
};

}

Could you advise please?

Your mfc-j5340dw.nix defines 2 derivations. In your NixOS module printer.nix, what happens if you try:

services.printing.drivers = let
  mfc-j5340dw = pkgs.callPackage ./mfc-j5340dw.nix {};
in
  [ mfc-j5340dw.driver mfc-j5340dw.cupswrapper ];

?

You might not need to add both the driver and the cupswrapper to services.printing.drivers but I’m not really sure. Mostly just spitballing based on reading the error you are getting.

1 Like

works perfectly! Thank you!

3 Likes