diff --git a/.vscode/settings.json b/.vscode/settings.json index 91f589673..cdb66a421 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,7 +30,7 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[java]": { - "editor.defaultFormatter": "richardwillis.vscode-spotless-gradle", + "editor.defaultFormatter": "redhat.java", "editor.codeActionsOnSave": { "source.fixAll.spotlessGradle": true } diff --git a/extension/package.json b/extension/package.json index f0a70aaa4..939904242 100644 --- a/extension/package.json +++ b/extension/package.json @@ -56,7 +56,7 @@ "gradleContainerView": [ { "id": "gradleTasksView", - "name": "Gradle Tasks", + "name": "Gradle Projects", "when": "gradle:activated" }, { @@ -216,7 +216,7 @@ }, { "command": "gradle.explorerFlat", - "title": "Show Flat List", + "title": "Show Flat Tasks", "icon": { "light": "resources/light/list-flat.svg", "dark": "resources/dark/list-flat.svg" @@ -224,7 +224,7 @@ }, { "command": "gradle.explorerTree", - "title": "Show Tree", + "title": "Show Hierarchical Tasks", "icon": { "light": "resources/light/list-tree.svg", "dark": "resources/dark/list-tree.svg" @@ -285,6 +285,11 @@ "light": "resources/light/loading.svg", "dark": "resources/dark/loading.svg" } + }, + { + "command": "gradle.omitted.reveal", + "title": "Go to Omitted Dependency", + "icon": "$(go-to-file)" } ], "menus": { @@ -396,6 +401,10 @@ { "command": "gradle.removeRecentTask", "when": "false" + }, + { + "command": "gradle.omitted.reveal", + "when": "false" } ], "view/title": [ @@ -539,6 +548,11 @@ "when": "view == gradleDaemonsView && viewItem =~ /^busy$|^idle$/", "group": "inline@0" }, + { + "command": "gradle.omitted.reveal", + "when": "view == gradleTasksView && viewItem == omitted", + "group": "inline@0" + }, { "command": "gradle.stopDaemon", "when": "view == gradleDaemonsView && viewItem =~ /^busy$|^idle$/", diff --git a/extension/src/Extension.ts b/extension/src/Extension.ts index 3fa648f11..448fba1f5 100644 --- a/extension/src/Extension.ts +++ b/extension/src/Extension.ts @@ -36,6 +36,8 @@ import { getConfigReuseTerminals, } from './util/config'; import { FileWatcher } from './util/FileWatcher'; +import { DependencyTreeItem } from './views/gradleTasks/DependencyTreeItem'; +import { GRADLE_OMITTED_REVEAL } from './views/gradleTasks/DependencyUtils'; export class Extension { private readonly client: GradleClient; @@ -104,7 +106,8 @@ export class Extension { this.context, this.rootProjectsStore, this.gradleTaskProvider, - this.icons + this.icons, + this.client ); this.gradleTasksTreeView = vscode.window.createTreeView(GRADLE_TASKS_VIEW, { treeDataProvider: this.gradleTasksTreeDataProvider, @@ -182,6 +185,16 @@ export class Extension { this.handleWatchEvents(); this.handleEditorEvents(); + vscode.commands.registerCommand( + GRADLE_OMITTED_REVEAL, + async (item: DependencyTreeItem) => { + const omittedTreeItem = item.getOmittedTreeItem(); + if (omittedTreeItem) { + await this.gradleTasksTreeView.reveal(omittedTreeItem); + } + } + ); + void this.activate(); } diff --git a/extension/src/client/GradleClient.ts b/extension/src/client/GradleClient.ts index f6dca5ef2..42fd8c3a3 100644 --- a/extension/src/client/GradleClient.ts +++ b/extension/src/client/GradleClient.ts @@ -22,6 +22,9 @@ import { CancelBuildReply, CancelBuildsRequest, CancelBuildsReply, + DependencyItem, + GetDependenciesRequest, + GetDependenciesReply, } from '../proto/gradle_pb'; import { GradleClient as GrpcClient } from '../proto/gradle_grpc_pb'; @@ -223,6 +226,42 @@ export class GradleClient implements vscode.Disposable { ); } + public async getDependencies( + projectDir: string, + gradleConfig: GradleConfig + ): Promise { + await this.waitForConnect(); + const request = new GetDependenciesRequest(); + request.setProjectDir(projectDir); + request.setGradleConfig(gradleConfig); + try { + return await new Promise((resolve, reject) => { + this.grpcClient!.getDependencies( + request, + ( + err: grpc.ServiceError | null, + getDependenciesReply: GetDependenciesReply | undefined + ) => { + if (err) { + reject(err); + } else { + resolve(getDependenciesReply?.getItem()); + } + } + ); + }); + } catch (err) { + logger.error( + `Error getting dependencies for ${projectDir}: ${ + err.details || err.message + }` + ); + this.statusBarItem.command = COMMAND_SHOW_LOGS; + this.statusBarItem.text = '$(warning) Gradle: Get Dependencies Error'; + this.statusBarItem.show(); + } + } + public async runBuild( projectFolder: string, cancellationKey: string, diff --git a/extension/src/test/unit/gradleTasks.test.ts b/extension/src/test/unit/gradleTasks.test.ts index c5284b3da..d95bf17dc 100644 --- a/extension/src/test/unit/gradleTasks.test.ts +++ b/extension/src/test/unit/gradleTasks.test.ts @@ -111,7 +111,8 @@ describe(getSuiteName('Gradle tasks'), () => { mockContext, rootProjectsStore, gradleTaskProvider, - new Icons(mockContext) + new Icons(mockContext), + client ); logger.reset(); logger.setLoggingChannel(buildMockOutputChannel()); @@ -234,7 +235,6 @@ describe(getSuiteName('Gradle tasks'), () => { groupItem.label, mockTaskDefinition1ForFolder1.group ); - assert.strictEqual(groupItem.iconPath, vscode.ThemeIcon.Folder); assert.strictEqual( groupItem.parentTreeItem, projectItem, diff --git a/extension/src/test/unit/pinnedTasks.test.ts b/extension/src/test/unit/pinnedTasks.test.ts index 46a5d4510..4745e1d7e 100644 --- a/extension/src/test/unit/pinnedTasks.test.ts +++ b/extension/src/test/unit/pinnedTasks.test.ts @@ -88,7 +88,8 @@ describe(getSuiteName('Pinned tasks'), () => { mockContext, rootProjectsStore, gradleTaskProvider, - icons + icons, + client ); pinnedTasksStore = new PinnedTasksStore(mockContext); pinnedTasksTreeDataProvider = new PinnedTasksTreeDataProvider( diff --git a/extension/src/views/gradleTasks/DependencyConfigurationTreeItem.ts b/extension/src/views/gradleTasks/DependencyConfigurationTreeItem.ts new file mode 100644 index 000000000..12fc4179b --- /dev/null +++ b/extension/src/views/gradleTasks/DependencyConfigurationTreeItem.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from 'vscode'; + +export class DependencyConfigurationTreeItem extends vscode.TreeItem { + private children: vscode.TreeItem[] | undefined; + constructor( + name: string, + collapsibleState: vscode.TreeItemCollapsibleState, + public readonly parentTreeItem: vscode.TreeItem, + // TODO: https://github.com/microsoft/vscode-codicons/issues/77 + iconPath: vscode.ThemeIcon = new vscode.ThemeIcon('file-submodule') + ) { + super(name, collapsibleState); + this.iconPath = iconPath; + } + + public setChildren(children: vscode.TreeItem[]): void { + this.children = children; + } + + public getChildren(): vscode.TreeItem[] | undefined { + return this.children; + } +} diff --git a/extension/src/views/gradleTasks/DependencyTreeItem.ts b/extension/src/views/gradleTasks/DependencyTreeItem.ts new file mode 100644 index 000000000..d96cac545 --- /dev/null +++ b/extension/src/views/gradleTasks/DependencyTreeItem.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from 'vscode'; + +export class DependencyTreeItem extends vscode.TreeItem { + private children: vscode.TreeItem[] | undefined; + private omittedTreeItem: vscode.TreeItem | undefined; + constructor( + name: string, + collapsibleState: vscode.TreeItemCollapsibleState, + public readonly parentTreeItem: vscode.TreeItem, + iconPath: vscode.ThemeIcon = new vscode.ThemeIcon('library') + ) { + super(name, collapsibleState); + this.iconPath = iconPath; + } + + public setChildren(children: vscode.TreeItem[]): void { + this.children = children; + } + + public getChildren(): vscode.TreeItem[] | undefined { + return this.children; + } + + public setOmittedTreeItem(item: vscode.TreeItem): void { + this.omittedTreeItem = item; + } + + public getOmittedTreeItem(): vscode.TreeItem | undefined { + return this.omittedTreeItem; + } +} diff --git a/extension/src/views/gradleTasks/DependencyUtils.ts b/extension/src/views/gradleTasks/DependencyUtils.ts new file mode 100644 index 000000000..edfd9c7cb --- /dev/null +++ b/extension/src/views/gradleTasks/DependencyUtils.ts @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from 'vscode'; +import { DependencyItem, GradleDependencyType } from '../../proto/gradle_pb'; +import { DependencyConfigurationTreeItem } from './DependencyConfigurationTreeItem'; +import { DependencyTreeItem } from './DependencyTreeItem'; +import { ProjectDependencyTreeItem } from './ProjectDependencyTreeItem'; +export const GRADLE_OMITTED_REVEAL = 'gradle.omitted.reveal'; + +export function protocolItem2ProjectDependencyTreeItem( + protocolItem: DependencyItem, + parent: vscode.TreeItem +): ProjectDependencyTreeItem | undefined { + const name = 'Dependencies'; + const projectItem: ProjectDependencyTreeItem = new ProjectDependencyTreeItem( + name, + vscode.TreeItemCollapsibleState.Collapsed, + parent + ); + const children = protocolItem.getChildrenList(); + const treeChildren = []; + for (const child of children) { + if (child.getType() !== GradleDependencyType.CONFIGURATION) { + continue; + } + const configurationItem = protocolItem2DependencyConfigurationTreeItem( + child, + projectItem + ); + if (configurationItem) { + treeChildren.push(configurationItem); + } + } + if (!treeChildren.length) { + return undefined; + } + projectItem.setChildren(treeChildren); + return projectItem; +} + +function protocolItem2DependencyConfigurationTreeItem( + protocolItem: DependencyItem, + parent: vscode.TreeItem +): DependencyConfigurationTreeItem | undefined { + const name = protocolItem.getName(); + const storageMap = new Map(); + const configurationItem: DependencyConfigurationTreeItem = new DependencyConfigurationTreeItem( + name, + vscode.TreeItemCollapsibleState.Collapsed, + parent + ); + const children = protocolItem.getChildrenList(); + const treeChildren = []; + for (const child of children) { + if (child.getType() !== GradleDependencyType.DEPENDENCY) { + continue; + } + treeChildren.push( + protocolItem2DependencyTreeItem(child, configurationItem, storageMap) + ); + } + if (!treeChildren.length) { + return undefined; + } + configurationItem.setChildren(treeChildren); + return configurationItem; +} + +function protocolItem2DependencyTreeItem( + protocolItem: DependencyItem, + parent: vscode.TreeItem, + storageMap: Map +): DependencyTreeItem { + const name = protocolItem.getName(); + const dependencyItem: DependencyTreeItem = new DependencyTreeItem( + name, + vscode.TreeItemCollapsibleState.Collapsed, + parent + ); + if (storageMap.has(name)) { + const omittedTreeItem = storageMap.get(name); + if (omittedTreeItem) { + dependencyItem.setOmittedTreeItem(omittedTreeItem); + } + dependencyItem.contextValue = 'omitted'; + dependencyItem.label = dependencyItem.label + ' (*)'; + dependencyItem.collapsibleState = vscode.TreeItemCollapsibleState.None; + } else { + storageMap.set(name, dependencyItem); + const children = protocolItem.getChildrenList(); + const treeChildren = []; + for (const child of children) { + if (child.getType() !== GradleDependencyType.DEPENDENCY) { + continue; + } + treeChildren.push( + protocolItem2DependencyTreeItem(child, dependencyItem, storageMap) + ); + } + dependencyItem.collapsibleState = + treeChildren.length > 0 + ? vscode.TreeItemCollapsibleState.Collapsed + : vscode.TreeItemCollapsibleState.None; + dependencyItem.setChildren(treeChildren); + } + return dependencyItem; +} diff --git a/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts b/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts index 28911e4d0..eee3e233c 100644 --- a/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts +++ b/extension/src/views/gradleTasks/GradleTasksTreeDataProvider.ts @@ -14,6 +14,13 @@ import { isWorkspaceFolder } from '../../util'; import { isGradleTask } from '../../tasks/taskUtil'; import { RootProjectsStore } from '../../stores'; import { Icons } from '../../icons'; +import { GradleClient } from '../../client'; +import { getGradleConfig } from '../../util/config'; +import { DependencyConfigurationTreeItem } from './DependencyConfigurationTreeItem'; +import { DependencyTreeItem } from './DependencyTreeItem'; +import { protocolItem2ProjectDependencyTreeItem } from './DependencyUtils'; +import { ProjectDependencyTreeItem } from './ProjectDependencyTreeItem'; +import { ProjectTaskTreeItem } from './ProjectTaskTreeItem'; const gradleTaskTreeItemMap: Map = new Map(); const gradleProjectTreeItemMap: Map = new Map(); @@ -47,7 +54,8 @@ export class GradleTasksTreeDataProvider private readonly context: vscode.ExtensionContext, private readonly rootProjectStore: RootProjectsStore, private readonly gradleTaskProvider: GradleTaskProvider, - private readonly icons: Icons + private readonly icons: Icons, + private readonly client: GradleClient ) { const collapsed = this.context.workspaceState.get( 'gradleTasksCollapsed', @@ -91,7 +99,10 @@ export class GradleTasksTreeDataProvider element instanceof RootProjectTreeItem || element instanceof ProjectTreeItem || element instanceof TreeItemWithTasksOrGroups || - element instanceof GradleTaskTreeItem + element instanceof GradleTaskTreeItem || + element instanceof ProjectTreeItem || + element instanceof DependencyConfigurationTreeItem || + element instanceof DependencyTreeItem ) { return element.parentTreeItem || null; } @@ -105,7 +116,32 @@ export class GradleTasksTreeDataProvider return element.projects; } if (element instanceof ProjectTreeItem) { - return [...element.groups, ...element.tasks]; + const projectTaskItem = new ProjectTaskTreeItem( + 'Tasks', + vscode.TreeItemCollapsibleState.Collapsed, + element + ); + projectTaskItem.setChildren([...element.groups, ...element.tasks]); + const results: vscode.TreeItem[] = [projectTaskItem]; + const resourceUri = element.resourceUri; + if (!resourceUri) { + return results; + } + const dependencyItem = await this.client.getDependencies( + path.dirname(resourceUri.fsPath), + getGradleConfig() + ); + if (!dependencyItem) { + return results; + } + const projectDependencyItem = protocolItem2ProjectDependencyTreeItem( + dependencyItem, + element + ); + if (projectDependencyItem) { + results.push(projectDependencyItem); + } + return results; } if (element instanceof GroupTreeItem) { return element.tasks; @@ -116,6 +152,14 @@ export class GradleTasksTreeDataProvider ) { return []; } + if ( + element instanceof ProjectTaskTreeItem || + element instanceof ProjectDependencyTreeItem || + element instanceof DependencyConfigurationTreeItem || + element instanceof DependencyTreeItem + ) { + return element.getChildren() || []; + } if (!element) { return await this.buildTreeItems(); } diff --git a/extension/src/views/gradleTasks/ProjectDependencyTreeItem.ts b/extension/src/views/gradleTasks/ProjectDependencyTreeItem.ts new file mode 100644 index 000000000..6456f0251 --- /dev/null +++ b/extension/src/views/gradleTasks/ProjectDependencyTreeItem.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from 'vscode'; + +export class ProjectDependencyTreeItem extends vscode.TreeItem { + private children: vscode.TreeItem[] | undefined; + constructor( + name: string, + collapsibleState: vscode.TreeItemCollapsibleState, + public readonly parentTreeItem: vscode.TreeItem, + // TODO: https://github.com/microsoft/vscode-codicons/issues/77 + iconPath: vscode.ThemeIcon = new vscode.ThemeIcon('file-submodule') + ) { + super(name, collapsibleState); + this.iconPath = iconPath; + } + + public setChildren(children: vscode.TreeItem[]): void { + this.children = children; + } + + public getChildren(): vscode.TreeItem[] | undefined { + return this.children; + } +} diff --git a/extension/src/views/gradleTasks/ProjectTaskTreeItem.ts b/extension/src/views/gradleTasks/ProjectTaskTreeItem.ts new file mode 100644 index 000000000..c2393bc58 --- /dev/null +++ b/extension/src/views/gradleTasks/ProjectTaskTreeItem.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from 'vscode'; + +export class ProjectTaskTreeItem extends vscode.TreeItem { + private children: vscode.TreeItem[] | undefined; + constructor( + name: string, + collapsibleState: vscode.TreeItemCollapsibleState, + public readonly parentTreeItem: vscode.TreeItem, + iconPath: vscode.ThemeIcon = new vscode.ThemeIcon('file-submodule') + ) { + super(name, collapsibleState); + this.iconPath = iconPath; + } + + public setChildren(children: vscode.TreeItem[]): void { + this.children = children; + } + + public getChildren(): vscode.TreeItem[] | undefined { + return this.children; + } +} diff --git a/extension/src/views/gradleTasks/TreeItemWithTasksOrGroups.ts b/extension/src/views/gradleTasks/TreeItemWithTasksOrGroups.ts index 538a43f53..c2fa070e8 100644 --- a/extension/src/views/gradleTasks/TreeItemWithTasksOrGroups.ts +++ b/extension/src/views/gradleTasks/TreeItemWithTasksOrGroups.ts @@ -10,7 +10,7 @@ export class TreeItemWithTasksOrGroups extends vscode.TreeItem { private readonly _tasks: GradleTaskTreeItem[] = []; private readonly _groups: GroupTreeItem[] = []; public readonly parentTreeItem?: vscode.TreeItem; - public readonly iconPath = vscode.ThemeIcon.Folder; + public readonly iconPath = new vscode.ThemeIcon('file-submodule'); public readonly contextValue = 'folder'; constructor( name: string, diff --git a/gradle-server/build.gradle b/gradle-server/build.gradle index 27c26daa9..2e4132666 100644 --- a/gradle-server/build.gradle +++ b/gradle-server/build.gradle @@ -12,6 +12,7 @@ java { } dependencies { + implementation project(":gradle-plugin-api") implementation "org.gradle:gradle-tooling-api:${gradlingToolingApi}" implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation "io.grpc:grpc-protobuf:${grpcVersion}" @@ -76,8 +77,18 @@ clean { delete libsDirName } +task shadowBugWorkaround(type: Jar) { + // https://github.com/johnrengelman/shadow/issues/111 + dependsOn tasks.getByPath(':gradle-plugin:jar') + destinationDir file('build/shadow-bug-workaround') + baseName = 'nested-content' + from tasks.getByPath(':gradle-plugin:jar') +} + shadowJar { + dependsOn shadowBugWorkaround classifier = null + from shadowBugWorkaround // https://github.com/grpc/grpc-java/issues/5794#issuecomment-497976351 // minimize() } diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java index a334c58e4..673a77bda 100644 --- a/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/GradleService.java @@ -4,6 +4,7 @@ import com.github.badsyntax.gradle.handlers.CancelBuildsHandler; import com.github.badsyntax.gradle.handlers.GetBuildHandler; import com.github.badsyntax.gradle.handlers.GetDaemonsStatusHandler; +import com.github.badsyntax.gradle.handlers.GetDependenciesHandler; import com.github.badsyntax.gradle.handlers.RunBuildHandler; import com.github.badsyntax.gradle.handlers.StopDaemonHandler; import com.github.badsyntax.gradle.handlers.StopDaemonsHandler; @@ -17,6 +18,14 @@ public void getBuild(GetBuildRequest req, StreamObserver response getBuildHandler.run(); } + @Override + public void getDependencies( + GetDependenciesRequest req, StreamObserver responseObserver) { + GetDependenciesHandler getDependenciesHandler = + new GetDependenciesHandler(req, responseObserver); + getDependenciesHandler.run(); + } + @Override public void runBuild(RunBuildRequest req, StreamObserver responseObserver) { RunBuildHandler runBuildHandler = new RunBuildHandler(req, responseObserver); diff --git a/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetDependenciesHandler.java b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetDependenciesHandler.java new file mode 100644 index 000000000..690130866 --- /dev/null +++ b/gradle-server/src/main/java/com/github/badsyntax/gradle/handlers/GetDependenciesHandler.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2021 Microsoft Corporation and others. + * 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ + +package com.github.badsyntax.gradle.handlers; + +import com.github.badsyntax.gradle.DependencyItem; +import com.github.badsyntax.gradle.ErrorMessageBuilder; +import com.github.badsyntax.gradle.GetDependenciesReply; +import com.github.badsyntax.gradle.GetDependenciesRequest; +import com.github.badsyntax.gradle.GradleProjectConnector; +import com.github.badsyntax.gradle.exceptions.GradleConnectionException; +import com.microsoft.gradle.api.GradleDependencyNode; +import com.microsoft.gradle.api.GradleModelAction; +import com.microsoft.gradle.api.GradleToolingModel; +import io.grpc.stub.StreamObserver; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import org.gradle.tooling.BuildActionExecuter; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GetDependenciesHandler { + private static final Logger logger = + LoggerFactory.getLogger(GetDependenciesHandler.class.getName()); + + private GetDependenciesRequest req; + private StreamObserver responseObserver; + + public GetDependenciesHandler( + GetDependenciesRequest req, StreamObserver responseObserver) { + this.req = req; + this.responseObserver = responseObserver; + } + + public void run() { + GradleConnector gradleConnector; + try { + gradleConnector = GradleProjectConnector.build(req.getProjectDir(), req.getGradleConfig()); + } catch (GradleConnectionException e) { + logger.error(e.getMessage()); + responseObserver.onError(ErrorMessageBuilder.build(e)); + return; + } + + try (ProjectConnection connection = gradleConnector.connect()) { + BuildActionExecuter action = connection.action(new GradleModelAction()); + File initScript = File.createTempFile("init-build", ".gradle"); + initScript.deleteOnExit(); + File pluginFile = File.createTempFile("custom-plugin", ".jar"); + pluginFile.deleteOnExit(); + createPluginJar("/gradle-plugin.jar", pluginFile); + createTemplateScript(pluginFile, initScript); + action.withArguments("--init-script", initScript.getAbsolutePath()); + GradleToolingModel gradleModel = action.run(); + GradleDependencyNode root = gradleModel.getDependencyNode(); + responseObserver.onNext( + GetDependenciesReply.newBuilder().setItem(getDependencyItem(root)).build()); + responseObserver.onCompleted(); + } catch (IOException e) { + logger.error(e.getMessage()); + responseObserver.onError(ErrorMessageBuilder.build(e)); + } + } + + private DependencyItem getDependencyItem(GradleDependencyNode node) { + DependencyItem.Builder item = DependencyItem.newBuilder(); + item.setName(node.getName()); + item.setTypeValue(node.getType().ordinal()); + if (node.getChildren() == null) { + return item.build(); + } + List children = new ArrayList<>(); + for (GradleDependencyNode child : node.getChildren()) { + children.add(getDependencyItem(child)); + } + item.addAllChildren(children); + return item.build(); + } + + private void createPluginJar(String resource, File outputFile) throws IOException { + InputStream input = GetDependenciesHandler.class.getResourceAsStream(resource); + Files.copy(input, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + input.close(); + } + + private void createTemplateScript(File pluginFile, File outputFile) throws IOException { + String pluginFilePath = pluginFile.getAbsolutePath().replace("\\", "/"); + String template = + "initscript {\n" + + " dependencies {\n" + + " classpath files('" + + pluginFilePath + + "')\n" + + " }\n" + + "}\n" + + "\n" + + "allprojects {\n" + + " apply plugin: com.microsoft.gradle.GradlePlugin\n" + + "}\n"; + Files.writeString(outputFile.toPath(), template); + } +} diff --git a/proto/gradle.proto b/proto/gradle.proto index fe91670f9..eee2c2a8e 100644 --- a/proto/gradle.proto +++ b/proto/gradle.proto @@ -9,6 +9,7 @@ package gradle; service Gradle { rpc GetBuild(GetBuildRequest) returns (stream GetBuildReply) {} rpc RunBuild(RunBuildRequest) returns (stream RunBuildReply) {} + rpc GetDependencies(GetDependenciesRequest) returns (GetDependenciesReply) {} rpc CancelBuild(CancelBuildRequest) returns (CancelBuildReply) {} rpc CancelBuilds(CancelBuildsRequest) returns (CancelBuildsReply) {} rpc GetDaemonsStatus(GetDaemonsStatusRequest) returns (GetDaemonsStatusReply) {} @@ -38,6 +39,28 @@ message GetBuildResult { GradleBuild build = 2; } +message GetDependenciesRequest { + string project_dir = 1; + GradleConfig gradle_config = 2; +} + +message GetDependenciesReply { + DependencyItem item = 1; +} + +enum GradleDependencyType +{ + PROJECT = 0; + CONFIGURATION = 1; + DEPENDENCY = 2; +} + +message DependencyItem { + string name = 1; + GradleDependencyType type = 2; + repeated DependencyItem children = 3; +} + message RunBuildRequest { string project_dir = 1; string cancellation_key = 2;