diff --git a/vadl/build.gradle.kts b/vadl/build.gradle.kts
index 08b320c79..a8d44214f 100644
--- a/vadl/build.gradle.kts
+++ b/vadl/build.gradle.kts
@@ -39,6 +39,7 @@ dependencies {
implementation("org.apache.commons:commons-text:1.10.0")
implementation("io.github.rascmatt:z3-bootstrap:1.0.0")
+ testImplementation("io.github.kper:buildkitcli:0.12.0")
testCompileOnly(project(":java-annotations"))
testImplementation(platform("org.junit:junit-bom:5.11.4"))
diff --git a/vadl/main/resources/templates/lcb/LcbMakeFile b/vadl/main/resources/templates/lcb/LcbMakeFile
index 1114258d6..7ef0ed868 100644
--- a/vadl/main/resources/templates/lcb/LcbMakeFile
+++ b/vadl/main/resources/templates/lcb/LcbMakeFile
@@ -46,9 +46,7 @@ build:
-DLLVM_PARALLEL_COMPILE_JOBS=$(LLVM_PARALLEL_COMPILE_JOBS) \
-DLLVM_PARALLEL_LINK_JOBS=$(LLVM_PARALLEL_LINK_JOBS) \
-DLLVM_CCACHE_BUILD=$(LLVM_CCACHE_BUILD) \
- -DLLVM_ENABLE_ASSERTIONS=$(LLVM_ENABLE_ASSERTIONS) \
- -DCMAKE_C_COMPILER_LAUNCHER=sccache \
- -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
+ -DLLVM_ENABLE_ASSERTIONS=$(LLVM_ENABLE_ASSERTIONS)
cmake --build $(LLVM_SOURCE_PATH)/build
build-llc:
diff --git a/vadl/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/vadl/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 000000000..1e2e2239f
--- /dev/null
+++ b/vadl/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+vadl.RemovingDockerImagesTeardown
\ No newline at end of file
diff --git a/vadl/test/resources/images/lcb_execution_test_aarch64temp/Dockerfile b/vadl/test/resources/images/lcb_execution_test_aarch64temp/Dockerfile
index 26a2d06ff..bde31950d 100644
--- a/vadl/test/resources/images/lcb_execution_test_aarch64temp/Dockerfile
+++ b/vadl/test/resources/images/lcb_execution_test_aarch64temp/Dockerfile
@@ -1,3 +1,4 @@
+# syntax=docker/dockerfile:1.7
FROM ghcr.io/openvadl/lcb-execution-test-aarch64-base-image@sha256:78d418708da2656805f7bac1204a1adc55f5a72f749b6d2100e275e176c816cf
ARG TARGET
ARG SCCACHE_REDIS_ENDPOINT
@@ -5,6 +6,7 @@ ARG UPSTREAM_BUILD_TARGET
ARG UPSTREAM_CLANG_TARGET
ARG SPIKE_TARGET
ARG ABI
+LABEL key=VADL_TEST_CONTAINER
WORKDIR /llvm/build
ENV TARGET=${TARGET}
ENV UPSTREAM_CLANG_TARGET=${UPSTREAM_CLANG_TARGET}
@@ -12,9 +14,10 @@ ENV LLVM_UPSTREAM_TARGETS=${UPSTREAM_BUILD_TARGET}
ENV SCCACHE_REDIS_ENDPOINT=${SCCACHE_REDIS_ENDPOINT}
ENV SPIKE_TARGET=${SPIKE_TARGET}
ENV ABI=${ABI}
+ENV SCCACHE_DIR=/root/.cache/sccache
ENV PATH=/opt/aarch64/bin:/opt/qemu/bin:$PATH
COPY . /src
WORKDIR /src
-RUN make && sccache -s
+RUN --mount=type=cache,target=/root/.cache/sccache make && sccache -s
RUN mkdir /output
-CMD sh /work/compile.sh
\ No newline at end of file
+CMD sh /work/compile.sh
diff --git a/vadl/test/resources/images/lcb_execution_test_rv32im/Dockerfile b/vadl/test/resources/images/lcb_execution_test_rv32im/Dockerfile
index 57f499002..ac25babaa 100644
--- a/vadl/test/resources/images/lcb_execution_test_rv32im/Dockerfile
+++ b/vadl/test/resources/images/lcb_execution_test_rv32im/Dockerfile
@@ -1,3 +1,4 @@
+# syntax=docker/dockerfile:1.7
FROM ghcr.io/openvadl/lcb-execution-test-rv32im-base-image@sha256:cad1819961bae5b736649804f1ec1892b4a91966c3bd5460b343b12dfabe03af
ARG TARGET
ARG SCCACHE_REDIS_ENDPOINT
@@ -5,6 +6,7 @@ ARG UPSTREAM_BUILD_TARGET
ARG UPSTREAM_CLANG_TARGET
ARG SPIKE_TARGET
ARG ABI
+LABEL key=VADL_TEST_CONTAINER
WORKDIR /llvm/build
ENV TARGET=${TARGET}
ENV UPSTREAM_CLANG_TARGET=${UPSTREAM_CLANG_TARGET}
@@ -12,9 +14,10 @@ ENV LLVM_UPSTREAM_TARGETS=${UPSTREAM_BUILD_TARGET}
ENV SCCACHE_REDIS_ENDPOINT=${SCCACHE_REDIS_ENDPOINT}
ENV SPIKE_TARGET=${SPIKE_TARGET}
ENV ABI=${ABI}
+ENV SCCACHE_DIR=/root/.cache/sccache
ENV PATH=/opt/riscv/bin:/opt/qemu/bin:$PATH
COPY . /src
WORKDIR /src
-RUN make && sccache -s
+RUN --mount=type=cache,target=/root/.cache/sccache make && sccache -s
RUN mkdir /output
-CMD sh /work/compile.sh
\ No newline at end of file
+CMD sh /work/compile.sh
diff --git a/vadl/test/resources/images/lcb_execution_test_rv64im/Dockerfile b/vadl/test/resources/images/lcb_execution_test_rv64im/Dockerfile
index 50f394be3..dd1647b75 100644
--- a/vadl/test/resources/images/lcb_execution_test_rv64im/Dockerfile
+++ b/vadl/test/resources/images/lcb_execution_test_rv64im/Dockerfile
@@ -1,3 +1,4 @@
+# syntax=docker/dockerfile:1.7
FROM ghcr.io/openvadl/lcb-execution-test-rv64im-base-image@sha256:45115bb0a22747dd8d347051d2bcb5ee084f568c90afbdf71ef738b0849afb96
ARG TARGET
ARG SCCACHE_REDIS_ENDPOINT
@@ -5,6 +6,7 @@ ARG UPSTREAM_BUILD_TARGET
ARG UPSTREAM_CLANG_TARGET
ARG SPIKE_TARGET
ARG ABI
+LABEL key=VADL_TEST_CONTAINER
WORKDIR /llvm/build
ENV TARGET=${TARGET}
ENV UPSTREAM_CLANG_TARGET=${UPSTREAM_CLANG_TARGET}
@@ -12,9 +14,10 @@ ENV LLVM_UPSTREAM_TARGETS=${UPSTREAM_BUILD_TARGET}
ENV SCCACHE_REDIS_ENDPOINT=${SCCACHE_REDIS_ENDPOINT}
ENV SPIKE_TARGET=${SPIKE_TARGET}
ENV ABI=${ABI}
+ENV SCCACHE_DIR=/root/.cache/sccache
ENV PATH=/opt/riscv/bin:/opt/qemu/bin:$PATH
COPY . /src
WORKDIR /src
-RUN make && sccache -s
+RUN --mount=type=cache,target=/root/.cache/sccache make && sccache -s
RUN mkdir /output
-CMD sh /work/compile.sh
\ No newline at end of file
+CMD sh /work/compile.sh
diff --git a/vadl/test/resources/images/lcb_execution_test_varrisc/Dockerfile b/vadl/test/resources/images/lcb_execution_test_varrisc/Dockerfile
index 663def24e..af0e8d0ea 100644
--- a/vadl/test/resources/images/lcb_execution_test_varrisc/Dockerfile
+++ b/vadl/test/resources/images/lcb_execution_test_varrisc/Dockerfile
@@ -1,3 +1,4 @@
+# syntax=docker/dockerfile:1.7
FROM ghcr.io/openvadl/llvm19-base@sha256:743dc3df521b6721ee4a74126aff198f40b6fc585d2c06b76869cabbb3d2c015
WORKDIR /work
@@ -9,6 +10,7 @@ ARG UPSTREAM_BUILD_TARGET
ARG UPSTREAM_CLANG_TARGET
ARG SPIKE_TARGET
ARG ABI
+LABEL key=VADL_TEST_CONTAINER
WORKDIR /llvm/build
@@ -18,7 +20,8 @@ ENV LLVM_UPSTREAM_TARGETS=${UPSTREAM_BUILD_TARGET}
ENV SCCACHE_REDIS_ENDPOINT=${SCCACHE_REDIS_ENDPOINT}
ENV SPIKE_TARGET=${SPIKE_TARGET}
ENV ABI=${ABI}
+ENV SCCACHE_DIR=/root/.cache/sccache
COPY . /src
WORKDIR /src
-RUN make && sccache -s
-RUN mkdir /output
\ No newline at end of file
+RUN --mount=type=cache,target=/root/.cache/sccache make && sccache -s
+RUN mkdir /output
diff --git a/vadl/test/resources/junit-platform.properties b/vadl/test/resources/junit-platform.properties
new file mode 100644
index 000000000..b059a65dc
--- /dev/null
+++ b/vadl/test/resources/junit-platform.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
\ No newline at end of file
diff --git a/vadl/test/resources/logback-test.xml b/vadl/test/resources/logback-test.xml
index 1358e8c28..f3e6f679a 100644
--- a/vadl/test/resources/logback-test.xml
+++ b/vadl/test/resources/logback-test.xml
@@ -9,6 +9,7 @@
+
diff --git a/vadl/test/vadl/BuildkitDockerImage.java b/vadl/test/vadl/BuildkitDockerImage.java
new file mode 100644
index 000000000..e435fd966
--- /dev/null
+++ b/vadl/test/vadl/BuildkitDockerImage.java
@@ -0,0 +1,264 @@
+// SPDX-FileCopyrightText : © 2026 TU Wien
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package vadl;
+
+import io.github.kper.buildkitcli.lib.BuildLog;
+import io.github.kper.buildkitcli.lib.BuildOutputMode;
+import io.github.kper.buildkitcli.lib.BuildProgressListener;
+import io.github.kper.buildkitcli.lib.BuildResult;
+import io.github.kper.buildkitcli.lib.BuildVertex;
+import io.github.kper.buildkitcli.lib.BuildWarning;
+import io.github.kper.buildkitcli.lib.BuildkitClient;
+import io.github.kper.buildkitcli.lib.BuildkitConnectionConfig;
+import io.github.kper.buildkitcli.lib.BuildkitException;
+import io.github.kper.buildkitcli.lib.DockerfileBuildRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
+
+/**
+ * A small test helper that preserves the subset of the {@code ImageFromDockerfile} API used in
+ * this codebase while delegating image builds to buildkit.
+ */
+public final class BuildkitDockerImage {
+
+ private static final Logger logger = LoggerFactory.getLogger(BuildkitDockerImage.class);
+ private static final BuildkitConnectionConfig BUILDKIT_CONFIG =
+ BuildkitConnectionConfig.of("tcp://localhost:1234");
+
+ private final String imageName;
+ private final Map filesFromPath = new LinkedHashMap<>();
+ private final Map filesFromClasspath = new LinkedHashMap<>();
+ private final Map buildArgs = new LinkedHashMap<>();
+
+ private Path dockerfilePath;
+ private String dockerfileContents;
+ private String resolvedImageName;
+
+ public BuildkitDockerImage() {
+ this("vadl-test-" + UUID.randomUUID());
+ }
+
+ public BuildkitDockerImage(String imageName) {
+ this.imageName = imageName;
+ }
+
+ public BuildkitDockerImage withDockerfile(Path path) {
+ this.dockerfilePath = path.toAbsolutePath().normalize();
+ this.dockerfileContents = null;
+ return this;
+ }
+
+ public BuildkitDockerImage withDockerfileFromBuilder(Consumer consumer) {
+ var builder = new DockerfileBuilder();
+ consumer.accept(builder);
+ this.dockerfileContents = builder.build();
+ this.dockerfilePath = null;
+ return this;
+ }
+
+ public BuildkitDockerImage withFileFromPath(String path, Path source) {
+ filesFromPath.put(normalizeContextPath(path), source.toAbsolutePath().normalize());
+ return this;
+ }
+
+ public BuildkitDockerImage withFileFromClasspath(String path, String resourcePath) {
+ filesFromClasspath.put(normalizeContextPath(path), resourcePath);
+ return this;
+ }
+
+ public BuildkitDockerImage withBuildArg(String key, String value) {
+ buildArgs.put(key, value);
+ return this;
+ }
+
+ public synchronized String getDockerImageName() {
+ if (resolvedImageName != null) {
+ return resolvedImageName;
+ }
+
+ Path tempContext = null;
+ try {
+ Path contextDir;
+ Path dockerfile;
+
+ if (dockerfileContents != null) {
+ tempContext = Files.createTempDirectory("buildkit-context-");
+ stageSupplementalFiles(tempContext);
+ dockerfile = tempContext.resolve("Dockerfile");
+ Files.writeString(dockerfile, dockerfileContents);
+ contextDir = tempContext;
+ } else if (dockerfilePath != null) {
+ if (filesFromPath.isEmpty() && filesFromClasspath.isEmpty()) {
+ dockerfile = dockerfilePath;
+ contextDir = dockerfilePath.getParent();
+ } else {
+ tempContext = Files.createTempDirectory("buildkit-context-");
+ FileUtils.copyDirectory(dockerfilePath.getParent().toFile(), tempContext.toFile());
+ stageSupplementalFiles(tempContext);
+ dockerfile = tempContext.resolve(dockerfilePath.getFileName().toString());
+ contextDir = tempContext;
+ }
+ } else {
+ throw new IllegalStateException("No Dockerfile configured for image " + imageName);
+ }
+
+ buildImage(contextDir, dockerfile);
+ resolvedImageName = imageName;
+ return resolvedImageName;
+ } catch (IOException | InterruptedException e) {
+ throw new RuntimeException("Failed to build docker image " + imageName, e);
+ } finally {
+ if (tempContext != null) {
+ try {
+ FileUtils.deleteDirectory(tempContext.toFile());
+ } catch (IOException e) {
+ logger.warn("Could not delete temporary build context {}", tempContext, e);
+ }
+ }
+ }
+ }
+
+ private void buildImage(Path contextDir, Path dockerfile)
+ throws IOException, InterruptedException {
+ var requestBuilder = DockerfileBuildRequest.builder(contextDir, dockerfile, imageName)
+ .outputMode(BuildOutputMode.DOCKER);
+ buildArgs.forEach(requestBuilder::buildArg);
+
+ try (var client = new BuildkitClient(BUILDKIT_CONFIG)) {
+ var result = client.buildImage(requestBuilder.build(), new PrintingListener());
+ loadIntoDocker(result);
+ } catch (BuildkitException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void stageSupplementalFiles(Path contextDir) throws IOException {
+ for (var entry : filesFromPath.entrySet()) {
+ copyPath(entry.getValue(), contextDir.resolve(entry.getKey()));
+ }
+ for (var entry : filesFromClasspath.entrySet()) {
+ copyClasspathResource(entry.getValue(), contextDir.resolve(entry.getKey()));
+ }
+ }
+
+ private static void copyPath(Path source, Path destination) throws IOException {
+ if (Files.isDirectory(source)) {
+ FileUtils.copyDirectory(source.toFile(), destination.toFile());
+ return;
+ }
+
+ if (destination.getParent() != null) {
+ Files.createDirectories(destination.getParent());
+ }
+ Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private static void copyClasspathResource(String resourcePath, Path destination)
+ throws IOException {
+ URL resource = BuildkitDockerImage.class.getResource(resourcePath);
+ if (resource == null) {
+ throw new IllegalStateException("Could not find classpath resource " + resourcePath);
+ }
+
+ if ("file".equals(resource.getProtocol())) {
+ try {
+ copyPath(Path.of(resource.toURI()), destination);
+ return;
+ } catch (URISyntaxException e) {
+ throw new IOException("Invalid classpath resource URI for " + resourcePath, e);
+ }
+ }
+
+ if (destination.getParent() != null) {
+ Files.createDirectories(destination.getParent());
+ }
+ try (InputStream inputStream = resource.openStream()) {
+ Files.copy(inputStream, destination, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ private static String normalizeContextPath(String path) {
+ String normalized = path.replace('\\', '/');
+ while (normalized.startsWith("/")) {
+ normalized = normalized.substring(1);
+ }
+ while (normalized.startsWith("./")) {
+ normalized = normalized.substring(2);
+ }
+ if (normalized.isBlank()) {
+ throw new IllegalArgumentException("Context path must not be blank");
+ }
+ return normalized;
+ }
+
+ private static void loadIntoDocker(BuildResult result) throws IOException, InterruptedException {
+ Path exportedArchive = result.exportedArchive();
+ if (exportedArchive == null) {
+ throw new IllegalStateException("Build result did not include a docker archive");
+ }
+
+ try {
+ Process process = new ProcessBuilder("docker", "load", "-i", exportedArchive.toString())
+ .inheritIO()
+ .start();
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new IllegalStateException("docker load failed with exit code " + exitCode);
+ }
+ logger.info("Loading image was successful.");
+ } finally {
+ Files.deleteIfExists(exportedArchive);
+ }
+ }
+
+ static class PrintingListener implements BuildProgressListener {
+ private final Logger logger = LoggerFactory.getLogger(PrintingListener.class);
+
+ @Override
+ public void onVertex(BuildVertex vertex) {
+ if (!vertex.name().isBlank()) {
+ logger.info(vertex.name());
+ }
+ }
+
+ @Override
+ public void onLog(BuildLog log) {
+ String message = log.utf8Message();
+ if (!message.isBlank()) {
+ logger.info(message);
+ }
+ }
+
+ @Override
+ public void onWarning(BuildWarning warning) {
+ logger.warn("warning: {}", warning.shortMessage());
+ }
+ }
+}
diff --git a/vadl/test/vadl/DockerExecutionTest.java b/vadl/test/vadl/DockerExecutionTest.java
index fee88c614..21405688a 100644
--- a/vadl/test/vadl/DockerExecutionTest.java
+++ b/vadl/test/vadl/DockerExecutionTest.java
@@ -20,8 +20,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.github.dockerjava.api.DockerClient;
-import com.github.dockerjava.api.model.Mount;
-import com.github.dockerjava.api.model.MountType;
import com.google.errorprone.annotations.concurrent.LazyInit;
import java.io.File;
import java.io.FileOutputStream;
@@ -35,7 +33,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
@@ -51,9 +48,7 @@
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.output.Slf4jLogConsumer;
-import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.images.builder.Transferable;
-import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
import org.testcontainers.utility.MountableFile;
import org.testcontainers.utility.ThrowingFunction;
import vadl.utils.Pair;
@@ -65,9 +60,6 @@ public abstract class DockerExecutionTest extends AbstractTest {
@LazyInit
private static Network testNetwork;
- @Nullable
- private static RedisCache redisCache;
-
@BeforeAll
public static void beforeAll() {
testNetwork = Network.newNetwork();
@@ -76,11 +68,6 @@ public static void beforeAll() {
@AfterAll
public static void afterAll() {
- if (redisCache != null) {
- // this call will also persist the data cached in this test run
- redisCache.stop();
- }
-
testNetwork.close();
}
@@ -121,7 +108,7 @@ protected List assertStatusCodes(String resultPath)
* be copied to the host.
*/
protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
List> copyMappings,
String hostOutputPath,
String containerResultPath) {
@@ -129,6 +116,7 @@ protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
containerResultPath, null);
}
+
/**
* Starts a container and checks the status code for the exited container.
* It will copy the copy mappings into the container. After the container was
@@ -147,7 +135,7 @@ protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
* @param cmd overwrites the command which will be executed on startup.
*/
protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
- ImageFromDockerfile image,
+ String image,
List> copyMappings,
String hostOutputPath,
String containerResultPath,
@@ -165,6 +153,33 @@ protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
);
}
+ /**
+ * Starts a container and checks the status code for the exited container.
+ * It will copy the copy mappings into the container. After the container was
+ * executed it will copy a file back to read the result.
+ * It will assert that the status code is zero. If the check takes longer
+ * than 10 seconds or the status code is not zero then it will throw an
+ * exception.
+ *
+ * @param image is the docker image for the {@link GenericContainer}.
+ * @param copyMappings are mappings from the host to the container for the files which should
+ * be copied.
+ * @param hostOutputPath is the path where the {@code containerResultPath} should be copied
+ * to.
+ * @param containerResultPath is the path of a file which the container has computed and should
+ * be copied to the host.
+ * @param cmd overwrites the command which will be executed on startup.
+ */
+ protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
+ BuildkitDockerImage image,
+ List> copyMappings,
+ String hostOutputPath,
+ String containerResultPath,
+ @Nullable String cmd) {
+ runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(image.getDockerImageName(),
+ copyMappings, hostOutputPath, containerResultPath, cmd);
+ }
+
/**
* Starts a container and checks the status code for the exited container.
* It will write the given {@code content} into a temporary file. The
@@ -180,7 +195,7 @@ protected void runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(
* @throws IOException when the temp file is writable.
*/
protected void runContainerAndCopyInputIntoContainer(
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
String content,
String containerPath) throws IOException {
runContainer(image, (container) -> container
@@ -204,7 +219,34 @@ protected void runContainerAndCopyInputIntoContainer(
* @param cmd is the command which is executed.
*/
protected void runContainerAndCopyInputIntoContainer(
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
+ List> copyMappings,
+ Map environmentMappings,
+ String cmd) {
+ runContainerAndCopyInputIntoContainerAndCopyFromContainerToHost(image.getDockerImageName(),
+ copyMappings,
+ environmentMappings,
+ Collections.emptyList(),
+ cmd);
+ }
+
+
+ /**
+ * Starts a container and checks the status code for the exited container.
+ * It will write the given {@code content} into a temporary file. The
+ * temporary file requires a {@code prefix} and {@code suffix}.
+ * Copies the data from {@code copyMappings}. Additionally, it will
+ * set environment variables based on {@code environmentMappings}.
+ *
+ * @param image is the docker image for the {@link GenericContainer}.
+ * @param copyMappings is a list where each {@link Pair} indicates what should be copied
+ * from the host to the container.
+ * @param environmentMappings is a list where each entry defines an environment variable which
+ * will be set in the container.
+ * @param cmd is the command which is executed.
+ */
+ protected void runContainerAndCopyInputIntoContainer(
+ String image,
List> copyMappings,
Map environmentMappings,
String cmd) {
@@ -232,7 +274,7 @@ protected void runContainerAndCopyInputIntoContainer(
* @param cmd is the command which is executed.
*/
protected void runContainerAndCopyInputIntoContainerAndCopyFromContainerToHost(
- ImageFromDockerfile image,
+ String image,
List> copyMappings,
Map environmentMappings,
List> copyFromContainerToHost,
@@ -272,7 +314,7 @@ protected void runContainerAndCopyInputIntoContainerAndCopyFromContainerToHost(
* @param containerModifier a consumer that allows modification of the container configuration
* @param postExecution a consumer that is called when the container successfully terminated
*/
- protected void runContainer(ImageFromDockerfile image,
+ protected void runContainer(String image,
Function, GenericContainer>> containerModifier,
@Nullable Consumer> postExecution
) {
@@ -306,23 +348,25 @@ protected void runContainer(ImageFromDockerfile image,
}
}
- public static Network testNetwork() {
- return testNetwork;
- }
-
/**
- * Returns a running redis cache. If no redis cache exists yet, it will be created.
+ * Starts a container and checks the status code for the exited container.
+ * It will assert that the status code is zero. If the check takes longer
+ * than 10 seconds or the status code is not zero then it will throw an
+ * exception.
*
- * @return an object containing redis cache information
+ * @param image is the docker image for the {@link GenericContainer}.
+ * @param containerModifier a consumer that allows modification of the container configuration
+ * @param postExecution a consumer that is called when the container successfully terminated
*/
- protected static synchronized RedisCache getRunningRedisCache() {
- if (redisCache != null && redisCache.redisContainer.isRunning()) {
- return redisCache;
- }
+ protected void runContainer(BuildkitDockerImage image,
+ Function, GenericContainer>> containerModifier,
+ @Nullable Consumer> postExecution
+ ) {
+ runContainer(image.getDockerImageName(), containerModifier, postExecution);
+ }
- redisCache = new RedisCache("redis", testNetwork());
- redisCache.start();
- return redisCache;
+ public static Network testNetwork() {
+ return testNetwork;
}
/**
@@ -424,111 +468,4 @@ public static T copyPathFromContainer(GenericContainer> container, String
throw new RuntimeException(e);
}
}
-
-
- /**
- * A containerized redis cache that persists the cached data across runs.
- * If your test container should use the cache (e.g. via sccache) you must run the container
- * (or build step) in the same network as the redis cache.
- * The easiest way to do is by using the {@link RedisCache#setupEnv(ImageFromDockerfile)}
- * and {@link RedisCache#setupEnv(DockerfileBuilder)}.
- * While you must use the first method, the second one is only useful if you use
- * the {@link DockerfileBuilder}.
- */
- public record RedisCache(
- String host,
- int port,
- GenericContainer> redisContainer,
- Network network
- ) {
-
- private static final Logger log = LoggerFactory.getLogger(RedisCache.class);
-
- private static final String SCCACHE_REDIS_ENDPOINT = "SCCACHE_REDIS_ENDPOINT";
-
- RedisCache(String hostName, Network network) {
- this(hostName, 6379, constructContainer(hostName, network), network);
- }
-
-
- private void start() {
- redisContainer.start();
- }
-
- /**
- * This will stop the redis cache if it is still running.
- * It will also persist the data cached during this run, so it is available in
- * the next run.
- */
- private void stop() {
- try {
- if (!redisContainer.isRunning()) {
- log.info("Redis container isn't running anymore. Skipping shutdown.");
- return;
- }
- // persist cache before shutting down
- log.info("Persist redis cache data before shutdown...");
- redisContainer.execInContainer("redis-cli", "shutdown", "save");
-
- redisContainer.stop();
- log.info("Stopped redis cache container.");
- } catch (IOException | InterruptedException e) {
- redisContainer.stop();
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets an environment variable to indicate to the distributed cache to use the redis
- * docker instance as cache.
- */
- public DockerfileBuilder setupEnv(DockerfileBuilder d) {
- logger.info("Using redis cache: {}", redisCache);
- d.env(SCCACHE_REDIS_ENDPOINT, tcpAddress());
-
- // check if redis cache is available
- d.run(
- "timeout 5 bash -c ' modifier.withNetworkMode(network.getId()));
-
- return image;
- }
-
- private String tcpAddress() {
- return "tcp://" + host + ":" + port;
- }
-
- /**
- * Constructs a redis container that is mount to a cache volume.
- * And exposed to the given network and the given hostName.
- */
- private static GenericContainer> constructContainer(String hostName,
- Network network) {
- return new GenericContainer<>("redis:7.4")
- .withCreateContainerCmdModifier(cmd -> {
- var mount = new Mount()
- .withType(MountType.VOLUME)
- .withSource("open-vadl-redis-cache")
- .withTarget("/data");
-
- Objects.requireNonNull(cmd.getHostConfig())
- .withMounts(List.of(mount));
- })
- // we need this custom network, because other containers must access
- // the redis cache with the given hostname/alias
- // (which is only available on custom networks)
- .withNetwork(network)
- .withNetworkAliases(hostName);
- }
- }
-
}
diff --git a/vadl/test/vadl/RemovingDockerImagesTeardown.java b/vadl/test/vadl/RemovingDockerImagesTeardown.java
new file mode 100644
index 000000000..f5ea70849
--- /dev/null
+++ b/vadl/test/vadl/RemovingDockerImagesTeardown.java
@@ -0,0 +1,57 @@
+// SPDX-FileCopyrightText : © 2026 TU Wien
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+package vadl;
+
+import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
+
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.DockerClientFactory;
+import vadl.lcb.riscv.DockerRiscvImageProvider;
+
+public class RemovingDockerImagesTeardown
+ implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
+ private final Logger logger = LoggerFactory.getLogger(RemovingDockerImagesTeardown.class);
+
+ @Override
+ public void beforeAll(ExtensionContext context) {
+ context.getRoot().getStore(GLOBAL)
+ .getOrComputeIfAbsent("teardown", key -> this);
+ }
+
+ @Override
+ public void close() throws Throwable {
+ logger.info("All tests finished!");
+
+ var images = DockerRiscvImageProvider.images;
+
+ if (images.isEmpty()) {
+ logger.info("No images to delete");
+ }
+
+ for (var imageName : images) {
+ logger.info("Removing image: {}", imageName);
+ DockerClientFactory.instance().client()
+ .removeImageCmd(imageName)
+ .withForce(true)
+ .exec();
+ logger.info("Removing image completed: {}", imageName);
+ }
+ }
+}
diff --git a/vadl/test/vadl/cppCodeGen/BuiltinCTest.java b/vadl/test/vadl/cppCodeGen/BuiltinCTest.java
index 757d04fbd..b5ec625e0 100644
--- a/vadl/test/vadl/cppCodeGen/BuiltinCTest.java
+++ b/vadl/test/vadl/cppCodeGen/BuiltinCTest.java
@@ -36,8 +36,8 @@
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.testcontainers.containers.output.OutputFrame;
-import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.utility.MountableFile;
+import vadl.BuildkitDockerImage;
import vadl.DockerExecutionTest;
import vadl.cppCodeGen.common.PureFunctionCodeGenerator;
import vadl.types.BuiltInTable;
@@ -1803,7 +1803,7 @@ private Stream runTests(Function... functions) {
try (var in = BuiltInTable.class.getResourceAsStream("/templates/common/vadl-builtins.h")) {
Files.write(builtinLib, Objects.requireNonNull(in).readAllBytes());
}
- var gccImage = new ImageFromDockerfile().withDockerfileFromBuilder(builder -> builder
+ var gccImage = new BuildkitDockerImage().withDockerfileFromBuilder(builder -> builder
.from("gcc:latest"));
diff --git a/vadl/test/vadl/iss/CosimInstrTest.java b/vadl/test/vadl/iss/CosimInstrTest.java
index 0a5fda218..7379f15aa 100644
--- a/vadl/test/vadl/iss/CosimInstrTest.java
+++ b/vadl/test/vadl/iss/CosimInstrTest.java
@@ -29,8 +29,8 @@
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicTest;
-import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.utility.MountableFile;
+import vadl.BuildkitDockerImage;
public abstract class CosimInstrTest extends CosimTest {
@@ -61,7 +61,7 @@ protected final Stream runTestsWith(
return runQemuInstrTests(image, testCases);
}
- protected Stream runQemuInstrTests(ImageFromDockerfile image,
+ protected Stream runQemuInstrTests(BuildkitDockerImage image,
Collection testCases)
throws IOException {
diff --git a/vadl/test/vadl/iss/CosimTest.java b/vadl/test/vadl/iss/CosimTest.java
index 1f645f6b5..5bfb384a8 100644
--- a/vadl/test/vadl/iss/CosimTest.java
+++ b/vadl/test/vadl/iss/CosimTest.java
@@ -22,7 +22,7 @@
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.DockerExecutionTest;
import vadl.configuration.IssConfiguration;
import vadl.pass.PassOrders;
@@ -43,7 +43,7 @@ public abstract class CosimTest extends DockerExecutionTest {
"ghcr.io/openvadl/iss-test-base@sha256:134d41337274a2f54790c13582e14a23a78617bb90554214f8a6f721c5287e85";
// specification to image cache
- private static final ConcurrentHashMap issImageCache =
+ private static final ConcurrentHashMap issImageCache =
new ConcurrentHashMap<>();
private static final Logger log = LoggerFactory.getLogger(CosimTest.class);
@@ -62,7 +62,7 @@ public IssConfiguration getConfiguration(boolean doDump) {
* @param specPath path to VADL specification in testSource
* @return the image containing the generated QEMU ISS
*/
- protected ImageFromDockerfile generateIssSimulator(String specPath) {
+ protected BuildkitDockerImage generateIssSimulator(String specPath) {
var config = IssConfiguration.from(getConfiguration(false));
return generateSimulator(issImageCache, specPath, config);
}
@@ -71,7 +71,7 @@ protected ImageFromDockerfile generateIssSimulator(String specPath) {
* This will generate the simulator image if it is not already contained in the provided
* cache.
*/
- private ImageFromDockerfile generateSimulator(Map cache,
+ private BuildkitDockerImage generateSimulator(Map cache,
String specPath,
IssConfiguration configuration) {
return cache.computeIfAbsent(specPath, (path) -> {
@@ -103,37 +103,25 @@ private ImageFromDockerfile generateSimulator(Map c
* @param generatedIssSources the path to the generated ISS/QEMU sources.
* @return a new image that builds the ISS at build time.
*/
- private ImageFromDockerfile getIssImage(Path generatedIssSources,
+ private BuildkitDockerImage getIssImage(Path generatedIssSources,
IssConfiguration configuration
) {
- // get redis cache for faster compilation using sccache
- var redisCache = getRunningRedisCache();
-
var targetName = configuration.targetName().toLowerCase();
var softmmuTarget = targetName + "-softmmu";
var qemuBin = "qemu-system-" + targetName;
var refTarget = "," + withUpstreamTarget();
- var dockerImage = new ImageFromDockerfile()
+ return new BuildkitDockerImage()
.withDockerfileFromBuilder(d -> {
d
.from(QEMU_TEST_IMAGE)
.copy("iss", "/qemu");
- // use redis cache for building (sccache allows remote caching)
- var cc = "sccache gcc";
-
d.workDir("/qemu/build");
// configure qemu with the new target from the specification
- d.run("../configure --cc='" + cc + "' --target-list=" + softmmuTarget + refTarget);
- // setup redis cache endpoint environment variablef
- redisCache.setupEnv(d);
- // build qemu with all cpu cores and print if cache was used.
- // the sccache --start-server is required,
- // otherwise we get a deadlock after the last make step.
- // see https://github.com/mozilla/sccache/issues/2145
- d.run("sccache --start-server && make -j$(nproc) && sccache -s");
+ d.run("../configure --cc='gcc' --target-list=" + softmmuTarget + refTarget);
+ d.run("make -j$(nproc)");
// validate existence of generated qemu iss
d.run(qemuBin + " --version");
@@ -142,9 +130,8 @@ private ImageFromDockerfile getIssImage(Path generatedIssSources,
// build the cosim broker
d.copy("/vadl-cosim", "/work/vadl-cosim");
d.workDir("/work/vadl-cosim");
- d.env("RUSTC_WRAPPER", "sccache");
// use --frozen to ensure that cargo does not need to download any new dependencies
- d.run("sccache --start-server && cargo build --release -p vadl-cosim-broker --frozen && sccache -s");
+ d.run("cargo build --release -p vadl-cosim-broker --frozen");
// add cosim broker to path
d.run("mkdir -p /opt/cosim && cp ./target/release/vadl-cosim-broker /opt/cosim/");
@@ -167,9 +154,6 @@ private ImageFromDockerfile getIssImage(Path generatedIssSources,
.withFileFromClasspath("/cosim_scripts", "/cosim_scripts/" + getScriptFolder())
// add vadl-cosim to image builder
.withFileFromPath("/vadl-cosim", Path.of("..", "vadl-cosim"));
-
- // as we have to use the same network as the redis cache, we have to build it there
- return redisCache.setupEnv(dockerImage);
}
}
diff --git a/vadl/test/vadl/iss/IssInstrTest.java b/vadl/test/vadl/iss/IssInstrTest.java
index bba20672e..518d28bea 100644
--- a/vadl/test/vadl/iss/IssInstrTest.java
+++ b/vadl/test/vadl/iss/IssInstrTest.java
@@ -30,9 +30,9 @@
import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DynamicTest;
-import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.shaded.com.google.common.collect.Streams;
import org.testcontainers.utility.MountableFile;
+import vadl.BuildkitDockerImage;
public abstract class IssInstrTest extends QemuIssTest {
@@ -109,7 +109,7 @@ protected final Stream runTestsWith(
* @param testCases the test cases passed to the container which runs the tests
* @return the test result as DynamicTests as integration with JUnit
*/
- protected Stream runQemuInstrTests(ImageFromDockerfile image,
+ protected Stream runQemuInstrTests(BuildkitDockerImage image,
Collection testCases)
throws IOException {
diff --git a/vadl/test/vadl/iss/QemuIssTest.java b/vadl/test/vadl/iss/QemuIssTest.java
index 190d5b4d9..88e836658 100644
--- a/vadl/test/vadl/iss/QemuIssTest.java
+++ b/vadl/test/vadl/iss/QemuIssTest.java
@@ -23,7 +23,7 @@
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.DockerExecutionTest;
import vadl.configuration.IssConfiguration;
import vadl.pass.PassOrders;
@@ -45,9 +45,9 @@ public abstract class QemuIssTest extends DockerExecutionTest {
// specification to image cache
// we must separate CAS and ISS, otherwise the CAS test would use the ISS image
- private static final ConcurrentHashMap issImageCache =
+ private static final ConcurrentHashMap issImageCache =
new ConcurrentHashMap<>();
- private static final ConcurrentHashMap casImageCache =
+ private static final ConcurrentHashMap casImageCache =
new ConcurrentHashMap<>();
private static final Logger log = LoggerFactory.getLogger(QemuIssTest.class);
@@ -66,7 +66,7 @@ public IssConfiguration getConfiguration(boolean doDump) {
* @param specPath path to VADL specification in testSource
* @return the image containing the generated QEMU ISS
*/
- protected ImageFromDockerfile generateIssSimulator(String specPath) {
+ protected BuildkitDockerImage generateIssSimulator(String specPath) {
var config = IssConfiguration.from(getConfiguration(false));
return generateSimulator(issImageCache, specPath, config);
}
@@ -75,7 +75,7 @@ protected ImageFromDockerfile generateIssSimulator(String specPath) {
* This will generate the simulator image if it is not already contained in the provided
* cache.
*/
- private ImageFromDockerfile generateSimulator(Map cache,
+ private BuildkitDockerImage generateSimulator(Map cache,
String specPath,
IssConfiguration configuration) {
return cache.computeIfAbsent(specPath, (path) -> {
@@ -107,38 +107,26 @@ protected List withUpstreamTargets() {
* @param generatedIssSources the path to the generated ISS/QEMU sources.
* @return a new image that builds the ISS at build time.
*/
- private ImageFromDockerfile getIssImage(Path generatedIssSources,
+ private BuildkitDockerImage getIssImage(Path generatedIssSources,
IssConfiguration configuration
) {
- // get redis cache for faster compilation using sccache
- var redisCache = getRunningRedisCache();
-
var targetName = configuration.targetName().toLowerCase();
var softmmuTarget = targetName + "-softmmu";
var qemuBin = "qemu-system-" + targetName;
var refTargetString = String.join(",", withUpstreamTargets());
var refTarget = refTargetString.isEmpty() ? "" : "," + refTargetString;
- var dockerImage = new ImageFromDockerfile()
+ return new BuildkitDockerImage()
.withDockerfileFromBuilder(d -> {
d
.from(QEMU_TEST_IMAGE)
.copy("iss", "/qemu");
- // use redis cache for building (sccache allows remote caching)
- var cc = "sccache gcc";
-
d.workDir("/qemu/build");
// configure qemu with the new target from the specification
- d.run("../configure --cc='" + cc + "' --target-list=" + softmmuTarget + refTarget);
- // setup redis cache endpoint environment variablef
- redisCache.setupEnv(d);
- // build qemu with all cpu cores and print if cache was used.
- // the sccache --start-server is required,
- // otherwise we get a deadlock after the last make step.
- // see https://github.com/mozilla/sccache/issues/2145
- d.run("sccache --start-server && make -j$(nproc) && sccache -s");
+ d.run("../configure --cc='gcc' --target-list=" + softmmuTarget + refTarget);
+ d.run("make -j$(nproc)");
// validate existence of generated qemu iss
d.run(qemuBin + " --version");
@@ -155,9 +143,6 @@ private ImageFromDockerfile getIssImage(Path generatedIssSources,
.withFileFromPath("iss", generatedIssSources)
// make iss_qemu scripts available to image builder
.withFileFromClasspath("/scripts", "/scripts/iss_qemu");
-
- // as we have to use the same network as the redis cache, we have to build it there
- return redisCache.setupEnv(dockerImage);
}
}
diff --git a/vadl/test/vadl/lcb/AbstractPredicateCodeGeneratorCppVerificationTest.java b/vadl/test/vadl/lcb/AbstractPredicateCodeGeneratorCppVerificationTest.java
index 7025865d0..aec876fc6 100644
--- a/vadl/test/vadl/lcb/AbstractPredicateCodeGeneratorCppVerificationTest.java
+++ b/vadl/test/vadl/lcb/AbstractPredicateCodeGeneratorCppVerificationTest.java
@@ -31,7 +31,7 @@
import net.jqwik.api.Arbitrary;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.configuration.LcbConfiguration;
import vadl.cppCodeGen.common.PredicateFunctionCodeGenerator;
import vadl.cppCodeGen.model.GcbCppFunctionWithBody;
@@ -127,7 +127,7 @@ private static Map predicates(T
return output.predicates();
}
- private Pair setup(LcbConfiguration configuration,
+ private Pair setup(LcbConfiguration configuration,
String specification)
throws IOException, DuplicatedPassKeyException {
@@ -147,7 +147,7 @@ private Pair setup(LcbConfiguration configuratio
Path.of(configuration.outputPath() + "/encoding/"));
}
- return Pair.of(new ImageFromDockerfile()
+ return Pair.of(new BuildkitDockerImage()
.withDockerfile(Paths.get(configuration.outputPath() + "/encoding/Dockerfile")), setup);
}
diff --git a/vadl/test/vadl/lcb/EncodingCodeGeneratorCppVerificationTest.java b/vadl/test/vadl/lcb/EncodingCodeGeneratorCppVerificationTest.java
index 6e7cf45b0..cec334409 100644
--- a/vadl/test/vadl/lcb/EncodingCodeGeneratorCppVerificationTest.java
+++ b/vadl/test/vadl/lcb/EncodingCodeGeneratorCppVerificationTest.java
@@ -27,7 +27,7 @@
import net.jqwik.api.Arbitrary;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.configuration.LcbConfiguration;
import vadl.cppCodeGen.CppTypeMap;
import vadl.cppCodeGen.common.GcbAccessFunctionCodeGenerator;
@@ -64,7 +64,7 @@ Collection instructions() throws IOException, DuplicatedPassKeyExce
Path.of(configuration.outputPath() + "/encoding/"));
}
- var image = new ImageFromDockerfile()
+ var image = new BuildkitDockerImage()
.withDockerfile(Paths.get(configuration.outputPath() + "/encoding/Dockerfile"));
// Generate files and output them into the temporary directory.
@@ -72,7 +72,7 @@ Collection instructions() throws IOException, DuplicatedPassKeyExce
}
private Collection generateInputs(TestSetup setup,
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
Path path) throws IOException {
var passManager = setup.passManager();
diff --git a/vadl/test/vadl/lcb/LcbDockerExecutionTest.java b/vadl/test/vadl/lcb/LcbDockerExecutionTest.java
index 3045dfcda..8f47cdce5 100644
--- a/vadl/test/vadl/lcb/LcbDockerExecutionTest.java
+++ b/vadl/test/vadl/lcb/LcbDockerExecutionTest.java
@@ -33,10 +33,6 @@
import vadl.utils.Pair;
public abstract class LcbDockerExecutionTest extends AbstractLcbTest {
- protected boolean enableDebug() {
- return false;
- }
-
protected abstract String getTarget();
protected abstract String getUpstreamBuildTarget();
@@ -96,23 +92,20 @@ protected void run(String specPath, String cmd) throws DuplicatedPassKeyExceptio
protected void run(String specPath, String cmd, Map environments)
throws DuplicatedPassKeyException, IOException {
- var doDebug = enableDebug();
var configuration = getConfiguration();
runLcb(configuration, specPath);
copyIntoDockerContext(configuration);
- var redisCache = getRunningRedisCache();
var cachedImage =
- DockerRiscvImageProvider.image(redisCache,
+ DockerRiscvImageProvider.image(
configuration.outputPath() + "/lcb/Dockerfile",
getImageName(),
getTarget(),
getUpstreamBuildTarget(),
getUpstreamClangTarget(),
getSpikeTarget(),
- getAbi(),
- doDebug);
+ getAbi());
runContainerAndCopyInputIntoContainer(cachedImage,
List.of(Pair.of(Path.of("../../open-vadl/vadl-test/main/resources/llvm/riscv/spike"),
@@ -121,4 +114,3 @@ protected void run(String specPath, String cmd, Map environments
cmd);
}
}
-
diff --git a/vadl/test/vadl/lcb/LcbDockerInputFileExecutionTest.java b/vadl/test/vadl/lcb/LcbDockerInputFileExecutionTest.java
index 4139270dc..b4bb86cd2 100644
--- a/vadl/test/vadl/lcb/LcbDockerInputFileExecutionTest.java
+++ b/vadl/test/vadl/lcb/LcbDockerInputFileExecutionTest.java
@@ -63,23 +63,20 @@ protected List runEach(String specPath, List sourceDirector
String cmd)
throws DuplicatedPassKeyException,
IOException {
- var doDebug = false;
var configuration = getConfiguration();
runLcb(configuration, specPath);
copyIntoDockerContext(configuration);
- var redisCache = getRunningRedisCache();
var cachedImage =
- DockerRiscvImageProvider.image(redisCache,
+ DockerRiscvImageProvider.image(
configuration.outputPath() + "/lcb/Dockerfile",
getImageName(),
getTarget(),
getUpstreamBuildTarget(),
getUpstreamClangTarget(),
getSpikeTarget(),
- getAbi(),
- doDebug);
+ getAbi());
return sourceDirectories.stream()
.map(sourceDirectory -> Pair.of(sourceDirectory, inputFiles(sourceDirectory))).flatMap(
diff --git a/vadl/test/vadl/lcb/riscv/AbstractLcbBenchmarkTest.java b/vadl/test/vadl/lcb/riscv/AbstractLcbBenchmarkTest.java
index 02c3a2662..97fa0ea63 100644
--- a/vadl/test/vadl/lcb/riscv/AbstractLcbBenchmarkTest.java
+++ b/vadl/test/vadl/lcb/riscv/AbstractLcbBenchmarkTest.java
@@ -28,7 +28,6 @@ public abstract class AbstractLcbBenchmarkTest extends LcbDockerExecutionTest {
@Override
protected void run(String specPath, String cmd, Map environments)
throws DuplicatedPassKeyException, IOException {
- var doDebug = enableDebug();
var hostPath = System.getenv("EMBENCH_BENCHMARK_RESULT_HOST_PATH");
var guestPath = System.getenv("EMBENCH_BENCHMARK_RESULT_GUEST_PATH");
@@ -39,17 +38,15 @@ protected void run(String specPath, String cmd, Map environments
runLcb(configuration, specPath);
copyIntoDockerContext(configuration);
- var redisCache = getRunningRedisCache();
var cachedImage =
- DockerRiscvImageProvider.image(redisCache,
+ DockerRiscvImageProvider.image(
configuration.outputPath() + "/lcb/Dockerfile",
getTarget(),
getImageName(),
getUpstreamBuildTarget(),
getUpstreamClangTarget(),
getSpikeTarget(),
- getAbi(),
- doDebug);
+ getAbi());
runContainerAndCopyDirectoryIntoContainerAndCopyOutputBack(cachedImage,
List.of(),
diff --git a/vadl/test/vadl/lcb/riscv/DockerRiscvImageProvider.java b/vadl/test/vadl/lcb/riscv/DockerRiscvImageProvider.java
index 7f6b1fa2e..7378defd0 100644
--- a/vadl/test/vadl/lcb/riscv/DockerRiscvImageProvider.java
+++ b/vadl/test/vadl/lcb/riscv/DockerRiscvImageProvider.java
@@ -16,23 +16,59 @@
package vadl.lcb.riscv;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.Map;
-import org.testcontainers.images.builder.ImageFromDockerfile;
-import vadl.DockerExecutionTest;
+import io.github.kper.buildkitcli.lib.BuildLog;
+import io.github.kper.buildkitcli.lib.BuildOutputMode;
+import io.github.kper.buildkitcli.lib.BuildProgressListener;
+import io.github.kper.buildkitcli.lib.BuildResult;
+import io.github.kper.buildkitcli.lib.BuildVertex;
+import io.github.kper.buildkitcli.lib.BuildWarning;
+import io.github.kper.buildkitcli.lib.BuildkitClient;
+import io.github.kper.buildkitcli.lib.BuildkitConnectionConfig;
+import io.github.kper.buildkitcli.lib.DockerfileBuildRequest;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.HashSet;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
- * A singleton implementation to keep the reference to a {@link ImageFromDockerfile} to avoid
+ * A singleton implementation to keep the reference to the generated docker image to avoid
* recompilation between {@link LlvmRiscvAssemblyTest} and {@link SpikeRiscvSimulationTest}.
*/
public class DockerRiscvImageProvider {
- private static Map images = new HashMap<>();
+ private static final Logger logger = LoggerFactory.getLogger(DockerRiscvImageProvider.class);
+ public static HashSet images = new HashSet<>();
+
+ static class PrintingListener implements BuildProgressListener {
+ private final Logger logger = LoggerFactory.getLogger(PrintingListener.class);
+
+ @Override
+ public void onVertex(BuildVertex vertex) {
+ if (!vertex.name().isBlank()) {
+ logger.info(vertex.name());
+ }
+ }
+
+ @Override
+ public void onLog(BuildLog log) {
+ String message = log.utf8Message();
+ if (!message.isBlank()) {
+ logger.info(message);
+ }
+ }
+
+ @Override
+ public void onWarning(BuildWarning warning) {
+ logger.warn("warning: {}", warning.shortMessage());
+ }
+ }
+
/**
- * Create an {@link ImageFromDockerfile} or return an already existing image.
+ * Create a docker image or return an already existing image.
*
- * @param redisCache is the cache for building LLVM.
* @param pathDockerFile is the path to the dockerfile which should be built.
* @param imageName is the name of the generated and cached docker image
* @param target is the name of the processor.
@@ -41,40 +77,68 @@ public class DockerRiscvImageProvider {
* compiler.
* @param spikeTarget is the ISA for spike to run the executable.
* @param abi which should be chosen for the gcc linker.
- * @param doDebug if the flag is {@code true} then the image will not be deleted.
+ * @return the name of the image.
* @throws RuntimeException when the {@code isCI} environment variable and {@code doDebug} are
* activated.
*/
- public static ImageFromDockerfile image(DockerExecutionTest.RedisCache redisCache,
- String pathDockerFile,
- String imageName,
- String target,
- String upstreamBuildTarget,
- String upstreamClangTarget,
- String spikeTarget,
- String abi,
- boolean doDebug) {
- var image = images.get(imageName);
- if (image == null) {
+ public static String image(String pathDockerFile,
+ String imageName,
+ String target,
+ String upstreamBuildTarget,
+ String upstreamClangTarget,
+ String spikeTarget,
+ String abi) throws IOException {
+ var imageKey = "tc_spike_riscv_" + imageName;
+ var image = images.contains(imageKey);
+ if (!image) {
- var deleteOnExit = !doDebug;
+ try (var client = new BuildkitClient(
+ BuildkitConnectionConfig.of("tcp://localhost:1234").withTimeout(
+ Duration.ofHours(2)))) {
+ Path dockerfile = Path.of(pathDockerFile);
+ var requestBuilder = DockerfileBuildRequest.builder(
+ dockerfile.getParent(),
+ dockerfile,
+ imageKey
+ )
+ .buildArg("TARGET", target)
+ .buildArg("UPSTREAM_BUILD_TARGET", upstreamBuildTarget)
+ .buildArg("UPSTREAM_CLANG_TARGET", upstreamClangTarget)
+ .buildArg("ABI", abi)
+ .buildArg("SPIKE_TARGET", spikeTarget)
+ .outputMode(BuildOutputMode.DOCKER);
- if ("true".equals(System.getenv("isCI")) && !deleteOnExit) {
- throw new RuntimeException("It is not allowed to activate 'deleteOnExit' in the CI");
- }
+ BuildResult result = client.buildImage(requestBuilder.build(), new PrintingListener());
+ logger.info("Image was built: {}", imageKey);
+ logger.info("Loading image: {}", imageKey);
+ loadIntoDocker(result);
- var img = redisCache.setupEnv(new ImageFromDockerfile("tc_spike_riscv_"
- + imageName, deleteOnExit)
- .withDockerfile(Paths.get(pathDockerFile))
- .withBuildArg("TARGET", target)
- .withBuildArg("UPSTREAM_BUILD_TARGET", upstreamBuildTarget)
- .withBuildArg("UPSTREAM_CLANG_TARGET", upstreamClangTarget)
- .withBuildArg("ABI", abi)
- .withBuildArg("SPIKE_TARGET", spikeTarget));
- images.put(imageName, img);
- return img;
+ images.add(imageKey);
+ return imageKey;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
} else {
- return image;
+ logger.info("Image was already in the cache: {}", imageKey);
+ return imageKey;
+ }
+ }
+
+ private static void loadIntoDocker(BuildResult result) throws Exception {
+ Path exportedArchive = result.exportedArchive();
+ if (exportedArchive == null) {
+ throw new IllegalStateException("Build result did not include a docker archive");
+ }
+ try {
+ Process process = new ProcessBuilder("docker", "load", "-i", exportedArchive.toString())
+ .inheritIO()
+ .start();
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ throw new IllegalStateException("docker load failed with exit code " + exitCode);
+ }
+ } finally {
+ Files.deleteIfExists(exportedArchive);
}
}
}
diff --git a/vadl/test/vadl/lcb/riscv/riscv64/verification/ImmediateExtractionCodeGeneratorCppVerificationRiscv64Test.java b/vadl/test/vadl/lcb/riscv/riscv64/verification/ImmediateExtractionCodeGeneratorCppVerificationRiscv64Test.java
index 9e26b2361..9224fc561 100644
--- a/vadl/test/vadl/lcb/riscv/riscv64/verification/ImmediateExtractionCodeGeneratorCppVerificationRiscv64Test.java
+++ b/vadl/test/vadl/lcb/riscv/riscv64/verification/ImmediateExtractionCodeGeneratorCppVerificationRiscv64Test.java
@@ -29,8 +29,8 @@
import net.jqwik.api.Arbitrary;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.shaded.com.google.common.collect.Streams;
+import vadl.BuildkitDockerImage;
import vadl.cppCodeGen.common.GcbAccessFunctionCodeGenerator;
import vadl.cppCodeGen.model.GcbImmediateExtractionCppFunction;
import vadl.lcb.AbstractLcbTest;
@@ -65,7 +65,7 @@ Collection instructions() throws IOException, DuplicatedPassKeyExce
Path.of(configuration.outputPath() + "/encoding/"));
}
- var image = new ImageFromDockerfile()
+ var image = new BuildkitDockerImage()
.withDockerfile(Paths.get(configuration.outputPath() + "/encoding/Dockerfile"));
return generateInputs(testSetup, image, configuration.outputPath());
@@ -79,7 +79,7 @@ Collection instructions() throws IOException, DuplicatedPassKeyExce
* done in the container test with `set_bits` function.
*/
private Collection generateInputs(TestSetup setup,
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
Path path) throws IOException {
List> copyMappings = new ArrayList<>();
setup.specification()
diff --git a/vadl/test/vadl/lcb/riscv/riscv64/verification/RelocationCodeGeneratorCppVerificationRiscv64Test.java b/vadl/test/vadl/lcb/riscv/riscv64/verification/RelocationCodeGeneratorCppVerificationRiscv64Test.java
index 6dce457c6..3bf48a705 100644
--- a/vadl/test/vadl/lcb/riscv/riscv64/verification/RelocationCodeGeneratorCppVerificationRiscv64Test.java
+++ b/vadl/test/vadl/lcb/riscv/riscv64/verification/RelocationCodeGeneratorCppVerificationRiscv64Test.java
@@ -27,7 +27,7 @@
import net.jqwik.api.Arbitrary;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.cppCodeGen.CppTypeMap;
import vadl.cppCodeGen.common.UpdateFieldRelocationFunctionCodeGenerator;
import vadl.cppCodeGen.common.ValueRelocationFunctionCodeGenerator;
@@ -74,14 +74,14 @@ Collection instructions() throws IOException, DuplicatedPassKeyExce
Path.of(configuration.outputPath() + "/encoding/"));
}
- var image = new ImageFromDockerfile()
+ var image = new BuildkitDockerImage()
.withDockerfile(Paths.get(configuration.outputPath() + "/encoding/Dockerfile"));
return generateInputs(testSetup, image, configuration.outputPath(), testSetup.specification());
}
private Collection generateInputs(TestSetup testSetup,
- ImageFromDockerfile image,
+ BuildkitDockerImage image,
Path path,
Specification specification) throws IOException {
var output = (GenerateLinkerComponentsPass.Output) testSetup.passManager().getPassResults()
diff --git a/vadl/test/vadl/rtl/RtlDockerTest.java b/vadl/test/vadl/rtl/RtlDockerTest.java
index 04db2b2ab..95cf0d693 100644
--- a/vadl/test/vadl/rtl/RtlDockerTest.java
+++ b/vadl/test/vadl/rtl/RtlDockerTest.java
@@ -22,7 +22,7 @@
import java.nio.file.Path;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.DockerExecutionTest;
import vadl.configuration.RtlConfiguration;
import vadl.pass.PassOrders;
@@ -30,10 +30,10 @@
public abstract class RtlDockerTest extends DockerExecutionTest {
- private static final ConcurrentHashMap imageCache =
+ private static final ConcurrentHashMap imageCache =
new ConcurrentHashMap<>();
- protected ImageFromDockerfile generateRtlImage(String specPath,
+ protected BuildkitDockerImage generateRtlImage(String specPath,
RtlConfiguration configuration) {
final var cacheKey = String.valueOf(Set.of(specPath, configuration).hashCode());
@@ -51,7 +51,7 @@ protected ImageFromDockerfile generateRtlImage(String specPath,
});
}
- private ImageFromDockerfile getImage(RtlConfiguration configuration
+ private BuildkitDockerImage getImage(RtlConfiguration configuration
) {
// find output dir
@@ -60,10 +60,7 @@ private ImageFromDockerfile getImage(RtlConfiguration configuration
throw new IllegalStateException("RTL output path was not found (not generated?)");
}
- // Get redis cache for faster compilation using sccache
- var redisCache = getRunningRedisCache();
-
- var dockerImage = new ImageFromDockerfile()
+ return new BuildkitDockerImage()
.withDockerfileFromBuilder(d -> {
d.from(RTL_BASE_IMAGE);
@@ -82,9 +79,6 @@ private ImageFromDockerfile getImage(RtlConfiguration configuration
.withFileFromPath("/rtl", outputPath)
// make scripts available to image builder
.withFileFromClasspath("/scripts", "/scripts/rtl");
-
- // As we have to use the same network as the redis cache, we have to build it there
- return redisCache.setupEnv(dockerImage);
}
}
diff --git a/vadl/test/vadl/viam/algebraic_simplification/AlgebraicSimplificationTest.java b/vadl/test/vadl/viam/algebraic_simplification/AlgebraicSimplificationTest.java
index aa5681205..ce99f0f6e 100644
--- a/vadl/test/vadl/viam/algebraic_simplification/AlgebraicSimplificationTest.java
+++ b/vadl/test/vadl/viam/algebraic_simplification/AlgebraicSimplificationTest.java
@@ -29,7 +29,7 @@
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testcontainers.images.builder.ImageFromDockerfile;
+import vadl.BuildkitDockerImage;
import vadl.DockerExecutionTest;
import vadl.pass.PassResults;
import vadl.viam.Instruction;
@@ -47,7 +47,7 @@ public class AlgebraicSimplificationTest extends DockerExecutionTest {
private static final String MOUNT_PATH = "/app/main.py";
- private static final ImageFromDockerfile DOCKER_IMAGE = new ImageFromDockerfile()
+ private static final BuildkitDockerImage DOCKER_IMAGE = new BuildkitDockerImage()
.withDockerfileFromBuilder(builder ->
builder
.from("python:3.8")