This is an automated email from the ASF dual-hosted git repository.
merlimat pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push:
new bf67fbf62d2 [improve][build] Generate PulsarVersion build info as
resource (#25841)
bf67fbf62d2 is described below
commit bf67fbf62d2643b9256e951026ffcfb9ef56e06e
Author: Lari Hotari <[email protected]>
AuthorDate: Wed May 20 19:08:42 2026 +0300
[improve][build] Generate PulsarVersion build info as resource (#25841)
---
gradle.properties | 13 ++++
pulsar-common/build.gradle.kts | 89 +++++++++++++---------
.../org/apache/pulsar/PulsarVersion.java | 70 +++++++++++------
.../main/java/org/apache/pulsar/package-info.java | 23 ++++++
4 files changed, 138 insertions(+), 57 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 1c3095bfbd5..d370ce90209 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -28,3 +28,16 @@ org.gradle.jvmargs=-Xmx4g -Xss2m -XX:+UseG1GC
-XX:+HeapDumpOnOutOfMemoryError
# repository.apache.org does not support SHA-256/SHA-512 checksums yet
systemProp.org.gradle.internal.publish.checksums.insecure=true
+
+# Controls whether pulsar-common generates a pulsar-version.properties
resource with
+# git commit/branch/dirty/build host/build time metadata exposed via
PulsarVersion.
+# Default here is `false` so neither local development nor CI builds
regenerate the
+# resource (and re-run processResources/jar/shadowJar) every time the working
tree
+# changes; PulsarVersion then returns "unknown" placeholders at runtime.
Keeping the
+# default `false` in CI is important because changing the metadata would
invalidate
+# the build cache entries that downstream jobs reuse.
+#
+# Release builds must include the real metadata — pass
`-Ppulsar.includeBuildInfo=true`
+# on the gradle command line, or set `pulsar.includeBuildInfo=true` in
+# `~/.gradle/gradle.properties` before running the release build.
+pulsar.includeBuildInfo=false
diff --git a/pulsar-common/build.gradle.kts b/pulsar-common/build.gradle.kts
index 590eb2303cc..ef5d3cb8c1a 100644
--- a/pulsar-common/build.gradle.kts
+++ b/pulsar-common/build.gradle.kts
@@ -28,18 +28,35 @@ plugins {
alias(libs.plugins.lightproto)
}
-val generatePulsarVersion by tasks.registering {
- val templateFile =
file("src/main/java-templates/org/apache/pulsar/PulsarVersion.java")
- val outputDir =
layout.buildDirectory.dir("generated-sources/java-templates")
+// Generates pulsar-version.properties as a *resource* (not a Java source) so
that changes to
+// git metadata only invalidate this module's processResources / jar tasks and
do NOT trigger
+// a recompile of pulsar-common or any downstream module's compileJava.
+//
+// Set `pulsar.includeBuildInfo=false` (e.g. in `~/.gradle/gradle.properties`)
to skip generation
+// entirely during development. PulsarVersion then returns placeholder values
at runtime.
+val includeBuildInfo = providers.gradleProperty("pulsar.includeBuildInfo")
+ .map { it.toBoolean() }
+ .orElse(true)
+
+val generatePulsarBuildInfo by tasks.registering {
+ description = "Generates pulsar-version.properties with version and
(optionally) git/build metadata."
+ val outputFile =
layout.buildDirectory.file("generated-resources/buildinfo/org/apache/pulsar/pulsar-version.properties")
val projectVersion = project.version.toString()
+ val includeBuildInfoValue = includeBuildInfo
- // Resolve all providers at configuration time for configuration cache
compatibility
- val gitCommitId = providers.exec { commandLine("git", "rev-parse", "HEAD")
}
- .standardOutput.asText.map { it.trim() }
- val gitDirty = providers.exec { commandLine("git", "status",
"--porcelain") }
- .standardOutput.asText.map { it.isNotBlank().toString() }
- val gitBranch = providers.exec { commandLine("git", "rev-parse",
"--abbrev-ref", "HEAD") }
- .standardOutput.asText.map { it.trim() }
+ // Lazy providers — evaluated at execution time only (no impact on
configuration cache).
+ val gitCommitId = providers.exec {
+ commandLine("git", "rev-parse", "HEAD")
+ isIgnoreExitValue = true
+ }.standardOutput.asText.map { it.trim() }
+ val gitDirty = providers.exec {
+ commandLine("git", "status", "--porcelain")
+ isIgnoreExitValue = true
+ }.standardOutput.asText.map { it.isNotBlank().toString() }
+ val gitBranch = providers.exec {
+ commandLine("git", "rev-parse", "--abbrev-ref", "HEAD")
+ isIgnoreExitValue = true
+ }.standardOutput.asText.map { it.trim() }
val gitUserEmail = providers.exec {
commandLine("git", "config", "user.email")
isIgnoreExitValue = true
@@ -48,40 +65,42 @@ val generatePulsarVersion by tasks.registering {
commandLine("git", "config", "user.name")
isIgnoreExitValue = true
}.standardOutput.asText.map { it.trim() }
- val buildHost = provider<String> { InetAddress.getLocalHost().hostName }
- val buildTime = provider<String> {
-
ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
- }
- inputs.file(templateFile)
inputs.property("version", projectVersion)
- // TODO: Replace git/build metadata with a Gradle configuration cache and
build cache
- // compatible solution. Currently gitCommitId and gitDirty are not
declared as inputs
- // because they cause cascading rebuilds of pulsar-common and all its
dependents.
- // gitCommitId changes on every commit, and gitDirty changes whenever any
file is
- // modified (via `git status --porcelain`). The values are still captured
in the output
- // when the task runs for other reasons (same as buildTime/buildHost).
- outputs.dir(outputDir)
+ inputs.property("includeBuildInfo", includeBuildInfoValue)
+ outputs.file(outputFile)
doLast {
- val template = templateFile.readText()
- val generated = template
- .replace("\${project.version}", projectVersion)
- .replace("\${git.commit.id}", gitCommitId.getOrElse(""))
- .replace("\${git.dirty}", gitDirty.getOrElse("true"))
- .replace("\${git.branch}", gitBranch.getOrElse(""))
- .replace("\${git.build.user.email}", gitUserEmail.getOrElse(""))
- .replace("\${git.build.user.name}", gitUserName.getOrElse(""))
- .replace("\${git.build.host}", buildHost.get())
- .replace("\${git.build.time}", buildTime.get())
+ val entries = linkedMapOf<String, String>()
+ entries["version"] = projectVersion
+ if (includeBuildInfoValue.get()) {
+ entries["git.commit.id"] = gitCommitId.getOrElse("")
+ entries["git.dirty"] = gitDirty.getOrElse("true")
+ entries["git.branch"] = gitBranch.getOrElse("")
+ entries["git.build.user.email"] = gitUserEmail.getOrElse("")
+ entries["git.build.user.name"] = gitUserName.getOrElse("")
+ entries["git.build.host"] = InetAddress.getLocalHost().hostName
+ entries["git.build.time"] =
+
ZonedDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
+ }
- val outFile =
outputDir.get().file("org/apache/pulsar/PulsarVersion.java").asFile
+ val outFile = outputFile.get().asFile
outFile.parentFile.mkdirs()
- outFile.writeText(generated)
+ // Hand-rolled .properties writer so we don't get the
non-deterministic timestamp comment
+ // that java.util.Properties.store always emits. Values from
git/InetAddress are ASCII
+ // identifiers, so backslash escaping is sufficient.
+ outFile.writeText(buildString {
+ append("# Pulsar build info\n")
+ entries.forEach { (key, value) ->
+ append(key).append('=').append(value.replace("\\",
"\\\\")).append('\n')
+ }
+ })
}
}
-sourceSets["main"].java.srcDir(generatePulsarVersion.map {
layout.buildDirectory.dir("generated-sources/java-templates").get() })
+sourceSets["main"].resources.srcDir(generatePulsarBuildInfo.map {
+ layout.buildDirectory.dir("generated-resources/buildinfo").get()
+})
dependencies {
diff --git
a/pulsar-common/src/main/java-templates/org/apache/pulsar/PulsarVersion.java
b/pulsar-common/src/main/java/org/apache/pulsar/PulsarVersion.java
similarity index 57%
rename from
pulsar-common/src/main/java-templates/org/apache/pulsar/PulsarVersion.java
rename to pulsar-common/src/main/java/org/apache/pulsar/PulsarVersion.java
index 119e46b9536..ea144cdf359 100644
--- a/pulsar-common/src/main/java-templates/org/apache/pulsar/PulsarVersion.java
+++ b/pulsar-common/src/main/java/org/apache/pulsar/PulsarVersion.java
@@ -18,27 +18,54 @@
*/
package org.apache.pulsar;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+/**
+ * Exposes the Pulsar build/version metadata.
+ *
+ * <p>Values are loaded from {@code
/org/apache/pulsar/pulsar-version.properties}, a resource
+ * generated by the build only when the build info is included (controlled by
the
+ * {@code pulsar.includeBuildInfo} Gradle property). During development the
resource is typically
+ * absent, in which case this class returns {@link #UNKNOWN} placeholders.
+ */
public class PulsarVersion {
+ public static final String UNKNOWN = "unknown";
+ private static final String RESOURCE_PATH =
"/org/apache/pulsar/pulsar-version.properties";
+
private static final Pattern majorMinorPatchPattern =
Pattern.compile("([0-9]+)\\.([0-9]+)\\.([0-9]+)(.*)");
// Pattern for version missing the patch number: eg: 1.14-SNAPSHOT
private static final Pattern majorMinorPatter =
Pattern.compile("([0-9]+)\\.([0-9]+)(.*)");
+ private static final Properties PROPERTIES = loadProperties();
+
+ private static Properties loadProperties() {
+ Properties props = new Properties();
+ try (InputStream is =
PulsarVersion.class.getResourceAsStream(RESOURCE_PATH)) {
+ if (is != null) {
+ props.load(is);
+ }
+ } catch (IOException e) {
+ // Fall back to empty properties; getters will return UNKNOWN
placeholders.
+ }
+ return props;
+ }
+
// If the version string does not contain a patch version, add one so the
// version becomes valid according to the SemVer library (see
https://github.com/zafarkhaja/jsemver).
- // This method (and it's only call above in the ctor) may be removed when
SemVer accepts null patch versions
static String fixVersionString(String version) {
- if ( null == version ) {
+ if (null == version) {
return null;
}
Matcher majorMinorPatchMatcher =
majorMinorPatchPattern.matcher(version);
- if ( majorMinorPatchMatcher.matches() ) {
+ if (majorMinorPatchMatcher.matches()) {
// this is a valid version, containing a major, a minor, and a
patch version (and optionally
// a release candidate version and/or build metadata)
return version;
@@ -51,9 +78,11 @@ public class PulsarVersion {
int stopMinorVersion = matcher2.end(2);
int startReleaseCandidate = matcher2.start(3);
- String prefix = new String(version.getBytes(),
startMajorVersion, (stopMinorVersion-startMajorVersion));
+ String prefix = new String(version.getBytes(),
startMajorVersion,
+ (stopMinorVersion - startMajorVersion));
String patchVersion = ".0";
- String suffix = new String(version.getBytes(),
startReleaseCandidate, version.length() - startReleaseCandidate);
+ String suffix = new String(version.getBytes(),
startReleaseCandidate,
+ version.length() - startReleaseCandidate);
return (prefix + patchVersion + suffix);
} else {
@@ -64,39 +93,36 @@ public class PulsarVersion {
}
public static String getVersion() {
- return fixVersionString("${project.version}");
+ return fixVersionString(PROPERTIES.getProperty("version", UNKNOWN));
}
public static String getGitSha() {
- String commit = "${git.commit.id}";
- String dirtyString = "${git.dirty}";
- if (commit.contains("git.commit.id")){
- // this case may happen if you are building the sources
- // out of the git repository
- commit = "";
- }
- if (dirtyString == null || Boolean.valueOf(dirtyString)) {
- return commit + "(dirty)";
- } else {
- return commit;
+ String commit = PROPERTIES.getProperty("git.commit.id", "");
+ if (commit.isEmpty()) {
+ return UNKNOWN;
}
+ boolean dirty =
Boolean.parseBoolean(PROPERTIES.getProperty("git.dirty", "false"));
+ return dirty ? commit + "(dirty)" : commit;
}
public static String getGitBranch() {
- return "${git.branch}";
+ return PROPERTIES.getProperty("git.branch", UNKNOWN);
}
public static String getBuildUser() {
- String email = "${git.build.user.email}";
- String name = "${git.build.user.name}";
+ String name = PROPERTIES.getProperty("git.build.user.name", "");
+ String email = PROPERTIES.getProperty("git.build.user.email", "");
+ if (name.isEmpty() && email.isEmpty()) {
+ return UNKNOWN;
+ }
return String.format("%s <%s>", name, email);
}
public static String getBuildHost() {
- return "${git.build.host}";
+ return PROPERTIES.getProperty("git.build.host", UNKNOWN);
}
public static String getBuildTime() {
- return "${git.build.time}";
+ return PROPERTIES.getProperty("git.build.time", UNKNOWN);
}
}
diff --git a/pulsar-common/src/main/java/org/apache/pulsar/package-info.java
b/pulsar-common/src/main/java/org/apache/pulsar/package-info.java
new file mode 100644
index 00000000000..1466202555e
--- /dev/null
+++ b/pulsar-common/src/main/java/org/apache/pulsar/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Top-level Pulsar utilities such as {@link org.apache.pulsar.PulsarVersion}.
+ */
+package org.apache.pulsar;