From c89e8395b1d9cb3e1ae7ddf51bf9a1d212646213 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Tue, 6 Oct 2020 13:42:01 -0700 Subject: [PATCH] Improve error message for missing build script --- .../docker/runtarget/BuildScriptLocator.java | 78 +++++++++++++++++++ .../boot/dash/docker/runtarget/DockerApp.java | 78 ++++++++++++------- .../MissingBuildScriptException.java | 33 ++++++++ 3 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/BuildScriptLocator.java create mode 100644 eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/MissingBuildScriptException.java diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/BuildScriptLocator.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/BuildScriptLocator.java new file mode 100644 index 000000000..bd2514c98 --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/BuildScriptLocator.java @@ -0,0 +1,78 @@ +package org.springframework.ide.eclipse.boot.dash.docker.runtarget; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.springsource.ide.eclipse.commons.core.util.OsUtils; + +import com.google.common.collect.ImmutableList; + +public class BuildScriptLocator { + + public static enum BuildKind { + MAVEN, + GRADLE, + SHELL + }; + + public final File directory; + public final List command = new ArrayList<>(); + public final List checkedLocations = new ArrayList<>(); + private BuildKind buildKind; + + /** + * Tries to find build script in given directory. Various bits of information + * such as the type of script found, the build command to run etc. can + * be accessed via the public fields and accessors of the created object. + */ + public BuildScriptLocator(File directory) { + this.directory = directory; + findBuildScript(); + } + + private void findBuildScript() { + String buildShellScript; + this.buildKind = null; + if (OsUtils.isWindows()) { + buildShellScript = "sts-docker-build.bat"; + if (exists(directory.toPath().resolve(buildShellScript))) { + buildKind = BuildKind.SHELL; + command.addAll(ImmutableList.of("CMD", "/C", buildShellScript)); + } else if (exists(directory.toPath().resolve("mvnw.cmd"))) { + buildKind = BuildKind.MAVEN; + command.addAll(ImmutableList.of("CMD", "/C", "mvnw.cmd", "spring-boot:build-image", "-DskipTests")); + //, "-Dspring-boot.repackage.excludeDevtools=false" }; + } else if (exists(directory.toPath().resolve("gradlew.bat"))) { + buildKind = BuildKind.GRADLE; + command.addAll(ImmutableList.of("CMD", "/C", "gradlew.bat", "bootBuildImage", "-x", "test")); + } + } else { + buildShellScript = "sts-docker-build.sh"; + if (exists(directory.toPath().resolve(buildShellScript))) { + buildKind = BuildKind.SHELL; + command.addAll(ImmutableList.of("./sts-docker-build.sh")); + } else if (exists(directory.toPath().resolve("mvnw"))) { + buildKind = BuildKind.MAVEN; + command.addAll(ImmutableList.of("./mvnw", "spring-boot:build-image", "-DskipTests")); + } else if (exists(directory.toPath().resolve("gradlew"))) { + buildKind = BuildKind.GRADLE; + command.addAll(ImmutableList.of("./gradlew", "--stacktrace", "bootBuildImage", "-x", "test" )); + } + } + } + + private boolean exists(Path resolved) { + checkedLocations.add(resolved.toFile()); + return Files.exists(resolved); + } + + /** + * @return The type of build script that was found. Returns null to indicate no build script was found. + */ + public BuildKind getBuildKind() { + return this.buildKind; + } +} diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java index 8d9133c75..e3ab0ea85 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java @@ -12,7 +12,6 @@ package org.springframework.ide.eclipse.boot.dash.docker.runtarget; import java.io.File; import java.io.IOException; -import java.nio.file.Files; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; @@ -51,6 +50,7 @@ import org.springframework.ide.eclipse.boot.dash.api.TemporalBoolean; import org.springframework.ide.eclipse.boot.dash.console.LogType; import org.springframework.ide.eclipse.boot.dash.devtools.DevtoolsUtil; import org.springframework.ide.eclipse.boot.dash.docker.jmx.JmxSupport; +import org.springframework.ide.eclipse.boot.dash.docker.runtarget.BuildScriptLocator.BuildKind; import org.springframework.ide.eclipse.boot.dash.labels.BootDashLabels; import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.model.remote.ChildBearing; @@ -59,10 +59,10 @@ import org.springframework.ide.eclipse.boot.dash.util.LineBasedStreamGobler; import org.springframework.ide.eclipse.boot.launch.util.PortFinder; import org.springframework.ide.eclipse.boot.util.JavaProjectUtil; import org.springsource.ide.eclipse.commons.core.pstore.PropertyStoreApi; -import org.springsource.ide.eclipse.commons.core.util.OsUtils; import org.springsource.ide.eclipse.commons.frameworks.core.util.JobUtil; import org.springsource.ide.eclipse.commons.frameworks.core.util.StringUtils; import org.springsource.ide.eclipse.commons.livexp.core.AbstractDisposable; +import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; import org.springsource.ide.eclipse.commons.livexp.util.Log; import org.springsource.ide.eclipse.commons.livexp.util.OldValueDisposer; @@ -244,7 +244,19 @@ public class DockerApp extends AbstractDisposable implements App, ChildBearing, throw new IllegalStateException("The project '"+project.getName()+"' is not accessible"); } console.write("Deploying Docker app " + getName() + BootDashLabels.ELLIPSIS, LogType.STDOUT); - return build(console); + try { + return build(console); + } catch (Exception e) { + console.write(ExceptionUtil.getMessage(e), LogType.STDERROR); + if (e instanceof MissingBuildScriptException) { + console.write("Places we looked for build script: ", LogType.STDERROR); + for (File loc : ((MissingBuildScriptException) e).locationsChecked) { + console.write(" - "+loc, LogType.STDERROR); + } + showBuildScriptHelp(console, (MissingBuildScriptException) e); + } + throw e; + } }); refreshTracker.run("Starting container '" + image + "'" + + BootDashLabels.ELLIPSIS, () -> { @@ -253,6 +265,30 @@ public class DockerApp extends AbstractDisposable implements App, ChildBearing, }); } + private void showBuildScriptHelp(AppConsole console, MissingBuildScriptException e) { + String[] help = { + "To build a docker image, Boot Dash needs to run a build script from your project.", + "Three different types are supported and checked for in this order:", + "", + "1. "+e.locationsChecked.get(0).getAbsoluteFile().getName(), + " A custom script placed by you at the project root.", + " Typically this runs a custom maven or gradle command on your project.", + "", + "2. maven", + " If your project has a mvnw, we will use that to execute the `spring-boot:build-image` task", + "", + "3. gradle", + " If your project has a gradlew, we will use that to execute the `bootBuildImage` task", + }; + try { + for (String line : help) { + console.write(line, LogType.STDERROR); + } + } catch (Exception e1) { + //ignore + } + } + private void run(AppConsole console, String image, DockerDeployment deployment) throws Exception { if (client==null) { console.write("Cannot start container... Docker client is disconnected!", LogType.STDERROR); @@ -409,36 +445,19 @@ public class DockerApp extends AbstractDisposable implements App, ChildBearing, IVMInstall jvm = JavaRuntime.getVMInstall(JavaCore.create(project)); return jvm.getInstallLocation().toString(); } - - private String[] getBuildCommand(File directory) { - boolean isMaven = true; - List command = new ArrayList<>(); - if (OsUtils.isWindows()) { - if (Files.exists(directory.toPath().resolve("mvnw.cmd"))) { - command.addAll(ImmutableList.of("CMD", "/C", "mvnw.cmd", "spring-boot:build-image", "-DskipTests")); - //, "-Dspring-boot.repackage.excludeDevtools=false" }; - } else if (Files.exists(directory.toPath().resolve("gradlew.bat"))) { - isMaven = false; - command.addAll(ImmutableList.of("CMD", "/C", "gradlew.bat", "bootBuildImage", "-x", "test")); - } - } else { - if (Files.exists(directory.toPath().resolve("sts-docker-build.sh"))) { - command.addAll(ImmutableList.of("./sts-docker-build.sh")); - } else if (Files.exists(directory.toPath().resolve("mvnw"))) { - command.addAll(ImmutableList.of("./mvnw", "spring-boot:build-image", "-DskipTests")); - } else if (Files.exists(directory.toPath().resolve("gradlew"))) { - isMaven = false; - command.addAll(ImmutableList.of("./gradlew", "--stacktrace", "bootBuildImage", "-x", "test" )); - } - } - if (command.isEmpty()) { - throw new IllegalStateException("Neither sts-docker-build.sh nor Gradle/Maven wrapper was found!"); + + private String[] getBuildCommand(File directory) throws MissingBuildScriptException { + BuildScriptLocator buildScriptLocator = new BuildScriptLocator(directory); + BuildKind buildKind = buildScriptLocator.getBuildKind(); + if (buildKind==null) { + throw new MissingBuildScriptException(buildScriptLocator.checkedLocations); } boolean wantsDevtools = deployment().getSystemProperties().getOrDefault(DevtoolsUtil.REMOTE_SECRET_PROP, null)!=null; + List command = buildScriptLocator.command; if (wantsDevtools) { - if (isMaven) { + if (buildKind==BuildKind.MAVEN) { command.add("-Dspring-boot.repackage.excludeDevtools=false"); - } else { + } else if (buildKind==BuildKind.GRADLE) { try { command.addAll(gradle_initScript( "allprojects {\n" + @@ -458,7 +477,6 @@ public class DockerApp extends AbstractDisposable implements App, ChildBearing, } private synchronized static List gradle_initScript(String script) throws IOException { - System.out.println(script); if (initFile==null) { initFile = File.createTempFile("init-script", ".gradle"); FileUtils.writeStringToFile(initFile, script, "UTF8"); diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/MissingBuildScriptException.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/MissingBuildScriptException.java new file mode 100644 index 000000000..aaae5344a --- /dev/null +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/MissingBuildScriptException.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2020 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.eclipse.boot.dash.docker.runtarget; + +import java.io.File; +import java.util.List; + +/** + * Exception raised when BootDash wants to build a docker image but can not + * find / determine how to build it. I.e. we are looking for a number of + * different build scripts in project-relative locations. If none of + * them are found then this exception is raised. + */ +public class MissingBuildScriptException extends Exception { + + private static final long serialVersionUID = 1L; + + public final List locationsChecked; + + public MissingBuildScriptException(List locationsChecked) { + super("Niether maven wraper, gradle wrapper or custom build script found."); + this.locationsChecked = locationsChecked; + } + +}