Building a single-file distribution with 8.7

Tcl and Tk have regular builds of their code as single file distributions. You currently (2022-06-15) need a Github login to access the results (due to anti-bot measures by Github). The builds are processed using Github's infrastructure and do things like running the test suite in lots of configurations, but we're interested on this page in the distribution building code:

Tcl's is here:

Tk's is here:

General notes

They use the Github workflow format (which is YAML following a particular schema). Of interest at the top is this:

on: [push]

which says "build whenever a new push to the git repository is done" (which is usually daily when we synch from More info about workflow triggers is here .

Then there's one actual workflow job per platform (doesn't have to be like that, but it's mighty convenient). Jobs contain a bunch of steps; the workflow succeeds when all the steps for all the jobs succeed. Success of a shell-scripted step is the usual Unix shell definition.

Building Tcl

A walkthrough of the Tcl/Linux build process

Let's look at the stages for building Tcl for Linux:

      - name: Checkout
        uses: actions/checkout@v3

The "Checkout" stage just grabs the code from the Tcl repository (which repo is implicit in the workflow; Github knows that it is being run for the repo).

      - name: Prepare
        run: |
          touch generic/tclStubInit.c generic/tclOOStubInit.c
          mkdir 1dist
          echo "VER_PATH=$(cd tools; pwd)/addVerToFile.tcl" >> $GITHUB_ENV
        working-directory: .

The "Prepare" step sets some stuff up by running a small shell script. The addVertoFile.tcl script is in the tools directory; we're storing the path to it in the multi-step shared environment so that we can find it easily.

      - name: Configure
        run: ./configure --disable-symbols --disable-shared --enable-zipfs
        working-directory: unix

The "Configure" step runs the configure script in the unix directory. We're making a release build (no symbols) that is static (single file distributions should be this) and includes zipfs (I think that's enabled by default, but might as well be explicit as we definitely want it!)

      - name: Build
        run: |
          make tclsh
          make shell SCRIPT="$VER_PATH $GITHUB_ENV"
          echo "TCL_ZIP=`pwd`/`echo libtcl*.zip`" >> $GITHUB_ENV
        working-directory: unix

The "Build" step does the build. In particular, we make tclsh, then call our addVerToFile.tcl script from earlier to add the real version data into the environment. Finally, we locate the library zip file (produced normally in the build) so that we can also find that easily later.

After this step, the environment contains definitions for:

  • VER_PATH - The location of addVerToFile.tcl (not actually needed any more)
  • TCL_VERSION - The version of Tcl we've just built
  • TCL_PATCHLEVEL - The patchlevel of Tcl we've just built
  • TCL_ZIP - The location of the library ZIP

We don't actually need TCL_ZIP for this build. Probably because the makefiles have gained smarts that used to be held externally. That's good.

      - name: Package
        run: |
          cp ../unix/tclsh tclsh${TCL_PATCHLEVEL}_snapshot
          chmod +x tclsh${TCL_PATCHLEVEL}_snapshot
          tar -cf tclsh${TCL_PATCHLEVEL}_snapshot.tar tclsh${TCL_PATCHLEVEL}_snapshot
        working-directory: 1dist

The "Package" step (which works in its own directory) copies the tclsh, marks it as executable, and parcels it up into a TAR file (because that keeps the executable bit). We don't bother compressing it; the compression gains are going to be small (and Github's internal infrastructure compresses things anyway).

      - name: Upload
        uses: actions/upload-artifact@v3
          name: Tclsh ${{ env.TCL_PATCHLEVEL }} Linux single-file build (snapshot)
          path: 1dist/*.tar

The "Upload" step takes the built and packaged executable and ships it to the Github artifact distribution system. If we were to want to put the builds elsewhere, this is the step that would change.

Other platforms

Other platforms follow a very similar model, but with different details. The packaging step for macOS is particularly messy because we need to create a .dmg file, but the mess is pretty much localized to just there (and the "Prepare" step).

Any application of signatures would be done either during the "Package" step or as another step immediately afterwards. Code signing is very platform-specific (and isn't usually done for a simple TAR file distribution or single-executable distribution on Linux; it tends to be more the reserve of .rpm or .apt files, but those have a different set of intended consumers).

Building Tk

Tk follows a similar model, but there's a few more steps to get things right (because Tk depends on more things than Tcl).