DeveloperGuide.md #2

  • //
  • p4lf/
  • main/
  • docs/
  • DeveloperGuide.md
  • Markdown
  • View
  • Commits
  • Open Download .zip Download (9 KB)

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 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:

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 '\$'

Testing and Quality Checks

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 (Major.Minor.Patch).

The version is hand-maintained in internal/version/version.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:

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:

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

Cutting a release stream

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.

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.

# 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
#2 33277 C. Thomas Tyler Promote docs/DeveloperGuide.md wording clarification from dev to main: p4 switch vs.
dedicated per-stream workspace are both valid promotion approaches.
#1 33273 C. Thomas Tyler Promote v1.0.4 from dev to main: p4lf.service p4_vars fix, README install docs + DeveloperGuide link, DeveloperGuide.md, SemVer 1.0.4.
//p4lf/dev/docs/DeveloperGuide.md
#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.