1 Commits
Author SHA1 Message Date
Bitsy 6dbdc93122 style: replace em dashes with plain punctuation 2026-08-09 21:46:02 +02:00
21 changed files with 80 additions and 80 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
run: |
VERSION="${{ steps.version.outputs.version }}"
if gh release view "$VERSION" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
echo "Release $VERSION already exists — skipping."
echo "Release $VERSION already exists - skipping."
else
gh release create "$VERSION" \
--repo "$GITHUB_REPOSITORY" \
+1 -1
View File
@@ -5,6 +5,6 @@
local.properties
.DS_Store
# Local credential overrides — never commit tokens
# Local credential overrides - never commit tokens
gradle.properties.local
secrets.properties
+27 -27
View File
@@ -11,7 +11,7 @@ This file provides guidance to AI agents (Claude, Copilot, Codex, etc.) working
# Run unit tests only
./gradlew test
# Run functional tests (Gradle TestKit — starts real Gradle builds)
# Run functional tests (Gradle TestKit - starts real Gradle builds)
./gradlew functionalTest
# Run a single functional test
@@ -51,20 +51,20 @@ region) each with their own variants.
**Configuration cache compatibility** is a hard requirement throughout. This means:
- Never capture `project` inside a `Provider.map {}` or `Provider.zip {}` lambda, and **never
access `project` inside a `@TaskAction`** — both break caching. Use only declared
access `project` inside a `@TaskAction`** - both break caching. Use only declared
`@Input`/`@OutputDirectory` properties inside task actions.
- Use `Class<T>` (`.javaObjectType`) instead of `KClass<T>` — Kotlin's `KClass` uses
- Use `Class<T>` (`.javaObjectType`) instead of `KClass<T>` - Kotlin's `KClass` uses
`SoftReference` internally which Gradle can't serialize.
- Use `gradlePropertiesPrefixedBy()` to read groups of properties — **note:** in Gradle 9.x this
- Use `gradlePropertiesPrefixedBy()` to read groups of properties - **note:** in Gradle 9.x this
returns full property names as keys (prefix is NOT stripped), so always check both
`dimProps["env"]` and `dimProps["konfig.dimension.env"]`.
- All DSL field values are wrapped in `Provider<T>` from the start — literals via
- All DSL field values are wrapped in `Provider<T>` from the start - literals via
`constantProvider(value)` (a hand-written `ConstantProvider<T>`), external values via
`providers.gradleProperty()` / `providers.environmentVariable()` etc. **Never store
`ProviderFactory` anywhere in the DSL object graph** — it is not config-cache serializable.
`ProviderFactory` anywhere in the DSL object graph** - it is not config-cache serializable.
- `forceRegen` (`konfig.force` property) is evaluated eagerly at configuration time as a plain
`Boolean` via `providers.gradleProperty("konfig.force").isPresent` — not inside a provider
lambda — so the value is captured by value and is config-cache safe.
`Boolean` via `providers.gradleProperty("konfig.force").isPresent` - not inside a provider
lambda - so the value is captured by value and is config-cache safe.
**Dimension data in task inputs** uses flat-map encoding (`"<dimName>|<fieldName>"` as map keys)
in `MapProperty<String, String>` rather than a managed-type `ListProperty`. Values are
@@ -74,14 +74,14 @@ per-type maps down to one per scope and avoids Gradle's `@Nested` managed-type r
### DSL design
- **`@KonfigDsl` / `@DslMarker`** is applied to all DSL scope classes to prevent accidental scope leakage.
- **`ConstantProvider<T>`** wraps literal values — no `ProviderFactory` anywhere in the DSL object graph.
- **`ConstantProvider<T>`** wraps literal values - no `ProviderFactory` anywhere in the DSL object graph.
- **`field()` at the top level** returns `FieldHandle<T>` which exposes `.debug(value)` and
`.release(value)`, both returning `Unit` — chaining beyond the first call is intentionally impossible.
- **`debug {}` / `release {}` scope blocks** use `BuildTypedFieldDeclScope` as receiver —
`.release(value)`, both returning `Unit` - chaining beyond the first call is intentionally impossible.
- **`debug {}` / `release {}` scope blocks** use `BuildTypedFieldDeclScope` as receiver -
`field()` inside these returns `Unit`, since the build type is already fixed by the enclosing scope.
- **`common {}` block in `DimensionConfig`** — shared fallback fields for all variants; merged in
- **`common {}` block in `DimensionConfig`** - shared fallback fields for all variants; merged in
plugin with variant fields taking precedence.
- **Plain `var` properties on `KonfigExtension`** — `objectPackage`, `objectName`,
- **Plain `var` properties on `KonfigExtension`** - `objectPackage`, `objectName`,
`objectVisibility` are user-facing `var` properties backed by internal `Property<T>`
(`objectPackageProp`, `objectNameProp`, `objectVisibilityProp`) used for lazy task wiring.
@@ -91,7 +91,7 @@ per-type maps down to one per scope and avoids Gradle's `@Nested` managed-type r
2. `konfig.properties` file in the project directory: `konfig.dimension.<name>=<variant>`
3. Task-name detection (variant name substring match, if not disabled by `konfig.android.flavordetection=false`)
4. `defaultTo` fallback declared in DSL
5. **Omitted silently** if none of the above — no crash, dimension object not generated
5. **Omitted silently** if none of the above - no crash, dimension object not generated
`resolveWithSource()` in `KonfigPlugin` returns a tab-separated `"<TAG>\t<variant>\t<reason>"`
string for every dimension. This is stored as a task input (`dimensionResolutionLog`) so the task
@@ -103,7 +103,7 @@ action can emit structured lifecycle/warning/error log messages without re-runni
|---------------------------------------------|---------------------------------------------------------------------------------|
| `-Pkonfig.buildtype=DEBUG\|RELEASE` | Forces build type; falls back to task-name detection then RELEASE |
| `-Pkonfig.dimension.<name>=<variant>` | Selects a dimension variant explicitly |
| `-Pkonfig.force` | Disables UP-TO-DATE checks — task always re-runs (any value or bare flag works) |
| `-Pkonfig.force` | Disables UP-TO-DATE checks - task always re-runs (any value or bare flag works) |
| `-Pkonfig.android.buildtypedetection=false` | Disables task-name build-type detection |
| `-Pkonfig.android.flavordetection=false` | Disables task-name dimension variant detection |
@@ -111,12 +111,12 @@ action can emit structured lifecycle/warning/error log messages without re-runni
| File | Role |
|-------------------------------|----------------------------------------------------------------------------------------------------------------|
| `KonfigPlugin.kt` | Entry point — wires providers, registers task, auto-wires source sets, hooks compile tasks |
| `KonfigExtension.kt` | DSL (`konfig { }`) — top-level `field()`, `debug {}`, `release {}`, and `dimension()` functions |
| `DimensionConfig.kt` | DSL node for a dimension — holds variants, `common {}` block, `objectNameOverride`, `defaultVariant` |
| `VariantConfig.kt` | DSL node for a variant or common block — `field()` returns `FieldHandle<T>`; `debug {}`/`release {}` supported |
| `FieldConfig.kt` | Single typed field — holds default `Provider<T>?` and per-`BuildType` overrides; `resolve()` returns `Provider<T>?` |
| `GenerateKonfigTask.kt` | `@CacheableTask` — validates inputs, logs detection results, writes the `.kt` file |
| `KonfigPlugin.kt` | Entry point - wires providers, registers task, auto-wires source sets, hooks compile tasks |
| `KonfigExtension.kt` | DSL (`konfig { }`) - top-level `field()`, `debug {}`, `release {}`, and `dimension()` functions |
| `DimensionConfig.kt` | DSL node for a dimension - holds variants, `common {}` block, `objectNameOverride`, `defaultVariant` |
| `VariantConfig.kt` | DSL node for a variant or common block - `field()` returns `FieldHandle<T>`; `debug {}`/`release {}` supported |
| `FieldConfig.kt` | Single typed field - holds default `Provider<T>?` and per-`BuildType` overrides; `resolve()` returns `Provider<T>?` |
| `GenerateKonfigTask.kt` | `@CacheableTask` - validates inputs, logs detection results, writes the `.kt` file |
| `BuildType.kt` | `enum` with regex-based task-name detection |
| `Visibility.kt` | `PUBLIC` / `INTERNAL` enum |
| `KonfigDsl.kt` | `@DslMarker` annotation applied to all DSL scope classes |
@@ -132,8 +132,8 @@ action can emit structured lifecycle/warning/error log messages without re-runni
- **Repositories:** `https://maven.bitsycore.com/releases` (primary, no auth) and
`https://maven.pkg.github.com/bitsycore/bitsykonfig-gradle-plugin` (fallback, needs PAT)
- **JVM target:** 17 (set via `sourceCompatibility` + `KotlinCompile.compilerOptions.jvmTarget`,
no toolchain — avoids requiring a specific JDK installation)
- **AGP dependency:** `compileOnly("com.android.tools.build:gradle:8.7.3")` — never leaked to consumers
no toolchain - avoids requiring a specific JDK installation)
- **AGP dependency:** `compileOnly("com.android.tools.build:gradle:8.7.3")` - never leaked to consumers
### Publishing
@@ -144,7 +144,7 @@ Two Maven publications are created automatically by the `kotlin-dsl` + `gradlePl
| `pluginMaven` | `konfig-gradle-plugin` | Implementation jar + sources + POM |
| `konfigPluginMarkerMaven` | `com.bitsycore.konfig.gradle.plugin` | Marker POM that points to the impl |
**Never** use `publications { create<MavenPublication>("pluginMaven") { ... } }` — this replaces
**Never** use `publications { create<MavenPublication>("pluginMaven") { ... } }` - this replaces
the auto-wired publication and breaks the marker. Always configure existing publications via
`publications.withType<MavenPublication>().configureEach { ... }`.
@@ -175,9 +175,9 @@ Fast, no Gradle processes. Cover DSL model classes and type resolution in isolat
| File | What it covers |
|-------------------------------|------------------------------------------------------------------------|
| `BuildTypeExtendedTest.kt` | `BuildType.resolve()` — all regex edge cases, task names, enum values |
| `BuildTypeExtendedTest.kt` | `BuildType.resolve()` - all regex edge cases, task names, enum values |
| `VisibilityTest.kt` | `Visibility` enum entries, ordinals, valueOf |
| `ConstantProviderTest.kt` | `constantProvider()` / `ConstantProvider` — all Provider API methods |
| `ConstantProviderTest.kt` | `constantProvider()` / `ConstantProvider` - all Provider API methods |
| `FieldConfigTest.kt` | `FieldConfig` construction, default resolution, build-type overrides |
| `VariantConfigTest.kt` | `VariantConfig` field declarations, duplicate guard, scope blocks |
| `DimensionConfigTest.kt` | `DimensionConfig` objectName derivation, variants, common block |
@@ -186,7 +186,7 @@ Fast, no Gradle processes. Cover DSL model classes and type resolution in isolat
### Functional tests (`src/functionalTest/`)
Full Gradle TestKit builds — each test spins up a real Gradle project in a temp directory.
Full Gradle TestKit builds - each test spins up a real Gradle project in a temp directory.
`FunctionalTestBase` provides shared helpers (`withProject`, `withFailingProject`, `generatedFile()`, `writeBuildGradle()`).
| File | What it covers |
+17 -17
View File
@@ -1,6 +1,6 @@
# buildkonfig-gradle-plugin
A Gradle plugin that generates a `BuildKonfig` Kotlin object at build time — like Android's `BuildConfig`, but for any Kotlin project (JVM, Multiplatform, Android).
A Gradle plugin that generates a `BuildKonfig` Kotlin object at build time - like Android's `BuildConfig`, but for any Kotlin project (JVM, Multiplatform, Android).
Fields can be constant, overridden per build type (debug/release), or scoped to named **dimensions** (e.g. environment, region) with their own variants.
@@ -33,7 +33,7 @@ pluginManagement {
<details>
<summary>GitHub Packages fallback (authenticated)</summary>
Store credentials in `~/.gradle/gradle.properties` — never commit them:
Store credentials in `~/.gradle/gradle.properties` - never commit them:
```properties
gpr.user=YOUR_GITHUB_USERNAME
@@ -146,16 +146,16 @@ Three equivalent forms:
```kotlin
konfig {
// Fluent handle — default + one or both overrides
// Fluent handle - default + one or both overrides
field("BASE_URL", "https://prod.example.com").debug("https://dev.example.com")
// Scope blocks — build type is fixed, field() returns Unit, no chaining
// Scope blocks - build type is fixed, field() returns Unit, no chaining
debug { field("MOCK_API", true) }
release { field("MOCK_API", false) }
}
```
> `field()` inside `debug {}` / `release {}` blocks intentionally returns `Unit` — the build type is already fixed by the enclosing scope, so `.debug()` / `.release()` chaining is impossible by design.
> `field()` inside `debug {}` / `release {}` blocks intentionally returns `Unit` - the build type is already fixed by the enclosing scope, so `.debug()` / `.release()` chaining is impossible by design.
---
@@ -239,7 +239,7 @@ public object BuildKonfig {
}
```
Root-level name collisions **fail the build** — a flat field may not shadow a
Root-level name collisions **fail the build** - a flat field may not shadow a
built-in constant (`BUILD_TYPE`, `MODULE_NAME`, `IS_DEBUG`), a global field, or
a field from another flat dimension.
@@ -255,7 +255,7 @@ Variants are resolved in priority order:
| 2 | `konfig.properties` file | `konfig.dimension.env=dev` |
| 3 | Task-name detection | Running `assembleDevDebug` matches `dev` |
| 4 | `defaultTo` in DSL | `dimension("env", defaultTo = "prod")` |
| — | Omitted silently | No variant → no nested object generated |
| - | Omitted silently | No variant → no nested object generated |
### `konfig.properties` file
@@ -265,11 +265,11 @@ Place a `konfig.properties` file in your project directory:
konfig.dimension.env=dev
```
This file is tracked as a task input — changing it invalidates the build cache.
This file is tracked as a task input - changing it invalidates the build cache.
### Task-name matching rules
Variant detection respects **camelCase word boundaries** — a variant only
Variant detection respects **camelCase word boundaries** - a variant only
matches a whole segment of the task name, never a plain substring:
- `assemblePreprodRelease` matches variant `preprod`, **not** `prod`
@@ -283,7 +283,7 @@ Genuinely ambiguous matches are skipped with a warning.
### Selection logging
The resolved build type and every dimension decision are printed by the
`konfigInfo` task on **every** build — including fully cached / UP-TO-DATE
`konfigInfo` task on **every** build - including fully cached / UP-TO-DATE
builds with the configuration cache enabled:
```
@@ -301,7 +301,7 @@ Build type is resolved in priority order:
|----------|---------------------|-----------------------------------|
| 1 | Explicit property | `-Pkonfig.buildtype=DEBUG` |
| 2 | Task-name detection | Running `assembleDebug` → `DEBUG` |
| — | Default | `RELEASE` |
| - | Default | `RELEASE` |
---
@@ -311,7 +311,7 @@ Build type is resolved in priority order:
|---------------------------------------------|--------------------------------------------------|
| `-Pkonfig.buildtype=DEBUG\|RELEASE` | Forces build type |
| `-Pkonfig.dimension.<name>=<variant>` | Selects a dimension variant |
| `-Pkonfig.force` | Disables UP-TO-DATE checks — task always re-runs |
| `-Pkonfig.force` | Disables UP-TO-DATE checks - task always re-runs |
| `-Pkonfig.android.buildtypedetection=false` | Disables task-name build-type detection |
| `-Pkonfig.android.flavordetection=false` | Disables task-name dimension-variant detection |
@@ -324,7 +324,7 @@ Forces `generateKonfig` to re-run on every build, bypassing Gradle's UP-TO-DATE
./gradlew assembleRelease -Pkonfig.force
```
The flag is presence-based — any value (or no value) enables it.
The flag is presence-based - any value (or no value) enables it.
---
@@ -344,7 +344,7 @@ println(BuildKonfig.Env.VARIANT) // "dev" or "prod"
## Build-script queries (`konfig.isDebug`, `konfig.getCurrentDimension`)
The same recognition logic that drives generation is queryable from build
scripts — useful for wiring per-build-type dependencies in KMP projects:
scripts - useful for wiring per-build-type dependencies in KMP projects:
```kotlin
konfig {
@@ -371,7 +371,7 @@ val activeEnv: String? = konfig.getCurrentDimension("env") // "prod", or null
## Using Gradle providers as field values
Lazy `Provider<T>` values are supported — useful for reading Gradle properties or environment variables:
Lazy `Provider<T>` values are supported - useful for reading Gradle properties or environment variables:
```kotlin
konfig {
@@ -380,7 +380,7 @@ konfig {
}
```
> Do not call `System.getenv()` or `project.findProperty()` directly inside `field()` — these bypass the Provider API and break configuration cache.
> Do not call `System.getenv()` or `project.findProperty()` directly inside `field()` - these bypass the Provider API and break configuration cache.
---
@@ -427,7 +427,7 @@ The `generateKonfig` task is automatically wired as a dependency of all `compile
# Run unit tests only
./gradlew test
# Run functional tests (Gradle TestKit — starts real Gradle builds)
# Run functional tests (Gradle TestKit - starts real Gradle builds)
./gradlew functionalTest
# Run a specific functional test
+1 -1
View File
@@ -75,7 +75,7 @@ publishing {
// - "pluginMaven" → the real implementation jar (groupId:artifactId:version)
// - "konfigPluginMarkerMaven" → the plugin marker (pluginId:pluginId.gradle.plugin:version)
//
// We must NOT create a third "pluginMaven" manually — that breaks the marker.
// We must NOT create a third "pluginMaven" manually - that breaks the marker.
// Instead we configure the existing ones via withType.
publications.withType<MavenPublication>().configureEach {
// Only decorate the implementation publication, not the marker
@@ -7,7 +7,7 @@ import kotlin.test.assertTrue
/**
* Functional tests for the `common {}` block inside a dimension.
*
* The common block provides fallback fields for all variants — a variant field
* The common block provides fallback fields for all variants - a variant field
* with the same name must take precedence over the common field.
*/
class CommonBlockFunctionalTest : FunctionalTestBase() {
@@ -73,7 +73,7 @@ class DuplicateDetectionFunctionalTest : FunctionalTestBase() {
}
@Test fun `same field name in different variants is allowed`() = withProject { dir, run ->
// Different variants may each define the same field name — that is the whole point
// Different variants may each define the same field name - that is the whole point
dir.writeBuildGradle("""
plugins { id("com.bitsycore.konfig") }
group = "com.example"
@@ -106,7 +106,7 @@ class DuplicateDetectionFunctionalTest : FunctionalTestBase() {
assertTrue(result.output.contains("generateKonfig"))
}
// ── Duplicate dimension name — error message ──────────────────────────────
// ── Duplicate dimension name - error message ──────────────────────────────
@Test fun `duplicate dimension error message contains dimension name`() = withFailingProject { dir, run ->
dir.writeBuildGradle("""
@@ -121,7 +121,7 @@ class DuplicateDetectionFunctionalTest : FunctionalTestBase() {
assertTrue(result.output.contains("my-dim"))
}
// ── Duplicate global field — error message ────────────────────────────────
// ── Duplicate global field - error message ────────────────────────────────
@Test fun `duplicate global field error message contains field name`() = withFailingProject { dir, run ->
dir.writeBuildGradle("""
@@ -6,7 +6,7 @@ import kotlin.test.assertTrue
/**
* Functional tests for every supported field type emitted by the plugin:
* String, Boolean, Int, Long, Float, Double — including correct Kotlin
* String, Boolean, Int, Long, Float, Double - including correct Kotlin
* literal syntax (suffixes, special Float/Double values, escaping).
*/
class FieldTypesFunctionalTest : FunctionalTestBase() {
@@ -5,7 +5,7 @@ import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Functional tests for `flatDimension` — fields generated at the root of the
* Functional tests for `flatDimension` - fields generated at the root of the
* konfig object, with hard failure on root-level name collisions.
*/
class FlatDimensionFunctionalTest : FunctionalTestBase() {
@@ -9,9 +9,9 @@ import kotlin.io.path.createTempDirectory
* Base class for all functional tests.
*
* Provides:
* - [withProject] — creates a temp Gradle project, runs it, then cleans up.
* - [File.generatedFile] — walks the output dir to find the generated `.kt` file.
* - [File.writeBuildGradle] — shorthand for writing a `build.gradle.kts`.
* - [withProject] - creates a temp Gradle project, runs it, then cleans up.
* - [File.generatedFile] - walks the output dir to find the generated `.kt` file.
* - [File.writeBuildGradle] - shorthand for writing a `build.gradle.kts`.
*/
abstract class FunctionalTestBase {
@@ -41,7 +41,7 @@ abstract class FunctionalTestBase {
/**
* Same as [withProject] but Gradle is invoked with `buildAndFail()` so a build
* failure does NOT throw — the returned [BuildResult] carries the failed output.
* failure does NOT throw - the returned [BuildResult] carries the failed output.
*/
protected fun withFailingProject(block: (projectDir: File, run: (List<String>) -> BuildResult) -> Unit) {
val projectDir = createTempDirectory("konfig-ft-fail").toFile()
@@ -6,7 +6,7 @@ import kotlin.test.assertTrue
/**
* Functional tests for top-level (global) field declarations:
* String, Boolean, Int — defaults, debug overrides, release overrides,
* String, Boolean, Int - defaults, debug overrides, release overrides,
* and the debug/release scope block syntax.
*/
class GlobalFieldsFunctionalTest : FunctionalTestBase() {
@@ -258,7 +258,7 @@ abstract class GenerateKonfigTask : DefaultTask() {
}
}
/** `"my-env"` → `"MY_ENV_VARIANT"` — root constant holding the active variant of a flat dimension. */
/** `"my-env"` → `"MY_ENV_VARIANT"` - root constant holding the active variant of a flat dimension. */
private fun String.toVariantConstName(): String =
uppercase().replace(Regex("[^A-Z0-9]"), "_") + "_VARIANT"
@@ -285,7 +285,7 @@ abstract class GenerateKonfigTask : DefaultTask() {
"Long" -> """${indent}const val $name: Long = ${raw}L"""
"Float" -> """${indent}const val $name: Float = ${raw.toFloat().toKotlinFloat()}"""
"Double" -> """${indent}const val $name: Double = ${raw.toDouble().toKotlinDouble()}"""
else -> return@forEach // unknown type — skip
else -> return@forEach // unknown type - skip
}
appendLine(line)
}
@@ -45,7 +45,7 @@ abstract class KonfigExtension @Inject constructor(
get() = objectVisibilityProp.get()
set(value) = objectVisibilityProp.set(value)
/** Output directory — kept as [DirectoryProperty] for full Gradle lazy semantics. */
/** Output directory - kept as [DirectoryProperty] for full Gradle lazy semantics. */
val outputDir: DirectoryProperty = objects.directoryProperty()
// ==============================================================================
@@ -55,7 +55,7 @@ abstract class KonfigExtension @Inject constructor(
@PublishedApi
internal val dimensions: MutableList<DimensionConfig> = mutableListOf()
/** Backing store for global fields — reuses [com.bitsycore.konfig.configs.VariantConfig] for its field/debug/release logic. */
/** Backing store for global fields - reuses [com.bitsycore.konfig.configs.VariantConfig] for its field/debug/release logic. */
@PublishedApi
internal val globalScope: VariantConfig = VariantConfig("\$global")
@@ -117,10 +117,10 @@ abstract class KonfigExtension @Inject constructor(
// MARK: Build-script queries
// ==============================================================================
/** Wired by the plugin at apply time — same resolution chain as the generated object. */
/** Wired by the plugin at apply time - same resolution chain as the generated object. */
internal lateinit var buildTypeProviderInternal: Provider<BuildType>
/** Wired by the plugin at apply time — resolves a dimension with the same recognition logic. */
/** Wired by the plugin at apply time - resolves a dimension with the same recognition logic. */
internal lateinit var dimensionResolverInternal: (String) -> Provider<String>
/** Resolved build type as a lazy provider (`"debug"` / `"release"`). */
@@ -132,7 +132,7 @@ abstract class KonfigExtension @Inject constructor(
get() = buildTypeProviderInternal.map { it == BuildType.DEBUG }
/**
* True when the resolved build type is debug — uses the exact same recognition
* True when the resolved build type is debug - uses the exact same recognition
* as the generated object, so it can drive build-script decisions such as
* KMP `debugImplementation`-style wiring:
*
@@ -14,7 +14,7 @@ import org.gradle.work.DisableCachingByDefault
* [GenerateKonfigTask] only logs when it actually executes, so a cached /
* up-to-date build would silently hide which variant is active. This task is
* untracked (never up-to-date, never cached) and `generateKonfig` depends on
* it, guaranteeing the selection is always visible in the logs — even on a
* it, guaranteeing the selection is always visible in the logs - even on a
* fully cached build with the configuration cache enabled.
*/
@DisableCachingByDefault(because = "pure logging task, must run on every build")
@@ -293,11 +293,11 @@ class KonfigPlugin : Plugin<Project> {
* Resolves a dimension and encodes the result as a tab-separated string for logging.
*
* Format: `"<TAG>\t<variant>\t<reason>"`
* - TAG = `OK` — active, variant resolved successfully
* - TAG = `WARN_UNKNOWN` — property set but value is not a known variant
* - TAG = `WARN_AMBIGUOUS` — multiple variants matched task names
* - TAG = `SKIP` — no active variant could be determined
* - TAG = `ERROR` — configuration error (e.g. invalid defaultTo)
* - TAG = `OK` - active, variant resolved successfully
* - TAG = `WARN_UNKNOWN` - property set but value is not a known variant
* - TAG = `WARN_AMBIGUOUS` - multiple variants matched task names
* - TAG = `SKIP` - no active variant could be determined
* - TAG = `ERROR` - configuration error (e.g. invalid defaultTo)
*/
private fun resolveWithSource(
dim: DimensionConfig,
@@ -11,7 +11,7 @@ import java.util.function.BiFunction
*
* Values are stored as [Provider]<T> so lazy sources (e.g. Gradle property providers)
* are supported alongside plain constants. [org.gradle.api.provider.ProviderFactory]
* is intentionally NOT stored here — it is not configuration-cache serializable and
* is intentionally NOT stored here - it is not configuration-cache serializable and
* must never flow into the object graph captured by task input providers.
*/
class FieldConfig<T : Any> @PublishedApi internal constructor(
@@ -16,7 +16,7 @@ import org.gradle.api.provider.Provider
* field("TIMEOUT", 30).debug(5)
* field("URL", "https://prod.example.com").debug("https://dev.example.com").release("https://prod.example.com")
* ```
* Not available inside `debug {}` / `release {}` blocks — those return [Unit].
* Not available inside `debug {}` / `release {}` blocks - those return [Unit].
*/
class FieldHandle<T : Any> @PublishedApi internal constructor(
@PublishedApi internal val field: FieldConfig<T>
@@ -34,7 +34,7 @@ class FieldHandle<T : Any> @PublishedApi internal constructor(
/**
* Receiver of `debug { ... }` and `release { ... }` blocks inside [VariantConfig].
*
* `field()` here returns [Unit] — no `.debug()`/`.release()` chaining is possible
* `field()` here returns [Unit] - no `.debug()`/`.release()` chaining is possible
* because the build type is already fixed by the enclosing scope.
*/
@KonfigDsl
@@ -128,20 +128,20 @@ class BuildTypeExtendedTest {
}
@Test fun `prereleased does not resolve RELEASE`() {
// "release" is preceded by lowercase 'e' in "prere[lease]" — but "release"
// "release" is preceded by lowercase 'e' in "prere[lease]" - but "release"
// starts after "prere", let's verify actual behaviour via the regex:
// lookbehind (?<![a-z]) fails because char before 'r' of "release" is 'e'
assertNull(BuildType.resolve("prereleased"))
}
@Test fun `released does not resolve RELEASE`() {
// "released" — 'd' after "release" is NOT a lowercase letter... wait,
// "released" - 'd' after "release" is NOT a lowercase letter... wait,
// actually the regex checks (?![a-z]) so 'd' fails the lookahead.
assertNull(BuildType.resolve("released"))
}
@Test fun `debugMode resolves DEBUG`() {
// 'M' after "debug" — uppercase, not [a-z], so lookahead passes
// 'M' after "debug" - uppercase, not [a-z], so lookahead passes
assertEquals(BuildType.DEBUG, BuildType.resolve("debugMode"))
}
@@ -119,7 +119,7 @@ class DimensionConfigTest {
d.variant("prod") { field("URL", "https://prod.example.com") }
d.variant("prod") { field("KEY", "secret") }
val v = d.variants["prod"]!!
// Same VariantConfig instance reused — two fields total
// Same VariantConfig instance reused - two fields total
assertEquals(2, v.fields.size)
}
@@ -115,7 +115,7 @@ class FieldHandleTest {
@Test fun `handle with no debug or release set returns null for scope-only field`() {
val fc = FieldConfig<String>("F", String::class.java, null)
@Suppress("UNUSED_VARIABLE") val handle = FieldHandle(fc)
// No overrides set — resolve returns null
// No overrides set - resolve returns null
assertNull(fc.resolve(BuildType.DEBUG))
assertNull(fc.resolve(BuildType.RELEASE))
}
@@ -33,7 +33,7 @@ class TaskNameMatchingTest {
@Test fun `camelCase variant matches its camelCase segment`() {
assertTrue("assemblePreProdRelease".containsWordCamelCase("preProd"))
// "Prod" is a legitimate camelCase segment inside PreProd — the resolver's
// "Prod" is a legitimate camelCase segment inside PreProd - the resolver's
// longest-match rule (tested functionally) disambiguates this case.
assertTrue("assemblePreProdRelease".containsWordCamelCase("prod"))
}