Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
java-version: |
17
21

- name: Set Up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Build
run: ./gradlew build
run: ./gradlew build :gradle-plugin:publishToMavenLocal
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.gradle/
.kotlin/
.idea/
*.iml
**/build/
Expand Down
224 changes: 224 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
# Embed Code Gradle Plugin

The `io.spine.embed-code` plugin runs Embed Code without requiring developers
or CI jobs to download an executable manually. It selects the released binary
for the current platform, installs it under the project's `build/` directory,
and exposes separate `checkEmbedding` and `embedCode` tasks.

## Apply and Configure

After the plugin is published, apply its released version:

```kotlin
plugins {
id("io.spine.embed-code") version "<version>"
}
```

Until then, test the plugin directly from this checkout by adding its build to
the consuming project's `settings.gradle.kts`:

```kotlin
pluginManagement {
includeBuild("../embed-code-gradle-plugin")
}
```

The consuming `build.gradle.kts` can then apply `id("io.spine.embed-code")`
without a version while using that included build.

Configure Embed Code directly in `build.gradle.kts`; no `embed-code.yml` file
is required:

```kotlin
embedCode {
codePath.set(layout.projectDirectory.dir("src/main/java"))
docsPath.set(layout.projectDirectory.dir("docs"))
docIncludes.set(listOf("**/*.md", "**/*.html"))
docExcludes.set(listOf("drafts/**", "generated/**"))
separator.set("...")
info.set(false)
stacktrace.set(false)
}
```

`docsPath` is required. Configure either one unnamed `codePath` or one or more
named sources. By default, the plugin downloads the Embed Code release with the
same version as the plugin. The other properties use the same defaults as the
Embed Code command-line application.

| Property | Default | Purpose |
| --- | --- | --- |
| `version` | Plugin version | Selects the executable release. |
| `codePath` | Required without named sources | Sets one unnamed source root. |
| `namedSource(name, directory)` | Required without `codePath` | Adds a `$name/` source root. |
| `docsPath` | Required | Sets the documentation root to scan. |
| `docIncludes` | `**/*.md`, `**/*.html` | Selects documentation files. |
| `docExcludes` | Empty | Skips matching documentation files. |
| `separator` | `...` | Separates joined fragment parts. |
| `info` | `false` | Enables informational logging. |
| `stacktrace` | `false` | Prints stack traces after panics. |
| `downloadBaseUrl` | GitHub Releases | Selects a release mirror or test repository. |

If a matching CLI release has a problem, override only the executable version
while keeping the applied plugin version unchanged:

```kotlin
embedCode {
version.set("1.2.3")
}
```

### Named Source Roots

Use `namedSource` when documentation embeds code from multiple modules:

```kotlin
embedCode {
namedSource(
"company-site",
layout.projectDirectory.dir("company-site"),
)
namedSource(
"jxbrowser",
layout.projectDirectory.dir("browser"),
)
docsPath.set(layout.projectDirectory)
}
```

Embedding instructions select these roots with `$company-site/` and
`$jxbrowser/`. The plugin writes the corresponding Embed Code configuration
into the Gradle task's temporary directory and passes it to the executable;
the project does not need an `embed-code.yml` file.

`codePath` and `namedSource(...)` are mutually exclusive. Multiple independent
documentation targets are not exposed by this Gradle DSL.

## Run

Check that documentation already contains current source snippets:

```bash
./gradlew :checkEmbedding
```

Update documentation in place:

```bash
./gradlew :embedCode
```

Both tasks belong to the `embed code` group. `installEmbedCode` is an ungrouped
internal preparation task, so it is hidden from the normal `tasks` report but
remains visible with `tasks --all`. Gradle runs it automatically before either
execution task and reuses its output until the requested version, platform,
download URL, or build directory changes.

The plugin prefers the `checkEmbedding` and `embedCode` task names. If one is
already occupied, it prepends underscores until it finds an available name, for
example `_checkEmbedding` or `__checkEmbedding`. Existing tasks are unchanged;
use the `tasks` report to see the selected names. The leading `:` in the
commands above selects the root task explicitly; without it, a multi-project
build may also run every subproject task with the same name.

The plugin supports the platforms for which Embed Code currently publishes
release assets:

- Linux AMD64.
- Windows AMD64.
- macOS AMD64 and ARM64.

## Compatibility

The published plugin implementation targets Java 8 bytecode. Compatibility is
tested with Gradle 7.6.3 and the current wrapper version, Gradle 9.6.1. The JVM
used to run Gradle must also satisfy the selected Gradle version's own Java
compatibility requirements.

The plugin build uses Kotlin DSL and Kotlin tests, while its published classes
are Java. Keeping Kotlin 2.x off the consumer plugin classpath allows older
Gradle Kotlin DSL compilers to load the plugin.

The plugin declares support for Gradle's configuration cache. Functional tests
run plugin tasks with `--configuration-cache` and verify cache reuse.

## Develop

Run compilation, plugin validation, unit tests, and TestKit functional tests:

```bash
./gradlew check
```

The functional tests create local fake release assets. They do not download or
execute a real GitHub release.

Publish the current plugin version to the local Maven repository when testing
it from another checkout:

```bash
./gradlew :gradle-plugin:publishToMavenLocal
```

Then make the local repository available to plugin resolution in the consuming
project's `settings.gradle.kts`:

```kotlin
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}
```

