Symptom
A USB printer is plugged into a modern Mac and "macOS doesn't see it." On macOS 26.5.1 (Darwin 25.5, Apple silicon) the printer was in fact visible at every layer below CUPS:
$ ioreg -p IOUSB -w0 -l | grep 'USB Product Name' # -> "HL-L2340D series"
$ lpinfo -v
direct usb://Brother/HL-L2340D%20series?serial=U63879L4N570122
$ lpstat -t
lpstat: No destinations added.Enumeration is fine; there is simply no queue, and nothing will auto-create one.
Two things to check before blaming the cable
1. Is the printer IPP-over-USB (driverless)? Look at the USB interface descriptor, not the marketing:
$ ioreg -p IOService -n "HL-L2340D series" -r -l -w0 | grep bInterface
"bInterfaceClass" = 7 # printer
"bInterfaceSubClass" = 1
"bInterfaceProtocol" = 2 # bidirectional; protocol 4 == IPP-USBOnly protocol 4 gets an automatic AirPrint/ippusb queue. Protocol 1/2 means the OS needs a real driver, no matter what the printer's URF: capability string claims (URF/AppleRaster is only reachable through IPP).
2. What page description language does it actually speak? The IEEE-1284 device ID is authoritative:
$ lpinfo --include-schemes usb -l -v
device-id = SERN:...;MFG:Brother;CMD:PJL,HBP,URF;MDL:HL-L2340D series;...CMD:PJL,HBP = host-based printing (Brother HBP), no PostScript and no PCL. macOS 26 ships exactly 16 generic PPDs (lpinfo -m: Generic PostScript, Generic PCL, a few Epson/HP/Zebra/Dymo, raw, everywhere) and /Library/Printers/PPDs/Contents/Resources is empty. Picking "Generic PostScript" prints garbage; no bundled driver can work.
For this Brother mono-laser family the answer is brlaser (github.com/pdewacht/brlaser). Its brlaser.drv entries carry full 1284 device IDs, so the model match is verifiable rather than a guess. There is no Homebrew formula (brew info brlaser -> no such formula), so you build it.
Building brlaser on macOS without cmake
Four translation units, and the macOS SDK already has cups/raster.h plus libcupsimage.tbd:
git clone --depth 1 https://github.com/pdewacht/brlaser.git && cd brlaser
VERSION=$(sed -n 's/^set(BRLASER_VERSION "\([0-9]*\)").*/\1/p' CMakeLists.txt)
for f in config.h brlaser.drv; do
sed -e 's/@CMAKE_PROJECT_NAME@/brlaser/g' -e "s/@BRLASER_VERSION@/$VERSION/g" "$f.in" > "$f"
done
clang++ -std=c++11 -O2 -I. -o rastertobrlaser src/main.cc src/job.cc src/line.cc src/debug.cc -lcups
ppdc -d ppd brlaser.drv # Apple still ships ppdc/ppdi/ppdmerge in /usr/bincups-config --image --libs reports only -lcups on macOS; that is sufficient (no separate -lcupsimage). Upstream's four unit tests compile and pass the same way.
Ghostscript is not needed on macOS, unlike the Linux packaging: Apple's cgpdftoraster does PDF -> CUPS raster, so the chain is cgtexttopdf/cgpdftopdf -> cgpdftoraster -> rastertobrlaser.
Installing under the sealed system volume
/usr/libexec/cups/filter (CUPS ServerBin) is read-only on any SSV macOS, so the filter cannot go where PPDs normally reference it. Put it under /Library/Printers and give the PPD an absolute filter path:
*cupsFilter: "application/vnd.cups-raster 33 /Library/Printers/brlaser/filter/rastertobrlaser"CUPS resolves a bare filter name against ServerBin and an absolute path as-is; cupstestppd proves it by stat-ing the absolute path (Missing cupsFilter file "/Library/..." before install, PASS after). Ownership matters: install -o root -g wheel -m 755 for the filter, 644 for the PPD, or cupsd refuses to run it.
Then create the queue with the URI from lpinfo -v, serial and all:
lpadmin -p HL_L2340D -E -v 'usb://Brother/HL-L2340D%20series?serial=U63879L4N570122' \
-P /Library/Printers/PPDs/Contents/Resources/brlaser-HL-L2340D.ppd -o printer-is-shared=false
lpadmin -d HL_L2340D && cupsaccept HL_L2340D && cupsenable HL_L2340Dlpadmin warns that CUPS printer drivers are deprecated and will stop working in a future CUPS version. Noted, still the only option for host-based hardware.
Verify before spending paper
The chain runs by hand with no root and no printer attached:
cupsfilter -m application/pdf t.txt > t.pdf
PPD=./brlaser-HL-L2340D.ppd /usr/libexec/cups/filter/cgpdftoraster 1 user title 1 "" t.pdf > t.raster
PPD=./brlaser-HL-L2340D.ppd ./rastertobrlaser 1 user title 1 "" < t.raster > t.prn
strings t.prn | head # expect @PJL JOB, @PJL SET PAPER = LETTER, ENTER LANGUAGE = PCLA few KB of PJL-wrapped data with no ERROR lines on stderr means the driver is right and only transport is left to test.
Trap: patching the PPD with GNU sed syntax
Upstream PPDs default to A4. A patch like
sed -e 's/^\*Default\(PageSize\|PageRegion\): A4/*Default\1: Letter/' ...silently does nothing under BSD sed (/usr/bin/sed), which has no \| alternation in BREs: no match, exit 0, PPD still A4. Especially treacherous inside an agent harness whose PATH front-loads a GNU sed — the same line works interactively and fails in the privileged script. Use /usr/bin/sed -E with (a|b) and assert the result:
grep -q '^\*DefaultPageSize: Letter' out.ppd || { echo 'PPD patch failed' >&2; exit 1; }Getting root when the agent has no sudo password
osascript -e 'do shell script "..." with administrator privileges' raises the native auth dialog, which a human at the keyboard can satisfy. It blocks indefinitely if nobody looks at the screen, so run it as a supervised background process, not a foreground command with a timeout.
Verified on
macOS 26.5.1 (25F80), MacBook Air M5 (arm64), brlaser v6, Brother HL-L2340D over USB behind a VIA Labs hub. End-to-end lp job completed and printed; identical filter binary (same md5) reproduced from a clean clone by the committed install script.