LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Compiling OpenAccess and OAScript on Non-RHEL Linux: A Deep Dive

edaopenaccessoascripttclslesdockercompilingic-designsi2linux

Compiling OpenAccess and OAScript on Non-RHEL Linux: A Deep Dive

The SI2 OpenAccess source distribution was developed and validated against Red Hat Enterprise Linux. RHEL and its derivatives (CentOS, Rocky Linux, AlmaLinux) are the reference platforms, the ones the Makefiles assume, and the ones where you’ll find help in the SI2 forums. If you’re working on SLES 15, Ubuntu, Debian, or any other non-RHEL distribution, you’re off the beaten path.

That said, building OpenAccess on non-RHEL Linux is entirely achievable. The source is standard C++; the dependencies are well-known. What you’re really managing is two things: toolchain compatibility (GCC version, C++ ABI) and the subtleties of how different distributions package Tcl — which matters enormously for OAScript.

This post focuses on that second problem. Building the core OA libraries is straightforward once you have the right toolchain. Building OAScript correctly — getting the Tcl bindings, the interpreter embedding, and the plugin interface right — is where most of the pain lives, and it’s where we’ll spend most of our time.


Source Access and Licensing

OpenAccess source is available through SI2 membership. You need a free SI2 account at si2.org to download the source tarball. The license is the OALA (Open Access License Agreement) — a permissive research/internal-use license that allows building and using OpenAccess in commercial EDA tools as long as you remain an SI2 member and don’t redistribute modified source.

The current reference release is OpenAccess 22.50 (commonly written as oa22.50.x). Download links are in the SI2 member area under “OpenAccess Downloads.”

oa22.50.x_src.tar.gz        # Full source
oa22.50.x_doc.tar.gz        # API documentation (HTML)

Unpack to a build directory:

1
2
3
4
mkdir -p ~/oa-build && cd ~/oa-build
tar xzf oa22.50.x_src.tar.gz
ls
# oa/    <- source root

Throughout this post, $OA_HOME refers to this source root.


The Dependency Landscape

Before touching a Makefile, map out your dependencies. OpenAccess has two distinct build targets with different dependency profiles:

Core OA libraries (libOA*.so):

  • GCC 9+ (C++17 support required in oa22.50)
  • GNU make 3.82+
  • Doxygen (optional, for docs)
  • No external library dependencies beyond libc/libstdc++

OAScript (oascript binary and libOAScript*.so):

  • Everything above
  • Tcl 8.5 or 8.6 development headers and shared library
  • SWIG 4.x (for regenerating bindings, optional if using pre-generated wrappers)
  • Tk development headers (optional, for GUI support in oascript)
  • Python 3 (optional, for the oascript Python extension)

The Tcl dependency is where non-RHEL distributions diverge. On RHEL 8/9 and Rocky Linux, tcl-devel installs Tcl 8.6 headers into /usr/include/tcl8.6/ with a tclConfig.sh at /usr/lib64/tcl8.6/tclConfig.sh. Other distributions put these files in different locations, and the OAScript build system is not always graceful about finding them.


The SI2 Docker Build Environment

SI2 distributes a Docker-based build environment alongside the source. This is the most reliable path for non-RHEL systems: build inside a known-good container, then use the resulting binaries on your target system.

What the Docker Environment Provides

The SI2 Docker setup (found in $OA_HOME/docker/) provides:

  • A RHEL 8-compatible base image (typically UBI8 or Rocky Linux 8)
  • Pre-installed build dependencies at the correct versions
  • A wrapper script that mounts your source tree into the container and runs the build
  • Reproducible builds across any host OS that can run Docker
1
2
3
4
5
ls $OA_HOME/docker/
# Dockerfile
# build_in_docker.sh
# requirements.txt    (Python deps for optional tooling)
# entrypoint.sh

Examining the Dockerfile

Read the Dockerfile before running it — it tells you exactly what the reference build environment looks like:

1
cat $OA_HOME/docker/Dockerfile

A typical SI2 Dockerfile looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FROM rockylinux:8

RUN dnf install -y \
    gcc gcc-c++ make \
    tcl tcl-devel \
    tk tk-devel \
    swig \
    doxygen \
    git \
    && dnf clean all

