Skip to content

CI Integration

Running OCX in a CI pipeline has one core challenge: shell environment changes do not cross step boundaries.

When a pipeline runs eval "$(ocx env --shell=bash)", those exports live only in the current shell. The next step starts a fresh process — PATH is reset, variables are gone, and any tool installed in step one is invisible in step two.

ocx env --ci and ocx package env --ci solve this by writing the composed environment into the CI system's own persistence channel instead of printing shell export lines. The runner picks up that channel between steps, so later steps see the full tool environment without any extra glue code.

--shell vs --ci — which one to use

--shell emits eval-safe export lines for the current step only. Use it inside a single step that sources the env and immediately runs commands. --ci writes to the runner's persistence channel so the env is available to every subsequent step. Use it whenever tools installed in one step must be reachable in later steps.

GitHub Actions

GitHub Actions runners provide two file-based channels for sharing state across steps:

  • $GITHUB_PATH — each appended line is prepended to PATH for all later steps. OCX writes PATH entries here, so OCX-installed tools land leftmost (highest priority) in PATH regardless of when in the job ocx env --ci=github runs.
  • $GITHUB_ENV — each KEY=VALUE line (or heredoc block for multiline values) is exported to all later steps.

ocx env --ci=github reads the paths of those files from the runner's own GITHUB_PATH and GITHUB_ENV variables and appends the resolved tool paths and variables directly. No jq, no redirect.

Only the literal PATH variable goes to $GITHUB_PATH. All other path-type variables — LD_LIBRARY_PATH, MANPATH, PKG_CONFIG_PATH, and any others declared in package metadata — are written to $GITHUB_ENV as KEY=value, with OCX-provided directories prepended to the existing value.

Running --ci=github outside a GitHub Actions runner — where GITHUB_ENV and GITHUB_PATH are unset — exits 78 (configuration error).

Toolchain-tier example

This workflow installs the project toolchain in one step and uses the tools in a later step:

yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install OCX
        run: |
          curl -fsSL https://ocx.sh/install.sh | sh
          echo "$HOME/.ocx/bin" >> "$GITHUB_PATH"

      - name: Set up toolchain
        run: ocx env --ci=github

      - name: Build
        run: cmake --version && ninja --version

After the "Set up toolchain" step, every subsequent step sees the project's resolved tool directories in PATH and any declared environment variables.

OCI-tier example

For individual OCI packages rather than a full project toolchain:

yaml
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install OCX
        run: |
          curl -fsSL https://ocx.sh/install.sh | sh
          echo "$HOME/.ocx/bin" >> "$GITHUB_PATH"

      - name: Resolve tool environment
        run: ocx package env --ci=github node:20 python:3.12

      - name: Run tests
        run: node --version && python --version

Auto-detection

Bare --ci without =github also works inside GitHub Actions because the runner sets GITHUB_ACTIONS=true. Either form is valid; --ci=github is explicit and recommended for readability.

GitLab CI/CD

--ci=gitlab targets the GitLab step runner (run: keyword), which is an experimental feature available in self-managed instances running the GitLab 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 $. Each entry in that file is a JSON object on its own line:

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

ocx env --ci=gitlab produces this exact format — either 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 any other path-type variable such as LD_LIBRARY_PATH), joined with the platform path separator (: on Unix, ; on Windows), and emitted as a single JSON-lines entry. The step runner injects the resulting value into the environment of subsequent steps.

GitLab Functions / step runner only

--ci=gitlab produces JSON-lines output ({"name":"…","value":"…"}). This format is consumed by the GitLab step runner via $ — an experimental feature in GitLab, available on self-managed instances running the step runner, with the run: keyword only.

$ persists environment to later steps within the same job. It does not propagate across separate jobs by itself.

Traditional script: jobs cannot consume this JSON format at all. For cross-job variable passing in traditional script: jobs, GitLab CI/CD provides artifacts: reports: dotenv, which requires bare KEY=VALUE lines — not JSON. OCX does not currently emit dotenv format directly, so cross-job variable passing in traditional pipelines requires a separate solution.

Toolchain-tier example

This example uses the GitLab step runner's run: keyword. Install OCX and set up the toolchain in two steps of the same job, then use the tools in a third step of the same job:

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

$ is a runner-provided path, not a user-defined variable. The step runner reads the JSON-lines written there and injects each entry into the environment of later steps within the same job. The "Build" step sees the full tool environment because it follows "Set up toolchain" in the same job.

Redirect to stdout

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

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

The >> append-redirect is only needed when not using --export-file. With --export-file, OCX opens the file directly in append mode — no redirect required.

OCI-tier example

For individual OCI packages rather than a full project toolchain:

yaml
test:
  run:
    - name: Install OCX
      script: |
        curl -fsSL https://ocx.sh/install.sh | sh
        export PATH="$HOME/.ocx/bin:$PATH"
    - name: Resolve tool environment
      script: ocx package env --ci=gitlab --export-file="${{ export_file }}" node:20 python:3.12
    - name: Run tests
      script: |
        node --version
        python --version

Auto-detection

Bare --ci without =gitlab also works inside GitLab CI/CD because the runner sets GITLAB_CI=true.