Skip to content

Commit b866403

Browse files
committed
Fixed 'dockerFile' parameter
1 parent 21a9b75 commit b866403

File tree

9 files changed

+214
-77
lines changed

9 files changed

+214
-77
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010
}
1111

1212
group 'com.formkiq.gradle'
13-
version '1.7.5'
13+
version '1.7.6'
1414

1515
spotless {
1616
java {

src/main/java/com/formkiq/gradle/GraalvmNativePlugin.java

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,48 +34,42 @@ public void apply(final Project project) {
3434
Provider<GraalvmBuildService> svc = project.getGradle().getSharedServices().registerIfAbsent(
3535
"web", GraalvmBuildService.class, spec -> spec.getMaxParallelUsages().set(1));
3636

37-
project.afterEvaluate(p -> {
38-
// Treat "configured" as: user set mainClassName (adjust the predicate if you prefer)
39-
boolean configured = ext.getMainClassName().isPresent();
37+
// ✅ Register task immediately so tasks.named(...) always works
38+
TaskProvider<GraalvmNativeTask> nativeImage =
39+
project.getTasks().register("graalvmNativeImage", GraalvmNativeTask.class, task -> {
40+
task.setGroup("Graalvm");
41+
task.setDescription("Build GraalVM Native Image");
42+
task.setExtension(ext);
43+
task.usesService(svc);
44+
task.getBuildDirectory().set(project.getLayout().getBuildDirectory().dir("graalvm"));
4045

41-
if (!configured) {
42-
// User didn't declare nativeImage { ... } in this subproject — do nothing.
43-
return;
44-
}
45-
46-
// Register the task now that we know it's wanted in this project
47-
TaskProvider<GraalvmNativeTask> nativeImage =
48-
project.getTasks().register("graalvmNativeImage", GraalvmNativeTask.class, task -> {
49-
task.setGroup("Graalvm");
50-
task.setDescription("Build GraalVM Native Image");
51-
task.setExtension(ext); // inject the extension (nested inputs)
52-
task.usesService(svc);
53-
task.getBuildDirectory().set(project.getLayout().getBuildDirectory().dir("graalvm"));
46+
// ✅ Opt-in: task will only run if configured
47+
task.onlyIf(t -> {
48+
String dockerFile = ext.getDockerFile();
49+
return ext.getMainClassName().isPresent()
50+
|| (dockerFile != null && !dockerFile.isBlank());
5451
});
55-
56-
// Wire only if the Java plugin is applied
57-
project.getPlugins().withType(JavaPlugin.class, jp -> {
58-
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
59-
SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
60-
61-
// Inputs
62-
nativeImage.configure(t -> {
63-
t.getSources().from(main.getAllSource()); // java + resources
64-
t.getRuntimeClasspath().from(main.getRuntimeClasspath()); // runtime jars/classes
6552
});
6653

67-
// Ensure producers run first (jar is enough; avoids assemble cycles)
68-
nativeImage.configure(t -> t.dependsOn(project.getTasks().named(JavaPlugin.JAR_TASK_NAME)));
54+
// Wire only if Java plugin is applied
55+
project.getPlugins().withType(JavaPlugin.class, jp -> {
56+
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
57+
SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
58+
59+
nativeImage.configure(t -> {
60+
t.getSources().from(main.getAllSource());
61+
t.getRuntimeClasspath().from(main.getRuntimeClasspath());
62+
t.dependsOn(project.getTasks().named(JavaPlugin.JAR_TASK_NAME));
63+
t.dependsOn(project.getTasks().named(JavaPlugin.TEST_TASK_NAME));
6964
});
65+
});
7066

71-
// If you want assemble to run AFTER native image in projects that opted in
72-
project.getTasks()
73-
.named(org.gradle.language.base.plugins.LifecycleBasePlugin.ASSEMBLE_TASK_NAME)
74-
.configure(t -> t.dependsOn(nativeImage));
67+
// Safe: assemble depends on nativeImage, but nativeImage will be SKIPPED if not configured
68+
project.getTasks()
69+
.named(org.gradle.language.base.plugins.LifecycleBasePlugin.ASSEMBLE_TASK_NAME)
70+
.configure(t -> t.dependsOn(nativeImage));
7571

76-
// If the Distribution plugin is applied, make distZip wait for native image
77-
project.getPlugins().withId("distribution",
78-
__ -> project.getTasks().named("distZip").configure(t -> t.dependsOn(nativeImage)));
79-
});
72+
project.getPlugins().withId("distribution",
73+
__ -> project.getTasks().named("distZip").configure(t -> t.dependsOn(nativeImage)));
8074
}
8175
}

src/main/java/com/formkiq/gradle/GraalvmNativeTask.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
*/
1515
package com.formkiq.gradle;
1616

17+
import static com.formkiq.gradle.internal.NativeImageExecutor.GRAALVM_JAVA_MAIN;
18+
1719
import com.formkiq.gradle.internal.ArchiveUtils;
1820
import com.formkiq.gradle.internal.Downloader;
1921
import com.formkiq.gradle.internal.NativeImageExecutor;
@@ -169,12 +171,17 @@ public void setExtension(final GraalvmNativeExtension params) {
169171
@TaskAction
170172
public void createImage() {
171173

172-
if (extension.getMainClassName() != null) {
174+
String mainClassName = this.extension.getMainClassName().getOrNull();
175+
String dockerFile = this.extension.getDockerFile();
176+
boolean hasMainClass = mainClassName != null && !mainClassName.isBlank();
177+
boolean hasDockerFile = dockerFile != null && !dockerFile.isBlank();
178+
179+
if (hasMainClass || hasDockerFile) {
173180
try {
174181

175182
NativeImageExecutor executor = new NativeImageExecutor(this.extension);
176183

177-
if (this.extension.getDockerFile() != null) {
184+
if (hasDockerFile) {
178185
executeDockerFile();
179186
} else if (this.extension.getDockerImage() != null) {
180187

@@ -223,26 +230,28 @@ private Path getBuildDirectoryAsPath() {
223230

224231
private void executeDockerFile() throws IOException, InterruptedException {
225232

226-
DockerService service = new DefaultDockerService();
233+
DockerService service = new DefaultDockerService(getLogger());
227234
if (!service.isDockerRunning()) {
228235
throw new ResourceException("Docker is not running");
229236
}
230237

231238
String dockerfileContent = Files.readString(Path.of(this.extension.getDockerFile()));
232-
getLogger().info("Generating Dockerfile");
233-
getLogger().info("{}", dockerfileContent);
239+
getLogger().info("Building Dockerfile: " + this.extension.getDockerFile());
234240

235241
service.removeDockerImage(this.extension.getOutputImageTag());
236242

237243
Path buildDir = getBuildDirectoryAsPath();
238-
service.buildDockerImage(buildDir, this.extension.getOutputImageTag(), dockerfileContent);
244+
Path contextDir = Path.of(this.extension.getDockerFile()).getParent();
245+
contextDir = contextDir != null ? contextDir : Path.of(".");
246+
service.buildDockerImage(buildDir, this.extension.getOutputImageTag(), dockerfileContent,
247+
contextDir);
239248
service.runDockerImage(buildDir, this.extension.getOutputImageTag());
240249
}
241250

242251
private void executeDockerImage(NativeImageExecutor executor)
243252
throws IOException, InterruptedException {
244253

245-
DockerService service = new DefaultDockerService();
254+
DockerService service = new DefaultDockerService(getLogger());
246255
if (!service.isDockerRunning()) {
247256
throw new ResourceException("Docker is not running");
248257
}
@@ -264,7 +273,9 @@ private void executeDockerImage(NativeImageExecutor executor)
264273
service.removeDockerImage(this.extension.getOutputImageTag());
265274

266275
Path buildDir = getBuildDirectoryAsPath();
267-
service.buildDockerImage(buildDir, this.extension.getOutputImageTag(), dockerfileContent);
276+
Path contextDir = buildDir.resolve(GRAALVM_JAVA_MAIN);
277+
service.buildDockerImage(buildDir, this.extension.getOutputImageTag(), dockerfileContent,
278+
contextDir);
268279
service.runDockerImage(buildDir, this.extension.getOutputImageTag());
269280
}
270281

src/main/java/com/formkiq/gradle/services/DefaultDockerService.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package com.formkiq.gradle.services;
22

3-
import static com.formkiq.gradle.internal.NativeImageExecutor.GRAALVM_JAVA_MAIN;
4-
53
import com.github.dockerjava.api.DockerClient;
6-
import com.github.dockerjava.api.command.BuildImageResultCallback;
74
import com.github.dockerjava.api.command.CreateContainerResponse;
85
import com.github.dockerjava.api.exception.NotFoundException;
96
import com.github.dockerjava.api.model.Bind;
@@ -14,20 +11,32 @@
1411
import com.github.dockerjava.core.DockerClientConfig;
1512
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
1613
import com.github.dockerjava.transport.DockerHttpClient;
17-
import java.io.File;
1814
import java.io.IOException;
1915
import java.nio.file.Files;
2016
import java.nio.file.Path;
2117
import java.time.Duration;
2218
import java.util.Collections;
19+
import org.gradle.api.logging.Logger;
2320

2421
/** Default implementation using docker-java client (socket first, then TCP). */
2522
public final class DefaultDockerService implements DockerService {
2623
private final DockerClient dockerClient;
2724

28-
/** constructor. */
29-
public DefaultDockerService() {
25+
/** {@link Logger}. */
26+
private final Logger log;
27+
28+
/** {@link LoggingBuildImageResultCallback}. */
29+
private final LoggingBuildImageResultCallback callback;
30+
31+
/**
32+
* constructor.
33+
*
34+
* @param logger {@link Logger}
35+
*/
36+
public DefaultDockerService(final Logger logger) {
3037
this.dockerClient = initClient();
38+
this.log = logger;
39+
this.callback = new LoggingBuildImageResultCallback(this.log);
3140
}
3241

3342
private DockerClient initClient() {
@@ -68,40 +77,61 @@ public boolean isDockerRunning() {
6877

6978
@Override
7079
public Path buildDockerImage(final Path buildDir, final String imageTag,
71-
final String dockerFileContent) throws IOException {
80+
final String dockerFileContent, final Path contextDir) throws IOException {
7281

7382
Path dockerfile = writeDockerFile(buildDir, dockerFileContent);
7483

75-
File contextDir = buildDir.resolve(GRAALVM_JAVA_MAIN).toFile();
84+
String dockerCommand =
85+
String.format("docker build -f %s -t %s %s", dockerfile, imageTag, contextDir);
7686

77-
dockerClient.buildImageCmd().withBaseDirectory(contextDir).withDockerfile(dockerfile.toFile())
78-
.withTags(Collections.singleton(imageTag)).exec(new BuildImageResultCallback())
79-
.awaitImageId();
87+
log(dockerCommand);
88+
dockerClient.buildImageCmd().withBaseDirectory(contextDir.toFile())
89+
.withDockerfile(dockerfile.toFile()).withTags(Collections.singleton(imageTag))
90+
.exec(this.callback).awaitImageId();
8091

8192
return dockerfile;
8293
}
8394

95+
private void log(final String log) {
96+
if (this.log != null) {
97+
this.log.info(log);
98+
}
99+
}
100+
84101
@Override
85102
public void runDockerImage(final Path buildDir, final String imageTag)
86103
throws IOException, InterruptedException {
87104

88105
Path path = buildDir.resolve("output");
89106
Files.createDirectories(path);
90107

108+
String containerName = "copy-file-container-" + System.currentTimeMillis();
109+
String hostPath = path.toAbsolutePath().toString();
110+
111+
log(String.format("docker run --name %s -v %s:/output %s", containerName, hostPath, imageTag));
112+
91113
Volume containerOutputVolume = new Volume("/output");
92-
HostConfig hostConfig = HostConfig.newHostConfig()
93-
.withBinds(new Bind(path.toAbsolutePath().toString(), containerOutputVolume));
114+
HostConfig hostConfig =
115+
HostConfig.newHostConfig().withBinds(new Bind(hostPath, containerOutputVolume));
94116

117+
log("Creating container '" + containerName + "' from image '" + imageTag + "'");
95118
CreateContainerResponse container = dockerClient.createContainerCmd(imageTag)
96-
.withName("copy-file-container-" + System.currentTimeMillis()).withHostConfig(hostConfig)
97-
.exec();
119+
.withName(containerName).withHostConfig(hostConfig).exec();
98120

99121
String containerId = container.getId();
100-
dockerClient.startContainerCmd(containerId).exec();
122+
log("Container created with ID: " + containerId);
123+
101124
try {
125+
log("Starting container " + containerId);
126+
dockerClient.startContainerCmd(containerId).exec();
127+
128+
log("Waiting for container " + containerId + " to finish");
102129
dockerClient.waitContainerCmd(containerId).start().awaitCompletion();
130+
log("Container " + containerId + " finished successfully");
103131
} finally {
132+
log("Removing container " + containerId + " (force=true)");
104133
dockerClient.removeContainerCmd(containerId).withForce(true).exec();
134+
log("Container " + containerId + " removed");
105135
}
106136
}
107137

src/main/java/com/formkiq/gradle/services/DockerService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ public interface DockerService {
2121
* @param buildDir {@link Path}
2222
* @param imageTag {@link String}
2323
* @param dockerFileContent Docker file content
24+
* @param contextDir Docker Context dir
2425
* @return Path
2526
* @throws IOException IOException
2627
*/
27-
Path buildDockerImage(Path buildDir, String imageTag, String dockerFileContent)
28+
Path buildDockerImage(Path buildDir, String imageTag, String dockerFileContent, Path contextDir)
2829
throws IOException;
2930

3031
/**
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.formkiq.gradle.services;
2+
3+
import com.github.dockerjava.api.command.BuildImageResultCallback;
4+
import com.github.dockerjava.api.model.BuildResponseItem;
5+
import com.github.dockerjava.api.model.ResponseItem;
6+
import org.gradle.api.logging.Logger;
7+
8+
/** {@link BuildImageResultCallback} logger. */
9+
public class LoggingBuildImageResultCallback extends BuildImageResultCallback {
10+
11+
private long lastPercent = -1;
12+
private final Logger log;
13+
14+
/**
15+
* Create a Docker build logger.
16+
*
17+
* @param logger logging function (e.g. this::log)
18+
*/
19+
public LoggingBuildImageResultCallback(final Logger logger) {
20+
this.log = logger;
21+
}
22+
23+
@Override
24+
public void onNext(final BuildResponseItem item) {
25+
26+
// Dockerfile step output
27+
if (log != null) {
28+
String s = item.getStream();
29+
if (s != null) {
30+
log.info(s.trim());
31+
}
32+
}
33+
34+
// Layer download progress
35+
// (NO direct ProgressDetail reference)
36+
ResponseItem.ProgressDetail pd = item.getProgressDetail();
37+
if (pd != null && pd.getTotal() != null && pd.getCurrent() != null) {
38+
39+
long current = pd.getCurrent();
40+
long total = pd.getTotal();
41+
42+
if (total > 0) {
43+
long percent = (current * 100) / total;
44+
if (percent != lastPercent) {
45+
if (log != null) {
46+
log.info("Progress: {}%", percent);
47+
}
48+
49+
lastPercent = percent;
50+
}
51+
}
52+
}
53+
54+
// Error output
55+
if (log != null) {
56+
ResponseItem.ErrorDetail errorDetail = item.getErrorDetail();
57+
if (errorDetail != null) {
58+
log.error("ERROR: {}", errorDetail.getMessage());
59+
}
60+
}
61+
62+
super.onNext(item);
63+
}
64+
65+
@Override
66+
public void onError(final Throwable throwable) {
67+
if (log != null) {
68+
log.error("Docker build failed: {}", throwable.getMessage());
69+
}
70+
super.onError(throwable);
71+
}
72+
73+
@Override
74+
public void onComplete() {
75+
if (log != null) {
76+
log.info("Docker build completed");
77+
}
78+
super.onComplete();
79+
}
80+
}

0 commit comments

Comments
 (0)