Skip to content
pb-diff Help

Run pb-diff in CI

A CI job uses pb-diff-ci.exe, the pb-diff tool for build machines. It is one file, installed beside pb-diff.exe and pb-diff-cli.exe and shipped in the ZIP, and it runs copied on its own to any Windows machine, a hosted runner included, with nothing else installed.

Its commands are the commands of pb-diff-cli that stay at that level - compare-lib, the summary and summary-changes reports, check, info, list, validate, schema and help - with the same names, options, JSON and exit codes, so the Command-Line Tool Reference describes both.

Put it on the build machine

Copy pb-diff-ci.exe from a pb-diff installation or from the ZIP to wherever the job can reach it. Two arrangements work well:

It may be used on any machine, hosted runners included, for the work of an organisation with a pb-diff subscription; the usage terms say so in full.

Gate on what changed

compare-lib compares two libraries, two folders or a library with a folder, object by object, as the desktop application's Compare does. A folder can be a ws_objects folder, a PowerBuilder 2025 Solution or one of its PBL folders, or a folder of exported objects. Read the exit code before the output:

Code Meaning
0 Success; for compares, no differences; for check, nothing noticed
1 Compares: differences found. Validate: warnings only. Check: findings
2 Invalid input, missing file or entry, or bad arguments
4 Internal error

Exit code 1 is an answer and not a failure, so a gate that treats every non-zero code as broken turns "this pull request changes three objects" into a red build. In PowerShell, which every Windows runner has:

$raw = .\tools\pb-diff-ci.exe compare-lib release\orders.pbl src\ws_objects --changed-only --format json | Out-String
switch ($LASTEXITCODE) {
    0       { 'No object changed.' }
    1       { ($raw | ConvertFrom-Json).entries | ForEach-Object { '{0,-9} {1}' -f $_.status, $_.name } }
    default { throw "Comparing failed with exit code $LASTEXITCODE." }
}

A name two inputs share - the same object in two subfolders - is never paired by guessing: both are listed, and notes in the JSON says why. Renames are paired with --pair old=new.

Keep the report, and summarise it in the job

report summary-changes turns the same comparison into a report: HTML, PDF, CSV or RTF to keep as a build artefact, and Markdown for the page a CI system shows beside the job.

.\tools\pb-diff-ci.exe report summary release\orders.pbl src\ws_objects --output pb-diff-summary.html
.\tools\pb-diff-ci.exe report summary-changes release\orders.pbl src\ws_objects --report-format markdown >> $env:GITHUB_STEP_SUMMARY

The Markdown is one table row per object, with its status, type and sizes, and the notes, and it fits a GitHub Actions job summary, an Azure DevOps build summary or a pull-request comment. A pull-request comment holds 65,536 characters, so a large application is better summarised with summary-changes than with summary. Every report pb-diff-ci writes, but the JSON, ends with the line Generated by pb-diff-ci and its version, and a link to where the detail of each change is shown.

Check sources after a merge

A merge left half-done does not fail until PowerBuilder imports the file. check finds the damage first: merge conflict markers, a block or a DataWindow statement that never closes, a file that is not the format its extension names.

.\tools\pb-diff-ci.exe check src\ws_objects --format json --out check.json
switch ($LASTEXITCODE) {
    0       { 'Nothing noticed.' }
    1       { (Get-Content -Raw check.json | ConvertFrom-Json).findings | ForEach-Object { "$($_.path)($($_.line)): $($_.code)" }; throw 'Sources left broken.' }
    default { throw "check could not run (exit code $LASTEXITCODE)." }
}

Each finding names the file, the code and the first line concerned, and never quotes the file. A folder that holds no source at all is refused rather than passed, so a mistyped path cannot turn the check green. check works in one direction: a finding means a file is damaged, and no finding means nothing was noticed, not that the code compiles - the build itself is what says that.

Check the libraries for damage

validate checks libraries the way check checks sources: give it the workspace, a target or the folder of libraries, and it reports each library's state, with exit code 2 when any is damaged, unreadable or missing.

