Table of Contents

zkmake (on-premises)

One of Zeugwerk's CI/CD tools for TwinCAT. zkmake is the on-premises command-line tool for building TwinCAT PLC projects, managing packages, and running unit tests - without touching the IDE. It is the self-hosted counterpart to zkbuild-action. Use it on your own build servers, in Jenkins, Azure DevOps, or any CI/CD pipeline that can execute Windows binaries.

Note

zkmake runs on Windows only and requires TwinCAT XAE (or TcXaeShell) to be installed on the machine where it executes. If you prefer a zero-infrastructure approach, use zkbuild-action instead.


Availability

Option When to use
zkbuild-action Public repositories on GitHub, GitLab, or Bitbucket. Free tier (30 builds/month).
zkmake CLI (this page) Private repos, on-premises pipelines, or any CI that runs Windows executables. Requires a license.

Contact us for licensing, trials, and on-premises options.


Why use it

  • On-premises control - Runs entirely on your own infrastructure. No code or binaries leave your network.
  • CI/CD integration - Works with any pipeline that can execute a Windows .exe: Jenkins, Azure DevOps, TeamCity, GitLab Runner (Windows shell), and more.
  • Same config as zkbuild-action - Uses the same .Zeugwerk/config.json format, so you can switch between cloud and on-premises without reconfiguring your project.
  • Package management - Downloads, installs, updates, and removes Twinpack packages as part of the build.
  • Automated testing - Builds a testable configuration and runs unit tests on a real (or simulated) TwinCAT target.

Requirements

Requirement Notes
Windows Required; TwinCAT XAE runs only on Windows.
TwinCAT XAE or TcXaeShell zkmake selects the TwinCAT installation based on the --tcversion value: TC3.1 picks the first available installation, TC3.1.4026 picks the first available 4026.x build, and TC3.1.4026.33 selects that exact version.
.NET runtime Bundled with the zkmake distribution; no separate install required.
License file license.lic must be placed in the same folder as zkmake.exe. A trial license is available on request; contact us.
.Zeugwerk/config.json Must be present in the repository root (or the directory where you run zkmake). Generate it with Twinpack.

Installation & licensing

  1. Extract the zkmake ZIP archive to a folder on your build machine (e.g. C:\tools\zkmake\).
  2. Place license.lic (provided by Zeugwerk) in the same folder as zkmake.exe.
  3. Optionally add zkmake.exe to your PATH, or reference it by full path in your pipeline scripts.

zkmake uses the same license.lic mechanism as zkdoc - a single license file covers the tool. Contact us for trials, renewals, and volume licensing.

Verify the installation:

zkmake.exe --version

Quick start

  1. Set up Twinpack package sources once on the machine (or CI agent). Configure all repositories you need - the public Twinpack server, the official Beckhoff NuGet feed, and any private NuGet or Twinpack server on your own network:
    twinpack.exe config ^
      --name "Twinpack" ^
      --source "https://twinpack.dev" ^
      --type "Twinpack Repository" ^
      --username <user> --password <pass> ^
      --name "Beckhoff" ^
      --source "https://public.tcpkg.beckhoff-cloud.com/api/v1/feeds/stable" ^
      --type "Beckhoff Repository" ^
      --username <beckhoff_user> --password <beckhoff_pass> ^
      --name "Internal" ^
      --source "https://nuget.mycompany.com/v3/index.json" ^
      --type "NuGet Repository" ^
      --username <internal_user> --password <internal_pass>
    
  2. Run the build:
    zkmake.exe build --update-snapshots --kill-all
    

That's it. zkmake build restores packages, compiles the project, and runs unit tests in one step. Always pass --update-snapshots in CI so the project files are synced from config.json on a clean checkout.


How the build works

zkmake build restores Twinpack packages, applies any configured patches, compiles the project, and then builds and runs unit tests. Results are written to tests\TcUnit_xUnit_results.xml.

Important

Always pass --update-snapshots in CI/CD pipelines. This ensures the .plcproj files are updated from config.json before compilation, guaranteeing a reproducible build from a clean checkout.


Configuration

zkmake reads .Zeugwerk/config.json from the current working directory (or from the directory containing the .sln file). This file is shared with zkbuild-action and Twinpack. Use Twinpack to generate and maintain it from your solution.

For the full schema including all fields, packages, references, modules, and patches: config.json reference.


Commands

build

Builds the PLC project(s), optionally runs unit tests, and for library projects saves and installs the output .library file. Test projects under tests/ and in-solution UnitTestApplication PLCs are handled in the test phase; they are not packaged as release libraries.