ENV OA_HOME=/oa
ENV OA_UNSUPPORTED_PLAT=linux_rhel70_gcc83x
WORKDIR /oa

The OA_UNSUPPORTED_PLAT environment variable is critical — we’ll come back to it. It tells the build system which platform string to use for directory naming. The format is linux_<distro_tag>_<compiler_tag>.

Running the Docker Build

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
cd $OA_HOME

# Build the Docker image (one-time)
docker build -t oa-builder docker/

# Run the build inside the container
# Source tree is mounted read-write so build artifacts appear on the host
docker run --rm \
  -v $(pwd):/oa \
  -w /oa \
  oa-builder \
  make BUILDTYPE=opt all

# Or use the provided wrapper script
chmod +x docker/build_in_docker.sh
./docker/build_in_docker.sh

After the build completes, the output appears in $OA_HOME/data/ structured by platform string:

$OA_HOME/data/
└── linux_rhel70_gcc83x/
    ├── lib/
    │   ├── opt/
    │   │   ├── libOABase.so
    │   │   ├── libOACommon.so
    │   │   ├── libOALayout.so
    │   │   ├── libOASchematic.so
    │   │   ├── libOAScript.so       ← OAScript library
    │   │   └── ...
    │   └── dbg/
    ├── bin/
    │   └── opt/
    │       ├── oascript             ← OAScript interpreter
    │       └── ...
    └── include/
        └── oa/
            └── ...

Customizing the Docker Build for Your Target Platform

The Docker approach has a critical limitation: the resulting binaries are linked against the GLIBC version inside the container. If your target system has a newer GLIBC (almost certain on Ubuntu 22.04+, SLES 15), this is fine — GLIBC is backwards compatible. If your target has an older GLIBC, you’ll have symbol resolution failures at runtime.

Strategy 1: Match container GLIBC to target

Build with a container whose GLIBC matches or is older than your target:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# For SLES 15 SP5 (GLIBC 2.31):
# Using opensuse/leap:15.5 — GCC 12 is in the standard OSS repo on Leap.
# On actual SLES 15 (not Leap), replace this with ubi8 or rockylinux:8
# and register the Development Tools module inside the container first.
FROM opensuse/leap:15.5

RUN zypper install -y \
    gcc12 gcc12-c++ make \
    tcl-devel \
    tk-devel \
    swig \
    && zypper clean

ENV CC=gcc-12
ENV CXX=g++-12
ENV OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x
1
2
docker build -t oa-builder-sles15 -f Dockerfile.sles15 docker/
docker run --rm -v $(pwd):/oa -w /oa oa-builder-sles15 make BUILDTYPE=opt all

Strategy 2: Use --platform for architecture targeting

For cross-architecture builds (e.g., building on an x86 workstation for an ARM server):

1
2
3
4
5
6
docker run --rm \
  --platform linux/amd64 \
  -v $(pwd):/oa \
  -w /oa \
  oa-builder \
  make BUILDTYPE=opt all

Strategy 3: Static linking of libstdc++ and libgcc

Build with statically linked C++ runtime to avoid libstdc++ version mismatches. Add to the make invocation:

1
2
docker run --rm -v $(pwd):/oa -w /oa oa-builder \
  make BUILDTYPE=opt EXTRA_LDFLAGS="-static-libstdc++ -static-libgcc" all

This significantly reduces binary portability headaches at the cost of larger binaries.


Building Core OA Without Docker

If you want a native build on SLES 15 or Ubuntu directly (useful for development, where you want fast rebuild cycles without Docker overhead), here’s what changes.

SLES 15 Prerequisites

On actual SLES 15 (the enterprise product, not openSUSE Leap), GCC 12 lives in the Development Tools module, which must be registered before zypper can find it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# SLES 15 SP4/SP5 — register the Development Tools module first
# (requires an active SUSE subscription or a 60-day trial registration)
sudo SUSEConnect -p sle-module-development-tools/15.5/x86_64

# Now install the toolchain and build dependencies
sudo zypper install -y \
    gcc12 gcc12-c++ \
    make \
    tcl-devel \
    tk-devel \
    swig \
    doxygen

# Point the build system at GCC 12 explicitly via CC/CXX (see below)
# update-alternatives is optional — setting CC/CXX env vars is cleaner

