commit cdf1ddf3ca4dd716fc58b46cfe3efb769e96bc6b
parent 1d031c8c9e5116d459f4b93c4ac925471ff69934
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 20:28:54 +0000
build: add the explicit HarvestCircle build logic
- add independent contracts and convention-plugin included-build projects
- declare five inert plugin IDs for incremental migration
- prove every plugin applies in isolated TestKit fixtures
- expose a standalone build-logic verification target
Diffstat:
9 files changed, 194 insertions(+), 2 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,10 +5,10 @@ CARGO ?= cargo
CARGO_MANIFEST := core/Cargo.toml
EXTBUILD ?= $(if $(shell cargo extbuild --version 2>/dev/null),cargo extbuild run --)
-.PHONY: help doctor lock metadata format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
+.PHONY: help doctor lock metadata build-logic-check format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
help:
- @printf '%s\n' doctor lock metadata format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
+ @printf '%s\n' doctor lock metadata build-logic-check format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
doctor:
$(if $(strip $(EXTBUILD)),cargo extbuild doctor,@:)
@@ -22,6 +22,9 @@ lock: doctor
metadata: doctor
$(EXTBUILD) $(CARGO) metadata --manifest-path $(CARGO_MANIFEST) --locked --format-version 1 --no-deps
+build-logic-check: doctor
+ $(EXTBUILD) $(GRADLE) --no-daemon -p build-logic :contracts:check :plugins:check :plugins:functionalTest
+
format: doctor
$(EXTBUILD) $(CARGO) fmt --manifest-path $(CARGO_MANIFEST) --all -- --check
$(EXTBUILD) $(GRADLE) --no-daemon :app:shared:ktlintCheck :app:desktop:ktlintCheck
diff --git a/build-logic/contracts/build.gradle.kts b/build-logic/contracts/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ alias(libs.plugins.kotlin.jvm)
+}
+
+kotlin {
+ jvmToolchain(21)
+}
+
+dependencies {
+ testImplementation(kotlin("test"))
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
diff --git a/build-logic/contracts/src/main/kotlin/org/harvestcircle/buildlogic/contracts/BuildLogicContract.kt b/build-logic/contracts/src/main/kotlin/org/harvestcircle/buildlogic/contracts/BuildLogicContract.kt
@@ -0,0 +1,4 @@
+package org.harvestcircle.buildlogic.contracts
+
+/** Marker for the Gradle-independent HarvestCircle build-contract module. */
+public object BuildLogicContract
diff --git a/build-logic/contracts/src/test/kotlin/org/harvestcircle/buildlogic/contracts/BuildLogicContractTest.kt b/build-logic/contracts/src/test/kotlin/org/harvestcircle/buildlogic/contracts/BuildLogicContractTest.kt
@@ -0,0 +1,11 @@
+package org.harvestcircle.buildlogic.contracts
+
+import kotlin.test.Test
+import kotlin.test.assertNotNull
+
+class BuildLogicContractTest {
+ @Test
+ fun contractModuleLoadsWithoutGradleApi() {
+ assertNotNull(BuildLogicContract)
+ }
+}
diff --git a/build-logic/plugins/build.gradle.kts b/build-logic/plugins/build.gradle.kts
@@ -0,0 +1,71 @@
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.api.tasks.testing.Test
+
+plugins {
+ `java-gradle-plugin`
+ alias(libs.plugins.kotlin.jvm)
+}
+
+kotlin {
+ jvmToolchain(21)
+}
+
+dependencies {
+ implementation(project(":contracts"))
+ testImplementation(kotlin("test"))
+}
+
+val sourceSets = the<SourceSetContainer>()
+val functionalTestSourceSet = sourceSets.create("functionalTest")
+
+configurations[functionalTestSourceSet.implementationConfigurationName]
+ .extendsFrom(configurations.testImplementation.get())
+configurations[functionalTestSourceSet.runtimeOnlyConfigurationName]
+ .extendsFrom(configurations.testRuntimeOnly.get())
+
+dependencies {
+ add(functionalTestSourceSet.implementationConfigurationName, gradleTestKit())
+ add(functionalTestSourceSet.implementationConfigurationName, kotlin("test"))
+}
+
+gradlePlugin {
+ plugins {
+ create("harvestCircleRoot") {
+ id = "org.harvestcircle.build.root"
+ implementationClass = "org.harvestcircle.buildlogic.plugins.HarvestCircleRootPlugin"
+ }
+ create("harvestCircleKmpShared") {
+ id = "org.harvestcircle.build.kmp-shared"
+ implementationClass = "org.harvestcircle.buildlogic.plugins.HarvestCircleKmpSharedPlugin"
+ }
+ create("harvestCircleDesktopApp") {
+ id = "org.harvestcircle.build.desktop-app"
+ implementationClass = "org.harvestcircle.buildlogic.plugins.HarvestCircleDesktopAppPlugin"
+ }
+ create("harvestCircleRustFfi") {
+ id = "org.harvestcircle.build.rust-ffi"
+ implementationClass = "org.harvestcircle.buildlogic.plugins.HarvestCircleRustFfiPlugin"
+ }
+ create("harvestCirclePackaging") {
+ id = "org.harvestcircle.build.packaging"
+ implementationClass = "org.harvestcircle.buildlogic.plugins.HarvestCirclePackagingPlugin"
+ }
+ }
+ testSourceSets(functionalTestSourceSet)
+}
+
+tasks.withType<Test>().configureEach {
+ useJUnitPlatform()
+}
+
+val functionalTest by tasks.registering(Test::class) {
+ description = "Runs convention-plugin tests against isolated Gradle builds."
+ group = "verification"
+ testClassesDirs = functionalTestSourceSet.output.classesDirs
+ classpath = functionalTestSourceSet.runtimeClasspath
+ shouldRunAfter(tasks.test)
+}
+
+tasks.check {
+ dependsOn(functionalTest)
+}
diff --git a/build-logic/plugins/src/functionalTest/kotlin/org/harvestcircle/buildlogic/plugins/ConventionPluginSmokeTest.kt b/build-logic/plugins/src/functionalTest/kotlin/org/harvestcircle/buildlogic/plugins/ConventionPluginSmokeTest.kt
@@ -0,0 +1,38 @@
+package org.harvestcircle.buildlogic.plugins
+
+import org.gradle.testkit.runner.GradleRunner
+import kotlin.io.path.createTempDirectory
+import kotlin.io.path.createDirectories
+import kotlin.io.path.writeText
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class ConventionPluginSmokeTest {
+ @Test
+ fun everyConventionPluginAppliesToAnIsolatedBuild() {
+ val temporaryDirectory = createTempDirectory("harvestcircle-build-logic-")
+ val pluginIds =
+ listOf(
+ "org.harvestcircle.build.root",
+ "org.harvestcircle.build.kmp-shared",
+ "org.harvestcircle.build.desktop-app",
+ "org.harvestcircle.build.rust-ffi",
+ "org.harvestcircle.build.packaging",
+ )
+
+ pluginIds.forEach { pluginId ->
+ val fixture = temporaryDirectory.resolve(pluginId.substringAfterLast('.')).createDirectories()
+ fixture.resolve("settings.gradle.kts").writeText("rootProject.name = \"fixture\"\n")
+ fixture.resolve("build.gradle.kts").writeText("plugins { id(\"$pluginId\") }\n")
+
+ val result =
+ GradleRunner.create()
+ .withProjectDir(fixture.toFile())
+ .withPluginClasspath()
+ .withArguments("tasks", "--stacktrace")
+ .build()
+
+ assertTrue(result.output.contains("BUILD SUCCESSFUL"), pluginId)
+ }
+ }
+}
diff --git a/build-logic/plugins/src/main/kotlin/org/harvestcircle/buildlogic/plugins/HarvestCircleConventionPlugins.kt b/build-logic/plugins/src/main/kotlin/org/harvestcircle/buildlogic/plugins/HarvestCircleConventionPlugins.kt
@@ -0,0 +1,24 @@
+package org.harvestcircle.buildlogic.plugins
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+public class HarvestCircleRootPlugin : Plugin<Project> {
+ override fun apply(target: Project) = Unit
+}
+
+public class HarvestCircleKmpSharedPlugin : Plugin<Project> {
+ override fun apply(target: Project) = Unit
+}
+
+public class HarvestCircleDesktopAppPlugin : Plugin<Project> {
+ override fun apply(target: Project) = Unit
+}
+
+public class HarvestCircleRustFfiPlugin : Plugin<Project> {
+ override fun apply(target: Project) = Unit
+}
+
+public class HarvestCirclePackagingPlugin : Plugin<Project> {
+ override fun apply(target: Project) = Unit
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
@@ -0,0 +1,24 @@
+import org.gradle.api.initialization.resolve.RepositoriesMode
+
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ mavenCentral()
+ }
+}
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ mavenCentral()
+ }
+ versionCatalogs {
+ create("libs") {
+ from(files("../gradle/libs.versions.toml"))
+ }
+ }
+}
+
+rootProject.name = "harvestcircle-build-logic"
+
+include(":contracts", ":plugins")
diff --git a/settings.gradle.kts b/settings.gradle.kts
@@ -1,6 +1,8 @@
import org.gradle.api.initialization.resolve.RepositoriesMode
pluginManagement {
+ includeBuild("build-logic")
+
repositories {
google {
mavenContent {