zkmake.exe build [options]

How it works:

  1. Connects to Twinpack package servers and downloads required packages.
  2. Applies any configured patches (platform or argument patches).
  3. Opens TcXaeShell headlessly via the TwinCAT automation interface.
  4. Builds the project in release configuration (for in-solution tests, this includes the UnitTestApplication project in the same solution).
  5. If tests are not skipped: runs unit tests on the configured target. Embedded tests (Option A) use an additional testable-library pass and generated harness under tests/; in-solution tests (Option C) deploy the built test application directly.

Options:

Option Default Description
-t, --tcversion (system default) TwinCAT version to use (e.g. TC3.1.4024.56). Must match an installed XAE version.
--patch - Argument patch identifier to apply before compilation. The patch must be declared in config.json under patches.argument.
-C, --compiled false Build libraries as .compiled-library instead of .library.
--skip-build false Skip the release build; only run unit tests.
--skip-tests false Skip unit tests; only run the release build.
--force-checks false Enable all build checks (by default some checks are skipped for Applications, where BuildSolution handles them).
--netid 127.0.0.1.1.1 AMS Net ID of the TwinCAT target used for running unit tests. Use 127.0.0.1.1.1 for the local runtime.
-U, --update-snapshots false Sync dependency versions from config.json into the .plcproj files before building. Always use this in CI/CD. For local builds it is optional if the project is already configured.
-u, --username - Twinpack server username. Can also be set via the TWINPACK_USER environment variable (pipeline convention).
-p, --password - Twinpack server password.
--kill-all false Terminate all pre-existing TcXaeShell processes before starting. Useful on shared build machines where a previous run may have left a process open.
--static-analysis false Run static analysis during the build step.
--variant-build (default variant) Activate this variant before the release build. Only applies to PLC Applications.
--variant-test (same as --variant-build) Activate this variant before the test build. Only applies to PLC Applications.
--platform TwinCAT RT (x64) Target architecture for the release build. Only applies to PLC Applications.
--platform-test TwinCAT RT (x64) Target architecture for the test build. Only applies to PLC Applications.
--test-framework tcunit-zeugwerk Test framework for embedded unit tests (Option A) only. Ignored for in-solution UnitTestApplication projects.
--telemetry false Send anonymous usage data to Zeugwerk (install ID, tool name, version). No source code is transmitted.

Examples:

:: Basic library build
zkmake.exe build

:: Build and run tests on a local TwinCAT runtime
zkmake.exe build --netid 127.0.0.1.1.1

:: Build only (no tests), with explicit TwinCAT version
zkmake.exe build --skip-tests --tcversion TC3.1.4024.56

:: Build compiled library
zkmake.exe build --compiled --skip-tests

:: Build with static analysis enabled
zkmake.exe build --static-analysis --skip-tests

:: Build a specific Application variant
zkmake.exe build --variant-build Release --platform "TwinCAT RT (x64)"

:: Kill stale TcXaeShell and update package snapshots (CI/CD)
zkmake.exe build --kill-all --update-snapshots

configure

Configures a PLC project by inserting references, installing packages, and setting attributes - without performing a full build. Run this to bring a freshly cloned repository into a buildable state, or to apply changes to config.json without recompiling.

zkmake.exe configure [options]

Options:

Option Default Description
-t, --tcversion (system default) TwinCAT version.
-U, --update-snapshots false Sync dependency versions from config.json.
-u, --username - Twinpack server username.
-p, --password - Twinpack server password.
--kill-all false Terminate pre-existing TcXaeShell processes before starting.
--telemetry false Send anonymous usage data to Zeugwerk.

Example:

:: Configure after cloning
zkmake.exe configure --update-snapshots --username %USER% --password %PASS%

update

Downloads and installs packages from the Twinpack package servers configured in %APPDATA%\Zeugwerk\Twinpack\sourceRepositories.json. By default all packages declared in config.json are updated to their latest matching versions; use the filtering options to target specific packages.

zkmake.exe update [options]

Options:

