# Speedata Publisher FreeBSD Port — Workflow

This document covers the day-to-day workflow for maintaining the print/publisher port across multiple machines.

## Two phases

The workflow has two phases:

* Phase A: Development — until the port is merged into FreeBSD ports tree
* Phase B: Maintainer mode — after merge, update existing port

This document focuses on Phase A and notes what changes in Phase B at the end.

## Layout

The port consists of these files:

* Makefile
* distinfo
* pkg-descr
* pkg-message
* pkg-plist
* files/modules.txt

These six files are the entire port. Nothing else is needed.

Note on modules.txt: Go 1.21+ requires vendor/modules.txt to exist in any module-mode vendored build. GH_TUPLE extraction does not produce it. The port's post-extract step copies our pre-generated files/modules.txt into the build tree.

## Locations

Two places where the port lives during development:

=> ~/git/bsd/publisher/ Working copy under git (source of truth, edit here)
=> /usr/local/poudriere/ports/latest/print/publisher/ Poudriere ports tree (for clean jail tests)

The working copy is the master. The Poudriere copy is derived from it.

The local FreeBSD ports tree at /usr/ports/print/publisher/ is NOT used during development. It is kept clean and managed by FreeBSD via git pull of the official ports tree. This ensures no conflicts when the port is eventually merged.

## Upstream sources

=> https://github.com/speedata/publisher Upstream repository
=> https://github.com/speedata/publisher/archive/refs/tags/ Tag list (all versions)
=> https://github.com/speedata/publisher/archive/refs/tags/v5.4.3.tar.gz Current stable source tarball

# Part 1: Initial setup on a new machine

## Install required packages

```
doas pkg install -y go portlint poudriere git
```

## Create directory structure

```
mkdir -p ~/git/bsd
cd ~/git/bsd
```

## Get the port files

If you already have a git remote, clone:

```
cd ~/git/bsd
git clone <your-remote-url> publisher
```

Otherwise, copy the port files from another machine via scp or shared folder:

```
mkdir -p ~/git/bsd/publisher
```

Copy these files into ~/git/bsd/publisher/:

* Makefile
* distinfo
* pkg-descr
* pkg-message
* pkg-plist

# Part 2: Working with upstream source

## Why the source is needed

The port itself does not include upstream source. The FreeBSD build system fetches it automatically. The source is only needed manually when:

* Inspecting upstream files to update GH_TUPLE entries
* Debugging build issues locally

Upstream source is downloaded to /tmp, used, and discarded. It must NOT be committed to the git repo.

## Download upstream source for inspection

```
cd /tmp
fetch https://github.com/speedata/publisher/archive/refs/tags/v5.4.3.tar.gz
tar xzf v5.4.3.tar.gz
cd publisher-5.4.3
```

For a different version, change the tag in the URL:

```
fetch https://github.com/speedata/publisher/archive/refs/tags/v5.4.4.tar.gz
```

## Regenerate GH_TUPLE from upstream

GH_TUPLE lines tell the FreeBSD ports framework which Go dependencies to fetch and where to place them in the vendor tree. They must match the upstream go.mod, so they need to be regenerated whenever DISTVERSION changes.

Two tools are needed:

* lang/go (any version) — to run "go mod vendor" and produce vendor/modules.txt
* ports-mgmt/modules2tuple — to convert modules.txt into GH_TUPLE lines

Install:

```
doas pkg install -y go modules2tuple
```

Generate vendor tree and modules.txt from upstream go.mod:

```
cd /tmp/publisher-5.4.3/src/go
go mod vendor
```

Convert modules.txt to GH_TUPLE format:

```
modules2tuple vendor/modules.txt
```

The output is a ready-to-paste GH_TUPLE block. Replace the GH_TUPLE block in ~/git/bsd/publisher/Makefile with the new output. Note the line endings: the framework expects backslash-continued lines indented with one tab.

The default modules2tuple output places vendored modules at the top of the tarball. Our port uses a non-default layout (sources live in src/go/), so the destination subdirs need src/go prepended. Patch the output with sed:

```
modules2tuple vendor/modules.txt | sed 's|/vendor/|/src/go/vendor/|g'
```

The resulting lines look like:

```
account:repo:hash:label/src/go/vendor/path/to/module
```

## Save modules.txt to the working copy

The vendor/modules.txt file generated by "go mod vendor" must be shipped with the port. Copy it now:

```
mkdir -p ~/git/bsd/publisher/files
cp /tmp/publisher-5.4.3/src/go/vendor/modules.txt ~/git/bsd/publisher/files/
```

## Cleanup after use

```
cd /tmp
rm -rf publisher-5.4.3 v5.4.3.tar.gz
```

# Part 3: Bootstrap the auto-generated files

The port has six files in total. Four are hand-written (or generated from upstream), two are auto-generated by FreeBSD tooling:

* Makefile — hand-written
* pkg-descr — hand-written
* pkg-message — hand-written
* files/modules.txt — generated once from upstream (see Part 2)
* distinfo — auto-generated from Makefile by "make makesum"
* pkg-plist — auto-generated from a successful build by "make makeplist"

This section walks through how to create the auto-generated pair from scratch. Do this once at the start, or whenever DISTVERSION (for distinfo) or installed file layout (for pkg-plist) changes.

## Step 0: Update Poudriere jail and ports tree

Run this before any bootstrap or test. The ports tree update wipes any unsynced files in print/publisher/, so always sync after.

```
doas poudriere jail -u -j 15amd64
doas poudriere ports -u -p latest
```

## Step 1: Sync the hand-written files to Poudriere

```
doas rm -rf /usr/local/poudriere/ports/latest/print/publisher
doas mkdir -p /usr/local/poudriere/ports/latest/print/publisher/files
doas cp ~/git/bsd/publisher/Makefile /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-descr /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-message /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/files/modules.txt /usr/local/poudriere/ports/latest/print/publisher/files/
```

## Step 2: Generate distinfo

```
cd /usr/local/poudriere/ports/latest/print/publisher
doas make makesum
```

This downloads all distfiles (the main tarball plus every GH_TUPLE dependency) and writes their SHA256 sums to a new distinfo file.

Copy it back to the working copy:

```
cp /usr/local/poudriere/ports/latest/print/publisher/distinfo ~/git/bsd/publisher/
```

## Step 3: Generate pkg-plist

```
cd /usr/local/poudriere/ports/latest/print/publisher
doas make stage
doas make makeplist 2>/dev/null > /tmp/plist.txt
```

makeplist output has two quirks that must be cleaned up before saving:

1. First line is a placeholder reminder ("/you/have/to/check/what/makeplist/gives/you") that does not belong in pkg-plist.
2. The framework substitutes the literal string "5.3" in any path with "%%LUA_VER%%" (because LUA_VER=5.3). This breaks xmlresolver-5.3.3 paths. Restore them with sed.
3. When USE_LDCONFIG points to a custom directory, makeplist includes "libdata/ldconfig/publisher" in its output, but the ports framework also adds the same entry automatically when staging. Listing it explicitly causes a duplicate and breaks check-plist. Remove it.

Apply all three fixes in one pipeline:

```
grep -v '^/you/have' /tmp/plist.txt | grep -v 'libdata/ldconfig/publisher' > /tmp/pkg-plist
sed -i '' 's|%%LUA_VER%%|5.3|g' /tmp/pkg-plist
```

Sanity check (should show 5 JAR symlinks under lib/speedata-publisher/, no libdata line, xmlresolver paths with literal 5.3.3):

```
grep -E "saxon|jing|trang|xmlresolver|ldconfig" /tmp/pkg-plist
```

Save:

```
doas cp /tmp/pkg-plist /usr/local/poudriere/ports/latest/print/publisher/pkg-plist
```

Copy back to working copy:

```
cp /usr/local/poudriere/ports/latest/print/publisher/pkg-plist ~/git/bsd/publisher/
```

The working copy now has all six files.

# Part 4: Routine sync to Poudriere

