Skip to content
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

Provide fix suggestions for not covered Maven plugin execution in project build lifecycle #1949

Merged
merged 4 commits into from
Jun 24, 2021
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
111 changes: 111 additions & 0 deletions document/_java.notCoveredExecution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Not Covered Maven Plugin Execution

## Background
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).

## Workaround
- 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).

- 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.

```xml
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>some directory</source>
...
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
```

- Option 3: Configure a lifecycle mapping metadata in pom.xml that explicitly tells m2e what to do with your plugin.

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.

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"`.

```xml
<project>
...
<build>
<plugins>
<plugin>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<version>1.8.0</version>
<executions>
<execution>
<?m2e execute onConfiguration?>
<phase>generate-resources</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
```

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.

```xml
<project>
...
<build>
<pluginManagement>
<plugins>
<!-- This plugin's configuration is used to store m2e settings only.
It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>ro.isdc.wro4j</groupId>
<artifactId>wro4j-maven-plugin</artifactId>
<versionRange>[1.8.0,)</versionRange>
<goals>
<goal>run</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnConfiguration>true</runOnConfiguration>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
```
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,17 @@
"description": "Path to Maven's global settings.xml",
"scope": "window"
},
"java.configuration.maven.notCoveredPluginExecutionSeverity": {
"type": "string",
"enum": [
"ignore",
"warning",
"error"
],
"default": "warning",
"description": "Specifies severity if the plugin execution is not covered by Maven build lifecycle.",
"scope": "window"
},
"java.format.enabled": {
"type": "boolean",
"default": true,
Expand Down
2 changes: 2 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,7 @@ export namespace Commands {

export const TEMPLATE_VARIABLES = '_java.templateVariables';

export const NOT_COVERED_EXECUTION = '_java.notCoveredExecution';

export const RUNTIME_VALIDATION_OPEN = 'java.runtimeValidation.open';
}
3 changes: 3 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ export function activate(context: ExtensionContext): Promise<ExtensionAPI> {
context.subscriptions.push(commands.registerCommand(Commands.TEMPLATE_VARIABLES, async () => {
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.TEMPLATE_VARIABLES}.md`)), 'Predefined Variables', "", context);
}));
context.subscriptions.push(commands.registerCommand(Commands.NOT_COVERED_EXECUTION, async () => {
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `_java.notCoveredExecution.md`)), 'Not Covered Maven Plugin Execution', "", context);
}));

let storagePath = context.storagePath;
if (!storagePath) {
Expand Down
98 changes: 98 additions & 0 deletions src/pom/pomCodeActionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'use strict';

import { CodeActionProvider, CodeAction, TextDocument, Range, Selection, CodeActionContext, CancellationToken, ProviderResult, Command, CodeActionKind, Diagnostic, WorkspaceEdit, EndOfLine, ExtensionContext, commands, CodeActionProviderMetadata, workspace, Uri, window, TextEditor } from "vscode";
import { Commands } from "../commands";

export class PomCodeActionProvider implements CodeActionProvider<CodeAction> {
constructor(context: ExtensionContext) {
context.subscriptions.push(commands.registerCommand("_java.projectConfiguration.saveAndUpdate", async (uri: Uri) => {
const document: TextDocument = await workspace.openTextDocument(uri);
await document.save();
commands.executeCommand(Commands.CONFIGURATION_UPDATE, uri);
}));
}

provideCodeActions(document: TextDocument, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]> {
if (context?.diagnostics?.length && context.diagnostics[0].source === "Java") {
return this.collectCodeActionsForNotCoveredExecutions(document, context.diagnostics);
}

return undefined;
}

private collectCodeActionsForNotCoveredExecutions(document: TextDocument, diagnostics: readonly Diagnostic[]): CodeAction[] {
const codeActions: CodeAction[] = [];
for (const diagnostic of diagnostics) {
if (diagnostic.message?.startsWith("Plugin execution not covered by lifecycle configuration")) {
const indentation = this.getNewTextIndentation(document, diagnostic);
const saveAndUpdateConfigCommand: Command = {
title: "Save and update project configuration",
command: "_java.projectConfiguration.saveAndUpdate",
arguments: [ document.uri ],
};

const action1 = new CodeAction("Enable this execution in project configuration phase", CodeActionKind.QuickFix.append("pom"));
action1.edit = new WorkspaceEdit();
action1.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e execute onConfiguration?>");
action1.command = saveAndUpdateConfigCommand;
codeActions.push(action1);

const action2 = new CodeAction("Enable this execution in project build phase", CodeActionKind.QuickFix.append("pom"));
action2.edit = new WorkspaceEdit();
action2.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e execute onConfiguration,onIncremental?>");
action2.command = saveAndUpdateConfigCommand;
codeActions.push(action2);

const action3 = new CodeAction("Mark this execution as ignored in pom.xml", CodeActionKind.QuickFix.append("pom"));
action3.edit = new WorkspaceEdit();
action3.edit.insert(document.uri, diagnostic.range.end, indentation + "<?m2e ignore?>");
action3.command = saveAndUpdateConfigCommand;
codeActions.push(action3);
}
}

return codeActions;
}

private getNewTextIndentation(document: TextDocument, diagnostic: Diagnostic): string {
const textline = document.lineAt(diagnostic.range.end.line);
if (textline.text.lastIndexOf("</execution>") > diagnostic.range.end.character) {
return "";
}

let tabSize: number = 2; // default value
let insertSpaces: boolean = true; // default value
const activeEditor: TextEditor | undefined = window.activeTextEditor;
if (activeEditor && activeEditor.document.uri.toString() === document.uri.toString()) {
tabSize = Number(activeEditor.options.tabSize);
insertSpaces = Boolean(activeEditor.options.insertSpaces);
}

const lineSeparator = document.eol === EndOfLine.LF ? "\r" : "\r\n";
let newIndentation = lineSeparator + textline.text.substring(0, textline.firstNonWhitespaceCharacterIndex);
if (insertSpaces) {
for (let i = 0; i < tabSize; i++) {
newIndentation += ' '; // insert a space char.
}
} else {
newIndentation += ' '; // insert a tab char.
}

return newIndentation;
}
}

export const pomCodeActionMetadata: CodeActionProviderMetadata = {
providedCodeActionKinds: [
CodeActionKind.QuickFix.append("pom")
],
documentation: [
{
kind: CodeActionKind.QuickFix.append("pom"),
command: {
title: "Learn more about not covered Maven execution",
command: Commands.NOT_COVERED_EXECUTION
}
}
],
};
7 changes: 7 additions & 0 deletions src/standardLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { RefactorDocumentProvider, javaRefactorKinds } from "./codeActionProvide
import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree";
import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol";
import { buildFilePatterns } from './plugin';
import { pomCodeActionMetadata, PomCodeActionProvider } from "./pom/pomCodeActionProvider";

const extensionName = 'Language Support for Java';
const GRADLE_CHECKSUM = "gradle/checksum/prompt";
Expand Down Expand Up @@ -410,6 +411,12 @@ export class StandardLanguageClient {
const sectionId: string = javaRefactorKinds.get(kind) || '';
markdownPreviewProvider.show(context.asAbsolutePath(path.join('document', `${Commands.LEARN_MORE_ABOUT_REFACTORING}.md`)), 'Java Refactoring', sectionId, context);
}));

languages.registerCodeActionsProvider({
language: "xml",
scheme: "file",
pattern: "**/pom.xml"
}, new PomCodeActionProvider(context), pomCodeActionMetadata);
});
}

Expand Down