Option Default Description
--project <PROJECT> (all projects) Restrict the update to a specific project.
--plc <PLC> (all PLCs) Restrict the update to a specific PLC within the project.
--package (all) One or more package names to update.
--framework (all) One or more framework names to update all packages belonging to that framework. Can be used without specifying individual packages.
--version (latest) Desired version(s) for the specified package(s). If omitted, the latest available version is used.
--branch (auto) Branch(es) of the package(s) to fetch. If omitted, Twinpack determines the appropriate branch (e.g. main).
--target (auto) TwinCAT target(s) for the package(s) (e.g. TC3.1).
--configuration (auto) Package configuration(s) (e.g. Distribution, Release).
--include-provided-packages false Also update packages that are produced by this solution (i.e. PLCs that are themselves packages).
--skip-download false Update config.json and .plcproj files without downloading the package files.
--force-download false Re-download packages even if they are already present in the local cache.
-U, --update-snapshots false Sync dependency versions from config.json into .plcproj.
--skip-plc-update false Update only config.json; do not modify .plcproj files.
-u, --username - Twinpack server username.
-p, --password - Twinpack server password.
--telemetry false Send anonymous usage data to Zeugwerk.

Examples:

:: Update all packages to latest
zkmake.exe update --username %USER% --password %PASS%

:: Update a specific package to a pinned version
zkmake.exe update --package ZCore --version 1.3.0.0

:: Update all packages from a specific framework
zkmake.exe update --framework ZCore --branch release/1.3

set-version

Sets the version of the PLC(s) in config.json and synchronizes it into the corresponding .plcproj files. Optionally also bumps the versions of framework packages to keep them aligned.

zkmake.exe set-version <VERSION> [options]

Arguments:

Argument Description
<VERSION> Version string in Major.Minor.Patch.Build format, e.g. 1.4.0.0.

Options:

Option Default Description
--project (all) Restrict to a specific project.
--plc (all) Restrict to a specific PLC.
--sync-framework-packages false Also set the version for all packages that belong to the same framework as the PLC being versioned.
--purge-packages false Remove any packages from the PLC that are not declared in config.json.
--branch (auto) Together with --sync-framework-packages: preferred branch for framework packages.
--target (auto) Together with --sync-framework-packages: preferred TwinCAT target for framework packages.
--configuration (auto) Together with --sync-framework-packages: preferred configuration for framework packages.
-U, --update-snapshots false Sync dependency versions from config.json into .plcproj.
--skip-plc-update false Update only config.json; do not modify .plcproj files.
--recursive false Search for config.json files in all subdirectories and apply the version change to each one. Useful for monorepos.
-u, --username - Twinpack server username.
-p, --password - Twinpack server password.
--telemetry false Send anonymous usage data to Zeugwerk.

Examples:

:: Set version for all PLCs in the current directory
zkmake.exe set-version 2.0.0.0

:: Set version for a specific PLC only
zkmake.exe set-version 2.0.0.0 --plc MainPLC

:: Set version and sync all framework packages
zkmake.exe set-version 2.0.0.0 --sync-framework-packages

:: Set version recursively across a monorepo
zkmake.exe set-version 2.0.0.0 --recursive

clean

Uninstalls all packages that are provided by the current project configuration from the TwinCAT library manager. Useful for cleanup after a build or when switching between project versions.

zkmake.exe clean [options]

Options:

Option Default Description
-t, --tcversion (system default) TwinCAT version.
-U, --update-snapshots false Sync dependency versions from config.json before cleaning.
-u, --username - Twinpack server username.
-p, --password - Twinpack server password.
--telemetry false Send anonymous usage data to Zeugwerk.

Example:

zkmake.exe clean

kill-all

Terminates all running TcXaeShell processes on the machine. Use this for manual recovery from a stuck or hung TwinCAT automation session.

Note

For builds, use the --kill-all flag on zkmake build instead of running this as a separate step. zkmake build --kill-all terminates stale processes and immediately starts the build in one command.

zkmake.exe kill-all

This command has no options beyond --help and --telemetry.


Unit tests

zkmake build discovers and runs unit tests automatically; choose one per project. For setup options, test method signatures, DataRows, CI reporting, and the --test-framework flag, see the Unit tests reference.


Patches

zkmake build supports platform patches (applied automatically per TwinCAT version) and argument patches (applied when you pass --patch <identifier>). For the full patch configuration format, see the config.json reference - Patches.


Configuring Twinpack credentials

zkmake delegates all package downloads and version lookups to Twinpack. Twinpack maintains its own credential store in %APPDATA%\Zeugwerk\Twinpack\sourceRepositories.json. Once configured, zkmake picks up those credentials automatically - you do not need to pass --username or --password on the command line.

Configure Twinpack sources once per machine (or per CI agent) before running any zkmake command:

twinpack.exe config ^
  --name "Twinpack" ^
  --source "https://twinpack.dev" ^
  --type "Twinpack Repository" ^
  --username "%TWINPACK_USER%" ^
  --password "%TWINPACK_PASS%"

