Skip to content

feat: auto-apply features marked default = true in the manifest#1302

Open
krystophny wants to merge 8 commits into
fortran-lang:mainfrom
krystophny:feat/openmp-default-parallel-build
Open

feat: auto-apply features marked default = true in the manifest#1302
krystophny wants to merge 8 commits into
fortran-lang:mainfrom
krystophny:feat/openmp-default-parallel-build

Conversation

@krystophny

@krystophny krystophny commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add a default = true field to feature definitions in fpm.toml
  • When no explicit --features list is given, features with default = true
    are automatically applied (alongside the existing default-profile mechanism)
  • --features "" (explicit empty) opts out of auto-defaults
  • --features "openmp,mpi" (explicit list) applies only listed features

Context

After the argv spawn chain (#1299, #1300, #1301) and the mkdir fork-safety
fix (#1298), the parallel build backend is fully fork-safe. This PR lets
packages opt into default features automatically via the manifest.

A package that wants OpenMP by default declares:

[features.openmp]
default = true

[features.openmp.dependencies]
openmp = "*"

CI or users on compilers where a default feature is unsafe (e.g.
libgomp+--static on gcc 10-13, see #962) opt out with --features "".

Bootstrap note: fpm's own fpm.toml cannot set default = true yet
because the bootstrap fpm (v0.13.0) rejects unknown keys in feature tables.
Once this change lands in a release and the bootstrap is bumped, a follow-up
adds default = true to fpm.toml and updates CI to use --features "" on
the affected compilers.

Change

  • feature_collection_t gains is_default logical, parsed from default
    key in the feature subtable, serialized in dump/load/same
  • export_config iterates features with is_default = .true.
  • default added to allowed-key whitelist in feature table validation
  • traverse_feature_table skips the default key during traversal
  • CLI: --features "" allocates a zero-size features array so
    present(features) is true and auto-defaults are suppressed

Merge order

This PR is stacked on the full fork-safety chain. Merge in order:

  1. Fortran compiles via argv (feat: exec Fortran compiles via argv (posix_spawn/CreateProcess) #1299)
  2. Parallel link/archive via argv (feat: run link and archive in parallel via argv spawn #1300)
  3. C/C++ compiles via argv (feat: exec C/C++ compiles via argv, close the shell-fork gap #1301)
  4. Serialize mkdir on run_command lock (fix: serialize mkdir shell fork on the run_command lock #1298)
  5. This PR — auto-apply default features + CLI opt-out

Until #1299-#1301 and #1298 merge, the diff includes their commits.

Also in this series:

Refs: #962

Verification

$ fpm build --features openmp --compiler gfortran --flag '-O3 -g'
[100%] Project compiled successfully.

$ fpm test
TALLY;TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT
PASSED: all 35 tests passed

CI: gcc 10-13 pass (the mechanism is available but fpm.toml does not yet
set default = true due to the bootstrap constraint).

Extend the argv-based Fortran compile spawn to Windows so it no longer
falls back to the shell there. Tokenize with the OS-native lexer
(ms_split on Windows, sh_split on Unix) so backslash paths and quoting
survive, route run_argv through c_run_argv on both platforms, and add a
CreateProcessA path with per-process stdout/stderr redirect. The shell
run remains as a fallback when the spawn returns a negative status.

Windows std handles are only overridden when redirecting, to avoid
stripping the child console; out-of-range child exit codes are clamped
positive so they are not mistaken for the spawn-failure sentinel.
Replace the shell run() in link_executable/link_shared/make_archive
with an argv spawn through run_argv (posix_spawn on Unix, CreateProcess
on Windows), which is fork-safe under OpenMP, and drop the
critical(run_command) that previously serialized them. Link and archive
steps now run in parallel; the rare shell fallback (tokenization
failure) and run_argv's own shell fallback stay serialized under
critical(run_command).

make_archive reproduces 'self%ar // output' exactly: the archive name
glues onto the last flag token for MSVC 'lib /OUT:' and is a separate
token for GNU 'ar -rs '. Promote split_append/clean_shlex_token to
module scope so link/archive reuse the compile path tokenization.
Apply the argv spawn (run_argv: posix_spawn on Unix, CreateProcess on
Windows, both fork-safe) to compile_c and compile_cpp, matching
compile_fortran. Serialize the rare shell fallbacks (tokenization
failure) in compile_c/compile_cpp/compile_fortran under
critical(run_command). No unguarded concurrent shell fork() then remains
in the compile, link, or archive build paths.
Under OpenMP the build loop runs compile_fortran/compile_c/compile_cpp on
many threads. Each tokenizes its flags through fortran-shlex (split_append)
and then registers a compile_commands entry (cct_register), which tokenizes
again. The lexer returns a deferred-length result array that was read back
into the argv list outside the critical region, so a concurrent re-entry of
the lexer corrupted the in-flight tokens: the compiler received mangled
flags (-fPIC truncated to -fP, several flags merged into one), aborting the
build intermittently on CI.

Hold the fpm_shlex_split lock across both the split and the readback in
split_append, and lock cct_register's tokenize plus result consumption under
the same name so the two sites never enter the lexer concurrently.
build_target created the output directory inside an unnamed `!$omp critical`.
mkdir shells out twice (is_dir runs `test -d`, then `mkdir -p`), and both
fork. The unnamed critical is a different lock than `critical(run_command)`
that guards every other shell fork (the compile/link/archive fallbacks), so a
mkdir fork on one thread could run concurrently with a run_command fork on
another - the exact concurrent-fork hazard the run_command lock exists to
prevent. Put the directory creation under `critical(run_command)` so all
shell forks share one lock.
@krystophny
krystophny force-pushed the feat/openmp-default-parallel-build branch from 091126d to 441f782 Compare June 4, 2026 07:18
Add a `default` field to feature_collection_t. When a feature declares
`default = true` in fpm.toml and the build does not pass an explicit
`--features` list, apply that feature automatically (alongside the
existing default-profile mechanism). An explicit `--features` list
opts out.

This is a generic mechanism: any feature can be made default, not just
openmp. fpm's own fpm.toml does not set `default = true` for openmp
because libgomp+--static segfaults at atexit on gfortran 10-13 (known
issue, see acdaf19 and fortran-lang#962). Users opt in per-feature in their own
projects.

Changes:
- feature_collection_t gains `is_default` logical, parsed from the
  `default` key in the feature subtable
- export_config iterates features with is_default instead of looking
  up "openmp" by name
- `default` added to the allowed-key whitelist in feature validation
- traverse_feature_table skips the `default` key during traversal
- Serialization (dump/load/same) updated for the new field
When --features is specified but empty, allocate a zero-size features
array so present(features) is true in export_config. This suppresses
auto-default features, giving CI and users an explicit opt-out for
configurations where a default feature is unsafe (e.g. --static +
libgomp on gcc 10-13).

Without this, --features "" was treated the same as not specifying
--features at all, and auto-defaults would still apply.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant