Skip to content

Commit

Permalink
Add native-image.properties files to jars (#4208)
Browse files Browse the repository at this point in the history
This allows adding or removing entries from the set of classes that are 
initialized at native image build time without requiring changes to 
graalvm/native-build-tools. The new precompiled script plugin introduced
in this commit defines two Gradle tasks: `nativeImageProperties` and 
`validateNativeImageProperties`. The former is used for writing the 
`native-image.properties` file to the correct resource folder based (by
convention) on the `src/nativeImage/initialize-at-build-time` file in 
each affected subproject. The latter validates that all listed classes
exist and acts as a safeguard when classes are renamed or deleted.

Resolves #4207.
  • Loading branch information
marcphilipp authored Dec 20, 2024
1 parent aa922c7 commit 7593ded
Show file tree
Hide file tree
Showing 24 changed files with 187 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import junitbuild.graalvm.NativeImagePropertiesExtension
import java.util.zip.ZipFile

plugins {
`java-library`
}

val extension = extensions.create<NativeImagePropertiesExtension>("nativeImageProperties").apply {
val resourceFile: RegularFile = layout.projectDirectory.file("src/nativeImage/initialize-at-build-time")
if (resourceFile.asFile.exists()) {
initializeAtBuildTime.convention(providers.fileContents(resourceFile).asText.map { it.trim().lines() })
} else {
initializeAtBuildTime.empty()
}
initializeAtBuildTime.finalizeValueOnRead()
}

val outputDir = layout.buildDirectory.dir("resources/nativeImage")

val propertyFileTask = tasks.register<WriteProperties>("nativeImageProperties") {
destinationFile = outputDir.map { it.file("META-INF/native-image/${project.group}/${project.name}/native-image.properties") }
// see https://www.graalvm.org/latest/reference-manual/native-image/overview/BuildConfiguration/#configuration-file-format
property("Args", extension.initializeAtBuildTime.map {
if (it.isEmpty()) {
""
} else {
"--initialize-at-build-time=${it.joinToString(",")}"
}
})
}

val validationTask = tasks.register("validateNativeImageProperties") {
dependsOn(tasks.jar)
doLast {
val zipEntries = ZipFile(tasks.jar.get().archiveFile.get().asFile).use { zipFile ->
zipFile.entries().asSequence().map { it.name }.filter { it.endsWith(".class") }.toSet()
}
val missingClasses = extension.initializeAtBuildTime.get().filter { className ->
!zipEntries.contains("${className.replace('.', '/')}.class")
}
if (missingClasses.isNotEmpty()) {
throw GradleException("The following classes were specified as initialize-at-build-time but do not exist (you should probably remove them from nativeImageProperties.initializeAtBuildTime):\n${missingClasses.joinToString("\n- ", "- ")}")
}
}
}

tasks.check {
dependsOn(validationTask)
}

sourceSets {
main {
output.dir(propertyFileTask.map { outputDir })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package junitbuild.graalvm

import org.gradle.api.provider.SetProperty

abstract class NativeImagePropertiesExtension {
abstract val initializeAtBuildTime: SetProperty<String>
}
1 change: 1 addition & 0 deletions junit-jupiter-api/junit-jupiter-api.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("junitbuild.kotlin-library-conventions")
id("junitbuild.code-generator")
id("junitbuild.native-image-properties")
`java-test-fixtures`
}

Expand Down
4 changes: 4 additions & 0 deletions junit-jupiter-api/src/nativeImage/initialize-at-build-time
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
org.junit.jupiter.api.DisplayNameGenerator$Standard
org.junit.jupiter.api.TestInstance$Lifecycle
org.junit.jupiter.api.condition.OS
org.junit.jupiter.api.extension.ConditionEvaluationResult
1 change: 1 addition & 0 deletions junit-jupiter-engine/junit-jupiter-engine.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("junitbuild.kotlin-library-conventions")
id("junitbuild.native-image-properties")
`java-test-fixtures`
}

Expand Down
22 changes: 22 additions & 0 deletions junit-jupiter-engine/src/nativeImage/initialize-at-build-time
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
org.junit.jupiter.engine.JupiterTestEngine
org.junit.jupiter.engine.config.CachingJupiterConfiguration
org.junit.jupiter.engine.config.DefaultJupiterConfiguration
org.junit.jupiter.engine.config.EnumConfigurationParameterConverter
org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter
org.junit.jupiter.engine.descriptor.ClassTestDescriptor
org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor
org.junit.jupiter.engine.descriptor.DynamicDescendantFilter
org.junit.jupiter.engine.descriptor.ExclusiveResourceCollector$1
org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor
org.junit.jupiter.engine.descriptor.JupiterTestDescriptor
org.junit.jupiter.engine.descriptor.JupiterTestDescriptor$1
org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor
org.junit.jupiter.engine.descriptor.NestedClassTestDescriptor
org.junit.jupiter.engine.descriptor.TestFactoryTestDescriptor
org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor
org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor
org.junit.jupiter.engine.execution.ConditionEvaluator
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall
org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall$VoidMethodInterceptorCall
org.junit.jupiter.engine.execution.InvocationInterceptorChain
1 change: 1 addition & 0 deletions junit-jupiter-params/junit-jupiter-params.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id("junitbuild.kotlin-library-conventions")
id("junitbuild.shadow-conventions")
id("junitbuild.jmh-conventions")
id("junitbuild.native-image-properties")
}