In CI/CD, run this step before the build step, passing the credentials from your pipeline's secret store. On a developer workstation you typically configure it once through the Twinpack IDE extension and never need to repeat it.


Integration with CI/CD pipelines

The examples below show a complete pipeline that sets the version, builds, and publishes test results. Adapt the agent label and secret variable names to match your environment.

Jenkins (Declarative Pipeline)

pipeline {
    agent { label 'windows-twincat' }

    parameters {
        string(name: 'VERSION', defaultValue: '', description: 'Version to build (e.g. 1.2.3.4)')
    }

    environment {
        TWINPACK_CREDS  = credentials('twinpack-credentials')   // Jenkins username+password credential
        BECKHOFF_CREDS  = credentials('beckhoff-credentials')
    }

    stages {
        stage('Configure Twinpack') {
            steps {
                bat """
                    twinpack.exe config ^
                      --name "Twinpack" ^
                      --source "https://twinpack.dev" ^
                      --type "Twinpack Repository" ^
                      --username "%TWINPACK_CREDS_USR%" ^
                      --password "%TWINPACK_CREDS_PSW%" ^
                      --name "Beckhoff" ^
                      --source "https://public.tcpkg.beckhoff-cloud.com/api/v1/feeds/stable" ^
                      --type "Beckhoff Repository" ^
                      --username "%BECKHOFF_CREDS_USR%" ^
                      --password "%BECKHOFF_CREDS_PSW%"
                """
            }
        }

        stage('Set version') {
            steps {
                bat "zkmake.exe set-version ${params.VERSION} --update-snapshots --purge-packages"
            }
        }

        stage('Build') {
            steps {
                bat "zkmake.exe build --update-snapshots --kill-all --tcversion TC3.1"
            }
        }
    }

    post {
        always {
            junit testResults: 'tests/**/TcUnit_xUnit_results.xml', allowEmptyResults: true
            archiveArtifacts artifacts: '.Zeugwerk/libraries/**/*.library', allowEmptyArchive: true
        }
    }
}

Azure DevOps

jobs:
  - job: Build
    pool:
      name: 'windows-twincat-agents'
    steps:
      - script: |
          twinpack.exe config ^
            --name "Twinpack" ^
            --source "https://twinpack.dev" ^
            --type "Twinpack Repository" ^
            --username "$(TWINPACK_USER)" ^
            --password "$(TWINPACK_PASS)" ^
            --name "Beckhoff" ^
            --source "https://public.tcpkg.beckhoff-cloud.com/api/v1/feeds/stable" ^
            --type "Beckhoff Repository" ^
            --username "$(BECKHOFF_USER)" ^
            --password "$(BECKHOFF_PASS)"
        displayName: Configure Twinpack

      - script: |
          zkmake.exe set-version $(Build.BuildNumber) -U --purge-packages --sync-framework-packages
        displayName: Set version

      - script: |
          zkmake.exe build -U --kill-all --tcversion TC3.1
        displayName: Build and test

      - task: PublishTestResults@2
        condition: always()
        inputs:
          testResultsFormat: JUnit
          testResultsFiles: tests/**/TcUnit_xUnit_results.xml

      - task: PublishPipelineArtifact@1
        inputs:
          targetPath: .Zeugwerk/libraries
          artifact: libraries

Troubleshooting

Symptom Likely cause Fix
License verification failed license.lic not found or invalid Place license.lic in the same folder as zkmake.exe. Contact us for a trial or renewal.
Could not find config.json config.json missing or wrong working directory Run zkmake.exe from the repository root, or generate config.json with Twinpack.
Build hangs or TcXaeShell is unresponsive A previous TcXaeShell session is still open Add --kill-all to terminate stale processes before building.
TwinCAT version mismatch The installed XAE version does not match the project Pass --tcversion TC3.1.4024.56 (or the required version) explicitly.
Tests not running No tests detected Use embedded FBs (ZCore.IUnittest / Testbench.IUnittest), a tests/.Zeugwerk/config.json, or declare UnitTestApplication in the main config.json. See Unit tests.
Package download fails Network issue or wrong credentials Check --username / --password and verify access to the Twinpack server.
Build fails with COM exception TcXaeShell crashed mid-run zkmake retries automatically (up to a limit). If it still fails, add --kill-all and retry manually.

Comparison: zkmake vs. zkbuild-action

For a full feature-by-feature comparison including guidance on when to choose each tool, see the zkbuild-action vs. zkmake comparison.