If SUSEConnect isn’t available (air-gapped system, no subscription), fall back to the default system GCC. On SLES 15 SP3+ the default GCC is 10; on SP5 it may be 13. Any GCC >= 9 with C++17 support will work:

1
2
3
4
5
6
7
8
# Fallback: use whatever GCC the base system ships
sudo zypper install -y gcc gcc-c++ make tcl-devel tk-devel swig doxygen

# Check what version you got
gcc --version

# Set OA_UNSUPPORTED_PLAT to match (e.g. gcc10x, gcc13x)
export OA_UNSUPPORTED_PLAT=linux_sles15_gcc10x

Note on package names: the binary is gcc-12 (with a hyphen) but the zypper package is gcc12 (no hyphen). Similarly g++-12 vs gcc12-c++. There is no libstdc++6-devel-gcc12 package — the C++ headers are bundled with gcc12-c++.

1
2
3
4
5
6
7
# After installing gcc12, set CC/CXX — do NOT rely on update-alternatives on SLES
export CC=gcc-12
export CXX=g++-12

# Verify
$CC --version   # should print gcc 12.x.x
$CXX --version  # should print g++ 12.x.x

Ubuntu 22.04 / 24.04 Prerequisites

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sudo apt-get install -y \
    build-essential \
    gcc-12 g++-12 \
    tcl-dev \
    tk-dev \
    swig \
    doxygen

# Ubuntu may have multiple GCC versions; specify explicitly
export CC=gcc-12
export CXX=g++-12

The Platform String

OpenAccess uses a platform string to name output directories and select platform-specific code paths. For non-RHEL systems, set OA_UNSUPPORTED_PLAT:

1
2
3
4
5
6
7
8
9
# Format: linux_<distro>_<gccver>
# SLES 15 with GCC 12:
export OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x

# Ubuntu 22.04 with GCC 12:
export OA_UNSUPPORTED_PLAT=linux_ubuntu22_gcc12x

# The string is mostly for directory naming — it doesn't change compilation flags
# but it must be set or the build system uses an unsupported default

Makefile Configuration

The primary build configuration is in $OA_HOME/src/make/:

1
2
3
4
5
ls $OA_HOME/src/make/
# GNUmakefile          ← top-level make entry point
# platform.mk          ← platform detection
# config.mk            ← compiler flags
# rules.mk             ← build rules

Review platform.mk to understand how the platform is detected and what flags are set:

1
grep -n "RHEL\|SLES\|UNSUPPORTED" $OA_HOME/src/make/platform.mk

For unsupported platforms, the build system falls through to a generic Linux configuration. This usually works but may miss platform-specific optimizations.

Running the Core Build

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
cd $OA_HOME

# Build optimized libraries only
make BUILDTYPE=opt OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x

# Build both optimized and debug
make BUILDTYPE=all OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x

# Parallel build (adjust -j to your core count)
make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x

# Install to a prefix (optional)
make BUILDTYPE=opt OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x \
     DESTDIR=/opt/oa22 install

A successful core build produces the libOA*.so shared libraries. Verify:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ls $OA_HOME/data/linux_sles15_gcc12x/lib/opt/
# libOAAnalog.so
# libOABase.so
# libOACommon.so
# libOADesign.so
# libOAFramework.so
# libOALayout.so
# libOASchematic.so
# libOATech.so
# libOAWafer.so

# Check that they load correctly
ldd $OA_HOME/data/linux_sles15_gcc12x/lib/opt/libOABase.so
# Should show only standard system libraries (libc, libstdc++, libm)
# Any "not found" entries indicate missing dependencies

Building OAScript: The Deep Dive

OAScript is where the real complexity lives. It consists of:

  1. oascript binary: a standalone Tcl interpreter with OA extensions built in
  2. libOAScript.so: the OA Tcl extension loadable from any Tcl interpreter
  3. libOAScriptPlugin.so: the plugin interface for extending OAScript
  4. SWIG-generated wrappers: C++ glue code between the OA C++ API and Tcl

OAScript Source Layout

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
ls $OA_HOME/src/oa/
# include/     ← C++ headers
# src/
#   OABase/
#   OACommon/
#   OALayout/
#   OAScript/    ← OAScript source
#   ...

