Skip to content

Commit 7ac2be5

Browse files
Provide fix suggestions for not covered Maven plugin execution in project build lifecycle (#1949)
* Provide fix suggestions for not covered Maven plugin execution in project build lifecycle Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
1 parent d058e4e commit 7ac2be5

6 files changed

Lines changed: 232 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Not Covered Maven Plugin Execution
2+
3+
## Background
4+
Some Maven projects use 3rd party Maven plugins to generate sources or resources, and you may find that the generated code is not picked up by the project classpath, or the Maven goals are not executed by Java extension. This is a known technical issue with `m2e`, the underlying Maven integration tool for Java extension. The reason is that the Maven tooling `m2e` doesn't know if it's safe to run your Maven plugin automatically during a workspace build, so it does not run them by default and requires explicit instructions on how to handle them. Learn more about this issue from the wiki about [Execution Not Covered](https://www.eclipse.org/m2e/documentation/m2e-execution-not-covered.html).
5+
6+
## Workaround
7+
- Option 1: The best thing is still to request the Maven plugin authors to provide native integration with m2e. Here is a guideline on [how to make Maven plugins compatible with m2e](https://www.eclipse.org/m2e/documentation/m2e-making-maven-plugins-compat.html).
8+
9+
- Option 2: Use [build-helper-maven-plugin](http://www.mojohaus.org/build-helper-maven-plugin/usage.html) to explicitly add the unrecognized source folder to classpath.
10+
11+
```xml
12+
<project>
13+
...
14+
<build>
15+
<plugins>
16+
<plugin>
17+
<groupId>org.codehaus.mojo</groupId>
18+
<artifactId>build-helper-maven-plugin</artifactId>
19+
<version>3.2.0</version>
20+
<executions>
21+
<execution>
22+
<id>add-source</id>
23+
<phase>generate-sources</phase>
24+
<goals>
25+
<goal>add-source</goal>
26+
</goals>
27+
<configuration>
28+
<sources>
29+
<source>some directory</source>
30+
...
31+
</sources>
32+
</configuration>
33+
</execution>
34+
</executions>
35+
</plugin>
36+
</plugins>
37+
</build>
38+
</project>
39+
```
40+
41+
- Option 3: Configure a lifecycle mapping metadata in pom.xml that explicitly tells m2e what to do with your plugin.
42+
43+
For example, add [a processing instruction in the pom.xml](https://www.eclipse.org/m2e/documentation/release-notes-17.html#new-syntax-for-specifying-lifecycle-mapping-metadata) like `<?m2e execute onConfiguration?>` to execute it on every project configuration udpate.
44+
45+
You can use quick fixes to generate the inline lifecycle mapping in pom.xml, or manually configure it in pom.xml. If it's yourself that manually configure it, you have to let VS Code update the project configuration as well. The command is `"Java: Update Project"`.
46+
47+
```xml
48+
<project>
49+
...
50+
<build>
51+
<plugins>
52+
<plugin>
53+
<groupId>ro.isdc.wro4j</groupId>
54+
<artifactId>wro4j-maven-plugin</artifactId>
55+
<version>1.8.0</version>
56+
<executions>
57+
<execution>
58+
<?m2e execute onConfiguration?>
59+
<phase>generate-resources</phase>
60+
<goals>
61+
<goal>run</goal>
62+
</goals>
63+
</execution>
64+
</executions>
65+
</plugin>
66+
</plugins>
67+
</build>
68+
</project>
69+
```
70+
71+
If you have multiple Maven plugins that need to configure the lifecycle mapping metadata, you can also configure them together in a dedicated `pluginManagement` section.
72+
73+
```xml
74+
<project>
75+
...
76+
<build>
77+
<pluginManagement>
78+
<plugins>
79+
<!-- This plugin's configuration is used to store m2e settings only.
80+
It has no influence on the Maven build itself. -->
81+
<plugin>
82+
<groupId>org.eclipse.m2e</groupId>
83+
<artifactId>lifecycle-mapping</artifactId>
84+
<version>1.0.0</version>
85+
<configuration>
86+
<lifecycleMappingMetadata>
87+
<pluginExecutions>
88+
<pluginExecution>
89+
<pluginExecutionFilter>
90+
<groupId>ro.isdc.wro4j</groupId>
91+
<artifactId>wro4j-maven-plugin</artifactId>
92+
<versionRange>[1.8.0,)</versionRange>
93+
<goals>
94+
<goal>run</goal>
95+
</goals>
96+
</pluginExecutionFilter>
97+
<action>
98+
<execute>
99+
<runOnConfiguration>true</runOnConfiguration>
100+
</execute>
101+
</action>
102+
</pluginExecution>
103+
</pluginExecutions>
104+
</lifecycleMappingMetadata>
105+
</configuration>
106+
</plugin>
107+
</plugins>
108+
</pluginManagement>
109+
</build>
110+
</project>
111+
```

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,17 @@
318318
"description": "Path to Maven's global settings.xml",
319319
"scope": "window"
320320
},
321+
"java.configuration.maven.notCoveredPluginExecutionSeverity": {
322+
"type": "string",
323+
"enum": [
324+
"ignore",
325+
"warning",
326+
"error"
327+
],
328+
"default": "warning",
329+
"description": "Specifies severity if the plugin execution is not covered by Maven build lifecycle.",
330+
"scope": "window"
331+
},
321332
"java.format.enabled": {
322333
"type": "boolean",
323334
"default": true,

src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,7 @@ export namespace Commands {
247247

248248
export const TEMPLATE_VARIABLES = '_java.templateVariables';
249249

250+
export const NOT_COVERED_EXECUTION = '_java.notCoveredExecution';
251+
250252
export const RUNTIME_VALIDATION_OPEN = 'java.runtimeValidation.open';
251253
}

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
119119
context.subscriptions.push(commands.registerCommand(Commands.TEMPLATE_VARIABLES, async () => {
120120
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.TEMPLATE_VARIABLES}.md`)), 'Predefined Variables', "", context);
121121
}));
122+
context.subscriptions.push(commands.registerCommand(Commands.NOT_COVERED_EXECUTION, async () => {
123+
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `_java.notCoveredExecution.md`)), 'Not Covered Maven Plugin Execution', "", context);
124+
}));
122125

123126
let storagePath = context.storagePath;
124127
if (!storagePath) {

src/pom/pomCodeActionProvider.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
'use strict';
2+
3+
import { CodeActionProvider, CodeAction, TextDocument, Range, Selection, CodeActionContext, CancellationToken, ProviderResult, Command, CodeActionKind, Diagnostic, WorkspaceEdit, EndOfLine, ExtensionContext, commands, CodeActionProviderMetadata, workspace, Uri, window, TextEditor } from "vscode";
4+
import { Commands } from "../commands";
5+
6+
export class PomCodeActionProvider implements CodeActionProvider<CodeAction> {
7+
constructor(context: ExtensionContext) {
8+
context.subscriptions.push(commands.registerCommand("_java.projectConfiguration.saveAndUpdate", async (uri: Uri) => {
9+
const document: TextDocument = await workspace.openTextDocument(uri);
10+
await document.save();
11+
commands.executeCommand(Commands.CONFIGURATION_UPDATE, uri);
12+
}));
13+
}
14+
15+
provideCodeActions(document: TextDocument, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]> {
16+
if (context?.diagnostics?.length && context.diagnostics[0].source === "Java") {
17+
return this.collectCodeActionsForNotCoveredExecutions(document, context.diagnostics);
18+
}
19+
20+
return undefined;
21+
}
22+
23+
private collectCodeActionsForNotCoveredExecutions(document: TextDocument, diagnostics: readonly Diagnostic[]): CodeAction[] {
24+
const codeActions: CodeAction[] = [];
25+
for (const diagnostic of diagnostics) {
26+
if (diagnostic.message?.startsWith("Plugin execution not covered by lifecycle configuration")) {
27+
const indentation = this.getNewTextIndentation(document, diagnostic);
28+
const saveAndUpdateConfigCommand: Command = {
29+
title: "Save and update project configuration",
30+
command: "_java.projectConfiguration.saveAndUpdate",
31+
arguments: [ document.uri ],
32+
};
33+
34+
const action1 = new CodeAction("Enable this execution in project configuration phase", CodeActionKind.QuickFix.append("pom"));
35+
action1.edit = new WorkspaceEdit();
36+
action1.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e execute onConfiguration?>");
37+
action1.command = saveAndUpdateConfigCommand;
38+
codeActions.push(action1);
39+
40+
const action2 = new CodeAction("Enable this execution in project build phase", CodeActionKind.QuickFix.append("pom"));
41+
action2.edit = new WorkspaceEdit();
42+
action2.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e execute onConfiguration,onIncremental?>");
43+
action2.command = saveAndUpdateConfigCommand;
44+
codeActions.push(action2);
45+
46+
const action3 = new CodeAction("Mark this execution as ignored in pom.xml", CodeActionKind.QuickFix.append("pom"));
47+
action3.edit = new WorkspaceEdit();
48+
action3.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e ignore?>");
49+
action3.command = saveAndUpdateConfigCommand;
50+
codeActions.push(action3);
51+
}
52+
}
53+
54+
return codeActions;
55+
}
56+
57+
private getNewTextIndentation(document: TextDocument, diagnostic: Diagnostic): string {
58+
const textline = document.lineAt(diagnostic.range.end.line);
59+
if (textline.text.lastIndexOf("</execution>") > diagnostic.range.end.character) {
60+
return "";
61+
}
62+
63+
let tabSize: number = 2; // default value
64+
let insertSpaces: boolean = true; // default value
65+
const activeEditor: TextEditor | undefined = window.activeTextEditor;
66+
if (activeEditor && activeEditor.document.uri.toString() === document.uri.toString()) {
67+
tabSize = Number(activeEditor.options.tabSize);
68+
insertSpaces = Boolean(activeEditor.options.insertSpaces);
69+
}
70+
71+
const lineSeparator = document.eol === EndOfLine.LF ? "\r" : "\r\n";
72+
let newIndentation = lineSeparator + textline.text.substring(0, textline.firstNonWhitespaceCharacterIndex);
73+
if (insertSpaces) {
74+
for (let i = 0; i < tabSize; i++) {
75+
newIndentation += ' '; // insert a space char.
76+
}
77+
} else {
78+
newIndentation += ' '; // insert a tab char.
79+
}
80+
81+
return newIndentation;
82+
}
83+
}
84+
85+
export const pomCodeActionMetadata: CodeActionProviderMetadata = {
86+
providedCodeActionKinds: [
87+
CodeActionKind.QuickFix.append("pom")
88+
],
89+
documentation: [
90+
{
91+
kind: CodeActionKind.QuickFix.append("pom"),
92+
command: {
93+
title: "Learn more about not covered Maven execution",
94+
command: Commands.NOT_COVERED_EXECUTION
95+
}
96+
}
97+
],
98+
};

src/standardLanguageClient.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { RefactorDocumentProvider, javaRefactorKinds } from "./codeActionProvide
3030
import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree";
3131
import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol";
3232
import { buildFilePatterns } from './plugin';
33+
import { pomCodeActionMetadata, PomCodeActionProvider } from "./pom/pomCodeActionProvider";
3334

3435
const extensionName = 'Language Support for Java';
3536
const GRADLE_CHECKSUM = "gradle/checksum/prompt";
@@ -410,6 +411,12 @@ export class StandardLanguageClient {
410411
const sectionId: string = javaRefactorKinds.get(kind) || '';
411412
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.LEARN_MORE_ABOUT_REFACTORING}.md`)), 'Java Refactoring', sectionId, context);
412413
}));
414+
415+
languages.registerCodeActionsProvider({
416+
language: "xml",
417+
scheme: "file",
418+
pattern: "**/pom.xml"
419+
}, new PomCodeActionProvider(context), pomCodeActionMetadata);
413420
});
414421
}
415422

0 commit comments

Comments
 (0)