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

commit 612e571b5ac45085e8722b53b8f0add1904c21c6
Author: Wu Sheng <[email protected]>
AuthorDate: Wed Feb 18 18:17:40 2026 +0800

    Add build-time OAL class export tool
    
    Run the OAL engine at build time to export generated .class files for
    GraalVM native image compatibility (no runtime bytecode generation).
    
    OALClassExporter:
    - Leverages existing SW_OAL_ENGINE_DEBUG mechanism in OALClassGeneratorV2
      to export .class files via ctClass.toBytecode()
    - Processes all 9 OALDefine instances (Disable, Core, JVM, CLR, Browser,
      Mesh, EBPF, TCP, Cilium)
    - Initializes DefaultScopeDefine via AnnotationScan before running engines
    - Loads OAL scripts from skywalking submodule via 
additionalClasspathElements
    - Writes manifests: oal-metrics-classes.txt (620), 
oal-dispatcher-classes.txt
      (45), oal-disabled-sources.txt
    - Packages generated classes into classified JAR 
(oal-exporter-*-generated.jar)
    
    Tests:
    - Validates ALL_DEFINES covers all 9 expected OAL scripts
    - Verifies all OAL scripts are resolvable on classpath
    - Runs full export and asserts metrics, dispatchers, builders are generated
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 build-tools/oal-exporter/pom.xml                   | 152 ++++++++++++++++
 .../server/buildtools/oal/OALClassExporter.java    | 201 +++++++++++++++++++++
 .../buildtools/oal/OALClassExporterTest.java       | 130 +++++++++++++
 3 files changed, 483 insertions(+)

diff --git a/build-tools/oal-exporter/pom.xml b/build-tools/oal-exporter/pom.xml
new file mode 100644
index 0000000..d53d20e
--- /dev/null
+++ b/build-tools/oal-exporter/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.skywalking</groupId>
+        <artifactId>build-tools</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>oal-exporter</artifactId>
+    <name>OAL Exporter</name>
+    <description>Runs OAL engine at build time and exports generated .class 
files</description>
+
+    <dependencies>
+        <!-- OAL engine -->
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>oal-rt</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>server-core</artifactId>
+        </dependency>
+
+        <!-- Receiver plugins containing OALDefine subclasses -->
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>skywalking-jvm-receiver-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>skywalking-clr-receiver-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>skywalking-browser-receiver-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>skywalking-mesh-receiver-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>skywalking-ebpf-receiver-plugin</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>envoy-metrics-receiver-plugin</artifactId>
+        </dependency>
+
+        <!-- Fetcher plugin containing CiliumOALDefine -->
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>cilium-fetcher-plugin</artifactId>
+        </dependency>
+
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!-- Test -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- Run OALClassExporter at build time -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <version>3.5.0</version>
+                <executions>
+                    <execution>
+                        <id>export-oal-classes</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>java</goal>
+                        </goals>
+                        <configuration>
+                            
<mainClass>org.apache.skywalking.oap.server.buildtools.oal.OALClassExporter</mainClass>
+                            <arguments>
+                                
<argument>${project.build.directory}/generated-oal-classes</argument>
+                            </arguments>
+                            <!-- Load OAL scripts directly from skywalking 
submodule -->
+                            <additionalClasspathElements>
+                                
<additionalClasspathElement>${project.basedir}/../../skywalking/oap-server/server-starter/src/main/resources</additionalClasspathElement>
+                            </additionalClasspathElements>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Surefire: add OAL scripts to test classpath -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>3.2.5</version>
+                <configuration>
+                    <additionalClasspathElements>
+                        
<additionalClasspathElement>${project.basedir}/../../skywalking/oap-server/server-starter/src/main/resources</additionalClasspathElement>
+                    </additionalClasspathElements>
+                </configuration>
+            </plugin>
+
+            <!-- Package generated classes into a classified JAR -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.4.2</version>
+                <executions>
+                    <execution>
+                        <id>generated-classes-jar</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                        <configuration>
+                            
<classesDirectory>${project.build.directory}/generated-oal-classes</classesDirectory>
+                            <classifier>generated</classifier>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git 
a/build-tools/oal-exporter/src/main/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporter.java
 
