diff options
| author | Leitwolf | 2026-11-12 22:06:58 +0300 |
|---|---|---|
| committer | GitHub | 2026-11-12 22:06:58 +0300 |
| commit | 95cdafcf1aa01b5d32171c56e6a94137402643b9 (patch) | |
| tree | d15985510bec5aa895c1934ce8a1f429887de448 /api | |
| parent | 01036658064b95c24c1136d2bba9403c81c3337d (diff) | |
| parent | 9cff1e2b6a37959abc8a8b66946df61850a0aa47 (diff) | |
| download | tazel-95cdafcf1aa01b5d32171c56e6a94137402643b9.tar.gz | |
Merge pull request #4 from Progmasoft/feature/plugin-api-and-gradle-plugin
Bring the plugin API and the Gradle plugin from the legacy Tazel
Diffstat (limited to 'api')
6 files changed, 342 insertions, 0 deletions
diff --git a/api/build.gradle.kts b/api/build.gradle.kts new file mode 100644 index 0000000..135708e --- /dev/null +++ b/api/build.gradle.kts @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +// The API that plugins of the Tazel project file compile against: com.progmasoft.api.tazel and its +// packages, published as com.progmasoft.tazel:tazel-api to https://maven.progmasoft.com/tazel. +// A plugin does not bundle it; Tazel provides it at run time. +plugins { + kotlin("jvm") + `java-library` + `maven-publish` +} + +group = "com.progmasoft.tazel" +version = rootProject.version + +repositories { + mavenCentral() +} + +kotlin { + jvmToolchain(25) + compilerOptions { + jvmTarget = JvmTarget.JVM_25 + allWarningsAsErrors = true + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 + withSourcesJar() +} + +sourceSets { + main { kotlin.srcDir("sources/main/kotlin") } +} + +tasks.withType<AbstractArchiveTask>().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.jar { + manifest { + attributes("Implementation-Title" to "Tazel API", "Implementation-Version" to project.version) + } +} + +publishing { + publications { + create<MavenPublication>("api") { + artifactId = "tazel-api" + from(components["java"]) + pom { + name = "Tazel API" + description = "The API that plugins of the Tazel project file compile against." + url = "https://tazel.net/" + licenses { + license { + name = "MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0" + url = "https://github.com/Progmasoft/tazel/blob/main/LICENSE.txt" + } + } + developers { + developer { + name = "Progmasoft" + email = "support@progmasoft.com" + } + } + scm { + url = "https://github.com/Progmasoft/tazel" + connection = "scm:git:https://github.com/Progmasoft/tazel.git" + } + } + } + } + repositories { + // A local repository, which is copied to https://maven.progmasoft.com/tazel. + maven { + name = "staging" + url = + rootProject.layout.buildDirectory + .dir("maven") + .get() + .asFile + .toURI() + } + } +} diff --git a/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/TazelPluginDsl.kt b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/TazelPluginDsl.kt new file mode 100644 index 0000000..0babe77 --- /dev/null +++ b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/TazelPluginDsl.kt @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.api.tazel.dsl + +/** + * Marks the scopes of a plugin DSL, so that a nested block of the project file cannot reach the + * members of the block around it by mistake: inside `cc { c { } }`, `c` sees only its own members. + */ +@DslMarker +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.TYPE) +annotation class TazelPluginDsl diff --git a/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/BuildStep.kt b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/BuildStep.kt new file mode 100644 index 0000000..d295f94 --- /dev/null +++ b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/BuildStep.kt @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.api.tazel.dsl.plugin + +import java.nio.file.Path + +/** Work a plugin adds to the build of a project; see [ExtensionScope.buildStep]. */ +fun interface BuildStep { + fun run(context: BuildContext) +} + +/** What a [BuildStep] runs with. */ +interface BuildContext { + val projectRoot: Path + + /** The project the step builds. */ + val project: ProjectSummary + + /** The absolute directory the build mode of the project writes artifacts to. */ + val outputDirectory: Path + + val buildMode: BuildMode + val host: Host + + /** + * A directory of this step for intermediate files, created before the step runs: the same one in + * every build, below `.tazel/build` of the project. + */ + val workDirectory: Path + + /** Prints a status line, such as `Configuring cmake (Release)`. */ + fun status( + verb: String, + message: String, + ) + + fun warn(message: String) + + /** + * Runs a program with the variables of [environment] added to those of Tazel and returns its exit + * code. What it prints goes to the output of Tazel. + */ + fun run( + command: List<String>, + directory: Path = projectRoot, + environment: Map<String, String> = emptyMap(), + ): Int + + /** + * Runs a program like [run], but gathers what it prints, output and errors in the order written, + * instead of showing it: for a probe whose failure is an answer, or a program whose output the + * step reads. + */ + fun capture( + command: List<String>, + directory: Path = projectRoot, + environment: Map<String, String> = emptyMap(), + ): CapturedRun + + /** The program [name] found on `PATH`, with the executable extension of the host, or `null`. */ + fun findProgram(name: String): Path? + + /** Stops the build with [message]. */ + fun fail(message: String): Nothing +} + +/** The exit code of a program [BuildContext.capture] ran, and what it printed. */ +data class CapturedRun( + val exitCode: Int, + val output: String, +) + +/** Thrown by [BuildContext.fail]; Tazel reports its message and stops the build. */ +class BuildStepException(message: String) : RuntimeException(message) diff --git a/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/Host.kt b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/Host.kt new file mode 100644 index 0000000..1e138a8 --- /dev/null +++ b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/Host.kt @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.api.tazel.dsl.plugin + +enum class OperatingSystem { + LINUX, + MACOS, + WINDOWS, + FREEBSD, + OPENBSD, + NETBSD, + UNKNOWN, +} + +enum class Architecture { + X86_64, + AARCH64, + ARMV7H, + RISCV64, + UNKNOWN, +} + +/** The machine Tazel runs on. */ +data class Host( + val os: OperatingSystem, + val architecture: Architecture, +) { + /** The extension the host gives an executable file: `.exe` on Windows, none elsewhere. */ + val executableExtension: String + get() = if (os == OperatingSystem.WINDOWS) ".exe" else "" +} + +/** The build mode of the project. */ +enum class BuildMode { + DEBUG, + RELEASE, +} diff --git a/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/PluginExtensions.kt b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/PluginExtensions.kt new file mode 100644 index 0000000..f8ae8d1 --- /dev/null +++ b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/PluginExtensions.kt @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.api.tazel.dsl.plugin + +import java.nio.file.Path +import java.util.ServiceLoader + +/** + * The extensions of the project whose file is running. + * + * The DSL function of a plugin calls [require] with an extension its plugin registered and adds the + * work of its block through the [ExtensionScope] it receives: + * ``` + * fun cmake(block: CMakeScope.() -> Unit) { + * val settings = CMakeScope().apply(block) + * PluginExtensions.require("cmake").buildStep("configure") { context -> ... } + * } + * ``` + */ +object PluginExtensions { + private val host: ExtensionHost by lazy { + ServiceLoader.load(ExtensionHost::class.java, ExtensionHost::class.java.classLoader) + .firstOrNull() + ?: throw IllegalStateException("plugin extensions are available only inside Tazel") + } + + /** + * The scope of [extension] in the project whose file is running. It fails when no plugin of the + * project registered [extension], or when the project file did not declare that plugin. + */ + fun require(extension: String): ExtensionScope = host.require(extension) +} + +/** What the block of an extension can add to its project. */ +interface ExtensionScope { + val projectRoot: Path + val host: Host + + /** + * Adds [step] to the build of the project. `tazel build` and `tazel run` run the steps of a + * project in the order they were added, before its targets compile; a failed step stops the + * build. [name] names the step in the output and is one identifier, unique within the plugin. + */ + fun buildStep( + name: String, + step: BuildStep, + ) +} + +/** Provided by Tazel, which [PluginExtensions] finds as a service. Plugins do not implement it. */ +interface ExtensionHost { + fun require(extension: String): ExtensionScope +} diff --git a/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/ProjectPlugin.kt b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/ProjectPlugin.kt new file mode 100644 index 0000000..9c50fe9 --- /dev/null +++ b/api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/ProjectPlugin.kt @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> + * SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-2.0 + */ + +package com.progmasoft.api.tazel.dsl.plugin + +import java.nio.file.Path + +/** The version of this API; a plugin built for another one is not loaded. */ +const val PROJECT_PLUGIN_API_VERSION = 1 + +/** + * A plugin of the Tazel project file. + * + * Who the plugin is, its group, name and version, is in the descriptor of its JAR, + * `META-INF/tazel-plugin.properties`, which the Gradle plugin writes from `tazelPlugin { }` + * together with the API version, the imports it adds and what Tazel Portal shows about it. The + * service entry of the JAR, `META-INF/services/com.progmasoft.api.tazel.dsl.plugin.ProjectPlugin`, + * names the class, which needs a public constructor without parameters. Tazel creates it for every + * module that declares the plugin and calls [apply] before the project files run. The project files + * then use the DSL the plugin defines; see [PluginExtensions]. + * + * A plugin runs with the same JVM permissions as Tazel. The integrity and the compatibility of its + * JAR are checked before it is activated; its code is not sandboxed. + */ +fun interface ProjectPlugin { + fun apply(context: ProjectPluginContext) +} + +/** What a plugin can do while it is activated for one project. */ +interface ProjectPluginContext { + val projectRoot: Path + + /** The machine Tazel runs on. */ + val host: Host + + /** + * Claims [name] as an extension of this plugin: a block of the project file that the DSL of the + * plugin defines, such as `cmake`. Two plugins of one project cannot claim the same name. + */ + fun registerExtension(name: String) + + /** Records [value] under [key] in the plan of the project; each key is written once. */ + fun contribute( + key: String, + value: String, + ) + + /** Runs [action] once the project file has run and the plan of the project is finished. */ + fun onFinalize(action: (ProjectSummary) -> Unit) +} + +/** The project as a plugin sees it once its project file has run. */ +interface ProjectSummary { + val name: String? + val version: String? + val buildMode: BuildMode + + /** The directory the build mode writes artifacts to, relative to the project root. */ + val outputDirectory: String +} |