Use this after the bootstrap is done, whenever any of the six files in ~/git/bsd/publisher/ changes:

```
doas rm -rf /usr/local/poudriere/ports/latest/print/publisher
doas mkdir -p /usr/local/poudriere/ports/latest/print/publisher/files
doas cp ~/git/bsd/publisher/Makefile /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/distinfo /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-descr /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-message /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-plist /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/files/modules.txt /usr/local/poudriere/ports/latest/print/publisher/files/
```

## When upstream releases a new version

* Edit ~/git/bsd/publisher/Makefile and bump DISTVERSION
* If upstream go.mod changed, regenerate GH_TUPLE (Part 2)
* Re-run Part 3 to refresh distinfo (and pkg-plist if file layout changed)
* Sync (this section) and test on Poudriere (Part 5)

# Part 5: Test on Poudriere

If you have not updated Poudriere recently, do so first (see Part 3 Step 0). Then sync the working copy to Poudriere (Part 4).

Note: when testing with JAVA_TOOLS=on (default), the runtime dependencies (textproc/saxon-he, textproc/jing, textproc/trang, openjdk) must be available in the Poudriere package repo. If they have not been built yet in your jail, the first testport run will build them first, which can take a long time (especially openjdk). Subsequent runs reuse cached packages.

To test with JAVA_TOOLS off (faster, fewer deps):

```
echo "publisher_UNSET=JAVA_TOOLS" | doas tee -a /usr/local/etc/poudriere.d/15amd64-latest-make.conf
```

## Bulk build (default flavor)

```
doas poudriere bulk -j 15amd64 -p latest print/publisher
```

## Bulk build (pro flavor)

The pro flavor is non-default. Bulk build does not include non-default flavors automatically. To verify it builds correctly, use testport instead (see below).

## Full lifecycle test (default flavor)

The testport command runs build, stage, package, install, deinstall, and leak detection:

```
doas poudriere testport -j 15amd64 -p latest -o print/publisher
```

## Full lifecycle test (pro flavor)

```
doas poudriere testport -j 15amd64 -p latest -o print/publisher@pro
```

## Inspect logs on failure

```
ls /usr/local/poudriere/data/logs/bulk/15amd64-latest/latest/logs/
```

Look at the most recent log for the failed port and check the last 50 lines.

# Part 6: Run portlint

portlint runs against a ports tree, not against your working copy. Run it inside the Poudriere ports tree after you have synced your files there:

```
cd /usr/local/poudriere/ports/latest/print/publisher
portlint -C 2>&1 | tee /tmp/portlint.log
```

Real issues only. Ignore "not under git" and work-default symlink warnings.

# Part 7: Prepare for submission

## Verify with clean Poudriere build

Both default and pro flavors must pass testport with no errors before submission.

## Set git identity in the Poudriere ports tree (first time only)

Configure your maintainer identity in this repo only (no --global flag). Become root and work in the ports tree:

```
su -l
cd /usr/local/poudriere/ports/latest
git config user.email "misterd@renderdata.pro"
git config user.name "Your Real Name"
exit
```

This identity will appear in the patch header.

## Generate the patch

The submission must be a unified diff against the FreeBSD ports tree. The Poudriere ports tree is a git checkout of the official tree.

The Poudriere ports tree is owned by root, so all git operations run as root. Become root and work in the ports tree:

```
su -l
cd /usr/local/poudriere/ports/latest
```

If you have prior local commits from earlier attempts, check first:

```
git log origin/main..HEAD --oneline
```

If output shows old commits, drop them while keeping files:

```
git reset --soft origin/main
git reset HEAD
```

Stage, commit, and produce the patch:

```
git add print/publisher
git status
git commit -m "print/publisher: new port: speedata Publisher"
git format-patch -1 -o /tmp/
```

The patch is now at /tmp/0001-print-publisher-new-port-speedata-Publisher.patch (owned by root). Exit root:

```
exit
```

## Copy patch to your working copy

Copy (not move) the patch into your git working copy. Keep the original in /tmp until you confirm the copy is correct:

