SDP_DeveloperGuide.adoc #10

  • //
  • p4-sdp/
  • dev/
  • doc/
  • SDP_DeveloperGuide.adoc
  • View
  • Commits
  • Open Download .zip Download (18 KB)
= SDP Developer Guide
:revnumber: v2026.1
:revdate: 2026-09-08
:doctype: book
:icons: font
:toc:
:toclevels: 5
:sectnumlevels: 4
:xrefstyle: full
// Attribute for ifdef usage
:unix_doc: true

== DRAFT NOTICE

WARNING: This document is in DRAFT status and should not be relied on yet.  It is a preview of a document to be completed in a future release.

== Preface

This guide is to aid people who contribute to the P4 Server Deployment Package (P4SDP).  This includes Perforce Staff and contributors from the general public.

== Terminology

* _Emergency Bug Fix_: A direct edit made in an already-cut release stream that changes something which actually ships in that stream's tarball -- a script, not just a doc page -- bypassing the normal `dev` -> `main` -> release-stream flow on purpose, to get a critical fix out faster than a full new Patch Release would allow. Always requires bumping that release stream's `Version` file and republishing its tarball. This is exceptional by design; if it starts feeling routine, that's a signal to cut a real Patch Release instead. See the link:ReleaseProcessOverview.md[Release Process Overview]'s "Hot Fixes and Emergency Bug Fixes" section.
* _GA Release_: A General Availability (GA) release is the first release of a new major version.  It includes a new SDP tarball as well as updates to files available on the web site with standard/published links.
* _Hot Fix_: A direct edit made in a release stream (or in `main`, ahead of the next Copy Up) that touches only web-facing, non-shipped content -- e.g. doc pages or `README.md` -- without generating a new SDP tarball. This is the preferred, lower-risk way to make an out-of-band correction, and should be the common case; it's not forbidden to go further, but if the fix needs to touch a script that actually ships, see _Emergency Bug Fix_ instead.
* _Merge Down, Copy Up_: A mantra for a well-designed release process.  See the link:https://workshop.perforce.com/projects/perforce_software-pds[Perforce Directory Standard (PDS)] for information about this. The two directions have very different cadences. *Merge Down* (pulling a parent stream's content, e.g. `main`'s, into `dev`) is safe and encouraged to do routinely, any time, to keep `dev` current. *Copy Up* (pushing `dev`'s content into `main`) is not routine: `main` is meant to reflect whatever was most recently released, not a preview of upcoming `dev` work, so it should only advance via coordinated release activity -- the link:ReleaseProcessOverview.md[Release Process Overview]'s own Copy Up step (part of actually cutting a release), an Emergency Bug Fix's Merge Down from a release stream, or a deliberate doc-only Hot Fix made directly in `main`. A `dev` fix that is tested and ready but isn't part of a release being cut right now should simply wait in `dev` -- don't Copy Up an individual change ad hoc just because it's ready.
* _Patch Release_: A Patch Release is functionally identical to a GA release, in that it includes a new SDP tarball and updates to files on the web site.  Any SDP release between major version releases of the P4 Server is a Patch.
* _Ready 5_: The property of a release process that ensures the team can always shift priorities and ship a patch quickly, even as various development tasks are in different states of readiness.  Doing work properly and keeping the `mainline` clear of work that is not ready to ship is key to maintaining readiness.

== Copy Up Scope: En-Masse vs. Selective

Beyond *when* a Copy Up happens (see the Terminology entry above), there's a separate question of *how much* of `dev` it should include. Two common models exist for this, and neither is an absolute best practice -- which one is appropriate depends on the team and the product:

* *Selective*: push up one change, or a hand-picked set of changes, at a time, explicitly marking which work is "ready" and leaving the rest behind in `dev`. This can be made reasonably safe, but only if every contributor has a strong, current understanding of the dependencies between everything they touch and everything else in the system, and if each change can be meaningfully QA'd in isolation. Even then, it carries a risk that's easy to overlook: the resulting state of `main` after a selective Copy Up is a specific combination of files that, as a whole, has never actually been tested -- by definition, that exact combination never existed as a real, checked-out tree until the moment the selective copy assembled it.
* *En-Masse* ("Copy Up Everything"): push up the entirety of `dev`'s content in one operation, with no picking and choosing. This trades away Selective's flexibility for a different kind of rigidity: it only makes sense in a workflow where comprehensive testing (integration, regression, unit, and performance testing where applicable) is done against `dev` as a whole immediately before the Copy Up -- but when that's true, the tree being copied up is *exactly* the tree that was tested, with no untested recombination possible.