The `mavenLocal()` declaration must be in `pluginManagement.repositories`.
Adding it only to the consuming project's regular `repositories` block does not
make locally published Gradle plugin markers available to the `plugins` block.
The consuming build can then apply the locally published version normally:

```kotlin
plugins {
id("io.spine.embed-code") version "<version>"
}
```

The plugin publication version and its default Embed Code executable version
are both read from `version.gradle.kts`.

## Publish

The plugin is configured for the [Gradle Plugin Portal][plugin-portal]. Before
publishing, verify that the matching `v<version>` GitHub release contains all
platform executables. The plugin uses its own version as the default executable
version, so publishing it before the binaries would leave new installations
without a downloadable asset.

Request validation from the Plugin Portal without publishing a version:

```bash
./gradlew :gradle-plugin:publishPlugins --validate-only
```

The Portal task requires API credentials even in validation-only mode. Provide
them through `GRADLE_PUBLISH_KEY` and `GRADLE_PUBLISH_SECRET`. The regular CI
build uses `publishToMavenLocal` instead, which assembles the plugin marker,
implementation publication, POM metadata, sources, and Javadocs without
contacting the Portal.

To publish after validation, run:

```bash
./gradlew :gradle-plugin:publishPlugins
```

The first publication of `io.spine.embed-code` requires manual Portal approval.
The publishing account must be able to establish ownership of the `io.spine`
namespace; this external approval cannot be validated by the local build.

## License

The plugin is available under the [Apache License 2.0](LICENSE).

[plugin-portal]: https://plugins.gradle.org/docs/publish-plugin
12 changes: 12 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,20 @@ plugins {
*/
val kotlinVersion = "2.4.0"

/**
* Version of the Gradle Plugin Publish plugin.
*
* `buildSrc` needs this version before its dependency objects are compiled.
* Keep in sync with `io.spine.embedcode.gradle.dependency.PluginPublish.version`.
*/
val pluginPublishVersion = "2.1.1"

dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
implementation(
"com.gradle.plugin-publish:com.gradle.plugin-publish.gradle.plugin:" +
pluginPublishVersion,
)
}

kotlin {
Expand Down
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/jvm-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ kotlin {

tasks.named<JavaCompile>("compileJava") {
options.release.set(BuildSettings.productionBytecodeVersion)
// Java 8 bytecode is intentional for the documented Gradle 7.6.3 floor.
options.compilerArgs.add("-Xlint:-options")
}

tasks.named<KotlinJvmCompile>("compileTestKotlin") {
Expand Down
106 changes: 106 additions & 0 deletions gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,112 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import io.spine.embedcode.gradle.dependency.PluginPublish
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.external.javadoc.StandardJavadocDocletOptions
import org.gradle.plugin.compatibility.compatibility

plugins {
id("jvm-module")
`java-gradle-plugin`
`maven-publish`
}

apply(plugin = PluginPublish.id)

base {
archivesName.set("embed-code-gradle-plugin")
}

java {
withJavadocJar()
withSourcesJar()
}

tasks.withType<Jar>().configureEach {
from(rootProject.layout.projectDirectory.file("LICENSE")) {
into("META-INF")
}
}

// Getter docs use concise "Returns..." prose instead of duplicate `@return` tags.
tasks.withType<Javadoc>().configureEach {
(options as StandardJavadocDocletOptions).addBooleanOption("Xdoclint:-missing", true)
}

tasks.test {
inputs.property(
"embedCodeGradle7JavaHome",
providers.environmentVariable("EMBED_CODE_GRADLE_7_JAVA_HOME")
.orElse(providers.environmentVariable("JAVA_HOME_17_X64"))
.orElse(""),
)
}

tasks.processResources {
val versionProperties = mapOf("embedCodeVersion" to project.version.toString())
inputs.properties(versionProperties)
filesMatching("**/version.properties") {
expand(versionProperties)
}
}

gradlePlugin {
website.set("https://github.com/SpineEventEngine/embed-code-gradle-plugin")
vcsUrl.set("https://github.com/SpineEventEngine/embed-code-gradle-plugin")
plugins {
create("embedCode") {
id = "io.spine.embed-code"
implementationClass = "io.spine.embedcode.gradle.EmbedCodePlugin"
displayName = "Embed Code Gradle Plugin"
description =
"Runs Embed Code from Gradle without a separately installed executable."
tags.set(listOf("documentation", "code-samples"))
compatibility {
features {
configurationCache = true
}
}
}
}
}

publishing {
publications.withType<MavenPublication>().configureEach {
if (name == "pluginMaven") {
artifactId = "embed-code-gradle-plugin"
}
pom {
name.set("Embed Code Gradle Plugin")
description.set(
"Runs Embed Code from Gradle without a separately installed executable.",
)
url.set("https://github.com/SpineEventEngine/embed-code-gradle-plugin")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
developers {
developer {
id.set("SpineEventEngine")
name.set("Spine Event Engine")
url.set("https://github.com/SpineEventEngine")
}
}
scm {
url.set("https://github.com/SpineEventEngine/embed-code-gradle-plugin")
connection.set(
"scm:git:https://github.com/SpineEventEngine/" +
"embed-code-gradle-plugin.git",
)
developerConnection.set(
"scm:git:ssh://git@github.com/SpineEventEngine/" +
"embed-code-gradle-plugin.git",
)
}
}
}
}
Loading