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;

Reply via email to