As a rule of thumb: the less confident a team is that everyone fully understands the system's obscure cross-file dependencies (the "if you touch this, watch out, it can break that" kind of knowledge), the more appealing En-Masse's forced simplicity becomes.

**Decision for SDP: En-Masse.** `dev` is Copied Up to `main` in its entirety as part of the Release Process's Copy Up step (see `ReleaseProcessOverview.md`'s Step 10, including its "this is an all-or-nothing operation for the whole tree" note) -- never a hand-picked subset of changes. This is also why `dev` is required to always be release-ready (see the "Working in Streams" section below): since the whole tree moves together at Copy Up time, anything submitted to `dev` needs to already be safe to ship as-is. The Release Process's "WARNING FOR PARTIAL RELEASES" case (deliberately reverting specific files before a Copy Up) is exactly that -- a rare, explicit exception, not the norm.

== Choosing an Emergency Bug Fix vs. a Patch Release

Once a bug is found in an already-shipped release, the question is whether to fix it with an Emergency Bug Fix (patch the existing release stream in place, bump `Version`'s changelist number, and add an entry to that release's existing Release Notes section) or to cut a real Patch Release (a new, independently-numbered release with its own Release Notes section). This section covers the *policy* -- when each is appropriate; see the link:ReleaseProcessOverview.md[Release Process Overview]'s "Hot Fixes and Emergency Bug Fixes" section for the *mechanics* of actually doing an Emergency Bug Fix.

An Emergency Bug Fix is a deliberate exception to the normal release flow, not a lightweight alternative to one. It trades the safety of the normal `dev` -> `main` -> release-stream flow (with its usual review and regression testing cadence) for speed, and it does so by editing history that customers may have already started relying on -- anyone who downloaded the tarball before the fix landed has a subtly different artifact than anyone who downloads it after, even though both believe they have "SDP 2026.1 GA." That trade is worth making only when the fix is needed urgently and the fix itself is small and well-understood enough not to need the normal cadence. Three factors govern the call:

* *How much changed.* A one- or two-line fix confined to a single, well-understood root cause (e.g. a hardcoded path, an off-by-one condition) is a reasonable Emergency Bug Fix candidate. A fix that touches several files, requires design judgment, or has ripple effects into other scripts is a signal to route it through the normal flow and a real Patch Release instead, even if it feels urgent -- the whole point of the normal flow's review and regression testing is to catch exactly the kind of ripple effect a rushed fix is prone to introducing.
* *How severe the bug is.* Data loss, security exposure, or a broken install/upgrade path (something that would give every affected customer a bad first experience, or actively damage their environment) justifies the urgency an Emergency Bug Fix trades for. A cosmetic issue, a misleading but harmless message, or a narrow edge case affecting few customers does not -- let it accumulate toward the next Patch Release instead.
* *How long it's been since the release shipped.* This is a proxy for how many customers have already taken the affected artifact, and for how "fresh" the release still feels as a single coherent unit of work. A bug caught within a day or two of shipping, before most customers have even had a chance to download it, is a very different situation from one caught weeks or months later, after the release has been in the field and other work has moved on. As more time passes, prefer a real Patch Release -- patching a release stream that's been stable for a month erodes the meaning of "the same release" for anyone tracking it by changelist number, in a way that patching a release from yesterday does not.

None of these factors is individually decisive, and there's no fixed threshold (e.g. no fixed "N hours" cutoff) -- weigh them together. A severe bug caught within hours, with a small and well-understood fix, is the clearest case for an Emergency Bug Fix. Anything that fails more than one of these tests -- a sprawling fix, a minor bug, or one caught long after release -- should default to a real Patch Release instead. When genuinely unsure, prefer the Patch Release: it costs more time up front, but it doesn't compromise the normal review/testing cadence or the stability of the release history.

== Script Versioning

Many individual SDP scripts are versioned using the `+k` "keyword expansion" file type modifier. This file type modifier causes the P4 Server to update keywords in the content of the versioned file, e.g. our scripts, with a new version identifier each time the script is submitted.

Scripts that use this versioning method support the `-V` (version check) option, which gives results like these examples:

  $ ccheck.sh -V
  ccheck.sh version r26.1.0.33441

  $ ccheck.sh -V
  ccheck.sh version DEV_C2S.31580

Due to the way we are using Streams, the path to the versioned file contains meaningful information about which version of SDP the script is released with.  For example, if the script path starts with `//p4-sdp/r26.1.0`, that script is part of the SDP 2026.1 GA release. Released versions of SDP align with the P4 Server format of `rXX.Y`, with an additional `.Z` patch digit that's always present (never omitted), where `XX` is a year identifier, `Y` increments with each major release in a year, and `Z` is `0` for the GA release and increments with each patch after it (e.g. `r26.1.0` for GA, `r26.1.1` for the first patch). Unreleasd versions, such as those being developed and tested, use an ALL-UPPERCASE form of the development stream name, .e.g. DEV_C2S relates to the `//p4-sdp/dev_c2s` development stream. The uppercase is used to emphasize that version is not released.

