Configuration¶
The Hopic build configuration is stored in a file in your repository.
The default location where it will look for this is ${repo}/hopic-ci-config.yaml
.
Build Phases¶
-
phases
¶
Hopic’s build flow is divided in phases
, during which a set of commands can be executed for different variants
.
The phases
option is a dictionary of dictionaries.
It’s top-level key specifies the name of each phase.
The keys within each phase specify the names of variants to be executed within that phase.
Phases are executed in the order in which they appear in the configuration. Within a phase each variant may be executed in parallel, possibly on different executors. Every next phase only starts executing when each variant within the previous phase finished successfully. I.e. the execution flow “forks” to each variant at the start of a phase and “joins” at the end.
A variant, identified by its name, may appear in multiple phases. Variants appearing in multiple phases are guaranteed to run on the same executor within each phase. This provides a stable environment (workspace) to work in and allows incremental steps, such as building in phase A and running built tests in phase B.
For example, the configuration example listed results in an execution flow as shown after that.
phases:
style:
commit-message-checker:
# Require each commit message to adhere to our requirements
- foreach: AUTOSQUASHED_COMMIT
sh: .ci/verify-commit-message.py ${AUTOSQUASHED_COMMIT}
clang-format:
# Show all changes necessary to have each commit formatted properly
- foreach: AUTOSQUASHED_COMMIT
sh: GIT_PAGER="tee ${AUTOSQUASHED_COMMIT}.format.diff" git clang-format --style=file --diff ${AUTOSQUASHED_COMMIT}~1 ${AUTOSQUASHED_COMMIT}
# Assert that no changes were necessary
- foreach: AUTOSQUASHED_COMMIT
sh: 'test ! -s ${AUTOSQUASHED_COMMIT}.format.diff'
build:
x64-release:
- cmake -B build-x64-release -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo
- cmake --build build-x64-release
x64-debug:
- cmake -B build-x64-debug -S . -DCMAKE_BUILD_TYPE=Debug
- cmake --build build-x64-debug
arm64-release:
- cmake -B build-arm64-release -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_AGL_AARCH64_TOOLCHAIN_FILE}
- cmake --build build-arm64-release
arm64-debug:
- cmake -B build-arm64-debug -S . -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE:PATH=${CMAKE_AGL_AARCH64_TOOLCHAIN_FILE}
- cmake --build build-arm64-debug
test:
x64-debug:
- junit: build-x64-debug/test-unit.xml
- cmake --build build-x64-debug --target test
package:
x64-release:
- fingerprint:
artifacts:
- build-x64-release/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
- cmake --build build-x64-release --target package
x64-debug:
- fingerprint:
artifacts:
- build-x64-debug/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
- cmake --build build-x64-debug --target package
arm64-release:
- fingerprint:
artifacts:
- build-arm64-release/TomTom-Stacktrace-${VERSION}-Linux-aarch64.tar.gz
- cmake --build build-arm64-release --target package
arm64-debug:
- fingerprint:
artifacts:
- build-arm64-debug/TomTom-Stacktrace-${VERSION}-Linux-aarch64.tar.gz
- cmake --build build-arm64-debug --target package
upload:
x64-release:
- run-on-change: only
- build-x64-release/do-upload.sh
x64-debug:
- run-on-change: only
- build-x64-debug/do-upload.sh
arm64-release:
- run-on-change: only
- build-arm64-release/do-upload.sh
arm64-debug:
- run-on-change: only
- build-arm64-debug/do-upload.sh
Example execution flow of a Hopic build configuration.
Clean¶
-
clean
¶
Hopic provides built-in clean check-out functionality by executing git clean -fxd
on the ${WORKSPACE}
.
It is possible to add a list of commands that is executed before the git clean -fxd
is executed.
Within the list of commands $HOME
and ~
will be expanded to the home directory of the current user.
clean:
- rm -rf ~/.cache
- rm -rf ${HOME}/.other_cache
Credentials¶
-
with-credentials
¶
Sometimes it’s necessary to execute commands with privileged access.
For that purpose the with-credentials
configuration option can be used for a variant within a phase.
You need to specify an identifier (id
), used for looking up the credential and its type (type
).
In addition to that you can specify the name of the config and environment variable that should be set to contain them.
with-credentials
value can be specified as a list to have multiple credentials for a variant within a phase.
The supported types of credential are:
- Username/password credential
type
:username-password
username-variable
default:USERNAME
password-variable
default:PASSWORD
- File credential
type
:file
filename-variable
default:SECRET_FILE
- String credential
type
:string
string-variable
default:SECRET
phases:
download:
toolchain:
- with-credentials:
id: access_key
type: string
- download --api-key=${SECRET}
build:
toolchain:
- with-credentials:
id: license_file
type: file
- build --license-key=${SECRET_FILE}
test:
toolchain:
- with-credentials:
- id: squish_cred
type: username_password
username-variable: SQUISH_USERNAME
password-variable: SQUISH_PASSWORD
- id: artifactory_creds
type: username-password
username-variable: ARTIFACTORY_USERNAME
password-variable: ARTIFACTORY_PASSWORD
- download test-certificate --username=${ARTIFACTORY_USERNAME} --password=${ARTIFACTORY_PASSWORD}
- test --username=${SQUISH_USERNAME} --password=${SQUISH_PASSWORD}
deploy:
toolchain:
- run-on-change: only
with-credentials:
id: artifactory_creds
type: username-password
- publish --user=${USERNAME} --password=${PASSWORD}
Container Image¶
-
image
¶
In order to execute commands within a Docker container Hopic needs to be told what image to use for creating a container.
This option can either contain a string in which case every variant will execute in a container constructed from that image.
Alternatively it can contain a mapping where the keys and values are the names of the variant and the image to execute those in respectively.
If using the mapping form, the default
key will be used for variants that don’t have an image specified explicitly.
The image can also be specified within a phase for a variant, this will override the global specified image for the specific variant for only the phase where it is specified.
An example of the mapping style where two different variants are executed in containers based on different images:
image:
default: buildpack-deps:testing
whoami: buildpack-deps:testing-curl
phases:
prepare:
hello:
- cc -o hello hello.c
whoami:
- curl --output ip-address.txt https://api.ipify.org
run:
hello:
- image: busybox:latest
- ./hello
For the purpose of using an image (name and version) specified in an Ivy dependency manifest file the !image-from-ivy-manifest type constructor exists. When used its contents are a mapping with these keys:
manifest
Path to the Ivy manifest file. Relative paths are interpreted relative to the first of the ${CFGDIR} or ${WORKSPACE} directories that exists. This defaults to the first of these to exist:
${WORKSPACE}/dependency_manifest.xml
${CFGDIR}/dependency_manifest.xml
repository
- Docker repository to fetch the image from.
path
- Directory within the repository to fetch from.
name
- Name of the image to fetch.
This defaults to the content of the
name
attribute in the Ivy manifest. rev
- Version of the image to fetch.
This defaults to the content of the
rev
attribute in the ivy manifest.
When used this will get treated as if the expansion of {repository}/{path}/{name}:{rev}
was specified as a string value of this field.
This allows using Ivy as a mechanism for automatically keeping the Docker image up to date.
For example, when using this dependency manifest in ${WORKSPACE}/dependency_manifest.xml
:
<ivy-module version="2.0">
<info module="p1cms" organisation="com.tomtom" revision="dont-care" />
<dependencies>
<dependency name="python" org="com.tomtom.toolchains" rev="3.6.5" revConstraint="[3.5,4.0[">
<!-- identify this as the dependency specifying the Docker image for Hopic -->
<conf mapped="toolchain" name="default" />
</dependency>
</dependencies>
</ivy-module>
And this Hopic config file:
image:
default: !image-from-ivy-manifest
repository: hub.docker.com
path: tomtom
Linux_AGL-aarch64: !image-from-ivy-manifest
repository: hub.docker.com
path: tomtom
name: agl-build-aarch64-toolchain
The result will be to use the hub.docker.com/tomtom/python:3.6.5
image by default.
The PyPy
build will instead use the hub.docker.com/tomtom/pypy:3.6.5
image.
I.e. for that build the image name is overridden from that used in the Ivy manifest, while still using the version from it.
Volumes¶
-
volumes
¶
In order to execute commands within Docker it is often required to mount directories or a file to the docker container.
This can be done by specifying volumes
.
volumes
doesn’t have any effect when there is no image
specified.
There are two formats how a volume can be specified:
Format 1
The volume can be specified as host-src
[:container-dest
][:<options>
].
The options
are [rw|ro]
The host-src
is an absolute path or a name value.
Format 2
- The volume can be specified using a dictionary with the following keys:
source
- [
target
] - [
read-only
]
Where source
is equal to host-src
, target
is equal to container-dest
and read-only
reflects the possible options
with a boolean value.
By default the host-src
is mounted rw.
When the given host-src
doesn’t exist it will be created as a directory.
If container-dest
is not specified, it will take the same value as host-src
.
For the host-src
path, $HOME
or ~
will be expanded to the home directory of the current user.
While for the container-dest
, $HOME
or ~
will be expanded to /home/sandbox
.
The following directories are mounted by default:
host-src | container-dest | <options> |
---|---|---|
/etc/passwd | /etc/passwd | read-only |
/etc/group | /etc/group | read-only |
WORKSPACE [*] |
/code | read-write |
[*] | WORKSPACE/code for repositories referring to other repositories for their code. |
volumes
can be declared in every scope and will be used during the specified scopes
e.g. volumes
specified in global scope are used with every command.
In case an inherited bind mount needs to be overridden, that can be accomplished by adding a volume with the same target_location
.
Consider the following example where /tmp/downloads is overridden:
example:
image: buildpack-deps:testing-curl
volumes:
- source: ~/
target: /tools/devenv
read-only: yes
- /tmp
- /tmp/Music:/home/sandbox/temp
phases:
list:
jira:
- with-credentials:
id: netrc
type: file
volumes:
- ${SECRET_FILE}:~/.netrc:ro
- curl -v --netrc --silent "https://jira.atlassian.com/rest/api/2/search?maxResults=100&fields=key,summary,assignee&jql=resolution+%3D+unresolved+AND+%28project+%3D+BSERV+OR+assignee+%3D+currentUser()%29+ORDER+BY+priority+DESC%2C+created+ASC"
print:
- echo "only global volumes will be mounted"
mounts:
print:
- sh: echo "/tmp/Music will be overridden with /tmp/Downloads"
volumes:
- /tmp/Downloads:/home/sandbox/temp
- volumes:
- /tmp/Pictures:/home/sandbox/temp
- /tmp/Desktop
- echo "override /tmp/Downloads with /tmp/Pictures and /tmp/Desktop is added"
Environment Variables¶
-
pass-through-environment-vars
¶
This option allows passing environment variables of the host environment through into containers. This is a list of strings. Each string is the name of an environment variable. If the named environment variable exists in the host environment, it will be set to the same value inside the container.
image: build-image:4.2.1
pass-through-environment-vars:
- JENKINS_TEST_INSTANCE
phases:
x86:
build:
- ./build.sh
x86:
upload:
- ./upload-from-non-test-only.sh
Mounting Volumes From Other Containers¶
-
volumes-from
¶
The option volumes-from
allows you to mount volumes that are defined in an external Docker image.
The behavior translates directly to a --volumes-from
Docker-run option; the volumes are mapped to the path as originally specified in the external image.
Note that this option does nothing if you haven’t specified a Docker image (see the image
option).
The option requires two keys to be specified:
image-name
- The full name of the Docker image.
image-version
- The targeted version of the Docker image.
The combination of <image-name>:<image-version>
should result in a correct, downloadable Docker image.
example:
phases:
coverity:
x64:
- description: Coverity run
- volumes-from:
- image-name: hub.docker.com/tomtom/coverity
image-version: 2.4.7
- sh -c 'cd build/x64-rel;
/opt/coverity/bin/cov-configure --template --compiler c++ --comptype gcc --version 7.3 --config my_config.conf;
/opt/coverity/bin/cov-build --config my_config.conf --dir cov-int ninja;
echo etc.'
Publish From Branch¶
-
publish-from-branch
¶
The publish-from-branch
option, when provided, specifies a regular expression matching the names of branches from which to allow publication.
Publication includes version bumping (see version
) and the execution of any steps marked with run-on-change
as only
.
If this option is omitted, Hopic allows publication from any branch.
The example below configures Hopic to only publish from the master
branch or any branch starting with release/
or rel-
.
example:
publish-from-branch: '^master$|^release/.*|^rel-.*'
Versioning¶
-
version
¶
Hopic provides some support for determining and bumping of the currently checked out version.
It currently supports the syntax, sorting and bumping strategies of these versioning policies.
The policy to use can be specified in the format
option of the version
option section.
semver
Semantic Versioning. This is the default when no policy is explicitly specified.
The default tag format is
{version.major}.{version.minor}.{version.patch}
.The default component to bump is the pre-release label.
carver
Caruso variation on Semantic Version for branching
The default tag format is
{version.major}{version.minor}{version.patch}+PI{version.increment}.{version.fix}
.The default component to bump is the pre-release label.
The version can be read from and stored in two locations:
version.file
- When this option is specified Hopic will always use this as the primary source for reading and storing the version.
The first line to contain only a syntactically valid version, optionally prefixed with
version=
, is assumed to be the version. When reading the version it’ll use this verbatim. When storing a (likely bumped) version it’ll only modify the version portion of that file. version.tag
When this option is set to
true
or a non-empty string Hopic will, when storing, create a tag every time it creates a new version. When this option is set to a string it will be interpreted according to Python Format Specification with the named variableversion
containing the version. When this option is set andfile
is not set it will use git describe to read the current version from tags. When used for reading, it will mark commits that don’t have a tag a virtual prerelease of the predicted next version.Setting this option to a string can, for example, be used to add a prefix like
v
to tags, e.g. by usingv{version}
. Having it set totrue
instead uses the version policy’s default formatting.
When and what to bump can be controlled by the version.bump
option.
When set to false
it disables automated bumping completely.
Otherwise it describes a version bumping policy in its version.bump.policy
member.
There are currently two version bumping policies available:
constant
- Its
version.bump.field
property specifies what field to bump. It will always bump that field and no other. When not specified it defaults to bumping the default-to-bump part of the used version policy.version: format: semver tag: 'v.{version.major}.{version.minor}.{version.patch}' bump: policy: constant field: patch
conventional-commits
When used this policy determines the version field to bump based on commit messages formatted according to Conventional Commits. It searches all to-merge commits for breaking changes, new features and fixes. If any of those are present it will bump, in order of precedence, the major, minor or patch number.
The
version.bump.strict
option of this policy controls whether each commit message is required to parse as a valid Conventional Commit. If set tofalse
invalidly formatted messages are just ignored and not taken into account to determine what to bump. Otherwise the merge will fail when encountering invalidly formatted messages.The
version.bump.reject-breaking-changes-on
andversion.bump.reject-new-features-on
options specify regular expressions matching branch names. Merges into these branches will be rejected if, respectively, they contain breaking changes or new features. The purpose of this is to prevent accidental inclusion of these kinds of changes into release branches.version: format: semver tag: 'v.{version.major}.{version.minor}.{version.patch}' bump: policy: conventional-commits strict: no # default reject-breaking-changes-on: 'release/.*|rel-.*' # default reject-new-features-on: 'release/\d+\..*|rel-\d+\..*' # default
In order to configure a version bumping policy without automatically bumping for every change the version.bump.on-every-change
option can be set to false
(defaults to true
).
When bumping is enabled, Hopic bumps each time that it applies a change.
Usually this means when it’s merging a pull request.
Another option is when it’s performing a modality change (currently only UPDATE_DEPENDENCY_MANIFEST
).
Todo
Describe after-submit
. Maybe?
Modality Changes¶
-
modality-source-preparation
¶
The modality-source-preparation
option allows for influencing the build according to the MODALITY
parameter.
If Hopic is called with a MODALITY
that is present in the configuration file, then the commands as specified in that section are executed before the other phases.
See the description of the apply-modality-change
parameter on the Usage page for the calling syntax.
Note that this is, above all, a remnant of the previous generation pipeline; it is currently only used to perform UPDATE_DEPENDENCY_MANIFEST
builds.
Note
Defining new functionality using this option is discouraged.
description
- An optional description for the command, which will be printed in the logs.
sh
- The actual command to be run. Variables will be expanded, similar to commands defined in the
phases
. changed-files
Specifies the files that are changed by the command, which are to be added to the commit.
If omitted, Hopic forces a clean repository before running the command specified by
sh
. Upon completion of the command, all files that are changed, removed and/or previously untracked are added to the commit.commit-message
The message that will be used to commit the changes when this modality is run.
If omitted, the value of the
MODALITY
parameter is used as the commit message.
example:
modality-source-preparation:
UPDATE_DEPENDENCY_MANIFEST:
- sh: update_dependency_manifest.py ${CFGDIR}/dependency_manifest.xml ${CFGDIR}/ivysettings.xml
changed-files:
- ${CFGDIR}/dependency_manifest.xml
commit-message: Update of dependency manifest
Restricting Variants to Specific Build Nodes¶
-
node-label
¶
The option node-label
executes the specified steps on an agent available in the Jenkins environment with the provided label(s).
example:
phases:
build:
Linux-x86_64-Release:
- node-label: Linux && Docker && Release
- Build/build.py -f Build/Linux-x86_64.yaml -m Release
Restricting Steps to Changes or Not¶
-
run-on-change
¶
The option run-on-change
allows you to specify when a step needs to be executed.
The value of the run-on-change
option can be:
always
- The steps will always be performed. (Default if not specified).
never
- The steps will never be performed.
only
- The steps will only be performed when the change is to be submitted in the current execution.
example:
phases:
build:
x64-release:
- cmake --build build-x64-release
x64-debug:
- cmake --build build-x64-debug
upload:
x64-release:
- run-on-change: only
- build-x64-release/do-upload.sh
x64-debug:
- run-on-change: only
- build-x64-debug/do-upload.sh
Sharing Output Data Between Variants¶
-
stash
¶
The option stash
will save files to be used in another Hopic phase.
The stashed files are available for every executor/node in every phase and workspace after the current one.
Use the option includes
to identify the files to be stashed.
Use Wildcards like module/dist/**/*.zip.
A * expands only to a single directory entry, where ** expands to multiple directory levels deep.
example:
phases:
upload:
Linux-x86_64-Release:
- run-on-change: only
stash:
includes: Build/Output/x86_64-Linux-clang/Release/ivy-repo/published_artifacts.yaml
- Build/build.py -f Build/Linux-x86_64.yaml Publish.ALL -m Release
Customizing Step Description¶
-
description
¶
The option description
adds a description to the step which will be printed in the logs.
example:
phases:
quality:
Coverity:
- description: Coverity run
- volumes-from:
- image-name: hub.docker.com/tomtom/coverity
image-version: 2.4.6
- echo -e "Run Coverity here, eg.:\n cov-configure --template --compiler gcc --comptype gcc\n cov-configure -co /tools/devenv/Linux/Linux/gcc-4.8.2/bin/c++ -- -msse -mfpmath=sse\netc.."
Repeating Steps for Commits¶
-
foreach
¶
Todo
Document foreach
option.
Change Request Commits¶
SOURCE_COMMIT
Change Request Autosquashed Commits¶
AUTOSQUASHED_COMMIT
JUnit Test Results¶
-
junit
¶
The option junit
triggers Jenkins Unit Testing.
For more information about this feature see: https://www.tutorialspoint.com/jenkins/jenkins_unit_testing.htm
example:
phases:
build:
x64-release:
- cmake --build build-x64-release
test:
x64-debug:
- junit: build-x64-debug/test-unit.xml
- cmake --build build-x64-debug --target test
Artifact Archiving¶
-
archive
¶
The option archive
allows you to archive build artifacts.
The artifacts can be stored on Jenkins and/or archived to Artifactory.
The base directory is the workspace. Artifacts specified are discovered relative to the workspace.
Use Wildcards like module/dist/**/*.zip. A * expands only to a single directory entry, where ** expands to multiple directory levels deep.
Use the pattern
option to identify and upload a specific artifact.
The specific artifact can then be uploaded to artifactory with the option target
.
example:
phases:
build:
x64-release:
- cmake -B build-x64-release -S . -DCMAKE_BUILD_TYPE=RelWithDebInfo
upload:
x64-release:
- cmake --build build-x64-release --target package
- archive:
artifacts:
- Build/Output/x64/release/**
- pattern: Build/Output/x64/release/TomTom-Package-${VERSION}-x64.tar.gz
target: cs-snapshot/com.tomtom.package/Package/x64/release/${VERSION}/TomTom-Package-x64-release-${VERSION}.tar.gz
run-on-change: only
Archiving To Artifactory¶
-
upload-artifactory
¶
The option upload-artifactory
allows you to archive build artifacts to Artifactory.
The option id
is the named identifier referring to a preconfigured Artifactory server from Jenkins’ global configuration.
The artifacts to be uploaded are specified with the artifacts
option.
example:
phases:
upload:
Linux-x86_64:
- archive:
artifacts:
- pattern: build-x86/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
target: cs-psa-p1cms-snapshot/com.tomtom.stacktrace/Stacktrace/linux/x86_64/release/${VERSION}/Stacktrace-linux-x86_64-release-custom-${VERSION}.tar.gz
- pattern: build-x86/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
target: cs-psa-p1cms-snapshot/com.tomtom.stacktrace/Stacktrace/linux/x86_64/debug/${VERSION}/Stacktrace-linux-x86_64-release-custom-${VERSION}.tar.gz
upload-artifactory:
id: artifactory-navkit
run-on-change: only
Promoting Builds in Artifactory¶
-
artifactory
¶
The option artifactory
lets the user create promotion definitions for specific Artifactory repositories.
The option promotion
is used to promote build artifacts. After this option the Artifactory server ID is specified.
The option target-repo
specifies the Artifactory target repository.
Only build artifacts that were uploaded with the option upload-artifactory
will be promoted.
example:
artifactory:
promotion:
artifactory-navkit:
target-repo: cs-psa-p1cms-release
phases:
build:
Linux-x86_64:
- cmake --build build-x86
Linux_AGL-aarch64:
- cmake --build build-agl-aarch64
package:
Linux-x86_64:
- fingerprint:
artifacts:
- build-x86/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
- cmake --build build-x86 --target package
upload:
Linux-x86_64:
- archive:
artifacts:
- pattern: build-x86/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
target: cs-psa-p1cms-snapshot/com.tomtom.stacktrace/Stacktrace/linux/x86_64/release/${VERSION}/Stacktrace-linux-x86_64-release-custom-${VERSION}.tar.gz
upload-artifactory:
id: artifactory-navkit
run-on-change: only
Artifact Fingerprint¶
-
fingerprint
¶
The option fingerprint
triggers fingerprinting of build artifacts on Jenkins.
Use the option artifacts
to identify the artifacts to be fingerprinted.
With the fingerprint
option, the Jenkins “fingerprint” feature is invoked.
For more information about this feature see: https://jenkins.io/doc/pipeline/steps/core/#fingerprint-record-fingerprints-of-files-to-track-usage
example:
phases:
build:
x64-release:
- cmake --build build-x64-release
package:
x64-release:
- fingerprint:
artifacts:
- build-x64-release/TomTom-Stacktrace-${VERSION}-Linux-x86_64.tar.gz
- cmake --build build-x64-release --target package