```
cp /tmp/0001-print-publisher-new-port-speedata-Publisher.patch ~/git/bsd/publisher/
ls -la ~/git/bsd/publisher/*.patch
head -20 ~/git/bsd/publisher/0001-print-publisher-new-port-speedata-Publisher.patch
```

The header should show your name, email, and "Subject: [PATCH] print/publisher: new port: speedata Publisher".

## Reset Poudriere ports tree

After submission (or after copying the patch out), undo the local commit so future "poudriere ports -u" works cleanly. Become root and work in the ports tree:

```
su -l
cd /usr/local/poudriere/ports/latest
git reset --soft HEAD~1
git restore --staged print/publisher
git status
exit
```

The status should show print/publisher/ as untracked, branch up to date with origin/main.

## Submit to Bugzilla

=> https://bugs.freebsd.org/bugzilla/ FreeBSD Bug Tracker

For a new port:

* Product: Ports & Packages
* Component: Individual Port(s)
* Summary: [NEW PORT] print/publisher: Non-interactive layout engine and typesetting system
* Attach: the .patch file from ~/git/bsd/publisher/

For a maintainer update:

* Summary: [MAINTAINER UPDATE] print/publisher: update to 5.4.4

# Part 8: End-user installation reference

## Supported architectures

This port currently supports amd64 only. arm64 (AArch64) is blocked at the Go toolchain level: Go does not support -buildmode=c-shared on freebsd/arm64, even though it does on linux/arm64. The Publisher build requires this mode to produce libsplib.so. When Go upstream adds freebsd/arm64 support for c-shared (or Publisher is refactored to avoid c-shared), the ONLY_FOR_ARCHS line in the port Makefile can be removed.

## Default install

```
pkg install publisher
```

This installs the default flavor with the JAVA_TOOLS option ON (see below).

## Pro flavor

The pro flavor is non-default. FreeBSD package builders do not automatically publish non-default flavors as binary packages. End users must build it from the ports tree:

```
cd /usr/ports/print/publisher
doas make FLAVOR=pro install clean
```

Or set in /etc/make.conf to make it the default for that user's system:

```
FLAVOR=	pro
```

## JAVA_TOOLS option

The port has one configurable option, JAVA_TOOLS, which is ON by default.

When JAVA_TOOLS is ON:

* textproc/saxon-he, textproc/jing, textproc/trang are added as runtime dependencies
* Symlinks are created from the FreeBSD JAR locations at ${JAVAJARDIR} into the publisher's expected paths under ${PREFIX}/lib/speedata-publisher/
* A Java runtime (openjdk) is pulled in transitively via saxon-he
* No manual setup is required for XSLT/RNG functionality

When JAVA_TOOLS is OFF:

* No Java dependencies are added
* No symlinks are created
* Users who need XSLT/RNG functionality must manually place the JARs (see pkg-message for paths and upstream download URLs)

To build with JAVA_TOOLS off:

```
cd /usr/ports/print/publisher
doas make config        # uncheck JAVA_TOOLS
doas make install clean
```

## Combining flavor and option

The pro flavor and the JAVA_TOOLS option are orthogonal. To build the pro flavor without JAVA_TOOLS:

```
cd /usr/ports/print/publisher
doas make config        # uncheck JAVA_TOOLS
doas make FLAVOR=pro install clean
```

# Part 9: Multi-machine workflow

## Source of truth: git repository

Keep the port files in a git repository. Push to a remote (GitHub, GitLab, Codeberg, or self-hosted). All machines pull from this remote.

Repository structure:

```
~/git/bsd/publisher/
├── Makefile
├── distinfo
├── pkg-descr
├── pkg-message
├── pkg-plist
├── files/
│   └── modules.txt
└── README.md
```

## Initialize the git repo (first time only, on one machine)

```
cd ~/git/bsd/publisher
git init
git add -A
git commit -m "Initial commit: print/publisher port"
git remote add origin <your-remote-url>
git push -u origin main
```

## On a new machine: clone

```
mkdir -p ~/git/bsd
cd ~/git/bsd
git clone <your-remote-url> publisher
```