ls $OA_HOME/src/oa/src/OAScript/
# GNUmakefile
# include/
# src/
#   oaScriptApp.cpp       ← main() for oascript binary
#   oaScriptInterp.cpp    ← Tcl interpreter embedding
#   oaScriptPlugin.cpp    ← plugin loading mechanism
#   oaScriptTcl.cpp       ← Tcl command registration
#   swig/                 ← SWIG interface files
#     OAScript.i          ← top-level SWIG interface
#     OABase.i
#     OALayout.i
#     ...

How OAScript Embeds Tcl

OAScript embeds the Tcl interpreter using Tcl’s embedding API rather than linking against Tcl as an extension. The key distinction:

  • Extension mode (Tcl_AppInit): your code is loaded into an existing tclsh or wish interpreter via package require. Your code calls Tcl_CreateCommand and similar.
  • Embedding mode (Tcl_CreateInterp): your binary creates and owns the Tcl interpreter. This is what oascript does.

The embedding approach gives OAScript control over the interpreter lifecycle, startup scripts, and the oaScript package search path — but it means OAScript must find and link against the Tcl library at build time, and the Tcl version must match what’s on the target system at runtime.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Simplified from oaScriptInterp.cpp
#include <tcl.h>

class oaScriptInterp {
    Tcl_Interp* interp_;

public:
    oaScriptInterp() {
        Tcl_FindExecutable(nullptr);
        interp_ = Tcl_CreateInterp();
        Tcl_Init(interp_);          // load init.tcl
        registerOACommands(interp_);
        loadStartupScripts(interp_);
    }

    int eval(const char* script) {
        return Tcl_Eval(interp_, script);
    }
};

Finding Tcl: The tclConfig.sh Problem

The OAScript build system locates Tcl via tclConfig.sh, a shell script installed by the Tcl package that exports all the paths and flags needed to compile against it.

On RHEL 8/9 with tcl-devel:

/usr/lib64/tcl8.6/tclConfig.sh

On SLES 15 with tcl-devel:

/usr/lib64/tclConfig.sh          ← note: no version subdirectory
# or
/usr/lib/tclConfig.sh

On Ubuntu 22.04 with tcl-dev:

/usr/lib/tcl8.6/tclConfig.sh
# or (tcl-dev symlink)
/usr/lib/x86_64-linux-gnu/tcl8.6/tclConfig.sh

The OAScript Makefile typically searches a list of known locations. On SLES 15, it often fails to find tclConfig.sh automatically. The fix is to set it explicitly:

1
2
3
4
5
6
# SLES 15: find where tclConfig.sh actually is
find /usr -name tclConfig.sh 2>/dev/null
# /usr/lib64/tclConfig.sh

# Set explicitly in the make invocation
export TCL_CONFIG=/usr/lib64/tclConfig.sh

Alternatively, patch the OAScript Makefile to add SLES paths to the search list:

1
2
3
4
5
6
7
8
9
# In $OA_HOME/src/oa/src/OAScript/GNUmakefile
# Add SLES 15 and Ubuntu paths to the TCL_CONFIG search
TCL_CONFIG_SEARCH_PATHS := \
    /usr/lib64/tcl8.6/tclConfig.sh \
    /usr/lib64/tclConfig.sh \
    /usr/lib/tclConfig.sh \
    /usr/lib/tcl8.6/tclConfig.sh \
    /usr/lib/x86_64-linux-gnu/tcl8.6/tclConfig.sh \
    /usr/local/lib/tclConfig.sh

Sourcing tclConfig.sh

tclConfig.sh exports variables like TCL_LIB_SPEC, TCL_INCLUDE_SPEC, and TCL_VERSION. Source it to see what your installation provides:

1
2
3
4
5
6
source /usr/lib64/tclConfig.sh
echo $TCL_VERSION          # 8.6
echo $TCL_LIB_SPEC         # -L/usr/lib64 -ltcl8.6
echo $TCL_INCLUDE_SPEC     # -I/usr/include/tcl8.6
echo $TCL_EXEC_PREFIX      # /usr
echo $TCL_SHARED_LIB_SUFFIX # .so

On SLES 15, the header location differs from RHEL:

