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
_images/parallel-phases.svg

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 variable version containing the version. When this option is set and file 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 using v{version}. Having it set to true 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 to false 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 and version.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.."

Branches in Subdirectory Worktrees

worktrees

Todo

Document worktrees option.

Repeating Steps for Commits

foreach

Todo

Document foreach option.

Change Request Commits

SOURCE_COMMIT

Change Request Autosquashed Commits

AUTOSQUASHED_COMMIT

Sub SCM

scm

Todo

Document scm option.

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