# gradle-common

Java 21 multi-project build with shared Gradle utilities and experimental
variant-oriented Gradle plugins.

The repository currently publishes to a local Ivy repository only. Maven Central
and Gradle Plugin Portal publication are intentionally not configured yet.

## Modules

- `common` - shared Gradle utilities, JSON helpers, shell execution helpers, and
  small core value/util classes.
- `variants` - Gradle plugins for variant topology, source-set materialization,
  and outgoing artifact slots.

## Requirements

- JDK 21.
- Gradle Wrapper from this repository, currently Gradle 8.10.2.

The produced bytecode targets Java 21.

## License

This project is licensed under the BSD 2-Clause "Simplified" License
(`BSD-2-Clause`). See [LICENSE](LICENSE).

## Local Build

```bash
./gradlew clean check javadoc jar sourcesJar javadocJar --rerun-tasks
```

Configuration cache smoke check:

```bash
./gradlew check --configuration-cache
```

## Local Ivy Publication

The current publication target is:

```text
${user.home}/ivy-repo
```

Publish both modules locally:

```bash
./gradlew :common:publishIvyPublicationToIvyRepository \
          :variants:publishIvyPublicationToIvyRepository
```

or:

```bash
./gradlew publish
```

The publication includes:

- main jar
- sources jar
- javadoc jar
- Ivy descriptor
- Gradle module metadata

## Local Consumption

Current plugin ids are packaged as classic Gradle plugin marker resources inside
the `variants` jar:

- `org.implab.gradle-variants`
- `org.implab.gradle-sources`
- `org.implab.gradle-variants-sources`
- `org.implab.gradle-variants-artifacts`

Until Gradle Plugin Portal marker artifacts are configured, consume the plugin
through `buildscript` classpath:

```groovy
buildscript {
    repositories {
        ivy {
            url "${System.properties['user.home']}/ivy-repo"
        }
        mavenCentral()
    }
    dependencies {
        classpath 'org.implab.gradle:variants:0.1.0'
    }
}

apply plugin: 'org.implab.gradle-variants'
apply plugin: 'org.implab.gradle-variants-sources'
```

The `plugins { id(...) version(...) }` DSL is not part of the current local Ivy
contract.

## Variants DSL

`variants` defines the finalized build topology. It does not create compile
tasks, source directories, or outgoing publications by itself.

```groovy
apply plugin: 'org.implab.gradle-variants'

variants.layers.create('main')
variants.layers.create('test')
variants.roles.create('main')
variants.roles.create('test')

variants.variant('browser') {
    role('main') {
        layers('main')
    }
    role('test') {
        layers('main', 'test')
    }
}

variants.whenFinalized { view ->
    println view.entries.collect {
        "${it.variant().name}:${it.role().name}:${it.layer().name}"
    }.sort()
}
```

The finalized model exposes cheap identity objects: `Variant`, `Role`, `Layer`,
and the normalized relation `(variant, role, layer)`.

## Sources DSL

`sources` creates standalone `GenericSourceSet` objects. This is useful for
fallback workflows that do not need variant topology.

```groovy
apply plugin: 'org.implab.gradle-sources'

sources.create('main') {
    declareOutputs('js')
    registerOutput('js', layout.projectDirectory.file('inputs/main.js'))

    sets.create('ts') {
        srcDir 'src/main/ts'
    }
}
```

`SourcesPlugin` applies layout conventions:

- `sourceSetDir = src/<sourceSet>`
- `outputsDir = build/out/<sourceSet>`

The base `GenericSourceSet` model itself is convention-free.

## Variant Sources DSL

`variantSources` derives compile units from finalized `variants`.

A compile unit is:

```text
(variant, layer)
```

Selectors configure materialized compile-unit source sets:

```groovy
apply plugin: 'org.implab.gradle-variants-sources'

variants.layers.create('main')
variants.roles.create('main')
variants.variant('browser') {
    role('main') {
        layers('main')
    }
}

variantSources {
    layer('main') {
        sourceSet {
            declareOutputs('js')
            registerOutput('js', layout.projectDirectory.file('inputs/browser.js'))
        }
    }

    configureEach {
        println "sourceSet=${sourceSet.name}, variant=${variant.name}, layer=${layer.name}"
    }
}
```

Selector order for future materialization is:

```text
configureEach -> variant -> layer -> unit
```

Late selector registration is controlled by:

```groovy
variantSources {
    lateConfigurationPolicy {
        failOnLateConfiguration()
    }
}
```

Compile-unit source set names are generated by default as:

```text
<variant> + capitalize(<layer>)
```

Name collisions fail by default and may be resolved deterministically:

```groovy
variantSources {
    namingPolicy {
        resolveNameCollision()
    }
}
```

## Variant Artifacts DSL

`variantArtifacts` is an experimental outgoing artifact layer over `variants`
and `variantSources`.

The current model maps:

- `Variant` to a variant-level consumable outgoing configuration.
- `Slot` to a Gradle outgoing artifact variant inside that configuration.
- `primarySlot` to the primary artifact set of the outgoing configuration.

```groovy
apply plugin: 'org.implab.gradle-variants-artifacts'

variants.layers.create('main')
variants.roles.create('main')
variants.variant('browser') {
    role('main') {
        layers('main')
    }
}

variantSources.layer('main') {
    sourceSet {
        declareOutputs('types', 'js')
        registerOutput('types', layout.projectDirectory.file('inputs/index.d.ts'))
        registerOutput('js', layout.projectDirectory.file('inputs/index.js'))
    }
}

variantArtifacts {
    variant('browser') {
        primarySlot('typesPackage') {
            fromVariant {
                output('types')
            }
        }
        slot('js') {
            fromVariant {
                output('js')
            }
        }
    }

    whenOutgoingConfiguration { publication ->
        publication.configuration {
            description = "Outgoing contract for ${publication.variant.name}"
        }
    }

    whenOutgoingSlot { publication ->
        println "slot=${publication.artifactSlot.slot.name}, primary=${publication.primary}"
    }
}
```

Slot bodies have two assembly modes:

- contribution-based assembly with `from(...)`, `fromVariant(...)`,
  `fromRole(...)`, or `fromLayer(...)`; the plugin copies selected inputs into a
  managed directory and publishes that directory;
- task-produced assembly with `producedBy(task) { outputFile }`; the mapped task
  output file or directory is published directly.

These modes are mutually exclusive for one slot.

The artifact API is still considered pre-1.0 and may change.

## Publication Status

Current status:

- local Ivy publication only
- no Maven Central publication metadata
- no signing
- no Gradle Plugin Portal marker artifacts
- BSD-2-Clause license committed

Before external publication, see [RELEASE_CHECKLIST.md](RELEASE_CHECKLIST.md).