## Daily workflow on any machine

Start of session, pull latest:

```
cd ~/git/bsd/publisher
git pull
```

Make changes. Sync to Poudriere ports tree (Part 4) and test (Part 5).

End of session, commit and push:

```
cd ~/git/bsd/publisher
git add -A
git commit -m "describe what changed"
git push
```

## What NOT to commit

The git repository should contain only the six port files. Never commit:

* work-default/ or work-pro/ directories
* distfiles
* Built packages (.pkg)
* Local Poudriere data
* Editor swap files
* Downloaded source tarballs (publisher-X.Y.Z/, *.tar.gz)
* Generated patches (*.diff, *.patch)

Add a .gitignore at ~/git/bsd/publisher/.gitignore:

```
work-default/
work-pro/
*.pkg
*.tar.gz
*.diff
*.patch
publisher-*/
.DS_Store
*.swp
*~
```

## Sync between machines without git

If git is not available, use rsync over ssh:

```
rsync -av --exclude 'work-*' --exclude '*.pkg' --exclude '*.tar.gz' \
    ~/git/bsd/publisher/ \
    user@othermachine:~/git/bsd/publisher/
```

Or use a shared cloud folder (Syncthing, Nextcloud) configured to ignore work directories.

## Per-machine state to keep separate

These should NOT be synced between machines:

* /usr/local/poudriere/data/ (build logs, jail state, machine-specific)
* /usr/local/poudriere/ports/latest/ (regenerated each time)
* /usr/local/poudriere/jails/ (machine-specific)

Each machine maintains its own Poudriere installation independently.

# Part 10: Phase B — after the port is merged

Once the port is accepted into FreeBSD, the workflow changes:

## On consumer machines

The port appears at /usr/ports/print/publisher/ via normal ports tree updates:

```
doas pkg install publisher          # default flavor (binary)
```

For pro flavor (source-only):

```
cd /usr/ports/print/publisher
doas make FLAVOR=pro install clean
```

## For maintainer updates

When upstream releases a new version:

* Edit DISTVERSION in ~/git/bsd/publisher/Makefile
* Check GH_TUPLE against new upstream modules (Part 2)
* Regenerate distinfo (Part 3)
* Sync to Poudriere (Part 4)
* Test on Poudriere (Part 5)
* Generate patch and submit as [MAINTAINER UPDATE] (Part 7)

The /usr/ports/print/publisher/ directory remains untouched throughout. It is read-only from the maintainer's perspective and updated only by FreeBSD via the official ports tree.

# Quick reference

## Files that must always match across all locations

The six port files (Makefile, distinfo, pkg-descr, pkg-message, pkg-plist, files/modules.txt) must be identical in:

* The git working copy at ~/git/bsd/publisher/
* /usr/local/poudriere/ports/latest/print/publisher/

When in doubt, treat ~/git/bsd/publisher/ as authoritative and copy from there.

## Common commands recap

Sync working copy to Poudriere:

```
doas rm -rf /usr/local/poudriere/ports/latest/print/publisher
doas mkdir -p /usr/local/poudriere/ports/latest/print/publisher/files
doas cp ~/git/bsd/publisher/Makefile /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/distinfo /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-descr /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-message /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/pkg-plist /usr/local/poudriere/ports/latest/print/publisher/
doas cp ~/git/bsd/publisher/files/modules.txt /usr/local/poudriere/ports/latest/print/publisher/files/
```

Poudriere bulk build (default flavor only):

```
doas poudriere bulk -j 15amd64 -p latest print/publisher
```

Poudriere full lifecycle test:

```
doas poudriere testport -j 15amd64 -p latest -o print/publisher
doas poudriere testport -j 15amd64 -p latest -o print/publisher@pro
```

Lint:

```
cd /usr/local/poudriere/ports/latest/print/publisher && portlint -C
```

Download upstream source for inspection:

```
cd /tmp && fetch https://github.com/speedata/publisher/archive/refs/tags/v5.4.3.tar.gz
```
