Compilation Flow

November 22, 2025 · View on GitHub

Overview

OpenPLC Runtime v4 uses a modern compilation flow where users upload a ZIP file generated by OpenPLC Editor v4, and the runtime automatically compiles and loads the PLC program.

Build Pipeline

1. Program Creation in OpenPLC Editor

Users create their PLC program in OpenPLC Editor v4:

  1. Write ladder logic, structured text, or other IEC 61131-3 languages
  2. Define location variables (e.g., %IX0.0, %QX0.0)
  3. Click "Compile" in the editor (local compilation: JSON → XML → ST → C)
  4. Click "Upload to Runtime" - Editor packages source files into program.zip and uploads

2. ZIP File Upload

The OpenPLC Editor uploads the ZIP file via REST API:

Endpoint: POST /api/upload-file Authentication: JWT token (Bearer header)

File Contents:

  • LOCATED_VARIABLES.h - Variable location definitions
  • Config0.c - PLC configuration
  • Res0.c - Resource configuration
  • debug.c - Debug support code
  • glueVars.c - Variable binding code
  • c_blocks_code.cpp - Custom C/C++ function blocks
  • lib/ - Header files for IEC 61131-3 types

3. Security Validation

Before extraction, the ZIP file undergoes security checks:

Checks Performed:

  • Valid ZIP file format
  • No path traversal attempts (.., absolute paths, :)
  • File size limits (10 MB per file, 50 MB total)
  • Compression ratio check (ZIP bomb detection, max ratio 1000:1)
  • Extension whitelist (blocks .exe, .dll, .sh, .bat, .js, .vbs, .scr)

Implementation: webserver/plcapp_management.py:analyze_zip()

4. Safe Extraction

If validation passes, files are extracted to core/generated/:

Extraction Features:

  • Strips macOS metadata (__MACOSX/, .DS_Store)
  • Auto-removes single root folder if present
  • Validates extraction paths stay within destination
  • Creates necessary subdirectories

Implementation: webserver/plcapp_management.py:safe_extract()

5. Compilation

The compilation process runs in a background thread:

Step 5a: Compile Script

Script: scripts/compile.sh

Process:

  1. Validates required files exist:

    • Config0.c
    • Res0.c
    • debug.c
    • glueVars.c
    • lib/ directory
  2. Compiles each source file to object code:

    gcc -w -O3 -fPIC -I core/generated/lib -c Config0.c -o build/Config0.o
    gcc -w -O3 -fPIC -I core/generated/lib -c Res0.c -o build/Res0.o
    gcc -w -O3 -fPIC -I core/generated/lib -c debug.c -o build/debug.o
    gcc -w -O3 -fPIC -I core/generated/lib -c glueVars.c -o build/glueVars.o
    g++ -w -O3 -fPIC -I core/generated/lib -c c_blocks_code.cpp -o build/c_blocks_code.o
    
  3. Links object files into shared library:

    g++ -w -O3 -fPIC -shared -o build/new_libplc.so \
        build/Config0.o build/Res0.o build/debug.o \
        build/glueVars.o build/c_blocks_code.o
    

Compiler Flags:

  • -w - Suppress warnings
  • -O3 - Maximum optimization
  • -fPIC - Position-independent code (required for shared libraries)

Step 5b: Cleanup Script

Script: scripts/compile-clean.sh

