You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/03/11 14:32:46 UTC
(skywalking-graalvm-distro) branch main updated: Fix native image boot: add Rules/LALConfigs replacements and resource config
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-graalvm-distro.git
The following commit(s) were added to refs/heads/main by this push:
new 7f8651a Fix native image boot: add Rules/LALConfigs replacements and resource config
7f8651a is described below
commit 7f8651a57c42ad369f84c3dc311ae1479ebbdb19
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Wed Mar 11 22:32:30 2026 +0800
Fix native image boot: add Rules/LALConfigs replacements and resource config
- Add same-FQCN replacement for v2 Rules.java (loads from META-INF/config-data/*.json)
- Add same-FQCN replacement for v2 LALConfigs.java (loads from META-INF/config-data/lal.json)
- Fix LAL DSL.of() lookup to match {yamlSource}_{ruleName} class naming pattern
- Add MeterConfigs FAIL_ON_UNKNOWN_PROPERTIES for MetricRuleConfig.sourceName field
- Fix precompiler resource-config.json: add mal-v2.manifest and mal-v2/* patterns
- Fix native-image reachability metadata: update 5 stale v1→v2 class references
- Add release.sh with GPG key display and @apache.org email validation
- Update submodule to 58652507b6 (K8s coordinator fix, LAL def variables)
Verified: E2E Simple Java Agent passes (51/51) on native image.
---
.../server/buildtools/precompiler/Precompiler.java | 3 +-
.../oap-graalvm-native/reachability-metadata.json | 10 +-
.../provider/meter/config/MeterConfigs.java | 4 +-
.../log-analyzer-for-graalvm/pom.xml | 2 +
.../skywalking/oap/log/analyzer/v2/dsl/DSL.java | 25 ++-
.../oap/log/analyzer/v2/provider/LALConfigs.java | 90 +++++++++
.../meter-analyzer-for-graalvm/pom.xml | 2 +
.../meter/analyzer/v2/prometheus/rule/Rules.java | 93 +++++++++
release.sh | 223 +++++++++++++++++++++
skywalking | 2 +-
10 files changed, 443 insertions(+), 11 deletions(-)
diff --git a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
index a3378e1..82007f2 100644
--- a/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
+++ b/build-tools/precompiler/src/main/java/org/apache/skywalking/oap/server/buildtools/precompiler/Precompiler.java
@@ -1447,7 +1447,8 @@ public class Precompiler {
includes.add(Map.of("pattern", "META-INF/annotation-scan/.*\\.txt"));
includes.add(Map.of("pattern", "META-INF/oal-.*\\.txt"));
includes.add(Map.of("pattern", "META-INF/mal-.*\\.txt"));
- includes.add(Map.of("pattern", "META-INF/mal-.*\\.properties"));
+ includes.add(Map.of("pattern", "META-INF/mal-v2\\.manifest"));
+ includes.add(Map.of("pattern", "META-INF/mal-v2/.*"));
includes.add(Map.of("pattern", "META-INF/lal-.*\\.txt"));
includes.add(Map.of("pattern", "META-INF/hierarchy-.*\\.txt"));
includes.add(Map.of("pattern", "META-INF/config-data/.*\\.json"));
diff --git a/oap-graalvm-native/src/main/resources/META-INF/native-image/org.apache.skywalking/oap-graalvm-native/reachability-metadata.json b/oap-graalvm-native/src/main/resources/META-INF/native-image/org.apache.skywalking/oap-graalvm-native/reachability-metadata.json
index 0811c65..59c872a 100644
--- a/oap-graalvm-native/src/main/resources/META-INF/native-image/org.apache.skywalking/oap-graalvm-native/reachability-metadata.json
+++ b/oap-graalvm-native/src/main/resources/META-INF/native-image/org.apache.skywalking/oap-graalvm-native/reachability-metadata.json
@@ -7916,7 +7916,7 @@
"allPublicMethods": true
},
{
- "type": "org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig",
+ "type": "org.apache.skywalking.oap.log.analyzer.v2.provider.LogAnalyzerModuleConfig",
"methods": [
{
"name": "<init>",
@@ -7925,13 +7925,13 @@
]
},
{
- "type": "org.apache.skywalking.oap.meter.analyzer.MetricRuleConfig"
+ "type": "org.apache.skywalking.oap.meter.analyzer.v2.MetricRuleConfig"
},
{
- "type": "org.apache.skywalking.oap.meter.analyzer.MetricRuleConfig$RuleConfig"
+ "type": "org.apache.skywalking.oap.meter.analyzer.v2.MetricRuleConfig$RuleConfig"
},
{
- "type": "org.apache.skywalking.oap.meter.analyzer.prometheus.rule.MetricsRule",
+ "type": "org.apache.skywalking.oap.meter.analyzer.v2.prometheus.rule.MetricsRule",
"methods": [
{
"name": "<init>",
@@ -7952,7 +7952,7 @@
]
},
{
- "type": "org.apache.skywalking.oap.meter.analyzer.prometheus.rule.Rule",
+ "type": "org.apache.skywalking.oap.meter.analyzer.v2.prometheus.rule.Rule",
"methods": [
{
"name": "<init>",
diff --git a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
index 0138a04..df1b316 100644
--- a/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
+++ b/oap-libs-for-graalvm/agent-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/config/MeterConfigs.java
@@ -18,6 +18,7 @@
package org.apache.skywalking.oap.server.analyzer.provider.meter.config;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.InputStream;
import java.util.Collections;
@@ -42,7 +43,8 @@ import org.apache.skywalking.oap.server.library.util.CollectionUtils;
@Slf4j
public class MeterConfigs {
- private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final ObjectMapper MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
/**
* Load all configs from pre-compiled JSON manifest.
diff --git a/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml b/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
index bf2ad94..b6f70f2 100644
--- a/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/log-analyzer-for-graalvm/pom.xml
@@ -64,6 +64,8 @@
<!-- v2 ANTLR4+Javassist classes replaced with pre-compiled loaders -->
<exclude>org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.class</exclude>
<exclude>org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL$*.class</exclude>
+ <exclude>org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfigs.class</exclude>
+ <exclude>org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfigs$*.class</exclude>
</excludes>
</filter>
</filters>
diff --git a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
index 57a03da..d1002f5 100644
--- a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
+++ b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/dsl/DSL.java
@@ -82,15 +82,34 @@ public class DSL {
final String yamlSource) throws ModuleStartException {
final Map<String, String> classMap = loadManifest();
- // Try to find by sanitized ruleName (matching precompiler's classNameHint)
+ // Try to find by sanitized ruleName (matching precompiler's classNameHint).
+ // Class names follow pattern: {yamlSource}_{ruleName}
+ // e.g., "default_default", "network_profiling_slow_trace_network_profiling_slow_trace"
final String sanitizedName = sanitizeName(ruleName);
- String className = classMap.get("LalExpr_" + sanitizedName);
+
+ // First try exact match on simple name
+ String className = classMap.get(sanitizedName);
+ // Then try {sanitizedName}_{sanitizedName} pattern (yamlSource == ruleName)
+ if (className == null) {
+ className = classMap.get(sanitizedName + "_" + sanitizedName);
+ }
+ // Fallback: try suffix match (_ruleName at end of simple name)
+ if (className == null) {
+ final String suffix = "_" + sanitizedName;
+ for (Map.Entry<String, String> entry : classMap.entrySet()) {
+ if (entry.getKey().endsWith(suffix)) {
+ className = entry.getValue();
+ break;
+ }
+ }
+ }
if (className == null) {
throw new ModuleStartException(
"Pre-compiled LAL expression not found for rule: " + ruleName
+ " (sanitized: " + sanitizedName + ")"
- + ". Available: " + classMap.size() + " expressions");
+ + ". Available: " + classMap.size() + " expressions"
+ + " keys: " + classMap.keySet());
}
try {
diff --git a/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfigs.java b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfigs.java
new file mode 100644
index 0000000..4dd33a9
--- /dev/null
+++ b/oap-libs-for-graalvm/log-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/provider/LALConfigs.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+package org.apache.skywalking.oap.log.analyzer.v2.provider;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+
+import static com.google.common.io.Files.getNameWithoutExtension;
+import static org.apache.skywalking.oap.server.library.util.CollectionUtils.isEmpty;
+
+/**
+ * GraalVM replacement for upstream LALConfigs.
+ * Original: skywalking/oap-server/analyzer/log-analyzer/src/main/java/.../v2/provider/LALConfigs.java
+ * Repackaged into log-analyzer-for-graalvm via maven-shade-plugin (replaces original .class in shaded JAR).
+ *
+ * Change: Complete rewrite. Loads pre-compiled LAL config data from JSON manifest
+ * (META-INF/config-data/lal.json) instead of filesystem YAML files via ResourceUtils.getPathFiles().
+ * Why: The distro intentionally excludes raw YAML config directories (lal/)
+ * — their DSL expressions are pre-compiled at build time. Config data is serialized as JSON
+ * by the precompiler for runtime wiring.
+ */
+@Data
+@Slf4j
+public class LALConfigs {
+ private List<LALConfig> rules;
+
+ private static final ObjectMapper MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ public static List<LALConfigs> load(final String path, final List<String> files) throws Exception {
+ if (isEmpty(files)) {
+ return Collections.emptyList();
+ }
+
+ log.info("Loading LAL configs from pre-compiled distro ({})", path);
+
+ String resourcePath = "META-INF/config-data/" + path + ".json";
+ try (InputStream is = LALConfigs.class.getClassLoader().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ throw new ModuleStartException(
+ "Pre-compiled LAL config data not found: " + resourcePath
+ + ". Ensure the precompiler has been run.");
+ }
+
+ Map<String, LALConfigs> allConfigs = MAPPER.readValue(
+ is, new TypeReference<Map<String, LALConfigs>>() { });
+
+ List<LALConfigs> result = allConfigs.entrySet().stream()
+ .filter(e -> files.contains(getNameWithoutExtension(e.getKey())))
+ .map(e -> {
+ LALConfigs configs = e.getValue();
+ if (configs != null && configs.getRules() != null) {
+ configs.getRules().forEach(c -> c.setSourceName(e.getKey()));
+ }
+ return configs;
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ log.info("Loaded {} pre-compiled LAL configs from {} (filtered from {} available)",
+ result.size(), path, allConfigs.size());
+ return result;
+ }
+ }
+}
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
index 09eb3b7..7b9eaaf 100644
--- a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/pom.xml
@@ -66,6 +66,8 @@
<exclude>org/apache/skywalking/oap/meter/analyzer/v2/dsl/DSL$*.class</exclude>
<exclude>org/apache/skywalking/oap/meter/analyzer/v2/dsl/FilterExpression.class</exclude>
<exclude>org/apache/skywalking/oap/meter/analyzer/v2/dsl/FilterExpression$*.class</exclude>
+ <exclude>org/apache/skywalking/oap/meter/analyzer/v2/prometheus/rule/Rules.class</exclude>
+ <exclude>org/apache/skywalking/oap/meter/analyzer/v2/prometheus/rule/Rules$*.class</exclude>
</excludes>
</filter>
</filters>
diff --git a/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/prometheus/rule/Rules.java b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/prometheus/rule/Rules.java
new file mode 100644
index 0000000..69dd0de
--- /dev/null
+++ b/oap-libs-for-graalvm/meter-analyzer-for-graalvm/src/main/java/org/apache/skywalking/oap/meter/analyzer/v2/prometheus/rule/Rules.java
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+package org.apache.skywalking.oap.meter.analyzer.v2.prometheus.rule;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * GraalVM replacement for upstream Rules.
+ * Original: skywalking/oap-server/analyzer/meter-analyzer/src/main/java/.../v2/prometheus/rule/Rules.java
+ * Repackaged into meter-analyzer-for-graalvm via maven-shade-plugin (replaces original .class in shaded JAR).
+ *
+ * Change: Complete rewrite. Loads pre-compiled rule data from JSON manifests
+ * (META-INF/config-data/{path}.json) instead of filesystem YAML files via ResourceUtils.getPath().
+ * Why: The distro intentionally excludes raw YAML config directories (otel-rules, envoy-metrics-rules, etc.)
+ * — their Groovy/DSL expressions are pre-compiled at build time. Config data (metric prefixes, rule names)
+ * is serialized as JSON by the precompiler for runtime wiring.
+ */
+public class Rules {
+ private static final Logger LOG = LoggerFactory.getLogger(Rules.class);
+
+ private static final ObjectMapper MAPPER = new ObjectMapper()
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ public static List<Rule> loadRules(final String path) throws IOException {
+ return loadRules(path, Collections.emptyList());
+ }
+
+ public static List<Rule> loadRules(final String path, List<String> enabledRules) throws IOException {
+ if (enabledRules == null || enabledRules.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ LOG.info("Loading rules from pre-compiled distro ({})", path);
+
+ String resourcePath = "META-INF/config-data/" + path + ".json";
+ try (InputStream is = Rules.class.getClassLoader().getResourceAsStream(resourcePath)) {
+ if (is == null) {
+ throw new IOException(
+ "Pre-compiled config data not found: " + resourcePath
+ + ". Ensure the precompiler has been run.");
+ }
+
+ List<Rule> allRules = MAPPER.readValue(is, new TypeReference<List<Rule>>() { });
+
+ // Normalize enabled rules: trim, remove leading "/", remove extension
+ Set<String> normalizedEnabled = enabledRules.stream()
+ .map(String::trim)
+ .map(r -> r.startsWith("/") ? r.substring(1) : r)
+ .map(r -> {
+ if (r.endsWith(".yaml")) {
+ return r.substring(0, r.length() - 5);
+ } else if (r.endsWith(".yml")) {
+ return r.substring(0, r.length() - 4);
+ }
+ return r;
+ })
+ .collect(Collectors.toSet());
+
+ List<Rule> result = allRules.stream()
+ .filter(r -> normalizedEnabled.contains(r.getName()))
+ .collect(Collectors.toList());
+
+ LOG.info("Loaded {} pre-compiled rules from {} (filtered from {} available, enabled: {})",
+ result.size(), path, allRules.size(), normalizedEnabled);
+ return result;
+ }
+ }
+}
diff --git a/release.sh b/release.sh
new file mode 100755
index 0000000..f3f7d66
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,223 @@
+#!/usr/bin/env bash
+
+# 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.
+
+set -euo pipefail
+
+ARTIFACT_PREFIX="apache-skywalking-graalvm-distro"
+RELEASE_DIR="release-package"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+
+# ─── Usage ───────────────────────────────────────────────────────────────────
+usage() {
+ cat <<EOF
+Usage: $0 <version>
+
+Create an Apache-style release for SkyWalking GraalVM Distro.
+
+Arguments:
+ version Release version (e.g. 1.0.0)
+
+What this script does:
+ 1. Generates a changelog template in changes/<version>.md
+ 2. Creates a git tag v<version> and pushes it
+ 3. Packages a clean source tarball (with submodule, excluding .git/binaries)
+ 4. Builds the Linux native binary via Docker cross-compilation
+ 5. Packages the native binary tarball with configuration
+ 6. Signs all tarballs with GPG (default key) and generates SHA-512 checksums
+
+Output:
+ ${RELEASE_DIR}/
+ ${ARTIFACT_PREFIX}-<version>-src.tar.gz{,.asc,.sha512}
+ ${ARTIFACT_PREFIX}-<version>-bin.tar.gz{,.asc,.sha512}
+EOF
+ exit 1
+}
+
+# ─── Helpers ─────────────────────────────────────────────────────────────────
+log() { echo "===> $*"; }
+error() { echo "ERROR: $*" >&2; exit 1; }
+
+sign_and_checksum() {
+ local file="$1"
+ log "Signing ${file}..."
+ gpg --armor --detach-sign "${file}"
+ log "Generating SHA-512 checksum for ${file}..."
+ shasum -a 512 "${file}" > "${file}.sha512"
+}
+
+# ─── Validate arguments ─────────────────────────────────────────────────────
+[[ $# -eq 1 ]] || usage
+VERSION="$1"
+TAG="v${VERSION}"
+
+# ─── Pre-flight checks ──────────────────────────────────────────────────────
+log "Pre-flight checks..."
+
+command -v gpg >/dev/null 2>&1 || error "gpg is not installed"
+command -v git >/dev/null 2>&1 || error "git is not installed"
+command -v tar >/dev/null 2>&1 || error "tar is not installed"
+command -v docker >/dev/null 2>&1 || error "docker is not installed (needed for native build)"
+
+# Verify default GPG key exists and print it
+gpg --list-secret-keys --keyid-format LONG >/dev/null 2>&1 \
+ || error "No GPG secret keys found. Import or generate a key first."
+GPG_KEY=$(gpg --list-secret-keys --keyid-format LONG 2>/dev/null \
+ | grep -A1 "^sec" | head -2)
+GPG_EMAIL=$(gpg --list-secret-keys --keyid-format LONG 2>/dev/null \
+ | grep "uid" | head -1 | sed 's/.*<\(.*\)>.*/\1/')
+log "GPG signing key:"
+echo "${GPG_KEY}"
+echo " uid: ${GPG_EMAIL}"
+echo ""
+[[ "${GPG_EMAIL}" == *@apache.org ]] \
+ || error "GPG key email '${GPG_EMAIL}' is not an @apache.org address. Apache releases must be signed with your Apache committer key."
+
+# Verify working directory is clean
+if [[ -n "$(git status --porcelain)" ]]; then
+ error "Working directory is not clean. Commit or stash changes first."
+fi
+
+# Verify tag does not already exist
+if git rev-parse "${TAG}" >/dev/null 2>&1; then
+ error "Tag ${TAG} already exists. Delete it first if re-releasing."
+fi
+
+# ─── Step 1: Changelog ──────────────────────────────────────────────────────
+log "Creating changelog for ${VERSION}..."
+
+mkdir -p "${SCRIPT_DIR}/changes"
+
+CHANGELOG="${SCRIPT_DIR}/changes/${VERSION}.md"
+{
+ echo "# ${VERSION} Release"
+ echo ""
+ echo "## Highlights"
+ echo ""
+ echo "<!-- Fill in the highlights of this release -->"
+ echo ""
+ echo "## Changes"
+ echo ""
+ # Collect commits since last tag, or all if no prior tag
+ PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
+ if [[ -n "${PREV_TAG}" ]]; then
+ git log --oneline "${PREV_TAG}..HEAD" | sed 's/^/- /'
+ else
+ git log --oneline | sed 's/^/- /'
+ fi
+} > "${CHANGELOG}"
+
+log "Changelog written to ${CHANGELOG}"
+log "Please review and edit the changelog before continuing."
+echo ""
+read -r -p "Press ENTER to continue after editing the changelog (or Ctrl+C to abort)..."
+
+# ─── Step 2: Git tag ────────────────────────────────────────────────────────
+log "Creating git tag ${TAG}..."
+git tag -a "${TAG}" -m "Release ${VERSION}"
+log "Pushing tag ${TAG} to origin..."
+git push origin "${TAG}"
+
+# ─── Step 3: Prepare release directory ───────────────────────────────────────
+log "Preparing release directory..."
+rm -rf "${SCRIPT_DIR}/${RELEASE_DIR}"
+mkdir -p "${SCRIPT_DIR}/${RELEASE_DIR}"
+
+# ─── Step 4: Source package ──────────────────────────────────────────────────
+log "Creating source package..."
+
+SRC_TARBALL="${ARTIFACT_PREFIX}-${VERSION}-src.tar.gz"
+SRC_CLONE_DIR="${SCRIPT_DIR}/${RELEASE_DIR}/src-clone"
+
+# Clean clone with submodules
+git clone --recurse-submodules "${SCRIPT_DIR}" "${SRC_CLONE_DIR}"
+
+# Remove all .git directories (root + submodules)
+find "${SRC_CLONE_DIR}" -name ".git" -type d -exec rm -rf {} + 2>/dev/null || true
+find "${SRC_CLONE_DIR}" -name ".gitmodules" -type f -delete 2>/dev/null || true
+
+# Remove build artifacts and unnecessary files
+find "${SRC_CLONE_DIR}" -type d -name "target" -exec rm -rf {} + 2>/dev/null || true
+find "${SRC_CLONE_DIR}" -name "*.jar" -delete 2>/dev/null || true
+find "${SRC_CLONE_DIR}" -name "*.class" -delete 2>/dev/null || true
+find "${SRC_CLONE_DIR}" -name ".DS_Store" -delete 2>/dev/null || true
+rm -rf "${SRC_CLONE_DIR}/.idea" "${SRC_CLONE_DIR}/.vscode" "${SRC_CLONE_DIR}/.claude"
+rm -rf "${SRC_CLONE_DIR}/${RELEASE_DIR}"
+
+# Create tarball from clean source
+tar -czf "${SCRIPT_DIR}/${RELEASE_DIR}/${SRC_TARBALL}" \
+ -C "${SCRIPT_DIR}/${RELEASE_DIR}" \
+ --exclude="src-clone/${RELEASE_DIR}" \
+ -s "/^src-clone/${ARTIFACT_PREFIX}-${VERSION}-src/" \
+ src-clone
+
+# Clean up clone
+rm -rf "${SRC_CLONE_DIR}"
+
+log "Source package created: ${RELEASE_DIR}/${SRC_TARBALL}"
+
+# ─── Step 5: Binary package (Linux native) ───────────────────────────────────
+log "Building Linux native binary via Docker cross-compilation..."
+log "(This may take a while — native-image compilation is resource-intensive)"
+
+make native-image-macos
+
+# Locate the native dist tarball produced by Maven assembly
+NATIVE_TARBALL=$(ls "${SCRIPT_DIR}"/oap-graalvm-native/target/oap-graalvm-native-*-native-dist.tar.gz 2>/dev/null | head -1)
+[[ -n "${NATIVE_TARBALL}" ]] || error "Native dist tarball not found. Build may have failed."
+
+BIN_TARBALL="${ARTIFACT_PREFIX}-${VERSION}-bin.tar.gz"
+
+# Repackage with Apache naming: extract, rename root dir, re-tar
+REPACK_DIR="${SCRIPT_DIR}/${RELEASE_DIR}/repack-tmp"
+mkdir -p "${REPACK_DIR}"
+tar -xzf "${NATIVE_TARBALL}" -C "${REPACK_DIR}"
+
+# The native assembly uses base directory "oap-native"
+mv "${REPACK_DIR}/oap-native" "${REPACK_DIR}/${ARTIFACT_PREFIX}-${VERSION}"
+
+tar -czf "${SCRIPT_DIR}/${RELEASE_DIR}/${BIN_TARBALL}" \
+ -C "${REPACK_DIR}" \
+ "${ARTIFACT_PREFIX}-${VERSION}"
+
+rm -rf "${REPACK_DIR}"
+
+log "Binary package created: ${RELEASE_DIR}/${BIN_TARBALL}"
+
+# ─── Step 6: Sign and checksum ──────────────────────────────────────────────
+log "Signing and checksumming release artifacts..."
+
+cd "${SCRIPT_DIR}/${RELEASE_DIR}"
+sign_and_checksum "${SRC_TARBALL}"
+sign_and_checksum "${BIN_TARBALL}"
+cd "${SCRIPT_DIR}"
+
+# ─── Summary ────────────────────────────────────────────────────────────────
+echo ""
+log "Release ${VERSION} complete!"
+echo ""
+echo "Release artifacts:"
+ls -lh "${SCRIPT_DIR}/${RELEASE_DIR}/"
+echo ""
+echo "Verification commands:"
+echo " cd ${RELEASE_DIR}"
+echo " gpg --verify ${SRC_TARBALL}.asc ${SRC_TARBALL}"
+echo " gpg --verify ${BIN_TARBALL}.asc ${BIN_TARBALL}"
+echo " shasum -a 512 -c ${SRC_TARBALL}.sha512"
+echo " shasum -a 512 -c ${BIN_TARBALL}.sha512"
+echo ""
+echo "Changelog: changes/${VERSION}.md"
+echo "Git tag: ${TAG}"
diff --git a/skywalking b/skywalking
index 247583b..5865250 160000
--- a/skywalking
+++ b/skywalking
@@ -1 +1 @@
-Subproject commit 247583be0bf1730941c65af5dc7e330a46ad4656
+Subproject commit 58652507b633d8783b4d9dbfce609bccb5bd11ef