1
2
3
4
5
6
7
8
9
# RHEL 8:
ls /usr/include/tcl8.6/
# tcl.h  tclDecls.h  tclPlatDecls.h  ...

# SLES 15:
ls /usr/include/
# tcl.h       ← directly in /usr/include, not a subdirectory
# tclDecls.h
# tclPlatDecls.h

This flat layout means -I/usr/include/tcl8.6 (the RHEL path) fails on SLES 15. Fix by setting TCL_INCLUDE_SPEC before building:

1
export TCL_INCLUDE_SPEC="-I/usr/include"

Or create a compatibility symlink:

1
2
3
4
sudo mkdir -p /usr/include/tcl8.6
sudo ln -sf /usr/include/tcl.h /usr/include/tcl8.6/tcl.h
sudo ln -sf /usr/include/tclDecls.h /usr/include/tcl8.6/tclDecls.h
sudo ln -sf /usr/include/tclPlatDecls.h /usr/include/tcl8.6/tclPlatDecls.h

The SWIG Wrapper Generation

OAScript uses SWIG to generate the Tcl bindings for the C++ OA API. The pre-generated SWIG output (.cpp files) is included in the source tarball, so you don’t need to re-run SWIG unless you’re adding new API coverage.

If you do need to regenerate (e.g., you’ve patched the OA headers or need to add new bindings):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Check SWIG version
swig -version
# SWIG Version 4.1.1