The number at the end is the changelist number of the individual change that produced that latest version of the script. For released versions, this changelist will be due to release-process related activities rather the development code changes.

The following standard block of code (bash in this example) illustrates how the keyword is used to automatically update the version number with each submit.

  # Version ID Block. Relies on +k filetype modifier.
  #------------------------------------------------------------------------------
  # shellcheck disable=SC2016
  declare VersionID='$Id: //p4-sdp/r26.1.0/doc/gen/gen_script_man_pages.sh#2 $ $Change: 31472 $'
  declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
  declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
  declare Version=${VersionStream}.${VersionCL}
  [[ "$VersionStream" == r* ]] || Version="${Version^^}"

The line that defines `VersionID` is modified by the P4 Server upon submit, with the '$Id:$` and `$Change:$` tags being replaced. This ensures that the version is reliably updated each time the script changes.  Note that in this bash example, single quotes are used rather than double quotes to prevent the bash shell from interpreting `$Id` and `$Change` as bash script variables. The `shellcheck disable=SC2016` comment silences as ShellCheck warning about accidental usage of single quotes suppressing expansion of variables. In this case, that is exactly the intent.

== Working in Streams

Different types of work are done in different streams:

// [%autowidth,cols="a,a,a",options="header",] <-- The autowidth looked ugly.
[cols="24%a,15%a,61%a",options="header",]
|===
|Stream Name|Type|Description of Work
|`//p4-sdp/r*` +
{empty}
Examples: +
{empty}
`//p4-sdp/r26.1.0` +
`//p4-sdp/r26.1.1` | `release` | Release process activities are done in release streams, such as updating the Version file and generating final versions of docs and Release Notes. Each release -- the initial GA and every subsequent patch -- gets its own freshly-cut release stream from `main`; release streams are never patched or otherwise modified in place after they ship. See `doc/ReleaseProcessOverview.md` for the full process.
|`//p4-sdp/main` | `mainline` | Reflects whatever was most recently released. Regression test suites target `dev` (the release candidate), not `main` -- `main` only receives content via Copy Up from `dev` as part of cutting a release. Humans should do very little direct work in this stream outside of the release process itself.
| `//p4-sdp/dev` | `development` | This is the default development stream.  Work on features that are committed to be in the next release can be done directly in this stream, such as straightforward bug fixes and small, low-risk features.  Anything submitted to this stream _must_ be in a state where, if released today due to a need to ship an urgent patch possibly unrelated to the current change being submitted, it would be a Good Thing.  Submitting something to the default dev branch is in effect saying, "Pending verification by regression test suites, this change is good enough to be shipped." Don't submit something in the default dev stream unless you intend to do any necessary iteration (e.g. based on regression test suite results) in short order.
| `//p4-sdp/dev_*` +
{empty}
Examples: +
{empty}
`//p4-sdp/dev_c2s` +
`//p4-sdp/dev_rebrand` +
`//p4-sdp/dev_SDP-1265` | `development` +
or +
`sparsedev`| Development tasks are done in feature streams if it is not certain they are ready or whether they will be included in the next release. The `tag` is a short tag name referencing the work, e.g. "c2s" for "Classic to Streams development work", or the tag can even be a JIRA issue tag, e.g SDP-1265. Types of work done in development streams may include:

* Projects with uncertainty in their development time frames, possibly large projects and/or those requiring significant iteration.
* Prototype or Research and Development work that may never be released. Work in dev* streams can terminate stream and never be promoted, or deferred indefinitely.

**Decision rule**: as a general rule, any job that seems large or potentially
destabilizing -- e.g. a change affecting some of SDP's most critical scripts, or one
involving a multi-step/multi-hour refactor -- should get its own south-of-`dev` stream
(e.g. `//p4-sdp/dev_SDP-568`) rather than being done directly in `dev`, even if it's
already committed to ship. The reason is the invariant on the `dev` row above: `dev`
should always be ready to ship, modulo perhaps an hour of tidying if an urgent release
need arises -- SDP's release cadence is slow enough to afford that much slack, but not a
change that's still "car apart in the garage" for hours or days. Doing the work in its own
stream lets the Copy Up of that work to `main` be deferred independently, until it's
actually ready to ship (Merge Downs from `dev` into the feature stream can still happen
routinely in the meantime, same as any other stream).

