Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/api
diff options
context:
space:
mode:
authorLeitwolf2026-11-12 22:06:58 +0300
committerGitHub2026-11-12 22:06:58 +0300
commit95cdafcf1aa01b5d32171c56e6a94137402643b9 (patch)
treed15985510bec5aa895c1934ce8a1f429887de448 /api
parent01036658064b95c24c1136d2bba9403c81c3337d (diff)
parent9cff1e2b6a37959abc8a8b66946df61850a0aa47 (diff)
downloadtazel-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')
-rw-r--r--api/build.gradle.kts93
-rw-r--r--api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/TazelPluginDsl.kt14
-rw-r--r--api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/BuildStep.kt77
-rw-r--r--api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/Host.kt40
-rw-r--r--api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/PluginExtensions.kt56
-rw-r--r--api/sources/main/kotlin/com/progmasoft/api/tazel/dsl/plugin/ProjectPlugin.kt62
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
+}