Concurrency handling
STIG coordinates two different shared resources. Consumer Gradle configuration files and the Bazel execution root have different owners, failure modes, and lock lifetimes, so they deliberately use separate coordinators.
Consumer project configuration
The root edit task stigInitElement and the element-scoped edit task stigConfigureAnalysis can change one or more of these files:
settings.gradle.ktswhen an element is registered;the root
build.gradle.ktstransform registry;the selected element's
build.gradle.ktssource-code-analysis blocks.
stigInitElement selects the new element with --elementDir. stigConfigureAnalysis selects an existing element with its exact Gradle task path and must run alone in its own invocation. Changed Gradle configuration takes effect in the next invocation. The race is between independent Gradle invocations. Task dependencies cannot solve that cross-process race: both builds could evaluate the same old Kotlin DSL before either edit task starts.
StigProjectConfigurationAccessCoordinator is installed by ai.exida.stig-settings and uses the OS file lock:
Access follows this contract:
Invocation | Lock mode | Acquisition | Release |
|---|---|---|---|
Direct request for | Exclusive | Settings-plugin application, before project build scripts are evaluated | End of the complete Gradle build |
Any other Gradle invocation | Shared | Settings-plugin application | After all projects have been evaluated |
Task detection matches the final task-path segment and accounts for declared task options and their arguments. help --task stigConfigureAnalysis is read-only. If a requested task is an editor, the complete invocation is exclusive. Normal invocations may configure concurrently with one another, and their task execution is not serialized after they have captured the Gradle model. An editor waits for active configuration readers and other editors; new readers wait until the editor has completed all related file updates.
Both edit tasks are deliberately incompatible with Gradle's configuration cache. A reused configuration cache skips settings-plugin application, which would skip the early exclusive lock and could restore a stale Kotlin DSL snapshot. Read-only invocations remain configuration-cache compatible: a fresh configuration holds the shared lock while reading project scripts, while a reused configuration already has an immutable captured Gradle model and does not read those scripts again. A Gradle ValueSource prepares the coordination file before the locking channel opens it. This keeps first-time lock-file creation out of the consumer's configuration inputs, so the first stored cache entry can be reused.
The coordinator is a lifecycle-managed Gradle build service, while the actual exclusion is an OS file lock shared by independent Gradle processes. Process termination releases the OS lock. Configuration replacements use a temporary file in the destination directory followed by an atomic move, so a process outside the locking protocol cannot observe a truncate-and-rewrite intermediate state.
The settings plugin must be applied for this contract. Manual file edits, tools that bypass Gradle, and Gradle builds that do not apply ai.exida.stig-settings do not participate. The plugin is reached while evaluating settings.gradle.kts; script loading and the plugins {} prefix necessarily precede lock acquisition, although atomic replacement still prevents partially written script text from being loaded.
Bazel execution-root access
StigBazelExecrootAccessCoordinator protects a different resource: Bazel's shared execution root, generated outputs, and external/ symlink tree. It is a build-scoped Gradle service with maxParallelUsages(1). STIG tasks that invoke Bazel or run standalone Clang/LLVM over execroot paths declare usesService(...), so only one such task in the current Gradle build accesses that tree at a time. It also caches bazel info execution_root per workspace.
This coordinator does not edit consumer Gradle files and does not coordinate separate Gradle invocations or Bazel processes started outside the build. See LLVM toolchain for the task families covered by the service.
Per-activity outputs
Generated harnesses and analysis artifacts live below each element's own build/stig/<task>/ directory. Different activities therefore do not need a shared file lock merely because their analysis pipelines run in parallel. A task's normal Gradle inputs, outputs, and dependencies order work within that activity. Coordination is added only when a task also reads a genuinely shared resource, such as the Bazel execroot, or when a root edit invocation changes shared consumer configuration.