Changelog¶
All notable changes to pcons will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
Added¶
- Python packaging: PEP 517 build backend (experimental). Python extension packages can now use pcons as their build system directly from
pyproject.toml(build-backend = "pcons.pyproject"). Supports building wheels, editable installs (PEP 660 — imports resolve to the build directory, soninjaalone picks up changes), and sdists. Honors PEP 621[project]metadata (requires-python,dependencies). Ninja is requested automatically in isolated builds when not already on PATH. Configure via[tool.pcons](variant,variables,install-target); see the user guide andexamples/50_pyproject(a nanobind C++ extension with Conan, exercising the fulluv syncworkflow). Marked experimental: the[tool.pcons]keys and thePCONS_BUILD_WHEELconvention may still change. (PR #37) - Importing a C++20 module whose compiled interface exists only under different BMI-sensitive flags is now a clear configure-time error (naming the module, the importer, and the fix), instead of a confusing compiler error at build time.
Fixed¶
- On macOS, toolchain auto-detection no longer mistakes Apple's
gccshim (which is actually clang) for a real GCC.find_c_toolchain(prefer=["gcc"])now falls through to the next available toolchain instead of configuring clang with GCC assumptions. (PR #37)
Contributors¶
- Sylvain Garcia (@Garcia6l20)
0.19.0 - 2026-06-04¶
Added¶
- Install prefix.
Install(),InstallAs(), andInstallDir()now place relative destinations under an install prefix (default<project-root>/dist), settable viapcons PCONS_INSTALL_PREFIX=.... Rooted destinations are used as-is;no_prefix=Truekeeps a destination inside the build directory. Newinstall_dir(env, target_type)helper returns the toolchain's conventional install subdirectory (binfor programs,libfor libraries; DLLs go inbin). (PR #38) - Multiple generators per run.
pcons -G ninja -G metadata(orPCONS_GENERATOR=ninja:metadata) runs several generators in one invocation. - C++20 module interfaces can be reused across targets. Compiled module interfaces (
.gcm/.pcm/.ifc) are keyed by a hash of their BMI-sensitive flags (C++ dialect, ABI options, stdlib feature macros) and stored undercxx_modules/<hash>/. Targets compiling a module interface with compatible flags share one BMI; targets with an incompatible dialect get their own. Previously, using the same module interface in more than one target failed with a duplicate-rule error. Works on GCC, clang, and MSVC; seeexamples/39_bmi_compat. (PR #39)
Changed¶
- BREAKING: relative
Install()destinations no longer land in the build directory.Install("lib", ...)now installs to<project-root>/dist/libinstead ofbuild/lib. Useno_prefix=Truefor the old behavior. - Metadata generator schema v2:
pcons_metadata.jsonnow serializes all projects (not just the top-level one) and includes qualified target names and subdirectories. Generator().generate(project)now enqueues generation and runs it once at script exit (or via the CLI), so it can be called anywhere in the script without risking double generation.
Fixed¶
- GCC C++ modules builds now rebuild correctly when headers change: header depfiles are kept for non-module translation units, and Ninja rules are no longer merged across edges with different dependency styles. Module dependency scanning also works on Windows now. (PR #38)
- Object-file reuse is now keyed by the compiler command as well as the compile flags. Previously, the same source compiled by two different compilers (or per-env
cmdoverrides) with otherwise identical flags would incorrectly share one object file. pcons buildnow propagates the underlying build tool's exit code (e.g. ninja failures).compile_commands.jsonnow includes compile flags set on the environment.
Contributors¶
- Sylvain Garcia (@Garcia6l20)
0.18.0 - 2026-05-21¶
Added¶
- First-class test support.
project.Test(name, program, ...)declares tests;pcons test(alsoninja test/make test) runs them with parallelism, label/regex filters, JUnit XML, and CTest-style reporting. Supportsdepends_onfixture chains,should_fail,serial,timeout,disabled, anddiscover="gtest"|"doctest"|"catch2"which expands one binary into per-case entries at run time. New example41_fuzzing+ Fuzzing user-guide section cover libFuzzer / AFL++ / Honggfuzz. (PR #29) add_subdirectory()for CMake-style sub-builds; returns named exports as aSimpleNamespace(or specific names you ask for).Project.currentis restored on exit. New targets auto-attach to the current project, and source/include/object paths resolve relative to the owning subdir.- Nested
Project()declarations. A subdirectory can declare its ownProject(...)(see13_subdirs). Combined withadd_subdirectory(), this is the supported way to compose multi-project trees. - Qualified target names (
project::target): cross-project references disambiguate by qualifying.Target.qualified_nameis exposed; equality and repr use the qualified form. pcons.context: global accessor for the current project / environment, intended for tooling and extensions.project.get_target()/project.get_targets()helpers (with a raising and aNone-returning overload).- Automatic generation.
pcons-build.pyno longer needs a trailingproject.generate()— it's invoked implicitly when the script finishes. - JSON metadata generator (
-G metadata). Writespcons_metadata.jsondescribing targets, deps, sources, outputs, aliases, and (forTest()targets) the fullTestSpec. Drives IDE plugins like vscode-pcons. - IDE-friendly typed stubs for the dynamic builder / environment API. Improves type checking and autocompletion. Stubs are generated from the builder registry + each toolchain's new
TOOL_NAMES. - New example
36_cxx_modules_multi_level: C++20 modules imported across nested subdirectories.
Changed¶
- BREAKING:
Project(...)must now be declared before creating anyTargetorEnvironment. Targets are owned by a project, and the previous implicit-top-level behavior no longer applies. target.add_sources(...)andTarget(sources=...)now behave identically. Removed an asymmetric_normalize_sourcesstep on the constructor path.
Fixed¶
- Xcode: static/shared library deps weren't actually linked.
_setup_dependenciescreatedPBXTargetDependencyentries (build ordering) but never added the library product to the consumer target'sPBXFrameworksBuildPhase, so links failed. Now wired up in a second pass during project-tree construction. Shared library outputs are also rewritten to.dylibfor the Xcode generator. - Xcode include directories are now resolved through
target.path_resolver, so headers in subdirectories work in the generated project. 05_multi_libraryon Windows:libphysicsgained dllexport/dllimport handling, and the test harness rewrites.so→.dllso the example runs cross-platform.install._deduplicate_target_name: fixed lookup so installing multiple targets with related names no longer collides.- C++20 modules rebuilt unnecessarily after a no-op
generate.cxx_module_scannernow writes dyndep / module-map files only when content actually changes (sidecar sha256), so ninja doesn't cascade-rebuild downstream targets. - C++20 modules: nondeterministic dyndep ordering entries are now sorted to prevent triggering unexpected rebuilds.
Contributors¶
- Sylvain Garcia (@Garcia6l20)
- Gary Oberbrunner (@garyo)
0.17.0 - 2026-05-15¶
Added¶
-
import std;on GCC. Completes the cross-toolchain trio after MSVC (0.15) and LLVM/libc++ (0.16). The synthesized std module is built once with the user's-std=and ABI-sensitive flags carried through, then linked into importers. CI exercises this against gcc-15. -
#includedependency tracking inside C++20 module interface units. Touching a header#included from a module's global module fragment now rebuilds the module and its consumers on all three toolchains (GCC via a new per-TU scan step, MSVC via/showIncludesparsing, LLVM via the existing-MD -MF).cxx_modules.dyndepstill handles inter-module ordering — the two are complementary. -
New example
35_cxx_modules_deps: regression test for module#includetracking — two interfaces sharing a header, with rebuild assertions on each input.
Improved¶
- More ABI-sensitive flags carried into GCC's std-module compile, matching the MSVC/LLVM passthrough behavior shipped in 0.15 / 0.16.
Fixed¶
-
Code-generator outputs now reach consuming compile steps. When target A links target B, non-library outputs from B (e.g., a generated header) were being passed to A's linker as explicit inputs and never reached A's compile steps. They are now routed as implicit deps on both the link and the compiles — so generated headers exist before
cppruns, and the linker doesn't choke on a.h. -
Ninja generator: implicit deps emit correct paths. Source files outside the build dir now get the
$topdir/prefix needed for ninja to locate them; deps inside the build dir (e.g., a generated.dyndepfile) emit the build-relative path so they match references like thedyndep = ...directive. -
GCC std-module source lookup tolerates the include-trace command exiting non-zero with "module control-line cannot be in included file" (the resolution is still on the first stderr line).
-
LLVM
import std;manifest lookup now finds both the modernlibc++.modules.jsonand the olderc++/libc++.modules.jsonlayouts.
Docs¶
- README: pointer to an experimental Rust/Go interop branch (Rust crates linked into C/C++ via
cargo build+ optionalcbindgen). File an issue if you want it.
0.16.0 - 2026-05-04¶
Added¶
import std;/import std.compat;on clang/libc++. Brings the LLVM toolchain to parity with MSVC (shipped in 0.15.0). When the scanner reports a TU requiringstd, pcons queries the available clang forlibc++.modules.json(clang++ -stdlib=libc++ -print-file-name=c++/libc++.modules.json), parses it to findstd.cppmand the system include directories, synthesizes a build node that compiles the std module with the user's-std=/-stdlib=flags, and links the resulting.ointo every target whose TUs importstd. Requires Homebrew LLVM on macOS (Apple Clang doesn't ship the manifest yet) or libc++-dev (≥ 18) on Linux.- New example
32_cxx_import_std: a C++23 program built withimport std;— exercises the std-module wiring on both MSVC and clang/libc++. Self-skips on platforms without a usable std module.
Improved¶
- ABI-flag passthrough on the std-module compile. Both MSVC and clang now categorize the user's compile flags into a passthrough spec (
select_std_module_flagsincxx_module_scanner) and carry only the ABI-affecting ones onto the std-module build: - On clang:
-std=,-stdlib=,-fexceptions/-fno-exceptions,-frtti/-fno-rtti,-fexperimental-library,-isysroot,--target=,-arch,-march=, plus any user-D_LIBCPP_*define (so_LIBCPP_HARDENING_MODEand similar libc++ feature-test macros stay in sync between std.pcm and consumers). - On MSVC:
/MD,/MDd,/MT,/MTd(runtime library — was a real ABI footgun previously),/EHs*,/GR//GR-,/permissive//permissive-,/await*,/clr*,/Zc:*,/std:*,/arch:*, plus user/D_HAS_*,/D_ITERATOR_DEBUG_LEVEL,/D_CONTAINER_DEBUG_LEVEL,/D_SECURE_SCL,/D_CRT_*defines (so iterator debug level matches between std.ifc and consumers — mismatches here corrupt the heap). - User defines also get pulled from
env.cxx.defines, not onlyenv.cxx.flags. - Reject unknown libc++ manifest versions.
libc++.modules.jsondeclaresversionfor breaking format changes; pcons now refuses to parse a future version rather than silently misinterpret it. - Suppress
-Wreserved-user-defined-literalon the clang std-module compile (libc++'sstd.cppmuses reserved UDLs that would fail under user-Werror).
Refactor¶
wire_std_into_targets(the helper that links the synthesized std-module.o/.objinto every importing target) moved frompcons/toolchains/msvc.pytopcons/toolchains/cxx_module_scanner.pyso both toolchains share one implementation.
0.15.0 - 2026-05-04¶
Added¶
- Recognize
.ixx,.cxxm,.c++mas C++20 module interface units (in addition to existing.cppm). MSVC's preferred.ixxextension now works out of the box. The LLVM toolchain forces clang into module mode with-x c++-moduleso any of these extensions compile correctly with clang as well. import std;/import std.compat;on MSVC. When the scanner reports a TU requiring thestdorstd.compatlogical module, pcons synthesizes a build node for%VCToolsInstallDir%/modules/std.ixx(orstd.compat.ixx), wires its.ifcinto the dyndep file so importers compile correctly, and adds the resulting.objto the link inputs of every target whose TUs import it. The user's/std:*flag (default/std:c++latest) is propagated to the std-module compile.env.cxx.modules = Trueopt-in for module scanning. Targets whose module units live in.cpp/.ccfiles (e.g., fmtlib'ssrc/fmt.cc— its primary interface) or whose only module use isimport std;from a.cppfile can now request scanning explicitly. The historical extension-driven trigger (any source with a.cppm/.ixx/.cxxm/.c++mextension auto-enables scanning for that env) is preserved as the default, and scanning is restricted per-env so unrelated targets in the same project don't pay the scan cost.- New example
30_cxx_partitions: primary interface in.cppm, partition interface in.cpp(export module M:P;), internal partition in.cpp(module M:P;), module implementation unit, and a consumer. Exercises the scan-driven module detection on both LLVM and MSVC. - New example
31_cxx_modules_optin: target whose primary module interface lives in a.cppfile, opting in viaenv.cxx.modules = True. Documents the fmtlib-style layout. --ninja=PROGflag (andNINJAenv var) to swap in ninja-compatible runners. Lets users build with n2 (or any other ninja-compatible tool) for content-hash–based rebuilds, complementing the existingccache/sccachesupport.- Release artifacts are signed with Sigstore. The sdist and wheel are signed keylessly via GitHub Actions OIDC, and
.sigstore.jsonbundles are attached to each GitHub release. README documents how to verify a downloaded artifact withcosign. (PyPI uploads remain unsigned because PyPI removed support for external signature bundles.)
Changed¶
- C++20 module compilation is now scan-driven, not extension-driven. The C++ module scanner (
cl /scanDependenciesfor MSVC,clang-scan-depsfor LLVM) now runs at configure time, and its P1689R5 output drives per-source flag injection. This means partition units that live in.cppfiles (e.g., a partition interfaceexport module M:P;or an internal partitionmodule M:P;) are detected and compiled correctly even though their extension doesn't mark them as modules. The MSVC toolchain now emits/internalPartition(instead of/interface, which is incompatible) for partition implementation units that the scanner reports withis-interface: false. IFC/PCM filenames are derived from the logical module name, so partitions likeM:Presolve to<moddir>/M-P.ifc. The Ninjadyndepfile is generated once at configure time; the build-time scanner build node is gone.
Fixed¶
Install()andTarfile()no longer pull in intermediate object files when given aTargetsource._resolve_sourceswas walkingtarget.nodes(which includes intermediates) instead oftarget.output_nodes;project.Install("dest", [program])was copying.ofiles next to the binary, and tarballs were bundling them in.- Better error when the C++ module scanner is missing from PATH.
clang-scan-deps/cl.exenot found at configure time now raisesCxxModuleScannerNotFoundwith platform-specific install hints (vcvars64.bat for MSVC, package commands for clang-scan-deps), instead of silently producing empty dyndep files and confusing downstream build failures.
0.14.1 - 2026-04-15¶
Added¶
-
New example
34_multi_build_dir: Demonstrates CMake-style separate build directories per variant (build/debug/,build/release/) usingget_variant(). Complements example 03 which usesoutput_prefixfor variants in a single build directory. -
Test harness
variantssupport: Examples can now specifyvariants = ["debug", "release"]intest.tomlto run the build script once per variant, testing multi-variant workflows.
Fixed¶
env.link.flagsno longer dropped when target haspublic.link_flags: Settingtarget.public.link_flagswas replacingenv.link.flagsinstead of merging with them, making env-level link flags (e.g.,-fsanitize=address) unreliable. Fixed for both Unix and MSVC toolchains.
0.14.0 - 2026-04-14¶
Added¶
PathTokenexported from top-levelpconspackage: Allows embedding paths inside arbitrary flags (e.g.,-Wl,-force_load,<path>) with proper generator-relative path handling. See new example33_path_in_flags.
Fixed¶
-
PathToken.relativize()now respectspath_type: Build-relative and absolute paths are no longer incorrectly transformed by the generator's relativizer (e.g.,path_type="build"no longer gets a$topdir/prefix in Ninja output). -
Multi-component
build_dirpaths (e.g.,build/release) now work correctly: The Ninja generator was only stripping the last component of the build directory prefix from output paths, causing double-nested paths likebuild/release/build/release/libfoo.a. Fixed in the Ninja generator, project node resolution, and path normalization warnings.
0.13.0 - 2026-04-07¶
Added¶
-
output_prefixandoutput_suffixon targets: Override platform-default prefix/suffix independently (e.g.,target.output_prefix = ""to remove thelibprefix on Linux). Works alongsideoutput_namewhich sets the base name. -
get_output_prefix()/get_output_suffix()toolchain methods: Toolchains can now override platform naming conventions per target type. Used by Emscripten (.jsprograms) and WASI (.wasmprograms) toolchains. -
get_platform()exported from top-levelpconspackage: No longer need to import frompcons.configure.platform. -
CMake-to-pcons porting guide: New
docs/porting-from-cmake.mdwith side-by-side command reference, detailed pattern mappings, debugging tips, and common gotchas.
Changed¶
- BREAKING:
output_nameis now a base name, not a raw filename:target.output_name = "foo"produceslibfoo.so(notfoo), matching CMake'sOUTPUT_NAMEsemantics. Platform prefix and suffix are always applied. Useoutput_prefix/output_suffixto override them.
Improved¶
-
check_flag()now adds-Werror//WXautomatically: Compiler flag checks no longer silently pass for unknown flags on Clang (which accepts unknown-Wno-*flags without error by default). Detects MSVC/clang-cl toolchains and uses/WXinstead. -
link_libsvslink_flagsdocumentation: User guide and architecture docs now clearly explain thatlink_libsis for-llibraries (placed after objects) andlink_flagsis for other linker flags (placed before objects).
0.12.1 - 2026-04-07¶
Added¶
-
LaTeX contrib toolchain: New
latexmk-based toolchain for building LaTeX documents, with documentation and CI testing. -
User error experience test suite: 48 tests covering common user mistakes, ensuring clear error messages for misuse.
Improved¶
-
Input validation for common errors: Toolchain type mismatches, incorrect flag types, post-resolve mutation attempts, no-toolchain compilation, and unknown variant names now raise clear, actionable error messages instead of confusing failures.
-
UsageRequirements values validated: Passing a string instead of a list (e.g.,
target.public.defines = "-DFOO") now raises an immediate error with guidance. -
Command() template variable validation: Unknown
$variablesinCommand()templates are now caught at build-description time, not at ninja-run time.
Fixed¶
- ReadTheDocs build: Fixed pcons installation and
target_typestring rendering in documentation.
CI¶
- Bumped
wasi-sdkfrom v30 to v32. - Bumped
mymindstorm/setup-emsdkfrom 14 to 15.
0.12.0 - 2026-04-05¶
Added¶
-
project.generate_pc_file()for pkg-config.pcgeneration: Targets can now generate.pcfiles for downstream consumers. Handles prefix-relative paths, external include directories, and library flags automatically. -
target.nodescomputed property: Returns all nodes (intermediate + output) for a target, convenient for dependency inspection. -
Alias()accepts list arguments:project.Alias("name", [target1, target2])now works in addition toproject.Alias("name", target1, target2). -
Automatic
uvx ninjafallback: Whenninjais not in PATH, pcons now falls back to running it viauvx ninja, so users withuvinstalled don't need a separate ninja installation. -
MSVC detection via
vswhere: Whencl.exeis not in PATH, pcons now finds Visual Studio installations viavswhere.exe, improving out-of-the-box Windows experience. -
MSVC
link.exe/lib.exeresolution: When other tools shadow MSVC'slink.exeorlib.exein PATH (e.g., Cygwin, Git for Windows), pcons now resolves them from the Visual Studio installation directory.
Changed¶
-
object_nodesrenamed tointermediate_nodes: Better reflects that these are not always object files (e.g., archive or custom tool intermediates). -
TargetTypeenum replaced with plain strings:target.target_typeis now a simple string ("program","static_library", etc.) instead of an enum value. -
Core is now fully tool-agnostic: All C/C++ compile-link logic has been extracted from
core/resolver.pyintotools/compile_link.pyvia a factory dispatch system.UsageRequirementsis now generic and extensible with dict-based storage. The core knows nothing about compilers, linkers, or languages.
Fixed¶
.pcfile include paths: External include directories (outside the install prefix) are now correctly emitted as absolute-Iflags in theCflagsfield.
0.11.0 - 2026-04-02¶
Added¶
-
project.generate()convenience method: Replaces theGenerator().generate(project)pattern — build scripts no longer need to importGenerator. Selects the right backend (ninja/make/xcode) from CLI flags or environment variables. -
Smarter
Project()defaults:build_dirdefaults toPCONS_BUILD_DIRenv var (set by the CLI), androot_diris inferred from the calling script's directory via stack inspection. Build scripts no longer needPath(__file__).parentoros.environ.get("PCONS_BUILD_DIR", "build")boilerplate. -
Ninja
restatsupport:env.Command(..., restat=True)tells Ninja to re-check output timestamps after running a command. If the output didn't actually change, downstream rebuilds are skipped. -
target.depends()for non-linked dependencies: Targets can now declare implicit dependencies that propagate public usage requirements (includes, defines) without adding outputs to the linker command. Useful for generated headers. -
cppstdparameter forConanFinder.sync_profile(): Setscompiler.cppstdin the Conan profile. Can be specified explicitly (cppstd="23") or inferred automatically fromenv.cxx.flags(e.g.,-std=c++23). Many Conan packages require this setting. -
PackageDescriptionandImportedTargetexported from top-levelpconspackage: Users can now writefrom pcons import ImportedTarget, PackageDescriptioninstead of importing from subpackages. -
pcons-fetchheader-only packages:build="none"in deps.toml skips the build step for header-only libraries, using the source directory directly as the install prefix. -
pcons-fetchcommit SHA pinning: Git refs that look like commit SHAs now trigger a full clone + checkout instead of--depth=1 --branch, which only works for branch/tag names.
Fixed¶
-
Link flags no longer leak into
arcommands: When aStaticLibrarydepended on anImportedTargetwith-L,-pthread, or other link flags, those incorrectly appeared in the archiver command. The archiver (ar/lib.exe) only accepts object files. -
pcons-fetchgit URL detection with@refsuffix: URLs likehttps://...repo.git@mainare now correctly detected as git repos. Previouslyendswith(".git")failed because the URL ends with@main. -
pcons-fetch: Prefer.pcfiles over directory scanning when generating package descriptions.
Improved¶
-
Simplified examples: Removed boilerplate from all 30+ example build scripts. The minimal hello_c example is now 6 lines of code with a single import.
-
Package management documentation: New "Header-Only and Manual Packages" section in the user guide.
sync_profile()reference with all parameters. UpdatedImportedTargetdocstring to showfind_package()+link()pattern instead of manual flag copying. -
ARCHITECTURE.md: Replaced stale planned-API examples with current working API. Updated status notes for package management features that are now fully implemented.
0.10.0 - 2026-04-01¶
Added¶
-
MSVC C++20 module support:
.cppmmodule interface units are now handled byMsvcToolchainusingcl.exe /scanDependenciesfor dependency scanning and Ninja dyndep for correct build ordering. Includes a dedicatedMsvcCxxCompilerwith proper$cxx.*namespace and correct linker command handling (MSVC'slink.exeis separate fromcl.exe, unlike GCC/Clang). -
pcons-fetchSHA-256 verification: Archive downloads now support an optionalsha256field indeps.toml. When present, the downloaded archive is verified before extraction and aborts on mismatch. -
Safe archive extraction in
pcons-fetch: Archive extraction now rejects path traversal (../), absolute paths, and symlink/hardlink escape tricks in both tar and zip archives.
Fixed¶
-
CLI environment restoration:
run_script()now properly saves and restores pre-existing environment variables instead of unconditionally deleting them. Previously, running pcons could clobberPCONS_BUILD_DIRor other env vars set by an outer invocation. -
ReadTheDocs build: Fixed import error by lazily importing
pbxprojinXcodeGenerator, which is an optional dependency not available in the docs build environment. -
pkg-config version comparison: Strict inequalities (
>,<) are now handled correctly with a custom version comparator, instead of being silently mapped to>=/<=via pkg-config flags. -
Type safety cleanup: Replaced
type: ignorecomments with propercast()calls across toolchains, Xcode generator, and builder code.BuildInfois now a dataclass instead of a raw dict.
0.9.0 - 2026-03-20¶
Added¶
-
Fortran toolchain (
gfortran): Full GNU Fortran support viafind_fortran_toolchain(). Includes compiler, archiver, and linker tools; supports all standard Fortran source extensions (.f90,.f95,.f03,.f08,.f18,.F,.F90,.f,.for,.ftn). -
Ninja dyndep for Fortran module dependencies: Correct build ordering for projects using Fortran
MODULE/USEstatements. A configure-time manifest is written and a build-time Python scanner (pcons.toolchains.fortran_scanner) produces a.dyndepfile consumed by Ninja (requires Ninja ≥ 1.10). -
Mixed-language C++/Fortran builds:
env.add_toolchain()now supports mixing Fortran with C/C++ in a single target. Runtime libraries are automatically injected in both directions — gfortran as primary linker adds-lc++/-lstdc++for C++ objects; g++/clang++ as primary linker adds-lgfortranfor Fortran objects. On macOS the gfortran library directory is also injected automatically. -
C++20 named modules (LLVM toolchain):
.cppmmodule interface units are now handled byLlvmToolchain. The single-step compile (-fmodule-output=) produces both the.pcmprecompiled module and the object file, exactly like Fortran's-J modules. A build-time scanner (pcons.toolchains.cxx_module_scanner) callsclang-scan-deps -format=p1689to discover dependencies and writes a Ninja dyndep file for correct build ordering (requiresclang-scan-depsand Ninja ≥ 1.10). -
after_resolve()hook in toolchain protocol:BaseToolchainnow defines an optionalafter_resolve(project, source_obj_by_language)hook called after all targets are resolved but before command expansion. Toolchains override this to inspect or modify the build graph (used by both Fortran and C++20 module support). -
Five new examples:
25_fortran_hello— simple "Hello from Fortran!" program26_fortran_modules— Fortran MODULE / USE with correct dyndep ordering27_fortran_calls_cxx— Fortran primary calling C++ viaBIND(C)(gfortran links, C++ runtime injected)28_cxx_calls_fortran— C++ primary calling Fortran viaBIND(C)(clang++/g++ links, Fortran runtime injected)29_cxx_modules— C++20 named modules with a module interface unit (.cppm) and consumer (.cpp)
Fixed¶
- LLVM object suffix on Windows:
LlvmToolchainnow correctly produces.objfiles on Windows (COFF convention) instead of.o. Previously the Unix base class suffix was used even when targetingx86_64-pc-windows-msvc.
0.8.4 - 2026-03-19¶
Added¶
-C/--directoryoption: Likemake,cmake, andninja, pcons now supports-C DIRto change to a directory before doing anything else. The-Cshort flag previously used for--reconfigurehas been removed; use--reconfigureinstead.
Fixed¶
pcons inittemplate: The generated template now uses the public API (Generator,Project,find_c_toolchain) matching the style of all examples, instead of internal imports (NinjaGenerator,Configure) and unnecessary boilerplate.
0.8.3 - 2026-03-14¶
Fixed¶
-
$ORIGINin linker flags: Literal$in link flags (e.g.,-Wl,-rpath,$ORIGIN) was silently interpreted as a pcons variable, producing broken output. Now raises a clear error with a hint to use$$for literal dollar signs. The ninja generator also properly escapes literal$as\$$so it survives both ninja and shell expansion. -
Silent error swallowing: Narrowed overly-broad exception handlers in module loading, CLI variable parsing, and config cache loading to avoid hiding real errors.
Added¶
-
--debug=configuretracing: All configure checks (check_header,check_flag,try_compile, etc.) now log detailed trace output including the command run, exit codes, compiler errors, caller file:line, and source code previews. When active, check source files are preserved inbuild/.configure-checks/for inspection. -
--debug=help: Lists available debug subsystems with descriptions. Unknown subsystem names now produce an error instead of being silently ignored. -
check_header()gainsdefinesandextra_flagsparameters: Allows specifying preprocessor defines needed to include a header, e.g.,check_header("ucontext.h", defines=["_XOPEN_SOURCE"])for headers that require feature macros.
0.8.2 - 2026-03-13¶
Added¶
ToolChecks.try_compile()public method: Compile arbitrary source code snippets to probe for compiler features, struct members, intrinsics, etc. Handles compiler lookup and result caching automatically, matching the pattern ofcheck_header()andcheck_flag(). Previously this required reaching for private_try_compile()and_get_compiler()methods.
0.8.1 - 2026-03-13¶
Added¶
configure_file()for template-based config headers: CMake-style template substitution with@VAR@replacement and#cmakedefine/#cmakedefine01directives. Generates config headers from.intemplates during the build description phase.-
New example
24_configure_filedemonstrating usage -
Automatic install_name (macOS) and SONAME (Linux) for shared libraries: Unix toolchains now automatically set
-Wl,-install_name,@rpath/<name>on macOS and-Wl,-soname,<name>on Linux when building shared libraries. Override withtarget.set_option("install_name", value)or disable withtarget.set_option("install_name", ""). -
Generic
target.set_option()/target.get_option()API: Targets now support arbitrary key-value metadata viaset_option(key, value)andget_option(key, default). This provides a clean extension point for toolchain-specific options without adding domain-specific properties to the core Target class. -
Toolchain.get_link_flags_for_target()hook: Toolchains can now inject target-specific link flags (e.g., install_name, SONAME) during build context creation. The hook receives the target, output filename, and existing flags. -
Auto-generated toolchain and builder documentation:
docs/toolchains.mdanddocs/builders.mdare now auto-generated from registered toolchains and builders.
Fixed¶
- Object file name collisions for same-basename sources: Sources sharing a basename (e.g.,
foo.candfoo.cpp) no longer produce conflicting object files. Object filenames now include the source extension (foo.c.o,foo.cpp.o) and mirror the source directory structure underobj.<target>/(e.g.,src/lib/foo.cpp→obj.mylib/src/lib/foo.cpp.o).
Changed¶
- Improved
Generator()discoverability: Better documentation and source organization for generator selection.
0.8.0 - 2026-03-13¶
Added¶
- WASI toolchain: New
find_wasi_toolchain()for compiling C/C++ to standalone WebAssembly (.wasm) using wasi-sdk. Supportsclang,clang++,llvm-arfrom wasi-sdk with automatic SDK discovery viaWASI_SDK_PATHenv var or common install locations. Includeswasmtimerunner integration for executing built.wasmfiles. -
New example
22_wasm_wasidemonstrating WASI compilation and execution -
Emscripten toolchain: New
find_emscripten_toolchain()for compiling C/C++ to WebAssembly + JavaScript using Emscripten. UsesMultiOutputBuilderfor Program targets that produce both.js(primary) and.wasm(secondary) outputs. Supports Emscripten-ssettings viaenv.link.settingslist. - New example
23_wasm_emscriptendemonstrating Emscripten compilation and Node.js execution -
Automatic SDK discovery via
EMSDKenv var, common install locations, oremccin PATH -
Multi-output Program support in resolver: The resolver now generically supports Program builders that produce multiple outputs (e.g.,
.js+.wasm). When a toolchain's Program builder is aMultiOutputBuilder, secondary output nodes are automatically created and tracked. This is tool-agnostic — it introspects the builder, not the toolchain name. -
CI testing for WebAssembly toolchains: New
test-wasmCI job tests both WASI and Emscripten examples on ubuntu-latest and macos-latest, installing emsdk, wasi-sdk, and wasmtime.
Changed¶
-
TargetPath.indexdefault changed from0toNone: This distinguishes "automatic" ($outin ninja, expanding to all outputs) from "explicit primary" ($target_0, expanding to first output only). Important for multi-output builds where-o $outwould incorrectly expand to all outputs.SourcePath.indexsimilarly changed. -
CI actions pinned to commit SHAs: All GitHub Actions in the CI workflow are now pinned to specific commit SHAs instead of version tags, improving supply-chain security.
Fixed¶
compile_commands.jsonnow uses actual compiler command: Previously hardcoded generic names likecc/c++instead of reading the actual compiler path (e.g.,emcc,/usr/bin/gcc) from the environment's tool configuration.
0.7.4 - 2026-02-23¶
Added¶
- Auto-generate
compile_commands.jsonfrom build generators: Ninja, Makefile, and Xcode generators now automatically generatecompile_commands.jsonalongside build files for seamless IDE integration. A symlink is also created at the project root for tool discovery. No more manualCompileCommandsGenerator()calls needed. Opt out withgenerator.generate(project, compile_commands=False).
Fixed¶
compile_commands.jsonsymlink on Windows cross-drive paths:os.path.relpath()raisesValueErrorwhen source and build directories are on different Windows drives. Symlink creation is now skipped gracefully in that case, and also whenbuild_diris the project root (file already in place).
0.7.3 - 2026-02-16¶
Fixed¶
- C++ flags no longer leak to C compilation in mixed-language targets: When a target contains both
.cand.cppsources (e.g.,Program("app", env, sources=["main.cpp", "util.c"])), language-specific flags like-std=c++20set onenv.cxx.flagsno longer leak to.cfile compilation. Previously,compute_effective_requirements()merged the primary tool's flags into a shared list applied to all sources. Now per-tool base flags are applied during command expansion where the tool name is correctly determined per-source file.
0.7.2 - 2026-02-15¶
Added¶
pcons.contrib.windows.msvcupmodule: Install MSVC compiler and Windows SDK without Visual Studio using msvcup. Ideal for CI environments, lightweight dev setups, or reproducible locked compiler versions.ensure_msvc(): Downloads and installs MSVC + Windows SDK, sets up wrapper executables viamsvcup autoenv- Version pinning: Specify exact MSVC and SDK versions for reproducible builds
- Lock file support: Pin resolved versions for team consistency (
--lock-file) - Manifest update control:
manifest_updateparameter ("off","daily","always") - Integrates with pcons toolchain detection — msvcup-installed compiler is found automatically
Changed¶
- Dedicated CI job for msvcup testing: msvcup example tests now run on a separate Windows runner without Visual Studio pre-installed, validating that msvcup can provide the full toolchain independently.
- Configurable test timeouts: Example integration tests now support a
timeoutfield intest.tomlfor tests that need longer execution time (e.g., downloading toolchains).
Internal¶
- Code simplification pass: reduced ~163 lines across 17 files with no functional changes.
0.7.1 - 2026-01-31¶
Added¶
$SRCDIRvariable forenv.Command(): Generator-agnostic variable for referencing the project source tree root in custom commands. Ninja replaces it with$topdir, Makefile replaces it with the absolute project root path. Useful for scripts and config files that live in the source tree:command="python $SRCDIR/tools/gen.py $SOURCE -o $TARGET".target.depends()method: Add implicit dependencies to any target. Files added viadepends()trigger rebuilds when changed but don't appear in$in/$SOURCE. Works withstr,Path,FileNode, orTargetarguments. Supports fluent chaining:app.depends("version.txt", "config.yaml").depends=parameter onenv.Command(): Shorthand for adding implicit dependencies at command creation time:env.Command(target=..., source=..., command=..., depends=["tools/gen.py"]).pcons buildauto-generates before building: Runningpcons buildnow automatically runspcons generatefirst if needed, so a single command handles the full workflow.
Changed¶
- Project as single authority for node creation: All
FileNodeobjects in production code are now created throughproject.node(), which ensures the same canonical path always returns the same object. This eliminates duplicate-node bugs where metadata (like_build_info) was split across separate objects for the same file. The_sync_output_nodes_to_project()workaround has been removed.
Fixed¶
- Post-build
$outexpansion in ninja:$outand$inin post-build commands are now left as literal ninja variables instead of being pre-expanded to project-root-relative paths. Since ninja runs from the build directory, pre-expanded paths were incorrect. - InstallDir child nodes as implicit deps: Child nodes of
InstallDirtargets now appear as implicit dependencies (after|in ninja) instead of explicit inputs. This prevents them from polluting$in, whichcopytreeexpects to contain only the source directory. - Install directory deps and node deduplication for ninja paths: Fixed ninja path generation for install directory dependencies and node deduplication.
create_pkgInstall target name collisions: Install targets created bycreate_pkg()now use unique names derived from the package name (e.g.,pkg_payload_MyAppinstead ofinstall_payload), eliminating rename warnings whencreate_pkgis called multiple times.
0.7.0 - 2026-01-30¶
Added¶
pcons info --targets: New CLI option to list all build targets grouped by type. Shows aliases first, then targets organized by type (program, shared_library, etc.) with their output paths.- Auto-detect directory sources in Install builder:
project.Install()now automatically detects when a source is a directory (by checking the node graph for child nodes) and usescopytreecmdwith depfile/stamp tracking instead ofcopycmd. This fixesIsADirectoryErrorwhen passing bundle directories through Install (e.g., fromcreate_pkgsources).
Changed¶
Generator.generate()no longer takesoutput_dirparameter: The generator always usesproject.build_diras the output directory. Callers that were passingoutput_dirshould remove the argument.- Improved
build_dirprefix warning:normalize_target_path()now provides clearer warnings when target paths start with the build directory name, explaining the double-prefix issue and suggesting the correct path. Accepts an optionaltarget_namefor better diagnostics.
Fixed¶
- Install directory detection: Fixed
_has_child_nodesfailing to detect directory sources. Source paths passed asproject.build_dir / subdir / ...include the build_dir prefix (e.g.,build/ofx-debug/bundle), but node paths inproject._nodesare build-dir-relative (e.g.,ofx-debug/bundle/Contents/...). The build_dir prefix is now stripped before comparison, fixing both absolute and relative build_dir cases. - Graph generators: path-based labels: Mermaid and DOT graph node labels now show full relative paths (e.g.,
obj.floss2/floss-core.o) instead of just filenames, disambiguating same-named files across targets. - Graph generators: directory containment edges: Install target outputs inside a bundle directory now have edges drawn to that directory node, completing the dependency chain from sources through to installers.
0.6.1 - 2026-01-30¶
Fixed¶
- Alias() now resolves Target references lazily:
Project.Alias()no longer eagerly readstarget.output_nodesat call time. Instead,AliasNode.targetsis now a property that resolves Target references on access. This fixes aliases forInstallDirand other targets whoseoutput_nodesare populated duringresolve()— previously these aliases produced empty no-op phony rules in Ninja.
Changed¶
alltarget includes every target:ninja all/make allnow builds every target in the project (commands, installers, archives, etc.), not just programs and libraries. The implicit default (whenproject.Default()is not called) remains programs and libraries only.
Documentation¶
- Show version number in docs site heading via mkdocs-macros-plugin
- Clarify Feature Detection docs: separate ToolChecks from Configure
- Add Platform Installers section to user guide
0.6.0 - 2026-01-29¶
Added¶
- Compiler cache wrapping: New
env.use_compiler_cache()method wraps compile commands with ccache or sccache. - Auto-detects available cache tool (tries sccache, then ccache)
- Explicit tool selection:
env.use_compiler_cache("ccache") - Only wraps cc/cxx commands, never linker/archiver
-
Warns about ccache + MSVC incompatibility (use sccache instead)
-
Semantic presets: New
env.apply_preset()for common flag combinations. "warnings": All warnings + warnings-as-errors (-Wall -Wextra -Wpedantic -Werror//W4 /WX)"sanitize": Address + undefined behavior sanitizers"profile": Profiling support (-pg//PROFILE)"lto": Link-time optimization (-flto//GL+/LTCG)"hardened": Security hardening flags (stack protector, FORTIFY_SOURCE, RELRO, etc.)-
Toolchain-specific: Unix and MSVC each define their own flags
-
Cross-compilation presets: New
env.apply_cross_preset()for common cross-compilation targets. android(ndk, arch, api): Android NDK cross-compilationios(arch, min_version, sdk): iOS cross-compilationwasm(emsdk): WebAssembly via Emscriptenlinux_cross(triple, sysroot): Generic Linux cross-compilationCrossPresetdataclass for custom presets-
Toolchains handle --target, --sysroot, /MACHINE flags automatically
-
project.find_package(): One-liner to find and use external packages. - Searches using FinderChain (PkgConfig → System by default)
- Returns ImportedTarget for use as dependency or with
env.use() - Caches results for repeated lookups
required=Falsefor optional dependencies-
project.add_package_finder()to prepend custom finders (Conan, vcpkg) -
Windows SxS manifest support: Support for Windows Side-by-Side (SxS) manifests
.manifestas source: Add.manifestfiles to Program/SharedLibrary sources; automatically passed to linker via/MANIFESTINPUT:pcons.contrib.windows.manifest: Helper module for generating manifests:create_app_manifest(): Generate application manifests with DPI awareness, visual styles, UAC settings, and assembly dependenciescreate_assembly_manifest(): Generate assembly manifests for private DLL assemblies
-
Works with both MSVC and clang-cl toolchains
-
Platform-specific installer generation: New
pcons.contrib.installerspackage for creating native installers - macOS:
create_pkg()for .pkg installers,create_dmg()for disk images,create_component_pkg()for simple packages - Windows:
create_msix()for MSIX packages (requires Windows SDK) - Auto-detects bundle vs non-bundle sources for proper macOS component plist handling
-
Signing helpers:
sign_pkg(),notarize_cmd()for macOS code signing -
CLI
uvx ninjafallback: Whenninjaisn't in PATH butuvxis available,pcons buildandpcons cleanautomatically useuvx ninja -
Targets as sources: Targets can now be used as sources for
Install(),Command(), and other builders. The target's outputs are resolved at build time, enabling auto-generated source files. -
Test framework
build_targetssupport: Example tests can now specify platform-specific build targets viabuild_targets_darwin,build_targets_windows, etc.
Fixed¶
- macOS pkgbuild for non-bundle files: Component plists are now only generated for .app bundles, fixing pkgbuild errors for CLI tools and libraries
0.5.0 - 2026-01-28¶
Added¶
- Add-on/Plugin module system: New extensible module system for creating reusable domain-specific add-ons.
- Module discovery: Auto-loads modules from
PCONS_MODULES_PATH,~/.pcons/modules/, and./pcons_modules/ pcons.modulesnamespace: Access loaded modules viafrom pcons.modules import mymodule--modules-pathCLI option: Specify additional module search paths-
Module API convention: Modules can define
__pcons_module__metadata andregister()function -
pcons.contribpackage: Built-in helper modules for common tasks: pcons.contrib.bundle: macOS bundle and flat bundle creation helpersgenerate_info_plist()- Generate Info.plist contentcreate_macos_bundle()- Create macOS .bundle structurecreate_flat_bundle()- Create flat directory bundles (Windows/Linux)get_arch_subdir()- Get architecture subdirectory names (e.g., "MacOS-x86-64")
pcons.contrib.platform: Platform detection utilitiesis_macos(),is_linux(),is_windows()- Platform checksget_shared_lib_extension(),format_shared_lib_name()- Library namingget_arch()- Get current architecture
Documentation¶
- User guide: Added comprehensive "Add-on Modules" section with examples
- Architecture doc: Added Module System section with implementation details
0.4.3 - 2026-01-28¶
Added¶
FlagPairmarker class for explicit flag+argument pairs: NewFlagPairclass allows users to explicitly mark flag+argument pairs that should be kept together during deduplication, even for custom flags not in the toolchain'sSEPARATED_ARG_FLAGSlist.- Usage:
env.cxx.flags.append(FlagPair("-custom-flag", "value")) - Immutable, hashable, and iterable (can be unpacked:
flag, arg = FlagPair(...)) - Exported from top-level
pconsmodule
Fixed¶
-
Flag pair deduplication for
-includeand similar flags: Flags like-include,-imacros, and-xthat take separate arguments are now properly handled during deduplication. Previously,-include header1.h -include header2.hwould incorrectly deduplicate to-include header1.h header2.h. Added-include,-imacros, and-xtoSEPARATED_ARG_FLAGSin Unix toolchains. -
ToolConfig.as_namespace() mutation bug: The
as_namespace()method now returns copies of mutable values (lists, dicts) instead of references to the original. This prevents accidental mutation of tool configuration during variable substitution, which was causing flag accumulation bugs. -
Resolver no longer double-merges flags: The resolver now uses
extra_flagsandldflagsdirectly instead of merging them with existing tool flags. These values already include base environment flags viacompute_effective_requirements(), so merging was duplicating flags.
Documentation¶
- User guide: Added "Build Script Lifecycle" section explaining the three phases (configure, describe, generate)
- User guide: Clarified when to use
project.node()vs raw paths - User guide: Added "Default and Alias Targets" section with examples
- User guide: Added output naming defaults table for libraries and programs
- User guide: Improved environment cloning documentation
- User guide: Added examples for multiple commands and post-build commands
0.4.2 - 2026-01-28¶
Fixed¶
-
Flag accumulation bug: Context flags (includes, defines, compile_flags) were being appended to the shared tool_config, causing flags to accumulate exponentially across multiple source files in a target. Now uses temporary overrides passed via extra_vars to avoid mutating shared state.
-
C++ linker selection: C++ programs and shared libraries now correctly use the C++ compiler (clang++/g++) as the linker instead of the C compiler (clang/gcc). This ensures proper C++ runtime linkage. The logic is in the toolchain layer (CompileLinkContext) to keep the core tool-agnostic.
-
InstallAs validation:
InstallAs()now raises a clearBuilderErrorwhen passed a list or tuple, directing users to useInstall()for multiple files. Previously it would silently fail.
Documentation¶
- Added practical example for
$$escaping in subst.py docstring (useful for$ORIGINin rpath) - User guide: Documented
$$for literal dollar signs with rpath example - User guide: Clarified that
Install()takes a list whileInstallAs()takes a single source
0.4.1 - 2026-01-23¶
Added¶
- Debug/trace system for build script debugging: New
--debug=<subsystems>CLI flag orPCONS_DEBUGenvironment variable enables selective tracing. Available subsystems:configure,resolve,generate,subst,env,deps,all. - New
pcons/core/debug.pymodule withtrace(),trace_value(),is_enabled()functions - Enhanced
__str__methods on Target, Environment, FileNode, Project for readable debug output - Source location tracking (
defined_at) shown in debug output -
Usage:
pcons --debug=resolve,substorPCONS_DEBUG=all pcons -
Xcode project generator: New
-G xcodeoption generates native.xcodeprojbundles that can be built withxcodebuildor opened in Xcode IDE. - Supports Program, StaticLibrary, and SharedLibrary targets
- Maps pcons include dirs, defines, and compile flags to Xcode build settings
- Handles target dependencies between libraries and executables
- Generates both Debug and Release configurations
-
Uses
pbxprojlibrary for robust project file generation -
Multi-generator build support in CLI: The
pcons buildcommand now auto-detects which generator was used and runs the appropriate build tool: build.ninja→ runsninjaMakefile→ runsmake-
*.xcodeproj→ runsxcodebuild -
Variant support for xcodebuild: The
--variantflag is passed to xcodebuild as-configuration, mapping variant names to Xcode configurations (e.g.,--variant debug→-configuration Debug).
0.4.0 - 2026-01-22¶
Changed¶
- BREAKING: Typed path markers replace string escaping: Command templates now use typed
SourcePath()andTargetPath()marker objects instead of string patterns like$$SOURCE/$$TARGET. This provides type-safe path handling and eliminates fragile string manipulation. - All toolchains (GCC, LLVM) migrated to use markers
- All standalone tools (install, archive, cuda) migrated to use markers
- Generators convert markers to appropriate syntax: Ninja uses
$in/$out, Makefile uses actual paths -
Custom tools should now use markers in command templates (see
test_external_tool.pyfor example) -
Unified command expansion path: Removed the dual mechanism where some tools used string patterns and others used markers. All tools now follow the same flow: markers → resolver → generators.
Fixed¶
-
CommandBuilder now stores env in _build_info: Fixes command expansion for nodes created via
env.cc.Object()and similar APIs. Previously commands weren't expanded because the resolver couldn't find the environment. -
Standalone tool commands properly converted:
_get_standalone_tool_command()now calls_relativize_command_tokens()to convert markers to Ninja variables. -
Makefile generator handles markers in context overrides:
_apply_context_overrides()now properly passes through marker objects instead of trying to do string replacement on them.
Removed¶
_convert_command_variables()from Ninja generator: String-based$SOURCE/$TARGETconversion is no longer needed since all tools use typed markers.
0.3.0 - 2026-01-21¶
Changed¶
- BREAKING: Generator-agnostic command templates: Toolchain command templates now use
$$SOURCE/$$TARGETinstead of Ninja-specific$$in/$$out. Each generator converts to its native syntax: - Ninja:
$in/$out - Makefile: actual paths
-
Conventions:
$$SOURCE(single input),$$SOURCES(multiple),$$TARGET(output),$$TARGET.d(depfile) -
BREAKING: ToolchainContext API changed:
get_variables()replaced withget_env_overrides(). Values are now set on the environment's tool namespace before command expansion, rather than written as per-build Ninja variables. Return type changed fromdict[str, list[str]]todict[str, object]. -
Command expansion moved to resolver: Commands are now fully expanded at resolution time with all effective requirements baked in. Generators receive pre-expanded commands, simplifying generator implementation.
-
Unified builder/tool architecture: Install and Archive builders are now implemented as
StandaloneToolsubclasses (InstallTool,ArchiveTool). Tools provide command templates viadefault_vars(), builders reference them viacommand_var. Enables customization:env.install.copycmd = ["cp", "$$SOURCE", "$$TARGET"]. -
Shell quoting improvements: Commands stored as token lists until final output. The
subst()function handles shell-appropriate quoting based on target format (shell="ninja"orshell="bash"). Paths with spaces properly quoted. -
Standardized on
$SOURCE/$TARGETin user commands: User-facing commands (e.g.,env.Command()) use SCons-style$SOURCE/$TARGETvariables. Generators convert to native syntax.
Fixed¶
-
Compile flags no longer passed to linker: The resolver now correctly separates
extra_flags(compile-only) fromldflags(link-only). Fixes MSVC builds where/W4was incorrectly passed to the linker. -
Windows platform suffixes in UnixToolchain:
get_program_name()andget_shared_library_name()now detect Windows and return.exe/.dllsuffixes for GCC/MinGW builds. -
Standalone tool context overrides: Install and Archive tools now correctly apply context overrides (like
$install.destdir) even when no Environment is present.
Removed¶
- Dead code cleanup: Removed ~100 lines of unused code from ninja.py:
_get_env_suffix()- superseded by command hash-based rule naming_get_rule_command()- superseded by pre-expanded commands_augment_command_with_effective_vars()- values now baked into commands
Documentation¶
- Updated ARCHITECTURE.md to reflect new
get_env_overrides()pattern - Updated CLAUDE.md with correct ToolchainContext file location
0.2.4 - 2026-01-20¶
Added¶
project.InstallDir()for recursive directory installation: Copies entire directory trees with proper incremental rebuild support using ninja's depfile mechanism. Stamp files stored inbuild/.stamps/to keep output directories clean.- Usage:
project.InstallDir("dist", src_dir / "assets")(paths relative to build_dir) - New
copytreecommand inpcons.util.commandswith--depfileand--stampoptions project.Command()for API consistency: Wrapper aroundenv.Command()for users who prefer the project-centric API.PathResolverfor consistent path handling: New centralized path resolution ensures all builders handle output paths consistently:- Target (output) paths: relative to
build_dir - Source (input) paths: relative to project root
- Absolute paths: pass through unchanged
- Warns when relative path starts with build_dir name (e.g.,
"build/foo") - Rebuild tests in example framework: New
[[rebuild]]sections intest.tomlverify incremental build behavior: touch: file to modify before rebuildexpect_no_work: verify ninja has nothing to doexpect_rebuild/expect_no_rebuild: verify specific targets- New example
14_install_dir: DemonstratesInstallDirfor copying directory trees.
Changed¶
- Tarfile/Zipfile output paths now relative to build_dir: No longer need
build_dir /prefix. Useoutput="file.tar.gz"instead ofoutput=build_dir / "file.tar.gz". - Install/InstallDir destinations relative to build_dir: Consistent with other builders.
0.2.3 - 2026-01-20¶
Added¶
- Auto-resolve in generators: Generators now automatically call
project.resolve()if the project hasn't been resolved yet. Users can still callresolve()explicitly (backward compatible), or simply omit it for simpler build scripts. - New example
12_env_override: Demonstrates usingenv.override()to compile specific source files with different flags (extra defines, include paths). - New example
13_subdirs: Demonstrates subdirectory builds where each subdir can be built standalone or as part of the parent project. - DotGenerator for GraphViz output: New
DotGeneratorclass for dependency graph visualization in DOT format. Usepcons generate --graphor importDotGeneratordirectly. allphony target in ninja: Generated ninja files now include analltarget (standard Make convention). Default target isallunless user specifies defaults viaproject.Default().
Fixed¶
env.override()andenv.clone()now work correctly with direct builder API: Previously, nodes created in a cloned/overridden environment were registered with the original environment, causing per-environment compiler flags to be lost. Fixed by:- Cloned environments now register with the project
BuilderMethodinstances are rebound to reference the new environment- Ninja generator creates per-environment rules for all environments
- Command target dependencies now shown in graphs: Both mermaid and dot generators now correctly show dependencies for
env.Command()targets (previously showed outputs with no edges).
Changed¶
03_variantsexample improved: Now uses a Python loop to build both debug and release variants, demonstrating the power of Python for build configuration.- Example cleanups: Removed verbose print statements from
05_multi_library,07_conan_example, and10_paths_with_spacesexamples. - Removed
project.dump_graph(): Replaced byDotGeneratorclass for consistency with other generators.
0.2.2 - 2026-01-19¶
Added¶
- Cross-platform command helpers (
pcons.util.commands): New module providingcopyandconcatcommands that handle forward slashes and spaces in paths on all platforms - Usage:
python -m pcons.util.commands copy <src> <dest> - Usage:
python -m pcons.util.commands concat <src1> [src2...] <dest> - Used by Install/InstallAs builders and concat example
Changed¶
- Install and InstallAs now use
pcons.util.commands copyinstead of platform-specific shell commands - Concat example (01_concat) now uses
pcons.util.commands concatfor better cross-platform support
0.2.1 - 2026-01-19¶
Added¶
- Relative paths in ninja files: Generated
build.ninjafiles now use relative paths instead of absolute paths - New
topdirvariable points from build directory to project root (e.g.,topdir = ..) - Source files use
$topdir/path/to/source.cformat - Include paths use
$topdir/prefix (e.g.,-I$topdir/include) - Build outputs remain relative to build directory
- Makes ninja files portable and more readable
- Proper escaping for paths with spaces:
ToolchainContext.get_variables()now returnsdict[str, list[str]]so generators can properly escape each token - Ninja generator uses Ninja escaping (
$for spaces) for cross-platform compatibility - Makefile generator uses appropriate quoting for Make
- compile_commands.json uses
shlex.quote()for POSIX compliance - All paths normalized to forward slashes (works on Windows)
- New example
08_paths_with_spaces: Demonstrates building with spaces in directory names, filenames, and define values - UnixToolchain base class: Shared implementation for GCC and LLVM toolchains (source handlers, separated arg flags, variant application, -fPIC handling)
- BuildInfo TypedDict: Type-safe dictionary for
node._build_infowith proper typing for tool, command, language, depfile, and context fields - Environment.name parameter: Environments can now have names for more readable ninja rule names
Changed¶
- Per-environment ninja rules: Each environment now generates its own ninja rules (e.g.,
link_sharedcmd_release_abc123) instead of sharing rules with_effectivesuffix. This fixesenv.Framework()and other env-specific settings. - Test runner uses
ninja -C build: Changed fromninja -f build/build.ninjato the correctninja -C buildinvocation per ninja best practices - Source suffix handling now centralized through toolchain handlers with deprecation warnings for legacy
SOURCE_SUFFIX_MAPfallback
Fixed¶
- env.Framework() now works correctly: Framework flags are now properly baked into each environment's rules instead of requiring per-target overrides
Documentation¶
- Added CLAUDE.md with project conventions and development guidelines
0.2.0 - 2025-01-19¶
Added¶
- Archive builders: New
project.Tarfile()andproject.Zipfile()methods for creating tar and zip archives - Supports all common compression formats:
.tar.gz,.tar.bz2,.tar.xz,.tgz,.tar,.zip - Compression auto-detected from output extension
- Cross-platform using Python's built-in
tarfile/zipfilemodules - Returns
Targetobjects that can be passed toInstall()and other builders
Changed¶
- BREAKING: Renamed default build script from
build.pytopcons-build.py - CLI now looks for
pcons-build.pyby default instead ofbuild.py pcons initcreatespcons-build.pyinstead ofbuild.py- All examples updated to use
pcons-build.py -
Use
-b build.pyflag to run legacy scripts -
BREAKING:
env.Command()signature changed: Now uses keyword-only arguments and returnsTargetinstead oflist[FileNode] - Old:
env.Command("output.txt", "input.txt", "cmd") - New:
env.Command(target="output.txt", source="input.txt", command="cmd") - Access output nodes via
target.output_nodesinstead of indexing the result -
Optional
nameparameter for explicit target naming -
Merged
tests/examples/intoexamples/- examples now serve as both tests and user documentation - Example tests now verify both invocation methods:
python pcons-build.pyandpython -m pcons
Fixed¶
- Windows
Installcommand now works correctly (usescmd /c copyinstead of barecopy)
Documentation¶
- Added "All Build Outputs Are Targets" section to ARCHITECTURE.md documenting the design principle
- Added archive builders documentation to user guide
- New
07_archive_installexample demonstrating Tarfile builders and Install targets
0.1.4 - 2025-01-18¶
Added¶
- Multi-architecture build support: New
env.set_target_arch()method for building for different CPU architectures - macOS: Uses
-archflags for arm64/x86_64 builds, enabling universal binary creation - Windows MSVC: Uses
/MACHINE:linker flags for x64/x86/arm64/arm64ec - Windows Clang-CL: Uses
--targetcompiler flags plus/MACHINE:linker flags - macOS universal binary helper: New
create_universal_binary()function inpcons.util.macoscombines architecture-specific binaries usinglipo env.Command()builder: Run arbitrary shell commands with automatic variable substitution ($SOURCE,$TARGET,$SOURCES,$TARGETS,${SOURCES[n]},${TARGETS[n]})- macOS Framework linking: New
env.Framework()method and-framework/-Fflag support in GCC/LLVM toolchains pairwise()substitution function: For flags that need interleaved prefix/value pairs (e.g.,-framework Foundation -framework Metal)
Changed¶
- Build scripts run in-process: CLI now uses
exec()instead of subprocess, enabling access toProject.build_dirafter script execution. This fixes issues where build scripts modify the build directory (e.g.,build_dir = PCONS_BUILD_DIR / variant) - Toolchain-aware flag deduplication: Flag merging now correctly handles flags with separate arguments (like
-F path,-framework Name). Each toolchain defines its own separated-argument flags viaget_separated_arg_flags()
Fixed¶
- Flag deduplication no longer incorrectly merges
-F foo -F barinto-F foo bar - CLI
pconscommand now uses the actual build directory from the Project, not just the initialPCONS_BUILD_DIR
0.1.3 - 2025-01-18¶
Added¶
- Multi-toolchain support: Environments can now have multiple toolchains for mixed-language builds (e.g., C++ with CUDA)
- Clang-CL toolchain: MSVC-compatible Clang driver for Windows with platform-aware defaults
- AuxiliaryInputHandler: New mechanism for files passed directly to downstream tools (e.g.,
.deffiles to linker) - Windows resource compiler: MSVC toolchain now supports
.rcfiles compiled to.res - Assembly support: Added
.s,.S(GCC/LLVM) and.asm(MASM) source file handling - Metal shader support: Added
.metalfile compilation on macOS - User Guide: Comprehensive documentation covering all pcons features
Changed¶
find_c_toolchain()now uses platform-aware defaults: prefers clang-cl/msvc on Windows, llvm/gcc on Unix- Toolchains now provide
get_archiver_tool_name()for correct archiver selection (MSVC useslib, others usear)
Fixed¶
- Cross-platform support for C examples (02-06) now working on Windows with MSVC
- Concat example (01) now works on Windows using
cmd /c type
Infrastructure¶
- CI now runs MSVC tests on Windows
- Release workflow waits for CI to pass before publishing
0.1.2 - 2025-01-17¶
Initial public release with Ninja generator, GCC/LLVM/MSVC toolchains, and Conan integration.