b/build-tools/oal-exporter/src/main/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporter.java
new file mode 100644
index 0000000..ded57e7
--- /dev/null
+++ 
b/build-tools/oal-exporter/src/main/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporter.java
@@ -0,0 +1,201 @@
+/*
+ * 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.server.buildtools.oal;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.skywalking.aop.server.receiver.mesh.MeshOALDefine;
+import org.apache.skywalking.oal.v2.OALEngineV2;
+import org.apache.skywalking.oal.v2.generator.OALClassGeneratorV2;
+import org.apache.skywalking.oap.server.core.analysis.DisableRegister;
+import org.apache.skywalking.oap.server.core.annotation.AnnotationScan;
+import org.apache.skywalking.oap.server.core.oal.rt.CoreOALDefine;
+import org.apache.skywalking.oap.server.core.oal.rt.DisableOALDefine;
+import org.apache.skywalking.oap.server.core.oal.rt.OALDefine;
+import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
+import org.apache.skywalking.oap.server.core.storage.StorageBuilderFactory;
+import org.apache.skywalking.oap.server.fetcher.cilium.CiliumOALDefine;
+import 
org.apache.skywalking.oap.server.receiver.browser.provider.BrowserOALDefine;
+import org.apache.skywalking.oap.server.receiver.clr.provider.CLROALDefine;
+import org.apache.skywalking.oap.server.receiver.ebpf.provider.EBPFOALDefine;
+import org.apache.skywalking.oap.server.receiver.envoy.TCPOALDefine;
+import org.apache.skywalking.oap.server.receiver.jvm.provider.JVMOALDefine;
+
+/**
+ * Build-time tool that runs the OAL engine for all 9 OALDefine configurations,
+ * exports generated .class files (metrics, builders, dispatchers), and writes
+ * manifest files listing all generated class names.
+ *
+ * OAL script files are loaded from the skywalking submodule directly via
+ * additionalClasspathElements in the exec-maven-plugin configuration.
+ *
+ * Uses the existing SW_OAL_ENGINE_DEBUG export mechanism in 
OALClassGeneratorV2.
+ */
+@Slf4j
+public class OALClassExporter {
+
+    private static final String METRICS_PACKAGE =
+        "org.apache.skywalking.oap.server.core.source.oal.rt.metrics.";
+    private static final String BUILDER_PACKAGE =
+        "org.apache.skywalking.oap.server.core.source.oal.rt.metrics.builder.";
+    private static final String DISPATCHER_PACKAGE =
+        "org.apache.skywalking.oap.server.core.source.oal.rt.dispatcher.";
+
+    static final OALDefine[] ALL_DEFINES = {
+        DisableOALDefine.INSTANCE,
+        CoreOALDefine.INSTANCE,
+        JVMOALDefine.INSTANCE,
+        CLROALDefine.INSTANCE,
+        BrowserOALDefine.INSTANCE,
+        MeshOALDefine.INSTANCE,
+        EBPFOALDefine.INSTANCE,
+        TCPOALDefine.INSTANCE,
+        CiliumOALDefine.INSTANCE
+    };
+
+    public static void main(String[] args) throws Exception {
+        final String outputDir = args.length > 0
+            ? args[0]
+            : "target/generated-oal-classes";
+
+        log.info("OAL Class Exporter: output -> {}", outputDir);
+
+        // Validate all OAL scripts are available on classpath before running
+        validateOALScripts();
+
+        // Initialize DefaultScopeDefine — scan @ScopeDeclaration annotations 
on source
+        // classes (Service, Endpoint, etc.) to populate the scope name → ID → 
columns
+        // registry. The OAL enricher needs this to resolve source metadata.
+        AnnotationScan scopeScan = new AnnotationScan();
+        scopeScan.registerListener(new DefaultScopeDefine.Listener());
+        scopeScan.scan();
+        log.info("Initialized DefaultScopeDefine scope registry");
+
+        // Set generated file path so debug output lands in proper package 
structure.
+        // writeGeneratedFile() appends "/metrics/", "/metrics/builder/", 
"/dispatcher/"
+        // which matches the actual Java package sub-paths.
+        OALClassGeneratorV2.setGeneratedFilePath(
+            outputDir + 
"/org/apache/skywalking/oap/server/core/source/oal/rt");
+
+        // Skip prepareRTTempFolder() which uses WorkPath.getPath() — not 
available
+        // in build tool context. Set the static guard to true so it becomes a 
no-op.
+        Field rtFolderInitField = OALClassGeneratorV2.class.getDeclaredField(
+            "IS_RT_TEMP_FOLDER_INIT_COMPLETED");
+        rtFolderInitField.setAccessible(true);
+        rtFolderInitField.set(null, true);
+
+        // Run all 9 OAL defines
+        for (OALDefine define : ALL_DEFINES) {
+            log.info("Processing: {}", define.getConfigFile());
+            OALEngineV2 engine = new OALEngineV2(define);
+            engine.getClassGeneratorV2().setOpenEngineDebug(true);
+            engine.setStorageBuilderFactory(new 
StorageBuilderFactory.Default());
+            engine.start(OALClassExporter.class.getClassLoader());
+        }
+
+        // Scan generated .class files and build manifests
+        List<String> metricsClasses = scanClassNames(outputDir, "metrics", 
METRICS_PACKAGE);
+        List<String> dispatcherClasses = scanClassNames(outputDir, 
"dispatcher", DISPATCHER_PACKAGE);
+        List<String> disabledSources = getDisabledSources();
+
+        // Write manifest files
+        Path metaInf = Path.of(outputDir, "META-INF");
+        Files.createDirectories(metaInf);
+        writeManifest(metaInf.resolve("oal-metrics-classes.txt"), 
metricsClasses);
+        writeManifest(metaInf.resolve("oal-dispatcher-classes.txt"), 
dispatcherClasses);
+        writeManifest(metaInf.resolve("oal-disabled-sources.txt"), 
disabledSources);
+
+        log.info("OAL Class Exporter: done - {} metrics, {} dispatchers, {} 
disabled sources",
+            metricsClasses.size(), dispatcherClasses.size(), 
disabledSources.size());
+    }
+
+    /**
+     * Validate that all OAL script files referenced by ALL_DEFINES are
+     * available on the classpath. Fails fast with a clear error if any are 
missing.
+     */
+    private static void validateOALScripts() {
+        ClassLoader cl = OALClassExporter.class.getClassLoader();
+        List<String> missing = new ArrayList<>();
+        for (OALDefine define : ALL_DEFINES) {
+            String configFile = define.getConfigFile();
+            if (cl.getResource(configFile) == null) {
+                missing.add(configFile + " (" + 
define.getClass().getSimpleName() + ")");
+            }
+        }
+        if (!missing.isEmpty()) {
+            throw new IllegalStateException(
+                "OAL script files not found on classpath. "
+                + "Ensure the skywalking submodule resource directory is on 
the classpath.\n"
+                + "Missing:\n  " + String.join("\n  ", missing));
+        }
+        log.info("Validated: all {} OAL scripts found on classpath", 
ALL_DEFINES.length);
+    }
+
+    /**
+     * Scan a subdirectory under the generated package path for .class files
+     * and compute fully-qualified class names.
+     */
+    private static List<String> scanClassNames(
+        String outputDir, String subDir, String packagePrefix) throws 
IOException {
+
+        Path dir = Path.of(outputDir,
+            "org/apache/skywalking/oap/server/core/source/oal/rt", subDir);
+        if (!Files.isDirectory(dir)) {
+            return Collections.emptyList();
+        }
+
+        List<String> classNames = new ArrayList<>();
+        try (Stream<Path> files = Files.list(dir)) {
+            files.filter(p -> p.toString().endsWith(".class"))
+                 .filter(p -> Files.isRegularFile(p))
+                 .forEach(p -> {
+                     String fileName = p.getFileName().toString();
+                     String simpleName = fileName.substring(0, 
fileName.length() - ".class".length());
+                     classNames.add(packagePrefix + simpleName);
+                 });
+        }
+        Collections.sort(classNames);
+        return classNames;
+    }
+
+    /**
+     * Read disabled source names from DisableRegister singleton via 
reflection.
+     */
+    @SuppressWarnings("unchecked")
+    private static List<String> getDisabledSources() throws Exception {
+        Field field = 
DisableRegister.class.getDeclaredField("disableEntitySet");
+        field.setAccessible(true);
+        Set<String> disabledSet = (Set<String>) 
field.get(DisableRegister.INSTANCE);
+        List<String> result = new ArrayList<>(disabledSet);
+        Collections.sort(result);
+        return result;
+    }
+
+    private static void writeManifest(Path path, List<String> lines) throws 
IOException {
+        Files.write(path, lines, StandardCharsets.UTF_8);
+    }
+}
diff --git 
a/build-tools/oal-exporter/src/test/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporterTest.java
 
