The Build Plan is a document that buildpacks can use to pass information between the detect
and build
phases, and between each other.
The build plan is passed (by the lifecycle) as a parameter to the detect
and build
binaries of each buildpack.
During the detect
phase, each buildpack may write something it requires
or provides
(or both) into the Build Plan.
A buildpack can require
or provide
multiple dependencies, and even multiple groupings of dependencies (using or
lists).
Additionally, multiple buildpacks may require
or provide
the same dependency.
For detailed information, consult the spec.
The lifecycle uses the Build Plan to determine whether a particular list of buildpacks can work together, by seeing whether all dependencies required can be provided by that list.
Later, during the build
phase, each buildpack may read the Buildpack Plan (a condensed version of the Build Plan, composed by the lifecycle) to determine what it should do.
Let’s see how this works with an example.
node-engine
buildpackLet’s walk through some possible cases a node-engine
buildpack may consider:
We will also consider what an NPM
and a JVM
buildpack may do.
A node-engine
buildpack is always happy to provide
the node
dependency. The build plan it will write may look something like:
[[provides]]
name = "node"
NOTE: If this was the only buildpack running, this would fail the
detect
phase. In order to pass, everyprovides
must be matched up with arequires
, whether in the same buildpack or in another buildpack. See the spec for particulars on how ordering buildpacks can adjust detection results.
During the detect
phase, the node-engine
buildpack sees in one configuration file (e.g. a .nvmrc
file in the app directory) that node v10.x
is explicitly requested by the application. Seeing that, it may write the below text to the build plan:
[[provides]]
name = "node"
[[requires]]
name = "node"
version = "10.x"
[requires.metadata]
version-source = ".nvmrc"
As always, the buildpack provides
node
. In this particular case, a version of node
(10.x
) is being requested in a configuration file (.nvmrc
). The buildpack chooses to add an additional piece of metadata (version-source
), so that it can understand where that request came from.
NPM
is the default package manager for node
. A NPM Buildpack may ensure that all the packages for the application are present (by running npm install
), and perhaps cache those packages as well, to optimize future builds.
NPM is typically distributed together with node. As a result, a NPM buildpack may require node
, but not want to provide
it, trusting that the node-engine
buildpack will be in charge of providing
node
.
The NPM buildpack could write the following to the build plan, if the buildpack sees that npm
is necessary (e.g., it sees a package.json
file in the app directory):
[[requires]]
name = "node"
If, looking in the package.json
file, the NPM buildpack sees a specific version of node
requested in the engines field (e.g. 14.1
), it may write the following to the build plan:
[[requires]]
name = "node"
version = "14.1"
[requires.metadata]
version-source = "package.json"
NOTE: As above, if this was the only buildpack running, this would fail the
detect
phase. In order to pass, everyprovides
must be matched up with arequires
, whether in the same buildpack or in another buildpack. See the spec for particulars on how ordering buildpacks can adjust detection results.
However, if the NPM Buildpack was run together with the Node Engine buildpack (which provides
node
), the lifecycle will see that all requirements are fulfilled, and select that group as the correct set of buildpacks.
Java is distributed in two formats - the jdk
(Java Development Kit), which allows for compilation and running of Java programs, and the jre
(Java Runtime Environment, which allows for running compiled Java programs).
A very naive implementation of the buildpack may have it write several provides
options to the build plan, detailing everything that it can provide,
while later buildpacks would figure out based on the application which options it requires, and would require
those.
In this particular case, we can use the or
operator to present different possible build plans the buildpack can follow:
# option 1 (`jre` and `jdk`)
[[provides]]
name = "jre"
[[provides]]
name = "jdk"
# option 2 (or just `jdk`)
[[or]]
[[or.provides]]
name = "jdk"
# option 3 (or just `jre`)
[[or]]
[[or.provides]]
name = "jre"
The buildpack gives three options to the lifecycle:
jre
jdk
jdk
and jre
As with the other buildpacks, this alone will not be sufficient for the lifecycle. However, other buildpacks that follow may require
certain things.
For example, another buildpack may look into the application and, seeing that it is a Java executable, require
the jre
in order to run it.
When the lifecycle analyzes the results of the detect
phase, it will see that there is a buildpack which provides jre
, and a buildpack that requires jre
,
and will therefore conclude that those options represent a valid set of buildpacks.