diff --git a/.project b/.project
index a201aca..7972f00 100644
--- a/.project
+++ b/.project
@@ -10,11 +10,6 @@
-
- org.eclipse.buildship.core.gradleprojectbuilder
-
-
-
org.eclipse.m2e.core.maven2Builder
@@ -24,7 +19,6 @@
org.eclipse.jdt.core.javanature
org.eclipse.m2e.core.maven2Nature
- org.eclipse.buildship.core.gradleprojectnature
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 8a069e1..0000000
--- a/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-
-CUR_DIR := $(abspath $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))))
-
-build-MarketStoreProductsGetFunction:
- cd $(CUR_DIR) && ./gradlew build
- cp $(CUR_DIR)/build/graalvm/server $(ARTIFACTS_DIR)
- cp $(CUR_DIR)/bootstrap $(ARTIFACTS_DIR)
diff --git a/README.md b/README.md
index 69a0c00..e61068e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,40 @@
+## Build and package
+
+Use Maven with JDK 25 (the Java version declared in `pom.xml`):
+
+```sh
+mvn clean verify
+```
+
+The executable shaded JAR is `target/lambda-native-.jar`, with the
+version taken from `pom.xml`. Customers can use this JAR with a compatible
+GraalVM Native Image installation to build their own native binary.
+
+To build the Linux native binary and AWS Lambda custom-runtime ZIP, install
+Docker and `zip`, then run:
+
+```sh
+bash native-image-linux.sh
+```
+
+The script runs `mvn clean install`, reads the JAR name and Java version from
+Maven, and uses `ghcr.io/graalvm/native-image-community:`.
+Set `GRAALVM_IMAGE` to select a pinned image compatible with that Java version.
+Docker builds for its host architecture; use a host matching your Lambda
+architecture and verify the resulting binary against your chosen Lambda runtime.
+The ZIP is `lambda-native-custom-runtime.zip` and contains executable
+`lambda-native` and `bootstrap` files. HTTP and HTTPS protocols are enabled.
+Build or packaging failures stop the script with a nonzero exit status.
+
+Run the native-build script regression tests separately with Python 3 and `zip`:
+
+```sh
+python3 tests/test_native_image_script.py
+```
+
+These tests stub Maven and Docker to check packaging and failure handling.
+They are not run by Maven or the current CI workflow.
+
### Test with Main
We have provided a Main class in the src folder for testing all the middleware handlers with a sample request. You can run the Main class within your IDE with debug mode to see how each middleware handler works.
diff --git a/bootstrap b/bootstrap
index 58dbd30..64f143e 100644
--- a/bootstrap
+++ b/bootstrap
@@ -1,3 +1,3 @@
#!/bin/sh
-set -euo pipefail
+set -eu
./lambda-native -Dlight-4j-config-dir=/opt -Dlogback.configurationFile=/opt/logback.xml
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 4196ecc..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,49 +0,0 @@
-
-plugins {
- id 'java'
- id "com.github.johnrengelman.shadow" version "5.2.0"
-}
-
-repositories {
- mavenCentral()
- mavenLocal()
- maven {
- url 'https://oss.sonatype.org/content/repositories/snapshots/'
- }
-}
-
-dependencies {
- implementation "com.networknt:custom-runtime:$light4jVersion"
- implementation "org.slf4j:slf4j-api:$slf4jVersion"
- implementation "ch.qos.logback:logback-classic:$logbackVersion"
- implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
- implementation "com.amazonaws:aws-lambda-java-core:$lambdaCoreVersion"
- implementation "com.amazonaws:aws-lambda-java-events:$lambdaEventsVersion"
- testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
- testImplementation "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
-}
-
-test {
- useJUnitPlatform()
-}
-
-jar {
- manifest {
- attributes 'Main-Class': 'com.networknt.aws.lambda.Runtime'
- }
-}
-
-task buildGraalvmImage {
- inputs.files("${project.projectDir}/src/main", configurations.compileClasspath)
- outputs.upToDateWhen {file("${buildDir}/graalvm/server").exists()}
- outputs.file file("${buildDir}/graalvm/server")
-
- doLast {
- exec {
- commandLine "bash", "-c", "chmod +x build_graalvm.sh; chmod +x bootstrap; ./build_graalvm.sh"
- }
- }
-}
-
-buildGraalvmImage.dependsOn shadowJar, test
-build.dependsOn buildGraalvmImage
diff --git a/build_graalvm.sh b/build_graalvm.sh
deleted file mode 100644
index e426e19..0000000
--- a/build_graalvm.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-
-#!/bin/bash
-
-docker run --rm --name graal -v $(pwd):/working springci/graalvm-ce:master-java11 \
- /bin/bash -c "native-image \
- --enable-url-protocols=http,https \
- --no-fallback \
- --allow-incomplete-classpath \
- --enable-all-security-services \
- -H:ReflectionConfigurationFiles=/working/reflect.json \
- -H:ResourceConfigurationFiles=/working/resource-config.json \
- -H:+ReportExceptionStackTraces \
- -jar /working/build/libs/MarketStoreProductsGetFunction-all.jar \
- ; \
- cp MarketStoreProductsGetFunction-all /working/build/graalvm/server"
-
-mkdir -p build/graalvm
-if [ ! -f "build/graalvm/server" ]; then
- echo "there was an error building graalvm image"
- exit 1
-fi
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 4e4cbd5..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-# Versions of Frequently used Libraries
-light4jVersion=2.1.34-SNAPSHOT
-lambdaCoreVersion=1.4.0
-lambdaEventsVersion=3.16.1
-jacksonVersion=2.17.0
-slf4jVersion=2.0.20
-logbackVersion=1.6.3
-junitVersion=5.11.4
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index ac98e42..0000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Thu Jan 08 17:23:48 EST 2026
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
deleted file mode 100755
index 9aa616c..0000000
--- a/gradlew
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/usr/bin/env bash
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn ( ) {
- echo "$*"
-}
-
-die ( ) {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then
- cd "$(dirname "$0")"
-fi
-
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100644
index f955316..0000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,84 +0,0 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/native-image-linux.sh b/native-image-linux.sh
index 08772f3..99eb3b5 100755
--- a/native-image-linux.sh
+++ b/native-image-linux.sh
@@ -1,42 +1,46 @@
-PROJECT_NAME=lambda-native
-PROJECT_VERSION=2.1.34-SNAPSHOT
+#!/usr/bin/env bash
+set -euo pipefail
-# mvn clean might fail due to permission issue on Linux, so remove target with root.
-sudo rm -rf target;
-# Generate Jar file
-mvn clean install;
-echo "Jar file generated successfully";
-echo $(pwd);
+cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
-# Generate Native Image
-docker run --rm --name graal -v $(pwd):/${PROJECT_NAME} springci/graalvm-ce:master-java11 \
- /bin/bash -c "echo 'Building native image'; \
- native-image \
- -H:EnableURLProtocols=http \
- --no-fallback \
- --verbose \
- --trace-class-initialization=ch.qos.logback.classic.Logger \
- --trace-object-instantiation=ch.qos.logback.core.AsyncAppenderBase$Worker \
- --initialize-at-build-time=org.slf4j.LoggerFactory,org.slf4j.MDC,ch.qos.logback \
- --initialize-at-run-time=io.netty \
- --allow-incomplete-classpath \
- --enable-all-security-services \
- -H:ReflectionConfigurationFiles=/${PROJECT_NAME}/reflect.json \
- -H:ResourceConfigurationFiles=/${PROJECT_NAME}/resource-config.json \
- -H:+ReportExceptionStackTraces \
- -jar /${PROJECT_NAME}/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar \
- ; \
- mkdir /${PROJECT_NAME}/target/custom-runtime \
- ; \
- cp ${PROJECT_NAME}-${PROJECT_VERSION} /${PROJECT_NAME}/target/custom-runtime/${PROJECT_NAME}";
+# Build the shaded JAR, then obtain its name and Java level from Maven.
+mvn clean install
+metadata_dir=$(mktemp -d)
+trap 'rm -rf -- "$metadata_dir"' EXIT
+mvn -q help:evaluate -Dexpression=project.build.finalName \
+ -Doutput="$metadata_dir/final-name"
+mvn -q help:evaluate -Dexpression=maven.compiler.target \
+ -Doutput="$metadata_dir/java-version"
+jar_name=$(cat "$metadata_dir/final-name")
+java_version=$(cat "$metadata_dir/java-version")
+[[ "$java_version" =~ ^[0-9]+$ ]] || { echo "Invalid Maven compiler target: $java_version" >&2; exit 1; }
+test -s "target/$jar_name.jar"
-# Copy the bootstrap to the custom-runtime
-sudo cp bootstrap target/custom-runtime/bootstrap;
+# Override with a pinned GraalVM image compatible with the POM's Java level.
+graalvm_image=${GRAALVM_IMAGE:-ghcr.io/graalvm/native-image-community:$java_version}
+mkdir -p target/custom-runtime
+docker run --rm --user "$(id -u):$(id -g)" \
+ -v "$PWD:/workspace" -w /workspace --entrypoint native-image \
+ "$graalvm_image" \
+ --enable-url-protocols=http,https \
+ --no-fallback \
+ --verbose \
+ --initialize-at-build-time=org.slf4j,ch.qos.logback \
+ --initialize-at-run-time=io.netty \
+ -H:ReflectionConfigurationFiles=/workspace/reflect.json \
+ -H:ResourceConfigurationFiles=/workspace/resource-config.json \
+ -H:+ReportExceptionStackTraces \
+ -jar "/workspace/target/$jar_name.jar" \
+ -o /workspace/target/custom-runtime/lambda-native
-# Make bootstrap executable
-sudo chmod +x target/custom-runtime/bootstrap;
-
-# Zip
-rm $PROJECT_NAME-custom-runtime.zip
-cd target/custom-runtime || exit
-zip -X -r ../../$PROJECT_NAME-custom-runtime.zip .
+# Never package a bootstrap-only archive after a failed native build.
+test -s target/custom-runtime/lambda-native
+test -x target/custom-runtime/lambda-native
+cp bootstrap target/custom-runtime/bootstrap
+chmod +x target/custom-runtime/bootstrap
+(
+ cd target/custom-runtime
+ zip -X ../lambda-native-custom-runtime.zip lambda-native bootstrap
+)
+mv target/lambda-native-custom-runtime.zip lambda-native-custom-runtime.zip
+echo "Created $PWD/lambda-native-custom-runtime.zip"
diff --git a/tests/test_native_image_script.py b/tests/test_native_image_script.py
new file mode 100644
index 0000000..f6faad3
--- /dev/null
+++ b/tests/test_native_image_script.py
@@ -0,0 +1,108 @@
+"""Script boundary tests; Maven and Docker are stubbed, ZIP packaging is real."""
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+import unittest
+from zipfile import ZipFile
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+class NativeImageScriptTest(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name)
+ self.bin = self.root / "bin"
+ self.bin.mkdir()
+ for name in ("native-image-linux.sh", "bootstrap"):
+ shutil.copy(ROOT / name, self.root / name)
+ self.stub("mvn", '''#!/usr/bin/env bash
+set -eu
+if [[ "$1" == clean ]]; then
+ [[ "${FAIL_STAGE:-}" != maven ]] || exit 21
+ mkdir -p target
+ printf jar > target/customer-runtime-9.9.jar
+else
+ for arg in "$@"; do
+ case "$arg" in
+ -Dexpression=*) expression=${arg#*=} ;;
+ -Doutput=*) output=${arg#*=} ;;
+ esac
+ done
+ case "$expression" in
+ project.build.finalName) printf customer-runtime-9.9 > "$output" ;;
+ maven.compiler.target) printf 25 > "$output" ;;
+ *) exit 22 ;;
+ esac
+fi
+''')
+ self.stub("docker", '''#!/usr/bin/env bash
+set -eu
+printf '%s\\n' "$@" > docker-args
+[[ "${FAIL_STAGE:-}" != docker ]] || exit 23
+[[ "${FAIL_STAGE:-}" != missing ]] || exit 0
+printf '#!/bin/sh\\nexit 0\\n' > target/custom-runtime/lambda-native
+chmod +x target/custom-runtime/lambda-native
+''')
+ self.env = dict(os.environ, PATH=f"{self.bin}:{os.environ['PATH']}")
+ self.env.pop("GRAALVM_IMAGE", None)
+
+ def stub(self, name, source):
+ path = self.bin / name
+ path.write_text(source)
+ path.chmod(0o755)
+
+ def run_script(self, failure=""):
+ return subprocess.run(
+ ["bash", str(self.root / "native-image-linux.sh")],
+ cwd="/tmp", env=dict(self.env, FAIL_STAGE=failure),
+ capture_output=True, text=True,
+ )
+
+ def test_packages_pom_named_jar_with_https_and_java_25(self):
+ result = self.run_script()
+ self.assertEqual(result.returncode, 0, result.stderr)
+ args = (self.root / "docker-args").read_text().splitlines()
+ self.assertIn("ghcr.io/graalvm/native-image-community:25", args)
+ self.assertIn("/workspace/target/customer-runtime-9.9.jar", args)
+ self.assertIn("--enable-url-protocols=http,https", args)
+ with ZipFile(self.root / "lambda-native-custom-runtime.zip") as archive:
+ self.assertEqual(set(archive.namelist()), {"lambda-native", "bootstrap"})
+ for info in archive.infolist():
+ self.assertTrue((info.external_attr >> 16) & 0o111)
+ result = subprocess.run(
+ ["sh", "bootstrap"], cwd=self.root / "target/custom-runtime",
+ capture_output=True, text=True,
+ )
+ self.assertEqual(result.returncode, 0, result.stderr)
+
+ def test_image_override(self):
+ self.env["GRAALVM_IMAGE"] = "example/pinned-graalvm:25"
+ self.assertEqual(self.run_script().returncode, 0)
+ self.assertIn("example/pinned-graalvm:25", (self.root / "docker-args").read_text())
+
+ def test_failures_never_publish_bootstrap_only_zip(self):
+ for stage in ("maven", "docker", "missing"):
+ with self.subTest(stage=stage):
+ shutil.rmtree(self.root / "target", ignore_errors=True)
+ result = self.run_script(stage)
+ self.assertNotEqual(result.returncode, 0)
+ self.assertFalse((self.root / "lambda-native-custom-runtime.zip").exists())
+ self.assertNotIn("Created ", result.stdout)
+
+ def test_zip_failure_preserves_previous_package(self):
+ self.stub("zip", "#!/bin/sh\nexit 24\n")
+ previous = self.root / "lambda-native-custom-runtime.zip"
+ previous.write_bytes(b"previous package")
+ result = self.run_script()
+ self.assertNotEqual(result.returncode, 0)
+ self.assertEqual(previous.read_bytes(), b"previous package")
+ self.assertNotIn("Created ", result.stdout)
+
+
+if __name__ == "__main__":
+ unittest.main()