- 1. Prerequisites
- 2. Installing PasBuild
- 3. Project Types
- 4. Creating Your First Project
- 5. Building Your Project
- 6. Advanced Features
- 7. Working with Resources
- 8. Available Goals
- 9. Configuration Reference
- 10. Command-Line Reference
- 11. Directory Layout
- 12. Troubleshooting
- 13. Next Steps
- 14. Getting Help
Complete guide to getting started with PasBuild.
PasBuild requires the Free Pascal Compiler (FPC) version 3.2.2 or later.
Verify FPC Installation:
fpc -iVThis should output a version number like 3.2.2 or higher.
FPC Must Be in PATH:
PasBuild needs to find the fpc command. Verify it’s in your PATH:
which fpc # Linux/macOS/FreeBSD
where fpc # WindowsIf FPC is not found:
-
Linux/FreeBSD: Install via package manager (
apt install fpc,pkg install fpc) -
macOS: Install via Homebrew (
brew install fpc) or download from https://www.freepascal.org -
Windows: Download installer from https://www.freepascal.org and add to PATH
Clone the repository:
git clone https://github.com/graemeg/pasbuild.git
cd pasbuildBootstrap compilation:
Follow the instructions in BOOTSTRAP.txt:
# Linux/macOS/FreeBSD
mkdir -p target/units
echo '1.0.0' > target\version.inc
fpc -Mobjfpc -O1 -FEtarget -FUtarget/units -Fitarget -Fusrc/main/pascal src/main/pascal/PasBuild.pas
# Verify build
./target/PasBuild --versionInstall to system (optional):
# Copy to /usr/local/bin (or ~/bin)
sudo cp target/PasBuild /usr/local/bin/pasbuildPasBuild supports two types of projects:
-
Application Projects - Executable programs with a main entry point
-
Library Projects - Frameworks/libraries consisting only of units (no executable)
| Aspect | Application | Library |
|---|---|---|
Main Source |
Required: must be |
Optional: auto-generated bootstrap program |
Compilation Output |
Executable binary |
Compiled units (.ppu files) |
Package Contents |
Executable + LICENSE + README |
Source code or compiled units |
Bootstrap Program |
Not needed |
Auto-generated in target/ |
The fastest way to start a new project:
mkdir myapp
cd myapp
pasbuild initInteractive Prompts:
Project type (application/library) [application]:
Project name [myapp]: MyApplication
Version [1.0.0]:
Author [yourusername]: Your Name
License (MIT/BSD-3-Clause/GPL-3.0/Apache-2.0/Proprietary) [MIT]: BSD-3-ClausePress ENTER to accept defaults shown in square brackets.
Generated Structure (Application):
myapp/
├── project.xml
├── LICENSE
└── src/
├── main/
│ └── pascal/
│ └── Main.pas
└── test/
└── pascal/
└── TestRunner.pasGenerated Structure (Library):
mylib/
├── project.xml
├── LICENSE
└── src/
├── main/
│ └── pascal/
│ (empty - add your library units here)
└── test/
└── pascal/
└── TestRunner.pasIf you prefer to create files manually:
1. Create directory structure:
mkdir -p myapp/src/main/pascal
cd myapp2. Create project.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<name>MyApplication</name>
<version>1.0.0</version>
<author>Your Name</author>
<license>BSD-3-Clause</license>
<build>
<mainSource>Main.pas</mainSource>
<executableName>myapp</executableName>
<outputDirectory>target</outputDirectory>
</build>
</project>3. Create src/main/pascal/Main.pas:
program Main;
{$mode objfpc}{$H+}
uses
SysUtils;
begin
WriteLn('Hello from MyApplication!');
end.Library projects (frameworks, toolkits) don’t have a main program. PasBuild automatically generates a bootstrap program to compile all units.
1. Create project.xml for a library:
<?xml version="1.0" encoding="UTF-8"?>
<project>
<name>MyLibrary</name>
<version>1.0.0</version>
<author>Your Name</author>
<license>BSD-3-Clause</license>
<build>
<projectType>library</projectType>
<outputDirectory>target</outputDirectory>
</build>
</project>2. Add your library units:
src/main/pascal/
├── MyLib.Core.pas
├── MyLib.Utils.pas
└── MyLib.Types.pas3. Compile:
pasbuild compilePasBuild will:
-
Auto-discover all .pas files
-
Generate
target/bootstrap_program.paswith all units in uses clause -
Compile the bootstrap to verify all units compile successfully
PasBuild supports Semantic Versioning 2.0.0 with optional pre-release identifiers.
Valid version formats:
-
Release versions:
MAJOR.MINOR.PATCH-
Examples:
1.0.0,2.1.0,3.4.5
-
-
Pre-release versions:
MAJOR.MINOR.PATCH-PRERELEASE-
Examples:
1.0.0-alpha,1.0.0-beta.2,1.0.0-rc.1,2.0.0-SNAPSHOT
-
Pre-release identifier rules:
-
Can contain alphanumeric characters, dots, and hyphens:
[a-zA-Z0-9.-]+ -
Common conventions:
-
-alpha,-alpha.1,-alpha.2- Alpha releases -
-beta,-beta.1,-beta.2- Beta releases -
-rc.1,-rc.2- Release candidates -
-SNAPSHOT- Development snapshots (Maven convention) -
-dev,-nightly- Development builds
-
Examples in project.xml:
<!-- Stable release -->
<version>1.0.0</version>
<!-- Beta release -->
<version>1.1.0-beta</version>
<!-- Release candidate -->
<version>2.0.0-rc.1</version>
<!-- Development snapshot -->
<version>3.0.0-SNAPSHOT</version>Package naming:
The version string is used in package filenames:
-
Release:
myapp-1.0.0-x86_64-linux.zip,myapp-1.0.0-src.zip -
Pre-release:
myapp-1.0.0-beta-x86_64-linux.zip,myapp-2.0.0-SNAPSHOT-src.zip
Compile your project:
pasbuild compileOutput:
[INFO] PasBuild 1.0.0
[INFO] Executing goal: compile
[INFO] Compiling project...
[INFO] Build command: fpc -Mobjfpc -O1 src/main/pascal/Main.pas -FEtarget ...
Free Pascal Compiler version 3.2.2 ...
Compiling src/main/pascal/Main.pas
Linking target/myapp
12 lines compiled, 0.1 sec
[INFO] Build successfulRun your program:
./target/myapp # Linux/macOS/FreeBSD
target\myapp.exe # WindowsBuild profiles allow different compiler settings without code changes.
Add profiles to project.xml:
<profiles>
<profile>
<id>debug</id>
<defines>
<define>DEBUG</define>
</defines>
<compilerOptions>
<option>-g</option> <!-- Debug symbols -->
<option>-gl</option> <!-- Line info -->
<option>-Criot</option> <!-- Range/overflow/I/O checks -->
<option>-gh</option> <!-- Heap trace -->
</compilerOptions>
</profile>
<profile>
<id>release</id>
<defines>
<define>RELEASE</define>
</defines>
<compilerOptions>
<option>-O3</option> <!-- Maximum optimization -->
<option>-CX</option> <!-- Smart linking -->
<option>-XX</option> <!-- Strip symbols -->
</compilerOptions>
</profile>
</profiles>Build with profile:
pasbuild compile -p debug # Debug build
pasbuild compile -p release # Release buildBuild with multiple profiles:
Profiles can be combined by specifying multiple profile IDs separated by commas. Profiles are applied in the order specified, with later profiles able to override settings from earlier ones.
# Activate multiple profiles at once
pasbuild compile -p base,debug,logging
# Profiles are applied in order (left to right)
# Each profile's defines and compiler options are added sequentiallyThis is useful for composing build configurations. For example:
<profiles>
<profile>
<id>base</id>
<defines>
<define>APP_NAME</define>
</defines>
</profile>
<profile>
<id>debug</id>
<defines>
<define>DEBUG</define>
</defines>
<compilerOptions>
<option>-g</option>
<option>-gl</option>
</compilerOptions>
</profile>
<profile>
<id>logging</id>
<defines>
<define>ENABLE_LOGGING</define>
</defines>
</profile>
</profiles>Then you can build with different combinations:
pasbuild compile -p base # Just base settings
pasbuild compile -p base,debug # Base + debug symbols
pasbuild compile -p base,debug,logging # Base + debug + loggingIn multi-module projects, profiles defined in the aggregator (parent) project.xml are automatically inherited by all child modules. This allows you to define shared profiles once at the top level rather than duplicating them in every module.
If a child module defines a profile with the same <id> as one in the parent, the child’s version takes precedence — the parent profile is not merged or combined.
Example:
Aggregator project.xml:
<project>
<name>my-app</name>
<version>1.0.0</version>
<build><packaging>pom</packaging></build>
<modules>
<module>framework</module>
<module>app</module>
</modules>
<profiles>
<profile>
<id>unix</id>
<defines>
<define>X11</define>
</defines>
</profile>
</profiles>
</project>Both framework and app modules will have the unix profile available, without needing to redeclare it. Running pasbuild compile -p unix will apply the -dX11 define to all child modules.
If framework/project.xml also defines a unix profile, that module-level definition replaces the inherited one entirely for that module.
PasBuild applies compiler options in a specific additive order, allowing you to set defaults and override them as needed:
-
Hardcoded defaults:
-Mobjfpc -O1(always applied) -
Global compiler options: From
<build><compilerOptions>(extends defaults) -
Profile-specific options: From
<profile><compilerOptions>(extends further, can override)
Example:
<build>
<compilerOptions>
<option>-vh</option> <!-- Show hints globally -->
</compilerOptions>
</build>
<profiles>
<profile>
<id>debug</id>
<compilerOptions>
<option>-g</option> <!-- Add debug symbols -->
<option>-O-</option> <!-- Disable optimization (overrides -O1) -->
</compilerOptions>
</profile>
</profiles>Result with pasbuild compile -p debug:
fpc -Mobjfpc -O1 -vh -g -O- ...
The -O- flag overrides the default -O1 because FPC applies the last occurrence of conflicting flags.
By default, PasBuild reads project.xml from the current directory. You can specify an alternate project file using the -f or --file option, similar to Maven’s -f flag.
Basic usage:
pasbuild compile -f custom.xml
pasbuild compile --file myproject.xmlUse cases:
-
Building from a subdirectory: Run PasBuild from anywhere by pointing to the project file
-
Multiple build configurations: Maintain separate project files for different deployment targets
-
Testing configurations: Test changes without modifying main project.xml
-
Build system integration: Use dynamically generated project files
Example: Building from a subdirectory
When editing source files deep in the project tree, you can build without navigating back to the project root. PasBuild automatically changes to the directory containing the project file, so all relative paths in project.xml resolve correctly — just like Maven’s -f flag.
# You're editing files in src/main/pascal/
cd src/main/pascal
# Build from here using a relative path to project.xml
pasbuild compile -f ../../../project.xml
# Output:
# [INFO] Changing to project directory: /home/user/myproject
# [INFO] Executing goal: compile
# [INFO] Build successfulAn absolute path works too:
# Build from anywhere on the system
cd /tmp
pasbuild compile -f /home/user/myproject/project.xmlExample: Multiple configurations
# Development build with custom settings
pasbuild compile -f project-dev.xml
# Production build with optimizations
pasbuild compile -f project-prod.xml
# CI/CD build with specific options
pasbuild compile -f project-ci.xmlExample: Testing new features
# Copy and modify for testing
cp project.xml project-test.xml
# Edit project-test.xml with experimental changes
# Test without affecting main project
pasbuild compile -f project-test.xmlNote: The file path can be relative or absolute. When a path to a different directory is provided, PasBuild changes to that directory before executing, ensuring all paths in project.xml resolve correctly.
By default, PasBuild uses the fpc command from your PATH. You can override this to use a specific compiler binary, which is useful for side-by-side compiler versions, patched compilers, alternative Pascal compilers, or CI environments.
PasBuild auto-detects the compiler type from the binary and selects the correct command-line dialect: FPC flags (-Fu, -FE, -Mobjfpc, etc.) for FPC, and Blaise flags (--unit-path, --output, etc.) for the Blaise compiler.
Precedence (highest to lowest):
-
CLI flag:
--compiler <path> -
Environment variable:
PASBUILD_COMPILER -
Default:
fpc(PATH resolution)
Using the --compiler flag:
# Use an explicit path to a specific FPC version
pasbuild compile --compiler /opt/fpc-3.3.1/bin/fpc
# Use a custom-named FPC binary on PATH
pasbuild compile --compiler fpc-ootb
# Use the Blaise compiler
pasbuild compile --compiler releases/v0.3.0/blaise
# Check version with custom compiler
pasbuild --version --compiler /opt/fpc-3.3.1/bin/fpcUsing the PASBUILD_COMPILER environment variable:
# Set for a single command
PASBUILD_COMPILER=/opt/fpc-3.3.1/bin/fpc pasbuild compile
# Set for the entire shell session
export PASBUILD_COMPILER=/opt/fpc-3.3.1/bin/fpc
pasbuild compile
pasbuild test
# CI environment example
export PASBUILD_COMPILER=/usr/local/fpc-3.2.2/bin/fpc
pasbuild compile -p release
pasbuild packageUse cases:
-
Testing against multiple FPC versions (e.g., stable vs trunk)
-
CI/CD pipelines with non-standard compiler installation paths
-
Using the Blaise compiler as an FPC alternative
-
Cross-compilation with platform-specific compiler binaries
Note: The --compiler flag always takes precedence over PASBUILD_COMPILER. When a custom compiler is configured, it is used for all operations including version detection, target CPU/OS detection, and compilation.
Deprecated aliases: --fpc and PASBUILD_FPC are deprecated aliases for --compiler and PASBUILD_COMPILER. They continue to work but emit a warning; migrate to the new names.
For large projects with multiple components, PasBuild supports Maven-style multi-module projects with:
-
Aggregator projects - Coordinate multiple child modules
-
Nested aggregators - Group related modules under sub-aggregators (e.g.,
examples/) -
Library modules - Reusable frameworks and utilities
-
Application modules - Executable projects depending on libraries
-
Automatic dependency resolution - Compile in correct order with no manual configuration
-
Module selection - Build specific modules using
-mflag
Example multi-module structure:
my-framework/ # Root aggregator (packaging=pom)
├── project.xml
├── core/ # Library module
│ └── project.xml
├── ui/ # Library depending on core
│ └── project.xml
└── examples/ # Nested aggregator (packaging=pom)
├── project.xml
├── demo1/ # Application depending on core
│ └── project.xml
└── demo2/ # Application depending on ui
└── project.xmlNested aggregators can be nested to arbitrary depth. Module discovery recursively walks the aggregator tree and produces a single flat module list for the reactor. Version inheritance flows through the full aggregator chain: root → sub-aggregator → leaf module.
Basic usage:
cd my-framework
pasbuild compile # Build all modules in dependency order
pasbuild compile -m demo1 # Build only demo1 (and its dependencies)
pasbuild dependency-tree # Show dependency graph without compiling
pasbuild dependency-tree -m demo1 # Show only demo1's dependenciesFor detailed multi-module documentation, see Multi-Module Tutorial.
PasBuild supports cross-project dependency management through a local repository. This allows one project to use compiled units from another project without manually managing -Fu paths.
Workflow:
-
Build and install a library project to the local repository
-
Declare the dependency in the consumer project’s
project.xml -
Compile the consumer project — dependencies are resolved automatically
Step 1: Install the library
cd my-library
pasbuild install
# [INFO] Installed my-library:1.0.0 [x86_64-linux-3.2.2]
# [INFO] -> /home/user/.pasbuild/repository/my-library/1.0.0/x86_64-linux-3.2.2Step 2: Declare the dependency
In the consumer project’s project.xml:
<project>
<name>my-app</name>
<version>1.0.0</version>
<build>
<mainSource>MyApp.pas</mainSource>
</build>
<dependencies>
<dependency>
<name>my-library</name>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>Step 3: Compile the consumer
cd my-app
pasbuild compile
# [INFO] Resolving 1 external dependency...
# [INFO] Dependencies resolved successfully
# [INFO] Compiling project...
# [INFO] Build successfulPasBuild automatically adds the installed library’s units to the compiler’s -Fu search path. Transitive dependencies (dependencies of dependencies) are resolved automatically through metadata stored in the repository.
Multi-module install:
For aggregator projects, pasbuild install installs all non-aggregator modules:
cd fpgui
pasbuild install # Installs framework module, then uidesigner moduleFor detailed dependency management documentation, see Dependency Management Design.
For cross-platform projects with platform-specific directories, use conditional unit paths.
Example: Cross-Platform GUI Library
src/main/pascal/
├── core/
│ ├── MyLib.Base.pas ← Always included
│ ├── MyLib.Common.pas ← Always included
│ ├── x11/ ← Linux/FreeBSD only
│ │ └── MyLib.X11.pas
│ ├── gdi/ ← Windows only
│ │ └── MyLib.GDI.pas
│ └── cocoa/ ← macOS only
│ └── MyLib.Cocoa.pasConfigure conditional paths in project.xml:
<build>
<projectType>library</projectType>
<outputDirectory>target</outputDirectory>
<unitPaths>
<!-- Only specify platform-specific paths -->
<!-- PasBuild auto-scans everything else -->
<path condition="UNIX">core/x11</path>
<path condition="WINDOWS">core/gdi</path>
<path condition="DARWIN">core/cocoa</path>
</unitPaths>
</build>How it works:
-
PasBuild auto-scans all directories (core/, etc.)
-
For conditional paths:
-
On Linux: includes
core/x11/, excludescore/gdi/andcore/cocoa/ -
On Windows: includes
core/gdi/, excludescore/x11/andcore/cocoa/ -
On macOS: includes
core/cocoa/, excludescore/x11/andcore/gdi/
-
-
Non-conditional directories always included
Supported platform conditions:
-
UNIX- All Unix-like systems (Linux, FreeBSD, macOS) -
LINUX- Linux specifically -
FREEBSD- FreeBSD specifically -
DARWIN- macOS / iOS -
WINDOWS- All Windows versions -
WIN32- 32-bit Windows -
WIN64- 64-bit Windows -
Custom defines from
<defines>or profiles
Include files (*.inc):
PasBuild automatically detects directories containing *.inc files and adds them as include paths (-Fi). By default, include path filtering uses the same conditional rules as <unitPaths>. If a platform-specific directory contains .inc files, it’s only added as an include path when that platform’s condition is met.
Advanced: Separate include path conditions
In most projects, include files are located in the same directories as units, so they naturally share the same conditional filtering. However, if you need different conditional rules for include paths, you can specify them explicitly:
<build>
<unitPaths>
<path condition="UNIX">core/x11</path>
<path condition="WINDOWS">core/gdi</path>
</unitPaths>
<!-- Optional: Separate conditions for include files -->
<includePaths>
<path condition="UNIX">templates/unix</path>
<path condition="WINDOWS">templates/windows</path>
</includePaths>
</build>How include path filtering works:
-
If
<includePaths>is specified: Uses those conditions for filtering*.incfiles -
If
<includePaths>is empty or not specified: Falls back to<unitPaths>conditions (most common case) -
Auto-scan behavior: All directories are scanned for
*.incfiles, then filtered based on the active conditions
This fallback behavior ensures that projects without explicit <includePaths> automatically get correct platform-specific filtering for their include files.
By default, PasBuild auto-scans all subdirectories. For explicit control, use manual mode:
<build>
<manualUnitPaths>true</manualUnitPaths>
<unitPaths>
<!-- Must list ALL paths explicitly -->
<path>core</path>
<path>gui</path>
<path>utils</path>
<!-- Platform-specific paths still support conditions -->
<path condition="UNIX">core/x11</path>
<path condition="WINDOWS">core/gdi</path>
</unitPaths>
</build>When to use manual mode:
-
Large projects where auto-scan is slow
-
Need explicit control over compilation order
-
Want to exclude certain directories from compilation
Note: Manual mode still respects conditional filtering for listed paths.
PasBuild supports an external plugin system that lets anyone extend it with custom goals
without modifying the core codebase. A plugin is any executable named pasbuild-<name>
placed on your PATH or in a plugin directory.
For example, a community plugin could generate Lazarus .lpk package files, produce
documentation, or deploy build artifacts — all triggered as pasbuild <plugin-name> just
like a built-in goal.
Plugins integrate with the build lifecycle by declaring which phase they run after (e.g.
after:compile), and receive project context via environment variables and the
pasbuild resolve command (see [_resolve]).
Quick example:
# Install a plugin
cp pasbuild-myplugin ~/.pasbuild/plugins/
# Run it like any goal
pasbuild myplugin -p release
# List installed plugins
pasbuild --helpFor the full plugin contract, example implementations (shell script and Pascal), and
documentation on writing your own plugins, see extras/plugins/hello-world/README.adoc
in the PasBuild source tree.
PasBuild can automatically copy resource files (images, configuration templates, help files, etc.) from your source directory to the output directory during the build process.
Directory structure:
src/
├── main/
│ ├── pascal/ # Pascal source code
│ └── resources/ # Resource files
│ ├── config/
│ │ └── app.conf
│ ├── images/
│ │ └── logo.png
│ └── help/
│ └── manual.html
└── test/
├── pascal/ # Test source code
└── resources/ # Test resource files
└── test-data.jsonAdd a <resources> section to your project.xml:
<build>
<mainSource>Main.pas</mainSource>
<executableName>myapp</executableName>
<!-- Resource configuration -->
<resources>
<directory>src/main/resources</directory> <!-- optional, default shown -->
<filtering>false</filtering> <!-- optional, default: false -->
</resources>
</build>Configuration options:
-
<directory>- Source directory for resources (default:src/main/resources) -
<filtering>- Enable variable substitution in resource files (default:false)
When <filtering> is set to true, PasBuild will substitute variables in your resource files before copying them to the target directory.
Project variables (from project.xml):
-
${project.name}- Project name -
${project.version}- Project version -
${project.author}- Project author -
${project.license}- Project license
Build-time variables (resolved at process-resources time, compiler-agnostic):
-
${build.date}- Current date in ISO-8601 format (yyyy-mm-dd) -
${build.time}- Current time inhh:mm:ssformat -
${build.timestamp}- Combined ISO-8601 datetime (yyyy-mm-ddThh:mm:ss)
Example resource file (src/main/resources/version.inc):
'${project.version}'Example with build metadata (src/main/resources/build-info.inc):
'Built ${build.date} at ${build.time}'After processing, target/build-info.inc will contain something like:
'Built 2026-05-01 at 14:30:45'Which can then be used in Pascal source as:
const
BUILD_INFO = {$I build-info.inc};With filtering enabled and <name>MyApp</name>, <version>1.0.0</version> in project.xml, a file containing all tokens:
Application: ${project.name}
Version: ${project.version}
Author: ${project.author}
License: ${project.license}
Built: ${build.date}After processing, target/version.txt will contain:
Application: MyApp
Version: 1.0.0
Author: Your Name
License: BSD-3-Clause
Built: 2026-05-01Use cases for filtering:
-
Version and build-date include files for Pascal
{$I}directives -
Version manifests and about dialog content
-
Configuration templates with project metadata
-
Help files with current version and build information
Test resources work the same way as main resources but are configured in the <test> section:
<test>
<testSource>TestRunner.pas</testSource>
<!-- Test resource configuration -->
<resources>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</resources>
</test>Test resources are copied to target/ before test compilation, making them available to your test code.
Resource processing is automatically integrated into the build lifecycle:
-
compile goal → First runs
process-resources, then compiles -
test-compile goal → First runs
compileandprocess-test-resources, then compiles tests
You can also run resource processing independently:
pasbuild process-resources # Copy main resources only
pasbuild process-test-resources # Copy test resources onlyExample output:
[INFO] Executing goal: process-resources
[INFO] Processing resources from src/main/resources...
[INFO] Filtering enabled
[INFO] Filtered: version.txt
[INFO] Filtered: config/app.conf
[INFO] Copied: images/logo.png
[INFO] Resources processed successfullyRemoves all build artifacts.
pasbuild cleanDeletes the target/ directory and all its contents.
Copies resource files from src/main/resources to target/.
pasbuild process-resourcesWhat it does:
-
Checks if
src/main/resourcesexists (skips if not found) -
Recursively copies all files preserving directory structure
-
If filtering is enabled, substitutes project variables
-
Reports files copied/filtered
Note: This goal is automatically run by the compile goal.
Compiles the project.
pasbuild compile # Default build
pasbuild compile -p debug # With debug profile
pasbuild compile -p release # With release profileWhat it does:
-
Verifies directory layout
-
Checks if FPC is available
-
Scans for unit paths
-
Creates output directories
-
Builds FPC command with all flags
-
Executes FPC with real-time output
Generated compiler flags:
-
-Mobjfpc- Object Pascal mode -
-O1- Basic optimization (default) -
-FEtarget- Executable output directory -
-FUtarget/units- Unit output directory -
-Fusrc/main/pascal- Unit search path (auto-scanned) -
-d<define>- Global and profile defines -
Profile compiler options override defaults
Copies test resource files from src/test/resources to target/.
pasbuild process-test-resourcesWhat it does:
-
Checks if
src/test/resourcesexists (skips if not found) -
Recursively copies all files preserving directory structure
-
If filtering is enabled, substitutes project variables
-
Reports files copied/filtered
Note: This goal is automatically run by the test-compile goal.
Compiles test code without running tests.
pasbuild test-compile
pasbuild test-compile -p debugDependencies: Automatically runs compile → process-test-resources → test-compile
What it does:
-
Compiles main code (via
compilegoal) -
Scans
src/test/pascal/for test files -
Compiles test runner executable to
target/TestRunner -
Links to already-compiled main units (Maven-style)
-
Outputs test units to
target/test-units(separate from main units)
Compiles and runs tests.
pasbuild test
pasbuild test -p debugDependencies: Automatically runs compile → process-test-resources → test-compile → test
What it does:
-
Compiles main code
-
Compiles tests
-
Executes test runner with configured options
-
Reports test results
Example output:
[INFO] Running tests...
[INFO] Execute command: target/TestRunner --all --format=plain
Time:00.001 N:5 E:0 F:0 I:0
TCalculatorTests Time:00.001 N:5 E:0 F:0 I:0
00.000 TestAddition
00.000 TestSubtraction
00.000 TestMultiplication
00.000 TestDivision
00.000 TestDivisionByZero
Number of run tests: 5
Number of errors: 0
Number of failures: 0
[INFO] All tests passedCreates a release archive.
pasbuild packageDependencies: Automatically runs clean → compile → package
What it creates:
A ZIP archive at target/<name>-<version>-<cpu>-<os>.zip containing:
-
Compiled executable
-
LICENSEfile (if exists) -
READMEfile (if exists - checks .md, .adoc, .txt, .rst)
Example:
pasbuild package
# Creates: target/myapp-1.0.0-x86_64-linux.zip
# Contains:
# myapp (or myapp.exe on Windows)
# LICENSE
# README.mdCreates a source code archive for distribution.
pasbuild source-packageWhat it creates:
A ZIP archive at target/<name>-<version>-src.zip containing source code and documentation.
Default includes (automatic):
-
src/directory (all source code) -
project.xml -
LICENSE*files (also checksCOPYING*) -
README*files -
BOOTSTRAP*files -
INSTALL*files
Optional includes (via configuration):
<build>
<sourcePackage>
<include>docs</include>
<include>examples</include>
<include>scripts</include>
</sourcePackage>
</build>Security:
All included paths must be:
-
Relative (no absolute paths)
-
Within project root (no
..parent references) -
Violations result in build failure with clear error
Example:
pasbuild source-package
# Creates: target/myapp-1.0.0-src.zip
# Contains:
# myapp-1.0.0/src/main/pascal/...
# myapp-1.0.0/project.xml
# myapp-1.0.0/LICENSE
# myapp-1.0.0/README.md
# myapp-1.0.0/BOOTSTRAP.txt
# myapp-1.0.0/docs/... (if configured)Use cases:
-
Submitting to software repositories (Debian, FreeBSD ports)
-
Conference/journal paper submissions
-
Creating "official" release source archives
-
Users without Git access
-
Archival purposes
Installs compiled units to the local repository (~/.pasbuild/repository/).
pasbuild installDependencies: Automatically runs process-resources → compile → install
What it does:
-
Compiles the project (if not already compiled)
-
Detects the FPC target triplet (e.g.,
x86_64-linux-3.2.2) -
Creates the repository directory structure
-
Copies all
.ppuand.ofiles fromtarget/units/to the repository -
Generates
metadata.xmlwith artifact identity and dependency information
Example:
pasbuild install
# Output:
# [INFO] Executing goal: install
# [INFO] Installing to local repository...
# [INFO] Found 5 unit(s) and 6 object file(s)
# [INFO] Copied unit files to repository
# [INFO] Installed my-library:1.0.0 [x86_64-linux-3.2.2]
# [INFO] -> /home/user/.pasbuild/repository/my-library/1.0.0/x86_64-linux-3.2.2Note: For multi-module aggregator projects, install runs on each non-aggregator module in dependency order.
Displays the dependency graph of a project without invoking the compiler.
pasbuild dependency-tree # All modules (or external deps for single-module)
pasbuild dependency-tree -m demo # Dependencies for the named module onlyWhat it does:
For multi-module (aggregator) projects, modules are listed in topological order — dependencies before dependents. Each module entry shows:
-
Local module dependencies (
[module]) -
External repository dependencies (
[external]) -
The module’s packaging type (
[library],[application],[aggregator])
For single-module projects, only the declared <dependencies> (external) are shown.
Example output — multi-module project:
[INFO] ------------------------------------------------------------------------
[INFO] Dependency Tree
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] math-core 1.0.0 [library]
[INFO] (no dependencies)
[INFO]
[INFO] math-utils 1.0.0 [library]
[INFO] └─ math-core [module]
[INFO]
[INFO] calculator 1.0.0 [application]
[INFO] ├─ math-utils [module]
[INFO] └─ some-lib:2.0.0 [external]Example output — single-module project with external dependencies:
[INFO] ------------------------------------------------------------------------
[INFO] Dependency Tree
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] my-app 1.0.0
[INFO] ├─ lib-alpha:1.0.0 [external]
[INFO] └─ lib-beta:3.2.1 [external]Use cases:
-
Inspect the dependency graph before a build, without compiler output interfering
-
Verify that module dependency declarations are correct
-
Quickly audit which external libraries a module relies on
-
Diagnose unexpected transitive dependency relationships
Outputs the fully resolved build configuration as JSON, without invoking the compiler.
pasbuild resolve # Single-module project
pasbuild resolve -p debug # With active profile
pasbuild resolve -m fpgui-framework # Specific module in a multi-module projectWhat it outputs:
The JSON includes everything PasBuild has resolved for the current configuration:
-
project— name, version, type, and project directory -
activeProfiles/availableProfiles— profile state -
compiler— FPC executable path and the full command line thatcompilewould invoke -
defines— all active preprocessor defines (global + profile) -
unitPaths— resolved source directories passed to FPC as-Fuflags -
includePaths— resolved include directories passed to FPC as-Fiflags -
dependencies— module and external dependencies with their resolved paths -
output— output directory, unit directory, and executable path
Example output:
{
"project": {
"name": "my-lib",
"version": "1.0.0",
"projectType": "library",
"projectDir": "/home/user/projects/my-lib"
},
"activeProfiles": [],
"availableProfiles": ["debug", "release"],
"compiler": {
"executable": "fpc",
"commandLine": "fpc -Mobjfpc -O1 target/bootstrap_program.pas ..."
},
"defines": [],
"unitPaths": [
"/home/user/projects/my-lib/src/main/pascal"
],
"includePaths": [
"/home/user/projects/my-lib/target"
],
"dependencies": [],
"output": {
"directory": "target",
"unitDirectory": "target/units"
}
}Note: Individual source file paths are intentionally not listed. PasBuild resolves
which directories are active for the current configuration; scanning for specific files
(.pas, .inc, etc.) is left to the caller, so the decision about which files to include
stays with the tool or plugin consuming the data.
Use cases:
-
IDE integration — supply exact compiler flags to an IDE’s code intelligence engine
-
Plugin development — call
pasbuild resolvefrom a plugin to obtain full project context without hardcoding any paths -
Debugging — inspect exactly which paths and defines PasBuild has resolved before running a build
Bootstraps a new project.
pasbuild initInteractive prompts for:
-
Project type (default: application)
-
Project name (default: current directory name)
-
Version (default: 1.0.0)
-
Author (default: $USER or $USERNAME)
-
License (default: MIT)
What it creates:
-
project.xml- Project configuration with test section -
src/main/pascal/Main.pas- Hello World template (application only) -
src/test/pascal/TestRunner.pas- FPCUnit test template -
LICENSE- Full license text (MIT, BSD-3-Clause, or Proprietary)
Project type behavior:
-
Application: Creates Main.pas, includes mainSource and executableName in XML
-
Library: No Main.pas created, minimal XML (bootstrap auto-generated at compile time)
Error handling:
If project.xml already exists, returns error: "Project already initialized"
<?xml version="1.0" encoding="UTF-8"?>
<project>
<name>MyApp</name>
<version>1.0.0</version>
<author>Your Name</author>
<license>BSD-3-Clause</license>
<build>
<mainSource>Main.pas</mainSource>
<executableName>myapp</executableName>
</build>
</project><?xml version="1.0" encoding="UTF-8"?>
<project>
<!-- Project Metadata -->
<name>MyApplication</name>
<version>2.1.0</version>
<author>Your Name</author>
<license>BSD-3-Clause</license>
<!-- Build Configuration -->
<build>
<!-- Project type (application or library, default: application) -->
<projectType>application</projectType>
<!-- Main source file (relative to sourceDirectory) -->
<mainSource>Main.pas</mainSource>
<!-- Executable name (without platform extension) -->
<executableName>myapp</executableName>
<!-- Source directory (default: src/main/pascal) -->
<!-- Use "." for project root, or any relative path -->
<!-- <sourceDirectory>.</sourceDirectory> -->
<!-- Output directory (default: target) -->
<outputDirectory>target</outputDirectory>
<!-- Global defines (available in all builds) -->
<defines>
<define>UseCThreads</define>
<define>MYAPP_FEATURE_X</define>
</defines>
<!-- Global compiler options (extends defaults: -Mobjfpc -O1) -->
<compilerOptions>
<option>-vh</option> <!-- Show hints -->
<option>-vn</option> <!-- Show notes -->
</compilerOptions>
<!-- Conditional unit paths (platform-specific directories) -->
<unitPaths>
<path condition="UNIX">platform/x11</path>
<path condition="WINDOWS">platform/gdi</path>
<path condition="DARWIN">platform/cocoa</path>
</unitPaths>
<!-- Optional: Conditional include paths (*.inc files) -->
<!-- If not specified, falls back to unitPaths conditions -->
<!-- <includePaths>
<path condition="UNIX">includes/unix</path>
<path condition="WINDOWS">includes/windows</path>
</includePaths> -->
<!-- Resources configuration -->
<resources>
<directory>src/main/resources</directory>
<filtering>true</filtering> <!-- Enable variable substitution -->
</resources>
<!-- Source package optional includes -->
<sourcePackage>
<include>docs</include>
<include>examples</include>
<include>scripts</include>
</sourcePackage>
</build>
<!-- Test Configuration -->
<test>
<!-- Test source file in src/test/pascal/ -->
<testSource>TestRunner.pas</testSource>
<!-- Framework-specific options passed to test runner -->
<frameworkOptions>
<option>--all</option> <!-- Run all tests -->
<option>--format=plain</option> <!-- Output format -->
</frameworkOptions>
<!-- Test resources configuration -->
<resources>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</resources>
</test>
<!-- External Dependencies (from local repository) -->
<dependencies>
<dependency>
<name>fpgui-framework</name>
<version>1.0.0</version>
</dependency>
</dependencies>
<!-- Build Profiles -->
<profiles>
<profile>
<id>debug</id>
<defines>
<define>DEBUG</define>
<define>VERBOSE_LOGGING</define>
</defines>
<compilerOptions>
<option>-g</option> <!-- Debug info -->
<option>-gl</option> <!-- Line info -->
<option>-Criot</option> <!-- Runtime checks -->
<option>-gh</option> <!-- Heap trace -->
</compilerOptions>
</profile>
<profile>
<id>release</id>
<defines>
<define>RELEASE</define>
</defines>
<compilerOptions>
<option>-O3</option> <!-- Max optimization -->
<option>-CX</option> <!-- Smart linking -->
<option>-XX</option> <!-- Strip symbols -->
<option>-Xs</option> <!-- Strip all -->
</compilerOptions>
</profile>
</profiles>
</project><?xml version="1.0" encoding="UTF-8"?>
<project>
<!-- Project Metadata -->
<name>MyLibrary</name>
<version>1.0.0</version>
<author>Your Name</author>
<license>BSD-3-Clause</license>
<!-- Build Configuration -->
<build>
<!-- Library project - no mainSource needed -->
<projectType>library</projectType>
<outputDirectory>target</outputDirectory>
<!-- Global defines -->
<defines>
<define>UseCThreads</define>
</defines>
<!-- Platform-specific unit paths -->
<!-- Bootstrap program auto-generated with all discovered units -->
<unitPaths>
<path condition="UNIX">corelib/x11</path>
<path condition="WINDOWS">corelib/gdi</path>
<path condition="DARWIN">corelib/cocoa</path>
</unitPaths>
<!-- Optional: Conditional include paths (*.inc files) -->
<!-- If not specified, falls back to unitPaths conditions -->
<!-- <includePaths>
<path condition="UNIX">includes/unix</path>
<path condition="WINDOWS">includes/windows</path>
</includePaths> -->
</build>
<!-- Build Profiles -->
<profiles>
<profile>
<id>debug</id>
<defines>
<define>DEBUG</define>
</defines>
<compilerOptions>
<option>-g</option>
<option>-gl</option>
</compilerOptions>
</profile>
<profile>
<id>release</id>
<defines>
<define>RELEASE</define>
</defines>
<compilerOptions>
<option>-O3</option>
<option>-CX</option>
</compilerOptions>
</profile>
</profiles>
</project>pasbuild clean # Remove build artifacts
pasbuild process-resources # Copy resources to target
pasbuild compile # Compile project (runs: process-resources → compile)
pasbuild compile -p <profile> # Compile with profile
pasbuild process-test-resources # Copy test resources to target
pasbuild test-compile # Compile tests (runs: compile → process-test-resources → test-compile)
pasbuild test # Run tests (runs: compile → process-test-resources → test-compile → test)
pasbuild package # Create release archive
pasbuild source-package # Create source archive
pasbuild install # Install to local repository (~/.pasbuild/repository/)
pasbuild dependency-tree # Display dependency graph (no compilation)
pasbuild resolve # Output resolved build configuration as JSON
pasbuild init # Bootstrap new project-p <profile[,profile...]> # Activate build profile(s)
--profile <profile-id> # Activate build profile (long form)
-f <file>, --file <file> # Use alternate project file (default: project.xml)
--compiler <path> # Use custom Pascal compiler (or set PASBUILD_COMPILER env var)
-v, --verbose # Show full compiler output
-h, --help # Show help message
--version # Show version information# Clean build with debug profile
pasbuild clean
pasbuild compile -p debug
# Release build and package
pasbuild compile -p release
pasbuild package
# Build with multiple profiles
pasbuild compile -p base,debug,logging
# Verbose build (show full FPC output)
pasbuild compile -v
# Use alternate project file
pasbuild compile -f custom.xml
# Build from a subdirectory using relative path
cd src/main/pascal
pasbuild compile -f ../../../project.xml
# Create source distribution
pasbuild source-package
# Get help
pasbuild --help
pasbuild compile --helpPasBuild follows Maven’s Standard Directory Layout:
project-root/
├── project.xml # Project configuration
├── LICENSE # License file
├── README.md # Project documentation
├── BOOTSTRAP.txt # Bootstrap instructions (optional)
│
├── src/
│ ├── main/
│ │ ├── pascal/ # Application source files
│ │ │ ├── Main.pas
│ │ │ ├── Unit1.pas
│ │ │ └── subdir/ # Subdirectories auto-scanned
│ │ │ └── Unit2.pas
│ │ └── resources/ # Resource files (optional)
│ │ ├── config/
│ │ │ └── app.conf
│ │ └── images/
│ │ └── logo.png
│ └── test/
│ ├── pascal/ # Test source files
│ │ ├── TestRunner.pas
│ │ └── subdir/ # Test subdirectories auto-scanned
│ │ └── MyTests.pas
│ └── resources/ # Test resource files (optional)
│ └── test-data.json
│
└── target/ # Build output (auto-created)
├── myapp # Executable
├── TestRunner # Test executable
├── myapp-1.0.0-x86_64-linux.zip # Package archive
├── config/ # Copied from src/main/resources/
│ └── app.conf # (filtered if enabled)
├── images/ # Copied from src/main/resources/
│ └── logo.png
├── units/ # Compiled main units
│ ├── Unit1.ppu
│ ├── Unit1.o
│ └── ...
└── test-units/ # Compiled test units (separate)
├── MyTests.ppu
└── ...Key Points:
-
Main source files go in
src/main/pascal/ -
Test source files go in
src/test/pascal/ -
Main resource files go in
src/main/resources/(optional) -
Test resource files go in
src/test/resources/(optional) -
Build output goes in
target/ -
target/is auto-created and cleaned -
Subdirectories are automatically scanned for units and include files
-
Resources are automatically copied to
target/(with optional filtering) -
Test units compiled separately to
target/test-units/(Maven-style)
For projects that do not follow the standard layout (e.g. small example applications), the <sourceDirectory> element in the <build> section overrides the default src/main/pascal/ path:
<build>
<mainSource>Demo.pas</mainSource>
<sourceDirectory>.</sourceDirectory> <!-- Source files in project root -->
</build>Common values:
-
.— source files are in the project root directory -
pascal— source files are in apascal/subdirectory -
src— source files are in asrc/subdirectory -
(omitted) — defaults to
src/main/pascal/
This is particularly useful for example projects within a nested aggregator, where the full standard layout would be unnecessarily verbose.
Error: "Free Pascal Compiler (fpc) not found in PATH"
Solution:
-
Verify FPC is installed:
fpc -iV -
Add FPC to PATH
-
Restart terminal
Error: "Main source file not found: src/main/pascal/Main.pas"
Solution:
-
Check file exists in correct location
-
Verify
<mainSource>inproject.xmlmatches filename -
File must be in
src/main/pascal/directory
Error: "Project already initialized (project.xml exists)"
Solution:
-
Delete
project.xmlif you want to re-initialize -
Or manually edit the existing
project.xml
Error: "Fatal: Can’t find unit MyUnit used by Main"
Solution:
-
Ensure unit file exists in
src/main/pascal/or subdirectory -
PasBuild automatically scans subdirectories for units
-
Check unit filename matches unit name in
usesclause
-
Read Design Document for architecture details
-
Check Implementation Progress for feature roadmap
-
Write a plugin — see
extras/plugins/hello-world/README.adocfor the plugin contract -
Join the community and contribute!
-
GitHub Issues: Report bugs and request features
-
FPC Documentation: https://www.freepascal.org/docs.html