# Makefile for p4lf — P4 Log Feeder
#
# Common targets:
# make build Build for the current platform (output: bin/p4lf)
# make test Run all unit tests
# make vet Run go vet
# make fmt Run gofmt check (no writes; use fmt-fix to apply)
# make fmt-fix Run gofmt and rewrite files
# make clean Remove build artifacts
#
# Cross-compilation targets:
# make build-linux Linux amd64 (primary deployment target)
# make build-linux-arm64 Linux arm64
# make build-darwin macOS arm64 (Apple Silicon dev machines)
# make build-darwin-amd64 macOS Intel
# make build-all All of the above
#
# Install target (Linux only, requires INSTANCE to be set):
# make install INSTANCE=1
#
# Release:
# make release VERSION=0.2.0 Build all platforms, create dist/ tarballs
# ── Variables ────────────────────────────────────────────────────────────────
MODULE := workshop.perforce.com/p4lf
CMD := ./cmd/p4lf
BIN_NAME := p4lf
BIN_DIR := bin
DIST_DIR := dist
# Version: read from version.go (the hand-maintained SemVer var).
# Override on the command line to inject a different value at build time:
# make build VERSION=1.0.0
VERSION ?= $(shell grep -m1 '^var SemVer' internal/version/version.go | sed 's/.*"\(.*\)".*/\1/')
LDFLAGS := -ldflags "-X workshop.perforce.com/p4lf/internal/version.SemVer=$(VERSION) -s -w"
# Perforce: capture current default changelist number for release metadata.
P4CL := $(shell p4 changes -m1 -s submitted //... 2>/dev/null | awk '{print $$2}' || echo "unknown")
# ── Phony targets ─────────────────────────────────────────────────────────────
.PHONY: build build-linux build-linux-arm64 build-darwin build-darwin-amd64 \
build-all test vet fmt fmt-fix clean install release help
# Default target.
all: build
# ── Build ─────────────────────────────────────────────────────────────────────
## build: Build p4lf for the current platform → bin/p4lf
build:
@mkdir -p $(BIN_DIR)
go build $(LDFLAGS) -o $(BIN_DIR)/$(BIN_NAME) $(CMD)
@echo "Built $(BIN_DIR)/$(BIN_NAME) ($(shell uname -s)/$(shell uname -m), v$(VERSION))"
## build-linux: Cross-compile for Linux amd64 → bin/p4lf-linux-amd64
build-linux:
@mkdir -p $(BIN_DIR)
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(BIN_NAME)-linux-amd64 $(CMD)
@echo "Built $(BIN_DIR)/$(BIN_NAME)-linux-amd64 (v$(VERSION))"
## build-linux-arm64: Cross-compile for Linux arm64 → bin/p4lf-linux-arm64
build-linux-arm64:
@mkdir -p $(BIN_DIR)
GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o $(BIN_DIR)/$(BIN_NAME)-linux-arm64 $(CMD)
@echo "Built $(BIN_DIR)/$(BIN_NAME)-linux-arm64 (v$(VERSION))"
## build-darwin: Cross-compile for macOS arm64 (Apple Silicon) → bin/p4lf-darwin-arm64
build-darwin:
@mkdir -p $(BIN_DIR)
GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(BIN_DIR)/$(BIN_NAME)-darwin-arm64 $(CMD)
@echo "Built $(BIN_DIR)/$(BIN_NAME)-darwin-arm64 (v$(VERSION))"
## build-darwin-amd64: Cross-compile for macOS Intel → bin/p4lf-darwin-amd64
build-darwin-amd64:
@mkdir -p $(BIN_DIR)
GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BIN_DIR)/$(BIN_NAME)-darwin-amd64 $(CMD)
@echo "Built $(BIN_DIR)/$(BIN_NAME)-darwin-amd64 (v$(VERSION))"
## build-all: Build for all platforms
build-all: build-linux build-linux-arm64 build-darwin build-darwin-amd64
@echo "All platform builds complete."
# ── Test & Quality ────────────────────────────────────────────────────────────
## test: Run all unit tests with race detector
test:
go test -race -count=1 ./...
## test-verbose: Run tests with verbose output
test-verbose:
go test -race -count=1 -v ./...
## vet: Run go vet
vet:
go vet ./...
## fmt: Check formatting (does not modify files; exits non-zero if changes needed)
fmt:
@unformatted=$$(gofmt -l .); \
if [ -n "$$unformatted" ]; then \
echo "The following files need gofmt:"; \
echo "$$unformatted"; \
exit 1; \
else \
echo "All files are properly formatted."; \
fi
## fmt-fix: Apply gofmt to all Go source files
fmt-fix:
gofmt -w .
## check: Run vet + fmt + test
check: vet fmt test
# ── Install ───────────────────────────────────────────────────────────────────
# Installs to the SDP log_feeder directory on a Linux system.
# Requires: INSTANCE variable (SDP instance number, e.g. make install INSTANCE=1)
SDP_SITE_DIR := /p4/common/site/log_feeder
SYSTEMD_DIR := /etc/systemd/system
## install: Install p4lf binary, config example, and systemd unit (requires root or sudo)
install: build-linux
@if [ -z "$(INSTANCE)" ]; then \
echo "Error: INSTANCE is required. Usage: make install INSTANCE=1"; \
exit 1; \
fi
@echo "Installing p4lf to $(SDP_SITE_DIR)..."
install -d $(SDP_SITE_DIR)
install -m 0755 $(BIN_DIR)/$(BIN_NAME)-linux-amd64 $(SDP_SITE_DIR)/$(BIN_NAME)
@if [ ! -f $(SDP_SITE_DIR)/p4lf.cfg ]; then \
install -m 0640 p4lf.cfg.example $(SDP_SITE_DIR)/p4lf.cfg; \
echo " Installed default config to $(SDP_SITE_DIR)/p4lf.cfg — please review and edit."; \
else \
echo " Config $(SDP_SITE_DIR)/p4lf.cfg already exists; not overwriting."; \
fi
@echo "Installing systemd unit..."
install -m 0644 p4lf.service $(SYSTEMD_DIR)/p4lf.service
systemctl daemon-reload
@echo ""
@echo "Installation complete. Next steps:"
@echo " 1. Edit $(SDP_SITE_DIR)/p4lf.cfg"
@echo " 2. systemctl enable p4lf"
@echo " 3. systemctl start p4lf"
@echo " 4. systemctl status p4lf"
# ── Release ───────────────────────────────────────────────────────────────────
## release: Build all platforms and create dist/ tarballs
release: build-all
@mkdir -p $(DIST_DIR)
@echo "Creating release archives for v$(VERSION) (P4CL=$(P4CL))..."
@for platform in linux-amd64 linux-arm64 darwin-arm64 darwin-amd64; do \
archive=$(DIST_DIR)/$(BIN_NAME)-$(VERSION)-$$platform.tar.gz; \
tar -czf $$archive \
-C $(BIN_DIR) $(BIN_NAME)-$$platform \
p4lf.cfg.example \
p4lf.service \
install.sh \
README.md; \
echo " Created $$archive"; \
done
@echo "Release archives written to $(DIST_DIR)/"
# ── Clean ─────────────────────────────────────────────────────────────────────
## clean: Remove build and release artifacts
clean:
rm -rf $(BIN_DIR) $(DIST_DIR)
@echo "Cleaned."
# ── Promote ───────────────────────────────────────────────────────────────────
# Copy-up workflow: dev → main → release stream.
# Run these from a //p4lf/main or //p4lf/rX.Y workspace, NOT from dev.
#
# After testing in dev:
# p4 copy //p4lf/dev/... //p4lf/main/...
# p4 submit -d "Promote vX.Y.Z from dev to main."
#
# To cut a release stream (replace rX.Y with the actual stream name):
# p4 copy //p4lf/main/... //p4lf/rX.Y/...
# # Then in the rX.Y workspace, 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."
#
# The //p4lf/main/bin/ binaries serve as the stable 'latest release' URL:
# https://workshop.perforce.com/download/p4lf/main/bin/p4lf-linux-amd64
#
# NOTE: Only promote to main code that has been tested in dev.
# main should always be safe to rely on as 'latest release'.
# ── Help ──────────────────────────────────────────────────────────────────────
## help: Show this help message
help:
@echo "p4lf Makefile — available targets:"
@echo ""
@grep -E '^## ' $(MAKEFILE_LIST) | sed 's/## / /'
@echo ""
@echo "Variables:"
@echo " VERSION=$(VERSION) (override: make build VERSION=1.2.3)"
@echo " INSTANCE=<n> (required for: make install)"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #5 | 32834 | C. Thomas Tyler |
Add install.sh and document release/promotion workflow. install.sh (type text+x): - Must be run as root (bails immediately if not) - Detects platform (Linux amd64/arm64, macOS arm64/amd64) - Downloads binary, p4lf.cfg.example, and p4lf.service via curl -fsSL from https://workshop.perforce.com/download/p4lf/<stream>/ - Installs binary to /p4/common/site/log_feeder/p4lf (or custom -d dir) - Installs p4lf.cfg.example; never overwrites an existing p4lf.cfg - Installs/updates /etc/systemd/system/p4lf.service only if changed; runs 'systemctl daemon-reload' if updated - Supports -n (dry run), -s <stream>, -d <dir>, -h options - References P4LF-2 in output (future: perforce user service management) Makefile: - release target now includes install.sh in dist/ tarballs - Added promote section documenting dev->main->rX.Y copy-up workflow, correct workshop.perforce.com download URLs, and how to add platform binaries in a release stream |
||
| #4 | 32824 | C. Thomas Tyler |
Rename Go module path from github.com/rcowham/p4lf to workshop.perforce.com/p4lf. The rcowham prefix was a carryover from early research into go-libtail (which is hosted at github.com/rcowham/go-libtail). p4lf is hosted on the Perforce Public Depot / Workshop, not GitHub, so workshop.perforce.com is the correct canonical module path. Updated: go.mod, all *.go files with internal imports, Makefile (MODULE and LDFLAGS). Session logs retain the old path as accurate historical record. |
||
| #3 | 32821 | C. Thomas Tyler |
Fix Makefile VERSION grep to anchor on '^var SemVer'. The previous grep 'SemVer\s*=' also matched the comment in version.go that shows the -ldflags example, causing VERSION to expand to the full ldflags string instead of '0.1.0'. Anchoring with '^var SemVer' ensures only the actual Go variable declaration is matched. |
||
| #2 | 32819 | C. Thomas Tyler |
Add version stamping via Perforce keyword expansion. internal/version/version.go (type text+k): - SemVer var: hand-maintained Major.Minor.Patch, injectable via -ldflags - P4ID/P4Change/P4DateTime constants: expanded by Perforce on each submit/sync - Get() parses expanded keywords into an Info struct (stream, CL, date, revision) - String() produces: 'p4lf 0.1.0 (CL 32819, //p4lf/dev#3, 2026/06/25)' - Gracefully handles unexpanded state (pre-first-submit, non-P4 builds) internal/version/version_test.go: - Tests stream/revision parsing for dev, main, release streams - Tests Change and DateTime regex parsing cmd/p4lf/main.go: - Uses version.String() and version.SemVer instead of hardcoded const Makefile: - VERSION now read from internal/version/version.go - LDFLAGS target github.com/rcowham/p4lf/internal/version.SemVer |
||
| #1 | 32818 | C. Thomas Tyler |
Initial implementation of p4lf in Go. Adds: - Go module (github.com/rcowham/p4lf) with fsnotify dependency - internal/config: KEY=VALUE config parser with all settings - internal/tailer: file reader with inode-based rotation detection and state file checkpoint/resume (inode + byte offset, JSON, atomic write) - internal/chunker: gzip chunk writer with MaxLogChunks/MinLogSpace guards - internal/logger: rotating log writer with gzip of rotated files - cmd/p4lf/main.go: service main loop, SIGHUP/SIGTERM/SIGINT handling, config hot-reload on modtime change - Makefile: build/test/install/release targets for Linux amd64/arm64, macOS arm64/amd64; version injected via ldflags - p4lf.cfg.example: fully documented example config - p4lf.service: systemd unit file (User=perforce, Restart=on-failure) - ai/session_log_2026-06-25.md: design session log - ai/session_log_2026-06-25-2.md: implementation session log - .p4ignore: added bin/ and dist/ |