Process:

  1. Stops the PLC runtime (if running)
  2. Removes old library files: build/libplc_*.so
  3. Renames build/new_libplc.so to build/libplc_<timestamp>.so
  4. Removes object files: build/*.o

Timestamp Format: Unix epoch seconds (e.g., libplc_1700000000.so)

Purpose: Unique library names prevent caching issues and allow rollback

6. Build Status Tracking

The build process maintains state throughout compilation:

States:

  • IDLE - No build in progress
  • UNZIPPING - Extracting ZIP file
  • COMPILING - Running compilation scripts
  • SUCCESS - Build completed successfully
  • FAILED - Build failed (see logs)

Status Endpoint: GET /api/compilation-status

Response:

{
  "status": "SUCCESS",
  "logs": ["[INFO] Starting compilation", "..."],
  "exit_code": 0
}

7. Program Loading

If compilation succeeds, the runtime automatically loads the new program:

  1. Stop PLC: Runtime transitions to STOPPED state
  2. Unload Old Program: Previous library unloaded via dlclose()
  3. Load New Library: dlopen() loads libplc_<timestamp>.so
  4. Resolve Symbols: dlsym() resolves function pointers:
    • ext_config_init__() - Initialization
    • ext_config_run__() - Scan cycle execution
    • ext_get_var_*() - Variable metadata functions
  5. Initialize: Calls ext_config_init__()
  6. Start PLC: Runtime transitions to RUNNING state

Implementation: core/src/plc_app/plc_state_manager.c

Compilation Logs

Build output is streamed in real-time to the client:

  • stdout - Normal compilation messages
  • stderr - Error messages (prefixed with [ERROR])

Logs are buffered in build_state.logs and accessible via the compilation-status endpoint.

Error Handling

Validation Failures

If ZIP validation fails:

  • Build status set to FAILED
  • Descriptive error message returned
  • No files extracted

Compilation Failures

If compilation fails:

  • Build status set to FAILED
  • Compiler error messages captured in logs
  • Exit code preserved for debugging
  • Old PLC program remains loaded (if any)

Recovery

After a failed build:

  1. Fix issues in OpenPLC Editor
  2. Generate new ZIP file
  3. Upload again (previous build state cleared automatically)

File Locations

Source Files

  • Upload destination: core/generated/
  • Temporary files: Cleaned after compilation

Build Artifacts

  • Object files: build/*.o (removed after linking)
  • Shared library: build/libplc_<timestamp>.so
  • Executable: build/plc_main (runtime core)

Logs

  • Build logs: In-memory buffer, accessible via API
  • Runtime logs: Unix socket to web server

Manual Compilation (Development)

For development and testing, you can compile manually:

Prerequisites

sudo apt-get install build-essential cmake python3

Steps

  1. Prepare source files in core/generated/:

    • Generate from OpenPLC Editor or extract from ZIP
  2. Run compilation:

    bash scripts/compile.sh
    
  3. Run cleanup:

    bash scripts/compile-clean.sh
    
  4. Restart runtime:

    sudo bash start_openplc.sh
    

Advanced Topics

Custom Compiler Flags

To modify compiler flags, edit scripts/compile.sh:

FLAGS="-w -O3 -fPIC"  # Current flags

Common modifications:

  • -g - Add debug symbols
  • -O0 - Disable optimization for debugging
  • -Wall - Enable all warnings

Cross-Compilation

For cross-compilation to different architectures:

  1. Install cross-compiler toolchain
  2. Modify scripts/compile.sh to use cross-compiler
  3. Set appropriate architecture flags

Example for ARM:

CC=arm-linux-gnueabihf-gcc
CXX=arm-linux-gnueabihf-g++

Library Dependencies

The compiled PLC program may depend on:

  • IEC 61131-3 runtime - Provided by header files in lib/
  • Math library - Link with -lm if using math functions
  • Thread library - Link with -lpthread if using threads

Add dependencies in scripts/compile.sh linking step.

Troubleshooting

Missing Source Files

Error: [ERROR] Missing required source files

Solution: Ensure ZIP file contains all required files from OpenPLC Editor

Compilation Errors

Error: Compiler errors in logs

Solution:

  1. Check PLC program syntax in OpenPLC Editor
  2. Verify custom C/C++ code in function blocks
  3. Review compilation logs for specific errors

Library Loading Failures

Error: Failed to load PLC program

Solution:

  1. Check library file exists: ls -la build/libplc_*.so
  2. Verify library is valid: file build/libplc_*.so
  3. Check runtime logs for dlopen errors

Permission Errors

Error: Permission denied during compilation

Solution:

  1. Ensure write permissions on build/ directory
  2. Run with appropriate privileges
  3. Check SELinux/AppArmor policies