.\tools\pb-diff-ci.exe validate src\app.pbw --output library-check.md
$code = $LASTEXITCODE
Get-Content library-check.md >> $env:GITHUB_STEP_SUMMARY
if ($code -ge 2) { throw 'A library is damaged, unreadable or missing.' }

Check libraries for damage says what each finding means and what to do.

GitHub Actions

A pull request is checked, compared with the branch it targets, summarised on the job's page and kept as an artefact:

name: PowerBuilder changes
on: pull_request

jobs:
  pb-diff:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 0

      - name: Check the sources
        shell: pwsh
        run: |
          .\tools\pb-diff-ci.exe check src\ws_objects --format json --out check.json
          if ($LASTEXITCODE -ne 0) { Get-Content check.json; exit 1 }

      - name: Compare with the target branch
        shell: pwsh
        run: |
          git worktree add ..\target "origin/${{ github.base_ref }}"
          .\tools\pb-diff-ci.exe report summary-changes ..\target\src\ws_objects src\ws_objects --report-format markdown >> $env:GITHUB_STEP_SUMMARY
          .\tools\pb-diff-ci.exe report summary ..\target\src\ws_objects src\ws_objects --output pb-diff-summary.html
          if ($LASTEXITCODE -ne 0) { exit 1 }

      - uses: actions/upload-artifact@v6
        with:
          name: pb-diff-summary
          path: pb-diff-summary.html

pb-diff runs this pipeline itself, on a GitHub-hosted Windows runner, over the Order Desk examples, with pb-diff-ci.exe copied there alone and its network blocked: that is how the claim that it runs on a hosted runner is checked before each release.

Azure DevOps

##vso[task.uploadsummary] attaches the Markdown to the build's summary page:

trigger:
  - main

pool:
  vmImage: windows-latest

steps:
  - checkout: self
    fetchDepth: 0

  - pwsh: |
      .\tools\pb-diff-ci.exe check src\ws_objects --format json --out check.json
      if ($LASTEXITCODE -ne 0) { Get-Content check.json; exit 1 }
    displayName: Check the sources

  - pwsh: |
      $summary = "$(Build.ArtifactStagingDirectory)\pb-diff-summary.md"
      .\tools\pb-diff-ci.exe report summary-changes release\orders.pbl src\ws_objects --output $summary
      if ($LASTEXITCODE -ne 0) { exit 1 }
      Write-Host "##vso[task.uploadsummary]$summary"
      .\tools\pb-diff-ci.exe report summary release\orders.pbl src\ws_objects --output "$(Build.ArtifactStagingDirectory)\pb-diff-summary.html"
    displayName: Summarise what changed

  - publish: $(Build.ArtifactStagingDirectory)
    artifact: pb-diff

Jenkins

On a Windows agent, in a declarative Jenkinsfile:

pipeline {
    agent { label 'windows' }
    stages {
        stage('Check the sources') {
            steps {
                powershell '''
                    & .\\tools\\pb-diff-ci.exe check src\\ws_objects --format json --out check.json
                    if ($LASTEXITCODE -ne 0) { Get-Content check.json; exit 1 }
                '''
            }
        }
        stage('Summarise what changed') {
            steps {
                powershell '''
                    & .\\tools\\pb-diff-ci.exe report summary release\\orders.pbl src\\ws_objects --output pb-diff-summary.html
                    if ($LASTEXITCODE -ne 0) { exit 1 }
                '''
                archiveArtifacts artifacts: 'pb-diff-summary.html'
            }
        }
    }
}

The Azure DevOps and Jenkins pipelines run the same commands as the GitHub Actions one, which is the one pb-diff runs itself; adjust the paths to where your libraries and sources are.

When a job needs more than the object

pb-diff-ci stops at the object by design. A job that has to show what changed inside an object, export objects or merge them runs pb-diff-cli, which needs a licence: activate the build server's service account once, as for a person, on a named subscription seat. See Automate with pb-diff-cli and Licence.