# SLES 15 ships SWIG 3.x from the standard repo; 4.x may need manual install or OBS
# Check if the OA SWIG interface files require SWIG 4
grep -r "SWIG_VERSION\|swig4\|4\." $OA_HOME/src/oa/src/OAScript/swig/*.i | head -10

# Regenerate a specific module
cd $OA_HOME/src/oa/src/OAScript/swig/
swig -c++ -tcl8 \
     -I$OA_HOME/src/oa/include \
     -o ../src/OALayoutTcl_wrap.cpp \
     OALayout.i

The SWIG interface files (.i) describe which C++ classes and methods are exposed to Tcl. Each major OA subsystem has its own .i file:

OAScript.i         ← master interface (includes all others)
OABase.i           ← base types, points, boxes, transforms
OALayout.i         ← physical layout (layers, shapes, instances)
OASchematic.i      ← schematic/netlist objects
OATech.i           ← technology database
OADesign.i         ← design hierarchy
OAConstraint.i     ← constraint objects

Building OAScript on SLES 15: Complete Procedure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
cd $OA_HOME

# 1. Set environment
export OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x
export CC=gcc-12
export CXX=g++-12

# 2. Locate Tcl
TCL_CONFIG=$(find /usr -name tclConfig.sh 2>/dev/null | head -1)
source $TCL_CONFIG
echo "Tcl version: $TCL_VERSION"
echo "Tcl libs: $TCL_LIB_SPEC"
echo "Tcl includes: $TCL_INCLUDE_SPEC"

# 3. Fix include path if needed (SLES flat layout)
if [ ! -d "/usr/include/tcl${TCL_VERSION}" ]; then
    echo "Creating Tcl include symlink for SLES layout"
    sudo mkdir -p /usr/include/tcl${TCL_VERSION}
    for f in tcl.h tclDecls.h tclPlatDecls.h; do
        [ -f "/usr/include/$f" ] && \
            sudo ln -sf /usr/include/$f /usr/include/tcl${TCL_VERSION}/$f
    done
fi

# 4. Build core OA libraries first
make -j$(nproc) BUILDTYPE=opt \
    OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT \
    oa_libs

# 5. Build OAScript
make -j$(nproc) BUILDTYPE=opt \
    OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT \
    TCL_CONFIG=$TCL_CONFIG \
    oascript

# 6. Verify the build output
PLAT_DIR=$OA_HOME/data/$OA_UNSUPPORTED_PLAT
ls $PLAT_DIR/bin/opt/oascript
ls $PLAT_DIR/lib/opt/libOAScript.so

# 7. Check library dependencies
ldd $PLAT_DIR/lib/opt/libOAScript.so | grep -i tcl
# Should show: libtcl8.6.so.0 => /usr/lib64/libtcl8.6.so.0

ldd $PLAT_DIR/bin/opt/oascript
# Should resolve all dependencies cleanly

Diagnosing OAScript Build Failures

tcl.h: No such file or directory

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Confirm tcl-devel is installed
rpm -q tcl-devel    # SLES/RHEL
dpkg -l tcl-dev     # Ubuntu/Debian

# Find the actual header location
find /usr -name tcl.h 2>/dev/null

# Pass include path explicitly
make BUILDTYPE=opt \
    OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT \
    EXTRA_CPPFLAGS="-I/usr/include"

undefined reference to Tcl_CreateInterp

The linker can’t find the Tcl shared library. Usually a path issue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Find the Tcl shared library
find /usr -name "libtcl*.so*" 2>/dev/null
# /usr/lib64/libtcl8.6.so.0.0
# /usr/lib64/libtcl8.6.so -> libtcl8.6.so.0.0  (symlink)

# The linker needs the unversioned .so symlink
# SLES sometimes only has the .so.0 and .so.0.0 variants
ls /usr/lib64/libtcl*.so
# If only libtcl8.6.so.0 exists (no unversioned symlink):
sudo ln -sf /usr/lib64/libtcl8.6.so.0 /usr/lib64/libtcl8.6.so

# Then retry the build with explicit library path
make BUILDTYPE=opt \
    OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT \
    EXTRA_LDFLAGS="-L/usr/lib64 -ltcl8.6"

version 'GLIBC_2.33' not found

The built binary requires a newer GLIBC than your target system. This is the Docker cross-build problem described earlier. Solutions:

  1. Build in a container based on your target OS (best)
  2. Build with -static-libstdc++ -static-libgcc (eliminates C++ runtime version mismatch, but not GLIBC)
  3. Use an older container base image that matches your target GLIBC
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Check what GLIBC version your binary requires
objdump -p $PLAT_DIR/bin/opt/oascript | grep GLIBC
# GLIBC_2.17  (symbols needed from libc)
# GLIBC_2.28  (symbols needed from libc)

# Check your target system's GLIBC version
ldd --version | head -1
# ldd (GNU libc) 2.31    ← SLES 15 SP4

# If the binary requires GLIBC 2.33 but target has 2.31, rebuild in a container
# with GLIBC 2.31 or earlier

OAScript crashes on startup with can't find package Tcl

This is a Tcl initialization path issue. OAScript embeds its own Tcl interpreter and must find init.tcl at startup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Run with verbose Tcl library path debugging
TCLLIBPATH=/usr/share/tcl8.6 \
TCL_LIBRARY=/usr/share/tcl8.6 \
$PLAT_DIR/bin/opt/oascript

# Check where Tcl init.tcl is on your system
find /usr -name init.tcl 2>/dev/null
# RHEL: /usr/share/tcl8.6/init.tcl
# SLES 15: /usr/share/tcl8.6/init.tcl (same) or /usr/lib64/tcl8.6/init.tcl

# Set TCL_LIBRARY in your wrapper script or environment
export TCL_LIBRARY=/usr/share/tcl8.6

Symbol conflicts between OAScript’s embedded Tcl and system Tcl

If your application loads both libOAScript.so and another Tcl extension, you may hit symbol conflicts where two versions of Tcl’s internal symbols are loaded. This is a known pain point:

1
2
3
4
5
6
7
8
9
# Check exported Tcl symbols from libOAScript.so
nm -D $PLAT_DIR/lib/opt/libOAScript.so | grep " T Tcl_" | head -10

# If libOAScript.so exports Tcl_ symbols publicly (not hidden),
# and your application also links libtcl directly, conflicts arise

# Mitigation: use RTLD_LOCAL when loading libOAScript.so
# In C++:
void* handle = dlopen("libOAScript.so", RTLD_NOW | RTLD_LOCAL);

For tools that use OAScript as a library (rather than running the oascript binary), compile with -fvisibility=hidden and explicit symbol export maps to contain the Tcl symbols:

1
2
3
4
make BUILDTYPE=opt \
    OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT \
    EXTRA_CXXFLAGS="-fvisibility=hidden" \
    EXTRA_LDFLAGS="-Wl,--version-script=$OA_HOME/src/oa/src/OAScript/oascript.map"

Using the Docker Build for Cross-Distro Libraries

The Docker approach is particularly useful when you need to maintain libraries for multiple Linux distributions from a single CI/CD pipeline. Here’s a practical multi-target setup:

Multi-Stage Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Dockerfile.multi
# Build OA for multiple target distributions in parallel

# ── RHEL 8 / Rocky 8 target ───────────────────────────────────────────────
FROM rockylinux:8 AS builder-rhel8
RUN dnf install -y gcc gcc-c++ make tcl-devel swig && dnf clean all
ENV OA_UNSUPPORTED_PLAT=linux_rhel8_gcc83x
COPY . /oa
WORKDIR /oa
RUN make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT

# ── SLES 15 target ────────────────────────────────────────────────────────
# opensuse/leap:15.5 has gcc12 in its standard OSS repo — no module registration needed.
# If you must build inside an actual SLES 15 image, add:
#   RUN SUSEConnect -p sle-module-development-tools/15.5/x86_64
# before the zypper install (requires a mounted subscription credential).
FROM opensuse/leap:15.5 AS builder-sles15
RUN zypper install -y gcc12 gcc12-c++ make tcl-devel swig && zypper clean
ENV OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x
ENV CC=gcc-12 CXX=g++-12
COPY . /oa
WORKDIR /oa
RUN ln -sf /usr/include/tcl.h /usr/include/tcl8.6/tcl.h 2>/dev/null || true
RUN make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT

# ── Ubuntu 22.04 target ───────────────────────────────────────────────────
FROM ubuntu:22.04 AS builder-ubuntu22
RUN apt-get update && apt-get install -y \
    gcc-12 g++-12 make tcl-dev && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
ENV OA_UNSUPPORTED_PLAT=linux_ubuntu22_gcc12x
ENV CC=gcc-12 CXX=g++-12
COPY . /oa
WORKDIR /oa
RUN make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT

# ── Collect artifacts ─────────────────────────────────────────────────────
FROM scratch AS artifacts
COPY --from=builder-rhel8   /oa/data/linux_rhel8_gcc83x/   /linux_rhel8_gcc83x/
COPY --from=builder-sles15  /oa/data/linux_sles15_gcc12x/  /linux_sles15_gcc12x/
COPY --from=builder-ubuntu22 /oa/data/linux_ubuntu22_gcc12x/ /linux_ubuntu22_gcc12x/
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Build all targets and extract artifacts
docker buildx build \
  --target artifacts \
  --output type=local,dest=./dist \
  -f Dockerfile.multi \
  .

ls ./dist/
# linux_rhel8_gcc83x/
# linux_sles15_gcc12x/
# linux_ubuntu22_gcc12x/

CI/CD Pipeline Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# .gitlab-ci.yml (or GitHub Actions equivalent)
stages:
  - build
  - test
  - package

build:sles15:
  stage: build
  image: opensuse/leap:15.5
  # gcc12 is in the standard OSS repo on openSUSE Leap — no SUSEConnect needed.
  # On actual SLES 15 runners, register the Development Tools module first.
  script:
    - zypper install -y gcc12 gcc12-c++ make tcl-devel swig
    - export OA_UNSUPPORTED_PLAT=linux_sles15_gcc12x
    - export CC=gcc-12 CXX=g++-12
    - make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT
  artifacts:
    paths:
      - data/linux_sles15_gcc12x/

build:ubuntu22:
  stage: build
  image: ubuntu:22.04
  script:
    - apt-get update && apt-get install -y gcc-12 g++-12 make tcl-dev
    - export OA_UNSUPPORTED_PLAT=linux_ubuntu22_gcc12x
    - export CC=gcc-12 CXX=g++-12
    - make -j$(nproc) BUILDTYPE=opt OA_UNSUPPORTED_PLAT=$OA_UNSUPPORTED_PLAT
  artifacts:
    paths:
      - data/linux_ubuntu22_gcc12x/

test:oascript:sles15:
  stage: test
  image: opensuse/leap:15.5
  dependencies: [build:sles15]
  script:
    - zypper install -y tcl
    - export LD_LIBRARY_PATH=data/linux_sles15_gcc12x/lib/opt
    - export TCL_LIBRARY=/usr/share/tcl8.6
    - echo "oaSession::open" | data/linux_sles15_gcc12x/bin/opt/oascript

Verifying OAScript Works

After a successful build, run the smoke test sequence:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PLAT=$OA_HOME/data/$OA_UNSUPPORTED_PLAT
export LD_LIBRARY_PATH=$PLAT/lib/opt:$LD_LIBRARY_PATH
export TCL_LIBRARY=/usr/share/tcl8.6  # adjust for your distro

# Basic interpreter test
echo 'puts "OAScript OK: [info tclversion]"' | $PLAT/bin/opt/oascript
# OAScript OK: 8.6

# OA session test
$PLAT/bin/opt/oascript << 'EOF'
package require oa
oaSession::open
set session [oaSession::getSession]
puts "OA session open: $session"
oaSession::close
puts "OA session closed cleanly"
EOF

# OAScript extension load test (from a standard tclsh)
tclsh << EOF
load $PLAT/lib/opt/libOAScript.so
package require oa
puts "libOAScript.so loaded successfully"
EOF

Runtime Environment Script

Wrap the environment setup in a sourced script for consistent use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# oa_env.sh — source this before using OpenAccess tools
OA_INSTALL=/opt/oa22/linux_sles15_gcc12x

export LD_LIBRARY_PATH=$OA_INSTALL/lib/opt:${LD_LIBRARY_PATH}
export PATH=$OA_INSTALL/bin/opt:${PATH}
export OA_HOME=$OA_INSTALL
export TCL_LIBRARY=/usr/share/tcl8.6
export TCLLIBPATH=/usr/share/tcl8.6

# Optional: add OAScript Tcl packages to auto_path
export OASCRIPT_PACKAGES=$OA_INSTALL/tcl

Tcl Version Compatibility

One final consideration: OAScript binaries built against Tcl 8.6 will not load on a system that only has Tcl 8.5 (or Tcl 9.0). The Tcl major.minor version must match between build and runtime.

SLES 15 ships Tcl 8.6. Ubuntu 24.04 ships Tcl 8.6 by default but has Tcl 9.0 in the repo. RHEL 9 ships Tcl 8.6. For maximum portability, build against Tcl 8.6.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Verify which Tcl version OAScript was linked against
ldd $PLAT/bin/opt/oascript | grep tcl
# libtcl8.6.so.0 => /usr/lib64/libtcl8.6.so.0

# Verify at runtime
$PLAT/bin/opt/oascript
% info tclversion
8.6
% info patchlevel
8.6.13

Summary

Step RHEL/Rocky SLES 15 (native) openSUSE Leap 15.5 (Docker) Ubuntu 22.04
Enable GCC 12 built-in SUSEConnect -p sle-module-development-tools/15.5/x86_64 not needed (in OSS repo) not needed
Install deps dnf install gcc gcc-c++ tcl-devel swig zypper install gcc12 gcc12-c++ tcl-devel swig zypper install gcc12 gcc12-c++ tcl-devel swig apt install gcc-12 g++-12 tcl-dev swig
GCC binary name gcc / g++ gcc-12 / g++-12 gcc-12 / g++-12 gcc-12 / g++-12
Tcl include path /usr/include/tcl8.6/ /usr/include/ (flat) /usr/include/ (flat) /usr/include/tcl8.6/
tclConfig.sh location /usr/lib64/tcl8.6/tclConfig.sh /usr/lib64/tclConfig.sh /usr/lib64/tclConfig.sh /usr/lib/tcl8.6/tclConfig.sh
Tcl .so symlink issue Usually fine Sometimes missing Sometimes missing Usually fine
TCL_LIBRARY at runtime /usr/share/tcl8.6 /usr/share/tcl8.6 /usr/share/tcl8.6 /usr/share/tcl8.6
Docker base image rockylinux:8 opensuse/leap:15.5 ubuntu:22.04

The Docker build path is the most reliable for producing distributable binaries, especially when targeting multiple distributions from a single build machine. For development iteration on a SLES 15 or Ubuntu workstation, the native build works well once you’ve resolved the tclConfig.sh location and the include path quirks outlined above. The OAScript-specific issues — Tcl header layout, missing .so symlinks, init.tcl discovery at runtime — are the consistent pain points regardless of which path you take, and the fixes are mechanical once you know what to look for.

Comments