MMOframework
Why AI Playground Libraries Documentation Search Agency
FR EN
Index

BuildTask

MMO\admin\tasks · 1 symbol · generated 27 Oct 2026

class

BuildTask

BuildTask.php:55

Turn a git commit into ONE immutable, versioned artifact: build/{name}-{version}-{commit}-{libraries}.tar.gz plus its sha256.

class MMO\admin\tasks\BuildTask extends MMO\admin\classes\aDeployTask
Namespace
MMO\admin\tasks
Extends
aDeployTask
This replaces the three-branch dance the previous script performed for every client (merge `develop` into `clients/x`, build the phars, commit, push, shuffle `packages/` and `www/bin` into `_prod` folders, merge into `prod/x`, `git rm --cached` the dev-only paths, commit, push). That dance existed only because the deploy step was a `git pull` on the server, so the production tree had to BE a branch. Once a release is an artifact, none of it is needed -- and neither are the `prod` branches: - nothing mutates your working tree except a `git archive` to a scratch folder, so a failure half-way leaves you exactly where you were, instead of on an arbitrary branch with a half-merged index; - the same artifact is shipped to every target that shares a commit, so two clients on one version are bit-for-bit identical, verified by checksum rather than by hoping two merges produced the same tree; - the build is a pure function of the commit, so re-running it is free and harmless. **The artifact is identified by the COMMIT, not just the version.** It used to be `(name, version, ref)`, which meant that forgetting to bump `mmo.json` made [`isNeeded`](https://mmoframework.fr/en/documentation/mmo.admin/php/tasks/BuildTask.md#buildtask-isneeded) answer "already built" and silently re-ship the previous code as though it were the new release -- a wrong deployment that looked exactly like a successful one. A commit sha cannot be forgotten to be bumped. The one precondition is explicit rather than assumed: the working tree must be clean AND already at $ref, because the `.phar` dependencies are built from the working tree while the rest of the tree comes from the commit. [`assertWorkingTree`](https://mmoframework.fr/en/documentation/mmo.admin/php/tasks/BuildTask.md#buildtask-assertworkingtree) refuses to build otherwise. **The libraries are built from their folders, not from a commit.** Each `.phar` is packaged from the library as it is on disk -- whatever branch its checkout is on, uncommitted changes included. So each one is checked like the project ([`checkLibraries`](https://mmoframework.fr/en/documentation/mmo.admin/php/tasks/BuildTask.md#buildtask-checklibraries)): uncommitted changes stop the build unless `--force` says otherwise, or the person at the terminal does; and each library's commit and branch go into the journal ([`Context::$libraries`](https://mmoframework.fr/en/documentation/mmo.admin/php/helpers/Context.md#context-libraries)), so `status` and `history` say which code of every library is live, not only which version number. **The libraries are part of what identifies the artifact.** Its name carries a fingerprint of the commit each one is on ([`getLibrariesFingerprint`](https://mmoframework.fr/en/documentation/mmo.admin/php/tasks/BuildTask.md#buildtask-getlibrariesfingerprint)): a commit in a library, and none in the project, used to find the artifact of the project's commit already built and ship it again -- the library's change never reached a release, and every step said there was nothing to do. A library with uncommitted changes has no fingerprint: what its phar holds is no commit, so an artifact built from it is never reused. **A cached phar is reused only when it holds what its folder would build.** The package manager keeps every phar it builds (`build/{name}.{version}.phar`) and reuses it while no source file is newer -- blind to the package's own mmo.json. my.oviepro 5.0.0 changed its modules' dependencies without changing their versions, and the release shipped their 4.x manifests from the cache. So each cached phar is opened and compared with its folder ([`CachedPhar`](https://mmoframework.fr/en/documentation/mmo.admin/php/helpers/CachedPhar.md#cachedphar)): a stale one is rebuilt, under the same version, and `plan` says which and why.

Index

buildFolder
buildOptions
built
commands
dirtyAccepted
exclude
fingerprints
git
include
manifest
apply($context)
assertWorkingTree($ref, $commit, $force)Refuse to build from a working tree that does not match what is being exported.
checkLibraries($context, $manifest)Refuse to package a library with uncommitted changes -- unless told to go on.
describeLibraries($libraries)
getArtifactFilename($manifest, $commit, $libraries)
getCachedPhars($manifest)The phars the package manager would reuse rather than build: each package built from a folder whose phar for its version is already there (PackageManifest::getBuildFilename), and whether it st…
getCommit($ref)
getDescription()
getIncludes()The packages the application includes (Application::getIncludes), as name => version.
getLibraries($manifest)The libraries a release packages, and the state of each one's checkout.
getLibrariesFingerprint($manifest)What the libraries a release packages are built from, in a few characters: the commit of each one, or its version when it is no git checkout (a phar, a plain folder).
getManifest()
getPackages($manifest)
isNeeded($context)An artifact for this (version, commit) that already exists is reused as-is -- which is the point of building a deterministic artifact in the first place.
key($commit, $version, $libraries)
librariesFilename($artifact)
loopOverlay($path)
overlay($context, $staging)Copy the generated (untracked) assets into the exported tree, and hash what was copied.
plan($context)
prime($context)Fill in what the rest of the pipeline needs to describe itself: the version and commit being deployed, and the artifact if one already exists.
readCheckout($folder)
readLibraries($artifact)
remember($key, $context)
reuseCachedPhars($context, $manifest, $options)Decide, from what the cached phars hold, which ones the build reuses -- not from their dates.
verify($context)
__construct($git, $manifest, $buildFolder, $exclude, $include, $commands, $buildOptions)

Properties10

buildFolder

protected string $buildFolder
Type
string

buildOptions

protected array $buildOptions
Type
array

built

protected array $built = []
Type
array

commands

protected array $commands
Type
array

dirtyAccepted

protected ?bool $dirtyAccepted = null
Type
null | bool

exclude

protected array $exclude
Type
array

fingerprints

protected WeakMap $fingerprints
Type
WeakMap

git

protected MMO\git\helpers\Repository $git
Type
Repository

include

protected array $include
Type
array

manifest

protected ?string $manifest
Type
null | string

Methods26

assertWorkingTree($ref, $commit, $force)

BuildTask.php:377

Refuse to build from a working tree that does not match what is being exported.

protected function assertWorkingTree(string $ref, string $commit, bool $force = false): void
$refstring
$commitstring
$force = falsebool
returnsvoid
throws

checkLibraries($context, $manifest)

BuildTask.php:540

Refuse to package a library with uncommitted changes -- unless told to go on.

protected function checkLibraries( MMO\admin\helpers\Context $context, MMO\core\helpers\PackageManifest $manifest ): arrayIts phar is built from its folder as it is, so what it ships would match no commit, and nobody could tell later what is running. The same rule as the project's own working tree ([`assertWorkingTree`](https://mmoframework.fr/en/documentation/mmo.admin/php/tasks/BuildTask.md#buildtask-assertworkingtree)), with a way through that does not stop the whole system: `--force`, or -- at a terminal -- a yes to the question, asked once per run. A script without `--force` is refused, as it cannot be asked.
$contextContext
$manifestPackageManifestthe project's.
returnsarraywhat the journal records of each library: {version, commit, branch, dirty}.
throwswhen a library has uncommitted changes and nobody said to go on.

describeLibraries($libraries)

BuildTask.php:573
protected function describeLibraries(array $libraries): array
$librariesarray
returnsarraythe lines of the plan saying what each library is built from.

getArtifactFilename($manifest, $commit, $libraries)

BuildTask.php:118
function getArtifactFilename( MMO\core\helpers\PackageManifest $manifest, string $commit, ?string $libraries = '' ): string
$manifestPackageManifest
$commitstringthe project's
$libraries = ''null | stringgetLibrariesFingerprint ('' for none, null when a library has uncommitted changes)
returnsstringthe artifact path for a given version, commit and libraries.

getCachedPhars($manifest)

BuildTask.php:442

The phars the package manager would reuse rather than build: each package built from a folder whose phar for its version is already there (PackageManifest::getBuildFilename), and whether it still holds that folder.

function getCachedPhars(MMO\core\helpers\PackageManifest $manifest): array
$manifestPackageManifestthe project's.
returnsarrayname => {version, namespace, phar, stale}: stale says what differs (CachedPhar::whyStale), null when the phar may be reused.

getCommit($ref)

BuildTask.php:103
function getCommit(string $ref): string
$refstring
returnsstringthe full sha of the commit $ref points at.
throwswhen the ref does not exist.

getIncludes()

BuildTask.php:488

The packages the application includes (Application::getIncludes), as name => version.

protected function getIncludes(): arrayAn include is written either way -- `['mmo.doc' => '*']` or `['mmo.doc']` -- and \MMO\inc() reads both; the package manager's buildProject() reads a map only, and took a list's `0 => 'mmo.doc'` for a package named `0`: a project listing its includes by name alone could not be built at all.
returnsarraystring>

getLibraries($manifest)

BuildTask.php:408

The libraries a release packages, and the state of each one's checkout.

function getLibraries(MMO\core\helpers\PackageManifest $manifest): arrayThe project's dependencies and the application's includes ([`Application::getIncludes`](https://mmoframework.fr/en/documentation/mmo.core/php/Application.md#application-getincludes)), as the package manager resolves them -- the same list buildProject builds -- minus the project itself.
$manifestPackageManifestthe project's.
returnsarrayname => {version, folder, commit, branch, dirty, changes}

getLibrariesFingerprint($manifest)

BuildTask.php:132

What the libraries a release packages are built from, in a few characters: the commit of each one, or its version when it is no git checkout (a phar, a plain folder). Two builds of one project commit with the same fingerprint package the same code.

function getLibrariesFingerprint(MMO\core\helpers\PackageManifest $manifest): ?string
$manifestPackageManifestthe project's.
returnsnull | string12 hex characters ('' when the project packages no library), null when a library has uncommitted changes: its phar then holds code no commit names, and nothing built from it may be reused.
$task->getLibrariesFingerprint($manifest); // '3f2a9c0d1e4b'

getPackages($manifest)

BuildTask.php:426
protected function getPackages(MMO\core\helpers\PackageManifest $manifest): array
$manifestPackageManifestthe project's.
returnsarraystring> every package the release builds, as name => version: the project, its dependencies and the application's includes -- the list buildProject builds.

isNeeded($context)

BuildTask.php:174

An artifact for this (version, commit) that already exists is reused as-is -- which is the point of building a deterministic artifact in the first place. prime still runs, so the rest of the pipeline knows the version and the checksum even when there is nothing to build.

function isNeeded(MMO\admin\helpers\Context $context): bool
$contextContext
returnsbool

librariesFilename($artifact)

BuildTask.php:584
protected function librariesFilename(string $artifact): string
$artifactstring
returnsstringwhere an artifact's libraries are recorded, beside it: a reused artifact still knows them.

loopOverlay($path)

BuildTask.php:334
protected function loopOverlay(string $path): Generator
$pathstring
returnsGeneratorevery file under $path (or $path itself when it is one), in a stable order.

overlay($context, $staging)

BuildTask.php:293

Copy the generated (untracked) assets into the exported tree, and hash what was copied.

protected function overlay(MMO\admin\helpers\Context $context, string $staging): ?stringThese are the one part of a release that does NOT come from the commit: they are whatever is in the working tree right now, and `git status --porcelain` cannot see them because they are gitignored. The hash goes into the journal so two releases built from one commit but different assets are distinguishable after the fact, and an overlay older than the commit it ships with is called out -- that is what a forgotten asset build looks like.
$contextContext
$stagingstring
returnsnull | stringsha256 over every overlaid file, or null when there is no overlay.

prime($context)

BuildTask.php:153

Fill in what the rest of the pipeline needs to describe itself: the version and commit being deployed, and the artifact if one already exists. Called by BOTH plan and apply, so a dry run's plan shows the real release name instead of a blank -- and the later tasks can tell whether they would have anything to do at all.

protected function prime(MMO\admin\helpers\Context $context): MMO\core\helpers\PackageManifest
$contextContext
returnsPackageManifest

readCheckout($folder)

BuildTask.php:503
protected function readCheckout(?string $folder): array
$foldernull | stringa library's folder.
returnsarraystring|null, commit: string|null, branch: string|null, dirty: int, changes: string[]} the commit and branch it is on (null when it is no git checkout, or a phar), and its uncommitted changes -- those under build/ aside: that is where its own phar is written, and some libraries do not ignore it.

readLibraries($artifact)

BuildTask.php:587
protected function readLibraries(string $artifact): array
$artifactstring
returnsarraythe libraries recorded beside $artifact, [] when it was built before they were.

reuseCachedPhars($context, $manifest, $options)

BuildTask.php:464

Decide, from what the cached phars hold, which ones the build reuses -- not from their dates.

protected function reuseCachedPhars( MMO\admin\helpers\Context $context, MMO\core\helpers\PackageManifest $manifest, array $options ): arrayA stale one is deleted, so the package manager builds it again under the same version; one that matches is marked not to be built (`$options[namespace] = false`), so a source merely touched since does not make the package manager increase its version in the working tree in the middle of a release. What the caller's build options already decide for a package stays as it is.
$contextContext
$manifestPackageManifestthe project's.
$optionsarraythe build options (PackageManager::buildProject).
returnsarray$options, with the decision for each cached phar.

__construct($git, $manifest, $buildFolder, $exclude, $include, $commands, $buildOptions)

BuildTask.php:65
function __construct( MMO\git\helpers\Repository $git, ?string $manifest = null, string $buildFolder = '{root}/build', array $exclude = [], array $include = [], array $commands = [], array $buildOptions = [] )
$gitRepository
$manifest = nullnull | string
$buildFolder = '{root}/build'string
$exclude = []array
$include = []array
$commands = []array
$buildOptions = []array
Inherited members (38)
ConstantsDeclared byDescription
ALERTiLogLevelAlert: Action must be taken immediately.
ALLiLogLevelMask for all
ALL_PLUSiLogLeveldebug everything
BACKTRACEiLogLevel
CRITICALiLogLevelCritical: Critical conditions.
DEBUGiLogLevelDebug: Detailed debug information.
DEEPiLogLeveldebug everything with arguments
EMERGENCYiLogLevelEmergency: System is unusable.
ERRORiLogLevelError: Runtime errors that do not require immediate action but should typically be logged and monitored.
ERROR_PLUSiLogLevelMask for all error
FATALiLogLevelMask for all fatal error
INFOiLogLevelInfo: Interesting events.
INFO_PLUSiLogLevelMask for all errors/warning/notices/info
MEMORYiLogLevelinclude backtrace object
NOTICEiLogLevelNotice: Normal but significant events.
NOTICE_PLUSiLogLevelMask for all errors/warning/notices/info
TRACEiLogLevelinclude backtrace as origins
WARNINGiLogLevelWarning: Exceptional occurrences that are not errors.
WARNING_PLUSiLogLevelMask for all errors and warnings
PropertiesDeclared byDescription
verbosityaDeployTask
MethodsDeclared byDescription
alert($message, $context)aDeployTaskAction must be taken immediately.
backtraces($message, $context)aDeployTaskForce-log a message at INFO level together with a full stack trace/memory snapshot (iLogLevel::BACKTRACE), bypassing the verbosity mask entirely (any non-zero verbosity is enough to trigger it).
critical($message, $context)aDeployTaskCritical conditions. No-op if CRITICAL is currently filtered out by the verbosity mask.
debug($message, $context)aDeployTaskDetailed debug information.
debugName()aDeployTask
emergency($message, $context)aDeployTaskSystem is unusable. No-op if EMERGENCY is currently filtered out by the verbosity mask.
error($message, $context)aDeployTaskRuntime errors that do not require immediate action but should typically be logged and monitored.
getName()aDeployTask
getVerbosity()aDeployTask
info($message, $context)aDeployTaskInteresting events. No-op if INFO is currently filtered out by the verbosity mask.
isChange()aDeployTaskWhether running this task CHANGES anything.
isRollbackCritical()aDeployTask
log($level, $message, $context)aDeployTaskLogs with an arbitrary level.
notice($message, $context)aDeployTaskNormal but significant events.
rollback($context)aDeployTaskUndo this task. Called by the runner, in reverse order, for every task already applied when a later one failed.
setVerbosity($verb)aDeployTaskSet the current verbosity mask, replacing whatever it was before.
testVerbosity($level)aDeployTaskTest whether a given level (or set of levels) is currently enabled by this instance's verbosity mask.
warning($message, $context)aDeployTaskExceptional occurrences that are not errors.