-
Notifications
You must be signed in to change notification settings - Fork 89
#2178: Make Release commandlet build-tool independent #2302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8fba622
60778ae
60c5453
a99f171
b9240bf
7b5b6f5
c33848d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,23 +50,35 @@ protected void doRun() { | |
| throw new CliException("Missing current working directory!"); | ||
| } | ||
|
|
||
| LocalToolCommandlet commandlet = findBuildCommandlet(this.context, buildPath); | ||
| if (commandlet == null) { | ||
| throw new CliException("Could not find a build descriptor in " + buildPath + " - no supported build tool detected."); | ||
| } | ||
| List<String> args = this.arguments.asList(); | ||
| LocalToolCommandlet commandlet = null; | ||
| if (args.isEmpty()) { | ||
| String variableName = commandlet.getName().toUpperCase(Locale.ROOT) + "_BUILD_OPTS"; | ||
| args = getDefaultToolOptions(variableName); | ||
| } | ||
| commandlet.runTool(args); | ||
| } | ||
|
|
||
| /** | ||
| * Detects the applicable build tool for the given {@code buildPath} by {@link LocalToolCommandlet#findBuildDescriptor(Path) querying} the available build | ||
| * commandlets (in order of priority) for a matching build descriptor (e.g. {@code pom.xml}, {@code build.gradle} or {@code package.json}). | ||
| * | ||
| * @param context the {@link IdeContext}. | ||
| * @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. | ||
| */ | ||
| static LocalToolCommandlet findBuildCommandlet(IdeContext context, Path buildPath) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should-fix — placement. Two things about this signature:
#2178 names the better home directly: "Ideally we should ask the commandlet manager for all commandlets that are build commandlets and then ask them for the build descriptor." LocalToolCommandlet commandlet = this.context.getCommandletManager().findBuildCommandlet(projectPath);That would also give If you would rather not touch
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the method be renamed to |
||
|
|
||
| for (Class<? extends LocalToolCommandlet> toolClass : BUILD_TOOLS) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should-fix — null contract.
Moving the check into the helper covers both callers and keeps static LocalToolCommandlet findBuildCommandlet(IdeContext context, Path buildPath) {
if (buildPath == null) {
throw new CliException("Missing current working directory!");
}
for (Class<? extends LocalToolCommandlet> toolClass : BUILD_TOOLS) {The existing
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we already return |
||
| 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 toolCommandlet = context.getCommandletManager().getCommandlet(toolClass); | ||
| if (toolCommandlet.findBuildDescriptor(buildPath) != null) { | ||
| return toolCommandlet; | ||
| } | ||
| } | ||
| if (commandlet == null) { | ||
| throw new CliException("Could not find build descriptor - no pom.xml, build.gradle, or package.json found!"); | ||
| } | ||
| commandlet.runTool(args); | ||
| return null; | ||
| } | ||
|
|
||
| private List<String> getDefaultToolOptions(String buildOptionName) { | ||
|
|
||
| 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; | ||||||||||
|
|
@@ -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; | ||||||||||
|
|
||||||||||
| /** | ||||||||||
|
|
@@ -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 = BuildCommandlet.findBuildCommandlet(this.context, 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."); | ||||||||||
| } | ||||||||||
|
laert-ll marked this conversation as resolved.
|
||||||||||
|
|
||||||||||
| 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."); | ||||||||||
| } | ||||||||||
|
|
||||||||||
|
|
@@ -94,14 +102,15 @@ private boolean warnIfFork(GitContext git, Path projectPath) { | |||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private boolean isTopLevelProject(Path projectPath) { | ||||||||||
| private boolean isTopLevelProject(LocalToolCommandlet buildTool, Path projectPath) { | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor — naming. This parameter is called
Suggested change
(and the two usages in the body). coding-conventions.adoc § Naming — "always use short but speaking names"; here the name actively points at the wrong abstraction. |
||||||||||
|
|
||||||||||
| // 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 a build descriptor is present here but not in the parent directory | ||||||||||
| Path parent = projectPath.getParent(); | ||||||||||
| return (buildTool.findBuildDescriptor(projectPath) != null) | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor — the first operand is dead. At the only call site (line 57) The method reduces to the question it is actually asking:
Suggested change
(uses the renamed parameter from the comment above). Non-blocking — the current code is correct, just doing redundant I/O. |
||||||||||
| && ((parent == null) || (buildTool.findBuildDescriptor(parent) == null)); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private void buildAndDeploy(Mvn buildTool) { | ||||||||||
| private void buildAndDeploy(BuildTool buildTool) { | ||||||||||
|
|
||||||||||
| while (true) { | ||||||||||
| ProcessResult result = buildTool.buildAndDeploy(this.arguments.asList()); | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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; | ||
|
|
||
|
|
@@ -118,4 +124,22 @@ void testBuildWithNoBuildFile() { | |
| context.setCwd(context.getWorkspacePath().resolve("empty"), context.getWorkspacePath().toString(), context.getIdeHome()); | ||
| assertThrows(CliException.class, buildCommandlet::run); | ||
| } | ||
|
|
||
| /** | ||
| * Tests {@link BuildCommandlet#findBuildCommandlet(com.devonfw.tools.ide.context.IdeContext, 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 testFindBuildCommandlet() { | ||
|
|
||
| IdeTestContext context = newContext(PROJECT_BUILD); | ||
| Path workspace = context.getWorkspacePath(); | ||
|
|
||
| assertThat(BuildCommandlet.findBuildCommandlet(context, workspace.resolve("mvn"))).isInstanceOf(Mvn.class); | ||
| assertThat(BuildCommandlet.findBuildCommandlet(context, workspace.resolve("gradle"))).isInstanceOf(Gradle.class); | ||
| assertThat(BuildCommandlet.findBuildCommandlet(context, 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(BuildCommandlet.findBuildCommandlet(context, workspace.resolve("yarn"))).isInstanceOf(Yarn.class); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should-fix — coverage gap on the behaviour that actually changed for users. This asserts the helper returns The second uncovered flip is quieter and worth at least a helper-level assertion here, since it costs nothing: a directory containing both // a polyglot project must be built by the highest-priority tool, not the last one that matches
assertThat(BuildCommandlet.findBuildCommandlet(context, workspace.resolve("mvn-and-npm"))).isInstanceOf(Mvn.class);would document the priority contract that |
||
| assertThat(BuildCommandlet.findBuildCommandlet(context, workspace.resolve("empty"))).isNull(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor — the entry only advertises the
ReleaseCommandletrefactoring, but the most visible effect of this PR for existing users is onide build: yarn projects were being built with npm, and polyglot repos were being built with the wrong tool and the wrong default options. That is a user-facing bugfix and per DoD.adoc belongs in the changelog, otherwise nobody upgrading will connect a changedide buildbehaviour to this issue.