Skip to content
ocx
install

Use ocx in GitLab CI

This page shows how to make an ocx toolchain available in a GitLab CI/CD job. It covers traditional script: jobs, which most pipelines use, and the experimental step runner’s export file.

GitLab has two ways to give a job its tools. A script: job runs every line in one shell, so a single eval reaches the lines after it. The step runner starts a fresh process per step and reads an export file between them.

Pick the route by the runner your job uses. The two do not mix: ocx env --ci=gitlab writes JSON lines that a script: job cannot read.

GitLab runs before_script and script of one job in a single shell session. Environment exported on one line is visible on the next. That makes ocx env --shell the right tool, and it needs no CI-specific flag.

build:
image: ubuntu:24.04
script:
- curl -fsSL https://setup.ocx.sh/sh | sh
- export PATH="$HOME/.ocx/symlinks/ocx.sh/ocx/cli/current/content/bin:$PATH"
- ocx pull
- eval "$(ocx env --shell=sh)"
- cmake --version
- shellcheck --version

ocx pull reads ocx.toml and ocx.lock from the repository, so the job needs the project files and nothing else. The eval line puts the composed environment on the rest of the job. For a single command, ocx exec runs it inside the composed environment without touching the shell:

build:
script:
- ocx exec -- cmake --version

The environment stops at the end of the job. A later job repeats the install and the eval, and the cache below keeps that cheap.

A job that downloads every package on each run is slow and depends on the registry. Point OCX_HOME inside the project directory, because GitLab caches only paths under it. Key the cache on ocx.lock, so the cache changes exactly when the toolchain does.

variables:
OCX_HOME: $CI_PROJECT_DIR/.ocx-home
cache:
key:
files:
- ocx.lock
paths:
- .ocx-home/blobs
- .ocx-home/layers
- .ocx-home/packages
- .ocx-home/index

OCX_HOME is the data root, so the installer and every later ocx command use the same directory. Add $OCX_HOME/symlinks/ocx.sh/ocx/cli/current/content/bin to PATH instead of the $HOME path shown above.

--ci=gitlab targets the GitLab step runner (run: keyword). It is an experimental feature, available on self-managed instances that run the step runner. It is not compatible with traditional script: jobs.

The step runner persists variables across steps within a job using an export file at ${{ export_file }}. Each entry in that file is a JSON object on its own line:

{"name":"KEY","value":"VALUE"}

ocx env --ci=gitlab produces this exact format. It writes to --export-file=PATH, or to stdout when --export-file is omitted.

GitLab CI/CD has no separate PATH channel. ocx env --ci=gitlab flattens all path-type entries. Package values are prepended to the current process value of PATH, and of any other path-type variable such as LD_LIBRARY_PATH. They are joined with the platform separator (: on Unix, ; on Windows) and emitted as one JSON-lines entry.

A list-type variable flattens the same way but folds the opposite direction. Package values are appended to the current process value, joined by the key’s own separator, and emitted as their own entry. The step runner injects the resulting values into the environment of later steps.

The example needs ocx.toml and ocx.lock committed to the repository, with kitware/cmake and shellcheck declared as tools. Your first project creates both files.

Install OCX and set up the toolchain in two steps of the same job. A third step of the same job then uses the binaries:

build:
run:
- name: Install OCX
script: |
curl -fsSL https://setup.ocx.sh/sh | sh
export PATH="$HOME/.ocx/symlinks/ocx.sh/ocx/cli/current/content/bin:$PATH"
- name: Set up toolchain
script: ocx env --ci=gitlab --export-file="${{ export_file }}"
- name: Build
script: |
cmake --version
shellcheck --version

${{ export_file }} is a runner-provided path, not a variable you define. The runner reads the JSON lines written there and injects each entry into later steps of the same job. The Build step sees the full environment because it follows the setup step.

The JSON line freezes the PATH of that moment, ambient entries included. A later step on a different image sees the paths of the first one.

When --export-file is omitted, output goes to stdout. Redirect it to the file the step runner expects:

- name: Set up toolchain
script: ocx env --ci=gitlab >> "${{ export_file }}"

The >> append is only needed without --export-file. With the option, OCX opens the file in append mode itself.

For individual OCI packages rather than a project toolchain:

test:
run:
- name: Install OCX
script: |
curl -fsSL https://setup.ocx.sh/sh | sh
export PATH="$HOME/.ocx/symlinks/ocx.sh/ocx/cli/current/content/bin:$PATH"
- name: Resolve package environment
script: ocx package env --ci=gitlab --export-file="${{ export_file }}" ocx.sh/kitware/cmake:3.28 ocx.sh/shellcheck/shellcheck:0.11
- name: Test
script: |
cmake --version
shellcheck --version