Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE

Release with new features and bugfixes:

* https://github.com/devonfw/IDEasy/issues/2178[#2178]: Make ReleaseCommandlet independent of specific build commandlet and fix `ide build` using npm instead of yarn
* https://github.com/devonfw/IDEasy/issues/2142[#2142]: Move IDE-specific metadata (.idea, .vscode) out of workspace
* https://github.com/devonfw/IDEasy/issues/989[#989]: Allow expressions in template variable definitions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,12 @@
import com.devonfw.tools.ide.context.IdeContext;
import com.devonfw.tools.ide.property.StringProperty;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.tool.gradle.Gradle;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.npm.Npm;
import com.devonfw.tools.ide.tool.yarn.Yarn;

/**
* Build tool {@link Commandlet} for automatically detecting build configuration files and running the respective tool.
*/
public class BuildCommandlet extends Commandlet {

private static final List<Class<? extends LocalToolCommandlet>> BUILD_TOOLS = List.of(Mvn.class, Gradle.class, Yarn.class, Npm.class);

/** The explicit build options to use (if empty use defaults). */
public final StringProperty arguments;

Expand Down Expand Up @@ -50,21 +44,14 @@ protected void doRun() {
throw new CliException("Missing current working directory!");
}

List<String> args = this.arguments.asList();
LocalToolCommandlet commandlet = null;
for (Class<? extends LocalToolCommandlet> toolClass : BUILD_TOOLS) {
LocalToolCommandlet toolCommandlet = this.context.getCommandletManager().getCommandlet(toolClass);
Path buildDescriptor = toolCommandlet.findBuildDescriptor(buildPath);
if (buildDescriptor != null) {
commandlet = toolCommandlet;
if (args.isEmpty()) {
String variableName = commandlet.getName().toUpperCase(Locale.ROOT) + "_BUILD_OPTS";
args = getDefaultToolOptions(variableName);
}
}
}
LocalToolCommandlet commandlet = this.context.getCommandletManager().findBuildTool(buildPath);
if (commandlet == null) {
throw new CliException("Could not find build descriptor - no pom.xml, build.gradle, or package.json found!");
throw new CliException("Could not find a build descriptor in " + buildPath + " - no supported build tool detected.");
}
List<String> args = this.arguments.asList();
if (args.isEmpty()) {
String variableName = commandlet.getName().toUpperCase(Locale.ROOT) + "_BUILD_OPTS";
args = getDefaultToolOptions(variableName);
}
commandlet.runTool(args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;

Expand Down Expand Up @@ -106,4 +107,13 @@ default LocalToolCommandlet getRequiredLocalToolCommandlet(String name) {
*/
Iterator<Commandlet> findCommandlet(CliArguments arguments, CompletionCandidateCollector collector);

/**
* Detects the applicable build tool for the given {@code buildPath} by {@link LocalToolCommandlet#findBuildDescriptor(Path) querying} the registered build
* commandlets (in order of priority) for a matching build descriptor (e.g. {@code pom.xml}, {@code build.gradle} or {@code package.json}).
*
* @param buildPath the {@link Path} to the directory to build.
* @return the applicable build {@link LocalToolCommandlet} or {@code null} if no build descriptor was found or {@code buildPath} was {@code null}.
*/
LocalToolCommandlet findBuildTool(Path buildPath);

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -19,6 +20,7 @@
import com.devonfw.tools.ide.git.repository.RepositoryCommandlet;
import com.devonfw.tools.ide.property.KeywordProperty;
import com.devonfw.tools.ide.property.Property;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.tool.androidstudio.AndroidStudio;
import com.devonfw.tools.ide.tool.aws.Aws;
import com.devonfw.tools.ide.tool.az.Azure;
Expand Down Expand Up @@ -82,6 +84,9 @@ public class CommandletManagerImpl implements CommandletManager {

private static final Logger LOG = LoggerFactory.getLogger(CommandletManagerImpl.class);

/** The build commandlets in order of priority - the first one with a matching build descriptor wins. */
private static final List<Class<? extends LocalToolCommandlet>> BUILD_TOOLS = List.of(Mvn.class, Gradle.class, Yarn.class, Npm.class);

private final IdeContext context;

private final Map<Class<? extends Commandlet>, Commandlet> commandletTypeMap;
Expand Down Expand Up @@ -279,6 +284,21 @@ public Iterator<Commandlet> findCommandlet(CliArguments arguments, CompletionCan
return new CommandletFinder(commandlet, arguments.copy(), collector);
}

@Override
public LocalToolCommandlet findBuildTool(Path buildPath) {

if (buildPath == null) {
return null;
}
for (Class<? extends LocalToolCommandlet> toolClass : BUILD_TOOLS) {
LocalToolCommandlet toolCommandlet = getCommandlet(toolClass);
if (toolCommandlet.findBuildDescriptor(buildPath) != null) {
return toolCommandlet;
}
}
return null;
}

private final class CommandletFinder implements Iterator<Commandlet> {

private final Commandlet firstCandidate;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.devonfw.tools.ide.commandlet;

import java.nio.file.Files;
import java.nio.file.Path;

import org.slf4j.Logger;
Expand All @@ -11,7 +10,8 @@
import com.devonfw.tools.ide.git.GitContext;
import com.devonfw.tools.ide.process.ProcessResult;
import com.devonfw.tools.ide.property.StringProperty;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.BuildTool;
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
import com.devonfw.tools.ide.version.VersionIdentifier;

/**
Expand Down Expand Up @@ -41,15 +41,23 @@ protected void doRun() {

Path projectPath = this.context.getCwd();
GitContext git = this.context.getGitContext();
Mvn buildTool = this.context.getCommandletManager().getCommandlet(Mvn.class);

LocalToolCommandlet commandlet = this.context.getCommandletManager().findBuildTool(projectPath);
if (commandlet == null) {
throw new CliException("Could not find a build descriptor in " + projectPath + ". There is nothing to release here.");
}
if (!(commandlet instanceof BuildTool buildTool)) {
throw new CliException("The build tool " + commandlet.getName() + " detected in " + projectPath + " does not support releasing.");
}

if (git.hasUntrackedFiles(projectPath)) {
throw new CliException("Your local git repository has uncommitted changes. Please use 'git stash' and rerun on clean repo.");
}
if (warnIfFork(git, projectPath)) {
confirmWarning("You seem to work on a fork. Releases should be done on the original repository!\nWe strongly recommend to abort and rerun on original repository.");
confirmWarning("You seem to work on a fork. Releases should be done on the original repository!\n"
+ "We strongly recommend to abort and rerun on original repository.");
}
if (!this.context.isForceMode() && !isTopLevelProject(projectPath)) {
if (!this.context.isForceMode() && !isTopLevelProject(commandlet, projectPath)) {
throw new CliException("Release has to be performed from the top-level project or using force option.");
}

Expand Down Expand Up @@ -94,14 +102,14 @@ private boolean warnIfFork(GitContext git, Path projectPath) {
return false;
}

private boolean isTopLevelProject(Path projectPath) {
private boolean isTopLevelProject(LocalToolCommandlet buildCommandlet, Path projectPath) {

// returns false in case there's no pom.xml present or if parent directory has a pom.xml
return Files.exists(projectPath.resolve("pom.xml"))
&& !Files.exists(projectPath.getParent().resolve("pom.xml"));
// top-level if the build descriptor found here is not also present in the parent directory
Path parent = projectPath.getParent();
return (parent == null) || (buildCommandlet.findBuildDescriptor(parent) == null);
}

private void buildAndDeploy(Mvn buildTool) {
private void buildAndDeploy(BuildTool buildTool) {

while (true) {
ProcessResult result = buildTool.buildAndDeploy(this.arguments.asList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static org.junit.jupiter.api.Assertions.assertThrows;

import java.nio.file.Path;

import org.junit.jupiter.api.Test;

import com.devonfw.tools.ide.cli.CliException;
Expand All @@ -11,6 +13,10 @@
import com.devonfw.tools.ide.log.IdeLogLevel;
import com.devonfw.tools.ide.os.SystemInfo;
import com.devonfw.tools.ide.os.SystemInfoMock;
import com.devonfw.tools.ide.tool.gradle.Gradle;
import com.devonfw.tools.ide.tool.mvn.Mvn;
import com.devonfw.tools.ide.tool.npm.Npm;
import com.devonfw.tools.ide.tool.yarn.Yarn;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;

Expand Down Expand Up @@ -118,4 +124,24 @@ void testBuildWithNoBuildFile() {
context.setCwd(context.getWorkspacePath().resolve("empty"), context.getWorkspacePath().toString(), context.getIdeHome());
assertThrows(CliException.class, buildCommandlet::run);
}

/**
* Tests {@link CommandletManager#findBuildTool(Path)} detecting the applicable build tool by its build
* descriptor and preferring {@link Yarn} over {@link Npm} when a {@code yarn.lock} is present.
*/
@Test
void testFindBuildTool() {

IdeTestContext context = newContext(PROJECT_BUILD);
Path workspace = context.getWorkspacePath();

assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("mvn"))).isInstanceOf(Mvn.class);
assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("gradle"))).isInstanceOf(Gradle.class);
assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("npm"))).isInstanceOf(Npm.class);
// both npm and yarn match package.json, but yarn.lock is present so yarn must take precedence over npm
assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("yarn"))).isInstanceOf(Yarn.class);
// a polyglot project must be built by the highest-priority tool, not the last one that matches
assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("mvn-and-npm"))).isInstanceOf(Mvn.class);
assertThat(context.getCommandletManager().findBuildTool(workspace.resolve("empty"))).isNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ void testReleaseWithoutBuildDescriptor() {
context.setCwd(context.getWorkspacePath().resolve("empty"), context.getWorkspacePath().toString(), context.getIdeHome());
ReleaseCommandlet releaseCommandlet = context.getCommandletManager().getCommandlet(ReleaseCommandlet.class);

assertThrows(CliException.class, releaseCommandlet::run);
CliException exception = assertThrows(CliException.class, releaseCommandlet::run);
assertThat(exception).hasMessageContaining("Could not find a build descriptor");
}

/**
* Tests that the release fails gracefully if a build descriptor is found but its build tool does not support releasing (does not implement
* {@link com.devonfw.tools.ide.tool.BuildTool}), e.g. a gradle project (only maven currently supports releasing).
*/
@Test
void testReleaseWithUnsupportedBuildToolThrowsException() {

IdeTestContext context = newReleaseContext(false);
context.setCwd(context.getWorkspacePath().resolve("gradle"), context.getWorkspacePath().toString(), context.getIdeHome());
ReleaseCommandlet releaseCommandlet = context.getCommandletManager().getCommandlet(ReleaseCommandlet.class);

CliException exception = assertThrows(CliException.class, releaseCommandlet::run);
assertThat(exception).hasMessageContaining("gradle").hasMessageContaining("does not support releasing");
}
}