b/build-tools/oal-exporter/src/test/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporterTest.java
new file mode 100644
index 0000000..27a1866
--- /dev/null
+++ 
b/build-tools/oal-exporter/src/test/java/org/apache/skywalking/oap/server/buildtools/oal/OALClassExporterTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.buildtools.oal;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.skywalking.oap.server.core.oal.rt.OALDefine;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class OALClassExporterTest {
+
+    /**
+     * All expected OAL script files. If a new OALDefine is added upstream,
+     * this test will fail — reminding us to update ALL_DEFINES.
+     */
+    private static final Set<String> EXPECTED_OAL_SCRIPTS = Set.of(
+        "oal/disable.oal",
+        "oal/core.oal",
+        "oal/java-agent.oal",
+        "oal/dotnet-agent.oal",
+        "oal/browser.oal",
+        "oal/mesh.oal",
+        "oal/ebpf.oal",
+        "oal/tcp.oal",
+        "oal/cilium.oal"
+    );
+
+    @Test
+    void allDefinesShouldCoverExpectedOALScripts() {
+        Set<String> actualScripts = Arrays.stream(OALClassExporter.ALL_DEFINES)
+            .map(OALDefine::getConfigFile)
+            .collect(Collectors.toSet());
+
+        assertEquals(
+            EXPECTED_OAL_SCRIPTS, actualScripts,
+            "ALL_DEFINES does not match expected OAL scripts"
+        );
+    }
+
+    @Test
+    void allOALScriptsShouldBeOnClasspath() {
+        ClassLoader cl = getClass().getClassLoader();
+        for (OALDefine define : OALClassExporter.ALL_DEFINES) {
+            assertNotNull(
+                cl.getResource(define.getConfigFile()),
+                "OAL script not found on classpath: " + define.getConfigFile()
+                    + " (" + define.getClass().getSimpleName() + ")"
+            );
+        }
+    }
+
+    @Test
+    void exportShouldGenerateClassesForEachNonDisableDefine(@TempDir Path 
tempDir)
+        throws Exception {
+        String outputDir = tempDir.toString();
+
+        // Run the full export
+        OALClassExporter.main(new String[] {outputDir});
+
+        // Verify metrics classes generated
+        Path metricsDir = tempDir.resolve(
+            "org/apache/skywalking/oap/server/core/source/oal/rt/metrics");
+        assertTrue(Files.isDirectory(metricsDir), "metrics directory should 
exist");
+
+        long metricsCount = Files.list(metricsDir)
+            .filter(p -> p.toString().endsWith(".class"))
+            .count();
+        assertTrue(metricsCount > 0, "Should generate metrics classes, got 0");
+
+        // Verify dispatcher classes generated
+        Path dispatcherDir = tempDir.resolve(
+            "org/apache/skywalking/oap/server/core/source/oal/rt/dispatcher");
+        assertTrue(Files.isDirectory(dispatcherDir), "dispatcher directory 
should exist");
+
+        long dispatcherCount = Files.list(dispatcherDir)
+            .filter(p -> p.toString().endsWith(".class"))
+            .count();
+        assertTrue(dispatcherCount > 0, "Should generate dispatcher classes, 
got 0");
+
+        // Verify builder classes generated
+        Path builderDir = tempDir.resolve(
+            
"org/apache/skywalking/oap/server/core/source/oal/rt/metrics/builder");
+        assertTrue(Files.isDirectory(builderDir), "builder directory should 
exist");
+
+        long builderCount = Files.list(builderDir)
+            .filter(p -> p.toString().endsWith(".class"))
+            .count();
+        assertTrue(builderCount > 0, "Should generate builder classes, got 0");
+
+        // Verify manifest files
+        Path metricsManifest = 
tempDir.resolve("META-INF/oal-metrics-classes.txt");
+        assertTrue(Files.exists(metricsManifest), "metrics manifest should 
exist");
+        List<String> metricsLines = Files.readAllLines(metricsManifest);
+        assertFalse(metricsLines.isEmpty(), "metrics manifest should not be 
empty");
+
+        Path dispatcherManifest = 
tempDir.resolve("META-INF/oal-dispatcher-classes.txt");
+        assertTrue(Files.exists(dispatcherManifest), "dispatcher manifest 
should exist");
+        List<String> dispatcherLines = Files.readAllLines(dispatcherManifest);
+        assertFalse(dispatcherLines.isEmpty(), "dispatcher manifest should not 
be empty");
+
+        // Verify disabled sources manifest exists (may be empty when all 
disable() calls are commented out)
+        Path disabledManifest = 
tempDir.resolve("META-INF/oal-disabled-sources.txt");
+        assertTrue(Files.exists(disabledManifest), "disabled sources manifest 
should exist");
+    }
+}

Reply via email to