When choosing `development` vs. sparsedev stream type, consider these factors:

* Use `development` streams if a plan on using push/fetch to work offline; as fetching doesn't a `sparsedev` stream only fetches stream-resdient files, not the whole workspace.
* Use `sparsedev` if you're working on focused changes, e.g. to just a few scripts or files.

|===

== Documentation Builds

HTML and PDF documentation are generated from AsciiDoc/Markdown sources via `make` in the `doc/` directory (see the link:ReleaseProcessOverview.md[Release Process Overview]'s doc-generation steps).

IMPORTANT: Regenerate PDFs only as part of the release process itself, or when specifically checking PDF rendering/formatting -- never as a routine step alongside an ordinary doc edit. PDFs are large, heavy files with almost no incremental value during development; regenerating one for every small doc change wastes disk space and adds bulk to changelists for no benefit. HTML is cheap to regenerate and should be kept current with its source; PDF generation is deliberately deferred to release time.

== Code Reviews

Code reviews can occur in any stream. Both pre- and post-commit reviews are allowed.  Reviews in the default dev stream can be done for changes initiated directly in the default dev stream, as well as Copy Up changes from dev* streams, thus reviewing the sum of a series of iterative changes in a lower dev* stream in a single review. More granular reviews can also occur in directly in dev* streams.

== P4 Code Review and Stream Paths

[appendix]
== Other Documentation

See Also: 

* link:ReleaseProcessOverview.html[SDP Release Process Overview].

[appendix]
== DRAFT NOTICE

WARNING: This document is in DRAFT status and should not be relied on yet.  It is a preview of a document to be completed in a future release.
# Change User Description Committed
#10 33744 Claude (AI Agent by Anthropic) Document south-of-dev stream policy for large/destabilizing work in the Developer Guide
#9 33711 Claude (AI Agent by Anthropic) SDP_DeveloperGuide.adoc: add 'Copy Up Scope: En-Masse vs.
Selective' section, documenting the two common models and their tradeoffs, and recording the decision that SDP uses En-Masse (Copy Up Everything) -- ties together with dev's always-release-ready requirement and the Release Process's all-or-nothing Copy Up step.

Prompted by the user expanding on yesterday's Copy Up cadence correction. Deliberately staying in dev only, per that same policy.
#8 33710 Claude (AI Agent by Anthropic) SDP_DeveloperGuide.adoc / ReleaseProcessOverview.md: make explicit that Copy Up is reserved for coordinated release activity (Release Process Step 10, EBF Merge Down, or a doc-only Hot Fix in main), not something to do ad hoc for an individual dev fix.
Merge Down remains fine to do routinely.

Prompted by the user's correction after today's SDP-1397 work: main is meant to reflect the most recently shipped release, not a preview of upcoming dev work. Deliberately staying in dev only for now, per the policy this documents.
#7 33702 Claude (AI Agent by Anthropic) SDP_DeveloperGuide.adoc: add 'Choosing an Emergency Bug Fix vs.
a Patch Release' policy section (change size, bug severity, detection lag), picking up the open question from 2026-09-09's SDP-1394 EBF. Cross-reference from ReleaseProcessOverview.md's Hot Fix/EBF section.
#6 33641 Claude (AI Agent by Anthropic) Merge Down main -> dev: brings in everything that had accumulated
in main without flowing back to dev -- today's SDP-1394/1395
Emergency Bug Fixes, the ReadMe.md hot fix, the P4D Version
Compatibility fix, the EBF-flow learnings in
ReleaseProcessOverview.md, and a backlog of doc-only revdate
stamps from Step 11 of the 2026.1 GA release that had never been
merged down. All files resolved cleanly (0 conflicting); dev's own
unique content (Step 22 "Announce the Release", the SampleStorage
AWS mount-point fix) was correctly preserved alongside main's
incoming changes.

Agent: Claude (Sonnet 5), on behalf of Tom Tyler.
#5 33540 Claude (AI Agent by Anthropic) Document the PDF-regeneration policy: only at release time, not
during ordinary development.

Per the user: PDFs are large, heavy files with almost no
incremental value day to day; regenerating them for every doc
change wastes disk space for no benefit. HTML stays cheap to
regenerate and should track its source; PDF generation is
deliberately deferred to the release process (Step 14 of
ReleaseProcessOverview.md), where it gets caught up all at once.

Added a "Documentation Builds" section to SDP_DeveloperGuide.adoc,
and a cross-referencing note at ReleaseProcessOverview.md's Step 14.

Agent: Claude (Sonnet 5), on behalf of Tom Tyler.
#4 33474 Claude (AI Agent by Anthropic) Formalize Hot Fix vs.
Emergency Bug Fix policy for direct patches to main/release streams.

Clarifies that dev -> main -> release is the preferred, natural flow but not
a rigid one-way gate: direct patches at developer discretion are allowed,
generally limited to doc/web-page-only content (a Hot Fix, no Version/tarball
impact since that content is isolate'd per stream) but not absolutely
restricted to that. Introduces Emergency Bug Fix as the distinct, exceptional
case where actual shipped software is patched directly in an already-cut
release stream -- the one non-negotiable rule being that any such change
requires bumping that stream's own Version file and republishing its
tarball, so Version and the tarball never drift out of sync. Updates the
SDP_DeveloperGuide.adoc Terminology section and adds a new 'Hot Fixes and
Emergency Bug Fixes' section to ReleaseProcessOverview.md. Supersedes the
old SDP_DeveloperGuide.adoc Hot Fix definition, which described release-
stream patching in terms that didn't match the actual established flow.

Agent: Claude Sonnet 5 (claude-sonnet-5), via Claude Code.
#3 33461 Claude (AI Agent by Anthropic) Fix outdated r25.1 examples and stale release-flow description in SDP_DeveloperGuide.adoc.

Updated version examples to r26.1.0, and fixed the naming-convention
description to reflect the actual decided format: rXX.Y.Z with Z always
present (0 for GA), not the old optional-patch-digit description. Also
corrected the release-stream/main table row, which described the OLD,
explicitly-rejected model (hotfixes made directly in release streams,
merged down into main, regression suites targeting main) -- now matches
the actual process: release streams are never patched in place, and
regression targets dev, not main.

Flagged separately (not fixed here, needs a decision): the 'Hot Fix'
glossary entry above this table describes making changes directly in a
release stream without a new tarball -- same outdated concept, left for
a deliberate decision rather than silently rewriting a named term.

Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic.
#2 33456 Claude (AI Agent by Anthropic) Merge Down from main into dev: reconcile today's release-rehearsal hotfixes.

All 19 files here are fixes made directly in main today while exercising
the release process for real (revnumber/revdate bumps, ReleaseNotes'
2026.1 GA section, gen_file_format_check.sh's stale-path/VersionID fix,
gen_script_man_pages.sh's missing-script/stale-path fix,
preview_changes_and_update_version.sh's first-release Version-add fix,
and ReleaseProcessOverview.md's several corrections). Verified these are
exactly today's own edits with nothing else touching these files
concurrently, so accept-theirs (main's content) is correct and safe here,
not a blind resolve.

Agent: Claude Code, Model: Claude Sonnet 5 (claude-sonnet-5), operating as bot_Claude_Anthropic.
#1 33409 Claude (AI Agent by Anthropic) Copy Up from //p4-sdp/dev_rebrand into //p4-sdp/dev.

This is the first promotion of dev_rebrand's work into dev since
dev_rebrand was created (2025-05-24) -- 303 files, covering the entire
2026.1 rebranding effort (SDP-1379), the Secure By Default adaptation
(SDP-1350), OrgName-aware auth.id/ServerID (SDP-1286), RCS-keyword
version identification (SDP-1161/SDP-799), and the Streams-native release
process redesign (Task 5) done this session, plus everything else
accumulated in dev_rebrand's history before this session.

Per the Merge Down/Copy Up flow, this is intentionally a full,
unconditional blast-replace of dev's content from dev_rebrand -- all
selectivity/care happened in the preceding Merge Down (dev -> dev_rebrand,
changes 33407-33408), which absorbed Robert Cowham's independent dev-side
work first so nothing of his is lost by this Copy Up.

Two files are worth calling out since they might look alarming in
isolation:
- tools/mdcu.sh is deleted -- intentional, retired this session in favor
  of the two direct Streams commands now documented in
  doc/ReleaseProcessOverview.md.
- tools/ReleaseProcessOverview.md is deleted -- this is a stale relic of
  a file move dev_rebrand made back in 2025-05-24 (tools/ -> doc/) that
  was never previously propagated to dev; the current, fully-rewritten
  doc/ReleaseProcessOverview.md is added/updated correctly by this same
  changelist.
//p4-sdp/dev_rebrand/doc/SDP_DeveloperGuide.adoc
#1 31752 C. Thomas Tyler In dev_rebrand, bringing in changes from dev_c2s ala:

p4 merge --from dev_c2s
p4 resolve -am

No interactive resolve was needed.
//p4-sdp/dev_c2s/doc/SDP_DeveloperGuide.adoc
#1 31696 C. Thomas Tyler Added preliminary SDP Developer Guide.
WIP.