This guide is for developers working on p4lf itself (as opposed to end users
installing/operating it — see README.md and install.sh for that). It
covers required tooling, the build system, versioning, and the Perforce
stream/promotion workflow.
go version.Makefile; no other build tool is required.make fmt / make fmt-fix.//p4lf/dev stream (or a //p4lf/dev_TAG
stream parented by it, for experimental/disposable work). See
Perforce Workflow below.install.sh end-to-end (it downloads
release artifacts from workshop.perforce.com).No other dependencies are required; p4lf's Go module has no third-party
runtime dependencies beyond the Go standard library (check go.mod if that
ever changes).
All builds are driven by the Makefile. Common targets:
make build # Build for the current platform → bin/p4lf
make build-linux # Cross-compile Linux amd64 (primary deployment target)
make build-linux-arm64 # Cross-compile Linux arm64
make build-darwin # Cross-compile macOS arm64 (Apple Silicon dev machines)
make build-darwin-amd64 # Cross-compile macOS Intel
make build-all # All of the above
Each binary embeds the current SemVer (see Versioning below)
via -ldflags, plus Perforce keyword expansion ($Id$, $Change$,
$DateTime$) baked into internal/version/version.go at submit time. Check
what's embedded in a given binary with:
bin/p4lf -version
# or, without running the binary:
strings bin/p4lf | grep '\$'
make test # go test -race -count=1 ./...
make test-verbose # same, with -v
make vet # go vet ./...
make fmt # Check gofmt compliance (fails if files need formatting)
make fmt-fix # Apply gofmt and rewrite files in place
make check # vet + fmt + test — run this before every submit
Always run make check before submitting a changelist. CI/reviewers expect
a clean make check as a baseline.
p4lf follows Semantic Versioning (Major.Minor.Patch).
The version is hand-maintained in internal/version/version.go:
var SemVer = "1.0.4"
To bump the version:
p4 edit internal/version/version.goSemVer string, e.g. "1.0.4" → "1.0.5".make check to confirm nothing is broken.Guidelines for which part to bump:
X.Y.Z+1) — bug fixes, doc-only changes, internal refactors
with no behavior change visible to operators.X.Y+1.0) — new config options, new features, backward-
compatible behavior changes.X+1.0.0) — breaking changes (e.g. config format changes,
removed options, changed on-disk state/chunk file formats).The version can also be overridden at build time without touching the source, e.g. for a CI/release pipeline that wants to stamp a different value:
make build VERSION=1.2.3
# equivalent to:
go build -ldflags "-X workshop.perforce.com/p4lf/internal/version.SemVer=1.2.3" ...
version.go also embeds Perforce keywords ($Id$, $Change$, $DateTime$)
which are expanded automatically by Perforce on submit — these are informational
(depot path/revision, changelist, submit date) and require no manual
maintenance. Do not hand-edit the keyword lines; just leave them as $Id$ etc.
in new files and let Perforce expand them.
See ai/P4WorkflowNotes.md for full details on streams, jobs, and how AI
sessions interact with version control. Summary for day-to-day development:
//p4lf/dev — the main development stream. Iterate freely here; the
quality bar is "passes make check", not "release-ready". This is where
we are currently working.//p4lf/dev_TAG — optional short-lived streams parented by dev, for
prototyping, R&D, or disposable/experimental work (TAG is a job-style
identifier, e.g. P4LF-3). Use only when a change is risky/experimental
enough to want an easy way to discard it.//p4lf/main — populated only via a Copy Up from dev, after the
change has had some testing in dev. main is expected to always be
usable as "latest release" — e.g. install.sh defaults to installing
from main, and https://workshop.perforce.com/download/p4lf/main/bin/...
is treated as the stable download URL.//p4lf/rX.Y — release streams, populated via a Copy Up from main
when cutting a release. Emergency fixes can be made directly in a release
stream if needed, but must then be Merged Down to main, then to
dev, and from there to any active dev_TAG streams.There are two equally valid ways to work across streams:
//p4lf/dev and a separate client mapped to //p4lf/main. This is the
more traditional approach and tends to be simpler and more predictable for
larger or more complex workspaces.p4 switch. This works fine for a small project like this one (it's how
this promotion was actually done), but p4 switch has more edge cases and
gotchas at larger scale or with more complex workspace mappings, so don't
treat it as the only or default approach — use whichever fits your setup.If using p4 switch:
p4 switch -l # list available streams; '*' marks the current one
p4 switch main # switch this client to //p4lf/main
If using a dedicated main workspace instead, just cd into it — no
p4 switch needed.
Preview what would be promoted before doing it for real:
p4 copy -n -S //p4lf/dev # dry run — shows what would sync/integrate/branch
p4 copy -S //p4lf/dev # opens the files for integrate/branch
p4 submit -d "Promote vX.Y.Z from dev to main: <summary>."
Only promote code from dev that has passed make check and had at least
some manual/integration testing. main should always be safe to treat as
the current stable release.
Important — rebuild binaries in main after promoting. Because bin/
is an isolated path (see The ai/ Folder below), the
copy-up above will not bring main's binaries up to date — the copy
only touches source/docs/config files. You must rebuild and submit binaries
separately, from within the main stream:
make build-all
p4 reconcile bin/... # or: p4 rec bin/...
p4 submit -d "Rebuild platform binaries for vX.Y.Z in main." bin/...
Then verify:
./bin/p4lf-<platform> -version # confirm the new SemVer and CL are stamped in
p4 status ... # confirm the workspace is clean (no reconcile needed)
When you're done, switch back to dev to resume development (if using
p4 switch; if using a dedicated workspace per stream, just cd back):
p4 switch dev
Run from the new //p4lf/rX.Y workspace, after promoting to main:
p4 copy //p4lf/main/... //p4lf/rX.Y/...
# Then, in the rX.Y workspace, build and add platform binaries:
make build-all
p4 add -t binary bin/p4lf-linux-amd64 bin/p4lf-linux-arm64
p4 add -t binary bin/p4lf-darwin-arm64 bin/p4lf-darwin-amd64
p4 submit -d "Release vX.Y.Z: add platform binaries."
See the ## Release section of the Makefile for the exact commands, and
make release VERSION=X.Y.Z to build all platforms and produce dist/
tarballs locally for testing before cutting a real release stream.
Perforce jobs (e.g. P4LF-3) are used in place of a separate issue tracker.
See ai/P4WorkflowNotes.md for the job spec format, Type/Severity
values, and how to link a job to a changelist with p4 fix -c <change> <job>.
ai/ FolderThe ai/ directory (this repo's root also symlinks ai/AGENTS.md to
copilot-instructions.md) holds AI-assistant working notes: the current
AGENTS.md task brief and dated session logs (e.g.
ai/session_log_2026-08-25.md). These are not part of the shipped
product — they exist purely to help a human or a future AI session pick up
context on prior work, decisions, and rationale.
Because of that, ai/ (along with bin/) is configured as an isolated
path in the //p4lf/main stream spec (Paths: includes share ... plus
isolate ai/... and isolate bin/...). This means:
ai/ content is versioned in //p4lf/dev (and any dev_TAG streams
parented by it), but a Copy Up from dev to main will not bring
ai/ along — it stays isolated to the dev side of the tree.bin/ is isolated for a different reason: it's expected to exist in every
stream, but each stream builds and commits its own binaries rather than
inheriting them from dev (e.g. main gets freshly built binaries as
part of promotion/release, per the release workflow above).Net effect: when reading main or a release stream, you'll see the product
source and binaries, but not the ai/ session history — that history is
intentionally kept dev-side.
# P4LF Developer Guide This guide is for developers working on p4lf itself (as opposed to end users installing/operating it — see `README.md` and `install.sh` for that). It covers required tooling, the build system, versioning, and the Perforce stream/promotion workflow. ## Required Tools * **Go 1.20+** — install from https://go.dev/dl/ if not already present. Verify with `go version`. * **make** — the project's build/test/release automation is driven entirely through the `Makefile`; no other build tool is required. * **gofmt** — ships with the Go toolchain; used by `make fmt` / `make fmt-fix`. * **A P4 client** — mapped to the `//p4lf/dev` stream (or a `//p4lf/dev_TAG` stream parented by it, for experimental/disposable work). See [Perforce Workflow](#perforce-workflow) below. * **curl** — only needed if testing `install.sh` end-to-end (it downloads release artifacts from workshop.perforce.com). No other dependencies are required; p4lf's Go module has no third-party runtime dependencies beyond the Go standard library (check `go.mod` if that ever changes). ## Building All builds are driven by the `Makefile`. Common targets: ```bash make build # Build for the current platform → bin/p4lf make build-linux # Cross-compile Linux amd64 (primary deployment target) make build-linux-arm64 # Cross-compile Linux arm64 make build-darwin # Cross-compile macOS arm64 (Apple Silicon dev machines) make build-darwin-amd64 # Cross-compile macOS Intel make build-all # All of the above ``` Each binary embeds the current `SemVer` (see [Versioning](#versioning) below) via `-ldflags`, plus Perforce keyword expansion (`$Id$`, `$Change$`, `$DateTime$`) baked into `internal/version/version.go` at submit time. Check what's embedded in a given binary with: ```bash bin/p4lf -version # or, without running the binary: strings bin/p4lf | grep '\$' ``` ## Testing and Quality Checks ```bash make test # go test -race -count=1 ./... make test-verbose # same, with -v make vet # go vet ./... make fmt # Check gofmt compliance (fails if files need formatting) make fmt-fix # Apply gofmt and rewrite files in place make check # vet + fmt + test — run this before every submit ``` Always run `make check` before submitting a changelist. CI/reviewers expect a clean `make check` as a baseline. ## Versioning p4lf follows [Semantic Versioning](https://semver.org/) (Major.Minor.Patch). The version is hand-maintained in `internal/version/version.go`: ```go var SemVer = "1.0.4" ``` **To bump the version:** 1. `p4 edit internal/version/version.go` 2. Update the `SemVer` string, e.g. `"1.0.4"` → `"1.0.5"`. 3. Run `make check` to confirm nothing is broken. 4. Submit as part of (or immediately alongside) the change(s) that justify the bump. Guidelines for which part to bump: * **Patch** (`X.Y.Z+1`) — bug fixes, doc-only changes, internal refactors with no behavior change visible to operators. * **Minor** (`X.Y+1.0`) — new config options, new features, backward- compatible behavior changes. * **Major** (`X+1.0.0`) — breaking changes (e.g. config format changes, removed options, changed on-disk state/chunk file formats). The version can also be overridden at build time without touching the source, e.g. for a CI/release pipeline that wants to stamp a different value: ```bash make build VERSION=1.2.3 # equivalent to: go build -ldflags "-X workshop.perforce.com/p4lf/internal/version.SemVer=1.2.3" ... ``` `version.go` also embeds Perforce keywords (`$Id$`, `$Change$`, `$DateTime$`) which are expanded automatically by Perforce on submit — these are informational (depot path/revision, changelist, submit date) and require no manual maintenance. Do not hand-edit the keyword lines; just leave them as `$Id$` etc. in new files and let Perforce expand them. ## Perforce Workflow See `ai/P4WorkflowNotes.md` for full details on streams, jobs, and how AI sessions interact with version control. Summary for day-to-day development: * **`//p4lf/dev`** — the main development stream. Iterate freely here; the quality bar is "passes `make check`", not "release-ready". This is where we are currently working. * **`//p4lf/dev_TAG`** — optional short-lived streams parented by `dev`, for prototyping, R&D, or disposable/experimental work (`TAG` is a job-style identifier, e.g. `P4LF-3`). Use only when a change is risky/experimental enough to want an easy way to discard it. * **`//p4lf/main`** — populated only via a **Copy Up** from `dev`, after the change has had some testing in `dev`. `main` is expected to always be usable as "latest release" — e.g. `install.sh` defaults to installing from `main`, and `https://workshop.perforce.com/download/p4lf/main/bin/...` is treated as the stable download URL. * **`//p4lf/rX.Y`** — release streams, populated via a Copy Up from `main` when cutting a release. Emergency fixes can be made directly in a release stream if needed, but must then be **Merged Down** to `main`, then to `dev`, and from there to any active `dev_TAG` streams. ### Promoting dev → main There are two equally valid ways to work across streams: * **A dedicated workspace per stream** — e.g. one client mapped to `//p4lf/dev` and a separate client mapped to `//p4lf/main`. This is the more traditional approach and tends to be simpler and more predictable for larger or more complex workspaces. * **A single stream-switchable client**, repurposed on the fly with `p4 switch`. This works fine for a small project like this one (it's how this promotion was actually done), but `p4 switch` has more edge cases and gotchas at larger scale or with more complex workspace mappings, so don't treat it as the only or default approach — use whichever fits your setup. If using `p4 switch`: ```bash p4 switch -l # list available streams; '*' marks the current one p4 switch main # switch this client to //p4lf/main ``` If using a dedicated `main` workspace instead, just `cd` into it — no `p4 switch` needed. Preview what would be promoted before doing it for real: ```bash p4 copy -n -S //p4lf/dev # dry run — shows what would sync/integrate/branch p4 copy -S //p4lf/dev # opens the files for integrate/branch p4 submit -d "Promote vX.Y.Z from dev to main: <summary>." ``` Only promote code from `dev` that has passed `make check` and had at least some manual/integration testing. `main` should always be safe to treat as the current stable release. **Important — rebuild binaries in `main` after promoting.** Because `bin/` is an `isolate`d path (see [The `ai/` Folder](#the-ai-folder) below), the copy-up above will **not** bring `main`'s binaries up to date — the copy only touches source/docs/config files. You must rebuild and submit binaries separately, from within the `main` stream: ```bash make build-all p4 reconcile bin/... # or: p4 rec bin/... p4 submit -d "Rebuild platform binaries for vX.Y.Z in main." bin/... ``` Then verify: ```bash ./bin/p4lf-<platform> -version # confirm the new SemVer and CL are stamped in p4 status ... # confirm the workspace is clean (no reconcile needed) ``` When you're done, switch back to `dev` to resume development (if using `p4 switch`; if using a dedicated workspace per stream, just `cd` back): ```bash p4 switch dev ``` ### Cutting a release stream Run from the new `//p4lf/rX.Y` workspace, after promoting to `main`: ```bash p4 copy //p4lf/main/... //p4lf/rX.Y/... # Then, in the rX.Y workspace, build and add platform binaries: make build-all p4 add -t binary bin/p4lf-linux-amd64 bin/p4lf-linux-arm64 p4 add -t binary bin/p4lf-darwin-arm64 bin/p4lf-darwin-amd64 p4 submit -d "Release vX.Y.Z: add platform binaries." ``` See the `## Release` section of the `Makefile` for the exact commands, and `make release VERSION=X.Y.Z` to build all platforms and produce `dist/` tarballs locally for testing before cutting a real release stream. ### Jobs Perforce jobs (e.g. `P4LF-3`) are used in place of a separate issue tracker. See `ai/P4WorkflowNotes.md` for the job spec format, `Type`/`Severity` values, and how to link a job to a changelist with `p4 fix -c <change> <job>`. ## The `ai/` Folder The `ai/` directory (this repo's root also symlinks `ai/AGENTS.md` to `copilot-instructions.md`) holds AI-assistant working notes: the current `AGENTS.md` task brief and dated session logs (e.g. `ai/session_log_2026-08-25.md`). These are **not part of the shipped product** — they exist purely to help a human or a future AI session pick up context on prior work, decisions, and rationale. Because of that, `ai/` (along with `bin/`) is configured as an **isolated** path in the `//p4lf/main` stream spec (`Paths:` includes `share ...` plus `isolate ai/...` and `isolate bin/...`). This means: * `ai/` content is versioned in `//p4lf/dev` (and any `dev_TAG` streams parented by it), but a Copy Up from `dev` to `main` will **not** bring `ai/` along — it stays isolated to the dev side of the tree. * `bin/` is isolated for a different reason: it's expected to exist in every stream, but each stream builds and commits its own binaries rather than inheriting them from `dev` (e.g. `main` gets freshly built binaries as part of promotion/release, per the release workflow above). Net effect: when reading `main` or a release stream, you'll see the product source and binaries, but not the `ai/` session history — that history is intentionally kept dev-side.
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #3 | 33276 | C. Thomas Tyler | docs/DeveloperGuide.md: clarify that a dedicated per-stream workspace and a single p4-switch-repurposed client are both valid ways to promote dev -> main; don't imply p4 switch is the only/default approach. | ||
| #2 | 33275 | C. Thomas Tyler |
Post-release retrospective: update DeveloperGuide.md promotion workflow, clean up ai/AGENTS.md, update session log with customer confirmation. - docs/DeveloperGuide.md: rewrite 'Promoting dev -> main' section to match actual practice (p4 switch, p4 copy -n dry run, and the previously undocumented requirement to rebuild+submit binaries in main afterward since bin/ is isolated per-stream). - ai/AGENTS.md: remove stale task-specific content (old NEW REQUIREMENTS list, stale 'Current Project State as of CL 32835'); replace with an evergreen structure (Current Status, Background Info, Quick Start, Key Files, Version Control Interaction, ai/ folder) that stays useful across future sessions. Current Status now notes v1.0.4 released to main and customer-confirmed fix for the p4lf.service startup issue (P4LF-3). - ai/session_log_2026-08-25.md: document the dev->main release walkthrough and the customer's confirmation that the fix resolved their issue. |
||
| #1 | 33271 | C. Thomas Tyler |
Add README install.sh docs, bump SemVer to 1.0.4, add DeveloperGuide.md. - README.md: add an Installation section (right after the intro) summarizing install.sh usage/options, so new readers see it first. - internal/version/version.go: bump SemVer 1.0.3 -> 1.0.4. - docs/DeveloperGuide.md (new): required tooling, Makefile build/test/release targets, how/when to bump SemVer, dev -> main -> release stream promotion/merge workflow, and why ai/ + bin/ are isolated stream paths. |