description = "JUnit Jupiter Params"
Expand Down
2 changes: 2 additions & 0 deletions junit-jupiter-params/src/nativeImage/initialize-at-build-time
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.junit.jupiter.params.provider.EnumSource$Mode
org.junit.jupiter.params.provider.EnumSource$Mode$Validator
1 change: 1 addition & 0 deletions junit-platform-commons/junit-platform-commons.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import junitbuild.java.UpdateJarAction
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.java-multi-release-sources")
id("junitbuild.native-image-properties")
`java-test-fixtures`
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
org.junit.platform.commons.util.StringUtils
org.junit.platform.commons.logging.LoggerFactory$DelegatingLogger
org.junit.platform.commons.logging.LoggerFactory
org.junit.platform.commons.util.ReflectionUtils
org.junit.platform.commons.util.LruCache
1 change: 1 addition & 0 deletions junit-platform-engine/junit-platform-engine.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.native-image-properties")
`java-test-fixtures`
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
org.junit.platform.engine.TestDescriptor$Type
org.junit.platform.engine.UniqueId
org.junit.platform.engine.UniqueId$Segment
org.junit.platform.engine.UniqueIdFormat
org.junit.platform.engine.support.descriptor.ClassSource
org.junit.platform.engine.support.descriptor.MethodSource
org.junit.platform.engine.support.hierarchical.Node$ExecutionMode
1 change: 1 addition & 0 deletions junit-platform-launcher/junit-platform-launcher.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.native-image-properties")
`java-test-fixtures`
}

Expand Down
19 changes: 19 additions & 0 deletions junit-platform-launcher/src/nativeImage/initialize-at-build-time
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
org.junit.platform.launcher.LauncherSessionListener$1
org.junit.platform.launcher.TestIdentifier
org.junit.platform.launcher.core.DefaultLauncher
org.junit.platform.launcher.core.DefaultLauncherConfig
org.junit.platform.launcher.core.EngineDiscoveryOrchestrator
org.junit.platform.launcher.core.EngineExecutionOrchestrator
org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider
org.junit.platform.launcher.core.InternalTestPlan
org.junit.platform.launcher.core.LauncherConfig
org.junit.platform.launcher.core.LauncherConfigurationParameters
org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$1
org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$2
org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$3
org.junit.platform.launcher.core.LauncherConfigurationParameters$ParameterProvider$4
org.junit.platform.launcher.core.LauncherDiscoveryResult
org.junit.platform.launcher.core.LauncherListenerRegistry
org.junit.platform.launcher.core.ListenerRegistry
org.junit.platform.launcher.core.SessionPerRequestLauncher
org.junit.platform.launcher.listeners.UniqueIdTrackingListener
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.native-image-properties")
id("junitbuild.shadow-conventions")
`java-test-fixtures`
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener
org.junit.platform.reporting.shadow.org.opentest4j.reporting.events.api.DocumentWriter$1
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.native-image-properties")
}

description = "JUnit Platform Suite Engine"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
org.junit.platform.suite.engine.SuiteEngineDescriptor
org.junit.platform.suite.engine.SuiteLauncher
org.junit.platform.suite.engine.SuiteTestDescriptor
org.junit.platform.suite.engine.SuiteTestEngine
1 change: 1 addition & 0 deletions junit-vintage-engine/junit-vintage-engine.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("junitbuild.java-library-conventions")
id("junitbuild.junit4-compatibility")
id("junitbuild.native-image-properties")
id("junitbuild.testing-conventions")
`java-test-fixtures`
groovy
Expand Down
5 changes: 5 additions & 0 deletions junit-vintage-engine/src/nativeImage/initialize-at-build-time
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
org.junit.vintage.engine.VintageTestEngine
org.junit.vintage.engine.descriptor.RunnerTestDescriptor
org.junit.vintage.engine.descriptor.VintageEngineDescriptor
org.junit.vintage.engine.support.UniqueIdReader
org.junit.vintage.engine.support.UniqueIdStringifier
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {

val jupiterVersion: String by project
val platformVersion: String by project
val vintageVersion: String by project

repositories {
maven { url = uri(file(System.getProperty("maven.repo"))) }
Expand All @@ -13,11 +14,16 @@ repositories {

dependencies {
testImplementation("org.junit.jupiter:junit-jupiter:$jupiterVersion")
testImplementation("junit:junit:4.13.2")
testImplementation("org.junit.platform:junit-platform-suite:$platformVersion")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$vintageVersion")
testRuntimeOnly("org.junit.platform:junit-platform-reporting:$platformVersion")
}

tasks.test {
useJUnitPlatform()
useJUnitPlatform {
includeEngines("junit-platform-suite")
}

val outputDir = reports.junitXml.outputLocation
jvmArgumentProviders += CommandLineArgumentProvider {
Expand All @@ -31,8 +37,7 @@ tasks.test {
graalvmNative {
binaries {
named("test") {
// TODO #3040 Add to native-image.properties
buildArgs.add("--initialize-at-build-time=org.junit.platform.launcher.core.HierarchicalOutputDirectoryProvider")
buildArgs.add("--strict-image-heap")
buildArgs.add("-H:+ReportExceptionStackTraces")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package com.example.project;

import org.junit.platform.suite.api.*;

@Suite
@SelectPackages("com.example.project")
public class GraalvmSuite {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package com.example.project;

import org.junit.Test;

public class VintageTests {
@Test
public void test() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ void runsTestsInNativeImage(@TempDir Path workspace, @FilePrefix("gradle") Outpu
.anyMatch(line -> line.contains("CalculatorTests > 1 + 1 = 2 SUCCESSFUL")) //
.anyMatch(line -> line.contains("CalculatorTests > 1 + 100 = 101 SUCCESSFUL")) //
.anyMatch(line -> line.contains("ClassLevelAnnotationTests$Inner > test() SUCCESSFUL")) //
.anyMatch(line -> line.contains("com.example.project.VintageTests > test SUCCESSFUL")) //
.anyMatch(line -> line.contains("BUILD SUCCESSFUL"));
}
}

0 comments on commit 7593ded

Please sign in to comment.