diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1722525fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store + +.gradle +.settings +.eclipse +build +target +bin + +.idea +*.iml +*.ipr +*.iws diff --git a/.project b/.project new file mode 100644 index 000000000..eb2226f94 --- /dev/null +++ b/.project @@ -0,0 +1,49 @@ + + + com.gradleware.tooling.eclipse.ROOT + + + + + + + + + + 1340825059592 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-name-matches-false-false-org.gradle.* + + + + 1340825059596 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-commons + + + + 1340825059599 + + 9 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-docs + + + + 1340825059602 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-docs + + + + diff --git a/README.md b/README.md new file mode 100644 index 000000000..2860db6f9 --- /dev/null +++ b/README.md @@ -0,0 +1,161 @@ +# Buildship: Eclipse Plug-ins for Gradle + +## Requirements + +Buildship can be used with Eclipse 3.6.x or newer. Older versions might work but have not been tested explicitly. Depending on the version of Gradle that +Buildship interacts with, certain features of Buildship may not be available. + + +## Documentation + +Documentation is available on [GitHub](https://github.com/gradleware/buildship). + + +## Usage Setup instructions + +This section describes the steps to install the latest snapshot version of Buildship into Eclipse. + +### Installing from snapshot update site + +Each commit to the master repository creates a new snapshot version of Buildship on +our [Continuous Integration Server](https://builds.gradle.org/project.html?projectId=Tooling_Master_Eclipse&tab=projectOverview). + +The following snapshot update sites are currently available for all the supported Eclipse versions: + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse45Build/.lastSuccessful/update-site` (latest 4.5 development snapshot) + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse44Build/.lastSuccessful/update-site` (latest 4.4 development snapshot) + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse43Build/.lastSuccessful/update-site` (latest 4.3 development snapshot) + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse42Build/.lastSuccessful/update-site` (latest 4.2 development snapshot) + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse37Build/.lastSuccessful/update-site` (latest 3.7 development snapshot) + * `https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse36Build/.lastSuccessful/update-site` (latest 3.6 development snapshot) + +Apply the following instructions to use one of the Eclipse update sites previously listed: + + 1. In Eclipse, open the menu item _Help >> Install New Software_. + 1. Paste the update site link that matches your Eclipse version into the _Work with_ text box. + 1. Click the _Add_ button at the top of the screen, give the update site a name, and press _OK_. + 1. Ensure that the option _Group Items by Category_ is enabled. + 1. Select the top-level node _Buildship: Eclipse Plug-ins for Gradle_ once it appears. This may take a moment. + 1. Click _Next_. This may take a while. + 1. Review the list of software that will be installed. Click _Next_ again. + 1. Review and accept the licence agreement and click _Finish_. + +If you have already installed the plugin previously, you can update to the most recent version by opening the menu item _Help >> Check for Updates_. + + +## Development Setup Instructions + +This section describes the steps to setup Eclipse such that it can be used for development of Buildship. + +### Setting up Eclipse + +We use Eclipse + + - as our development environment of Buildship and + - as our target platform against which we compile and run Buildship. + +We use our internally-packaged Eclipse distribution, but the latest Eclipse for RCP Developers package should be fine, too. + +Proceed as following to get all the required software installed: + + 1. Download the Eclipse distribution matching your OS from the http://dev1.gradle.org:8000/eclipse/distro/ site. + 1. Untar the downloaded distribution and start Eclipse. + 1. Install the latest version of Buildship from the e44 update site listed above. + +### Getting the source code + +The project and its source code is hosted on GitHub: `https://github.com/gradleware/buildship`. + +Apply the following command to get a clone of the source code: + + git clone git@github.com:gradleware/buildship.git + +#### Committers + +Navigate into the created _buildship_ directory and set the git username and email address: + + git config user.name "johnsmith" + git config user.email "john.smith@gradleware.com" + +And make sure to properly map the part before the domain of your email address in TeamCity under _My Settings & Tools_ >> +_General_ >> _Version Control Username Settings_ >> _Default for all of the Git roots_. + + john.smith + +From now on, when you commit to the _buildship_ project through Git, the change will be properly associated with your user in +TeamCity. You can verify that the setup is correct by seeing your full name next to each commit. + +In order to avoid extra commits in the Git history after merging local changes with remote changes, apply the +_rebase_ strategy when pulling in the remote changes. By applying the _update_ alias listed below, you can conveniently +pull in the remote changes from master without ending up with ‘merge branch’ commits in the Git history later on. + + git config --global alias.update=“pull --rebase origin master” + git update + +### Importing the source code into Eclipse + +The source code consists of a single root project and several sub-projects nested within it. You can use the +generic Eclipse 'Import Existing Projects' wizard. Select the root folder _buildship_ and +check the option 'Search for nested projects'. Select all _com.gradleware.*_ projects. You +can then press _Finish_. + +### Setting the target platform + +After importing all the projects into Eclipse, there will be some build errors due to code references to missing +plugins. To add these missing plugins to Eclipse, open the _tooling-e44.target_ file (or the one matching your +Eclipse version) located in the project root folder and click _Set as Target Platform_ in the top-right corner. This +will fix all compilation issues. Note, that it might take a while as it will download a whole SDK for the specified +version of Eclipse. + +### Running the tests inside of Eclipse + +To run the complete set of core tests from inside Eclipse, right-click +on the package _com.gradleware.tooling.eclipse.core.test_ and choose _Run As >> JUnit Plug-In-Test_ +(not as a _JUnit Test_!). Individual tests can be run the same way. + +To run the complete set of ui tests from inside Eclipse, right-click +on the package _com.gradleware.tooling.eclipse.ui.test_ and choose _Run As >> JUnit Plug-In-Test_ +(not as a _JUnit Test_!). Individual tests can be run the same way. + +### Running the Build + +To run the full build, execute + + ./gradlew build + +The final P2 repository will be created in the `com.gradleware.tooling.eclipse.site/build/repository` directory. If +the target platform had not been downloaded previously, it will appear in the _~/.tooling/eclipse/targetPlatforms_ folder. + +To run the build without running the tests, exclude the `eclipseTest` task: + + ./gradlew build -x eclipseTest + +To have full build ids in the name of the generated jars and in the manifest files, set the `build.invoker` property to _ci_: + + ./gradlew build -Pbuild.invoker=ci + +The default Eclipse version to use as the target platform is defined in the root project's _build.gradle_ file. To override the +Eclipse version to use as the target platform, set the `eclipse.version` accordingly: + + ./gradlew build -Peclipse.version=44 + +The possible values for `eclipse.version` are: 36, 37, 42, 43, 44, and 45. + +### Adding a new Eclipse plugin + +* Create a new folder under the _buildship_ root folder +* Create a project in that folder with Eclipse and use the same name for the project as for the folder +* Create a _build.gradle_ file and apply the `BundlePlugin` +* Add the project to the _settings.gradle_ file + +### References + +* [Eclipse Testing](http://wiki.eclipse.org/Eclipse/Testing) +* [PDE Test Automation](http://www.eclipse.org/articles/article.php?file=Article-PDEJUnitAntAutomation/index.html) + +## Continuous Integration + +The automated builds for each supported Eclipse version can be found on TeamCity: +`https://builds.gradle.org/project.html?projectId=Tooling_Buildship&tab=projectOverview` + +It is possible to reference the last successful run of a given TeamCity configuration like this: +`https://builds.gradle.org/repository/download/Tooling_Master_Commit_Eclipse44Build/.lastSuccessful/update-site` diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..1b114a2b6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,82 @@ +import org.gradle.internal.os.OperatingSystem + +apply plugin: eclipsebuild.BuildDefinitionPlugin + +// declare that by default the plugins are built and tested against Eclipse 3.7 +eclipseBuild { + eclipseVersion = '37' +} + +// read the current version from an external file and add a timestamp suffix if requested by the caller +version = getVersion(file('version.txt').text.trim()) + +subprojects { + // set the calculated version on all projects in the hierarchy + version = rootProject.version + + // the built plugins must be Java 6 compatible, try to compile with JDK 6 if the location is specified by the caller or if it can be derived + plugins.withType(eclipsebuild.BundlePlugin) { + sourceCompatibility = 1.6 + targetCompatibility = 1.6 + + tasks.withType(AbstractCompile).all { + options.compilerArgs << '-Xlint:all' + options.fork = true + + if (OperatingSystem.current().isMacOsX()) { + options.compilerArgs << '-Werror' + options.forkOptions.executable = "/usr/libexec/java_home -v $targetCompatibility".execute().text.trim() + "/bin/javac" + } else if (project.hasProperty('compiler.location')) { + // quotes required on TeamCity to pass property with spaces, e.g. a Windows file path + String compilerLocation = project.property('compiler.location').replace('\"', '').replace('\'', '') + options.compilerArgs << '-Werror' + options.forkOptions.executable = compilerLocation + } + } + } + + // apply Checkstyle plugin, mainly to ensure license/copyright and javadoc is present + apply plugin: 'checkstyle' + + // share checkstyle config across all sub-projects + def checkstyleConfigDir = "$rootDir/gradle/config/checkstyle" + tasks.withType(Checkstyle).all { + configFile = "$checkstyleConfigDir/checkstyle.xml" as File + configProperties = ['checkstyleConfigDir': checkstyleConfigDir] + inputs.file "$checkstyleConfigDir/suppressions.xml" as File + } + + // configure the repositories where the external dependencies can be found + repositories { + maven { + name = 'mavenized-target-platform' + url "${eclipsebuild.Config.on(project).mavenizedTargetPlatformDir}" + } + + maven { + name = 'gradle-snapshots' + url gradleSnapshotsRepositoryUrl + } + + maven { + name = 'gradle-releases' + url gradleReleasesRepositoryUrl + } + + maven { + name = 'gradle-remote' + url gradleRemoteRepositoryUrl + } + } +} + +String getVersion(def baseVersion) { + if (project.hasProperty('build.invoker') && project.property('build.invoker') == 'ci') { + // note that for Eclipse plugin versions, the '-' and '.' character are invalid in front of the build id + baseVersion + new Date().format('yyyyMMddkkmmss', TimeZone.getTimeZone('GMT')) + } else { + baseVersion + new Date().format('yyyyMMdd', TimeZone.getTimeZone('GMT')) + } +} + +wrapper.gradleVersion = '2.4-20150209230027+0000' diff --git a/buildSrc/.gitignore b/buildSrc/.gitignore new file mode 100644 index 000000000..32868bd20 --- /dev/null +++ b/buildSrc/.gitignore @@ -0,0 +1,2 @@ +.classpath +.project diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 000000000..f0c2b787d --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,18 @@ +apply plugin: 'groovy' +apply plugin: 'idea' +apply plugin: 'eclipse' + +repositories { + maven { + url gradleRemoteRepositoryUrl + } +} + +dependencies { + compile gradleApi() + compile localGroovy() + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'org.akhikhl.unpuzzle:unpuzzle-plugin:0.0.17' + compile 'nu.studer:java-ordered-properties:1.0.1' +} + diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties new file mode 100644 index 000000000..e9d200c4b --- /dev/null +++ b/buildSrc/gradle.properties @@ -0,0 +1 @@ +gradleRemoteRepositoryUrl = https://repo.gradle.org/gradle/remote-repos diff --git a/buildSrc/libs/org.eclipse.core.runtime_3.9.100.v20131218-1515.jar b/buildSrc/libs/org.eclipse.core.runtime_3.9.100.v20131218-1515.jar new file mode 100644 index 000000000..25729743f Binary files /dev/null and b/buildSrc/libs/org.eclipse.core.runtime_3.9.100.v20131218-1515.jar differ diff --git a/buildSrc/libs/org.eclipse.equinox.common_3.6.200.v20130402-1505.jar b/buildSrc/libs/org.eclipse.equinox.common_3.6.200.v20130402-1505.jar new file mode 100644 index 000000000..fee382e11 Binary files /dev/null and b/buildSrc/libs/org.eclipse.equinox.common_3.6.200.v20130402-1505.jar differ diff --git a/buildSrc/libs/org.eclipse.jdt.junit.core_3.7.300.v20140409-1618.jar b/buildSrc/libs/org.eclipse.jdt.junit.core_3.7.300.v20140409-1618.jar new file mode 100644 index 000000000..860c0d9fc Binary files /dev/null and b/buildSrc/libs/org.eclipse.jdt.junit.core_3.7.300.v20140409-1618.jar differ diff --git a/buildSrc/libs/org.eclipse.osgi_3.10.1.v20140909-1633.jar b/buildSrc/libs/org.eclipse.osgi_3.10.1.v20140909-1633.jar new file mode 100644 index 000000000..b83fd2e76 Binary files /dev/null and b/buildSrc/libs/org.eclipse.osgi_3.10.1.v20140909-1633.jar differ diff --git a/buildSrc/src/main/groovy/eclipsebuild/BuildDefinitionPlugin.groovy b/buildSrc/src/main/groovy/eclipsebuild/BuildDefinitionPlugin.groovy new file mode 100644 index 000000000..66182e6e5 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/BuildDefinitionPlugin.groovy @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.akhikhl.unpuzzle.eclipse2maven.EclipseSource +import org.akhikhl.unpuzzle.osgi2maven.Deployer +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * Gradle plugin for the root project of the Eclipse plugin build. + */ +class BuildDefinitionPlugin implements Plugin { + + static class Extension { + String eclipseVersion + } + + @Override + public void apply(Project project) { + project.extensions.create("eclipseBuild", Extension) + + Config config = Config.on(project) + installTargetPlatformTask(project, config) + downloadEclipseSdkTask(project, config) + } + + void downloadEclipseSdkTask(Project project, Config config) { + project.task("downloadEclipseSdk") { + group = Constants.gradleTaskGroupName + + outputs.upToDateWhen { + config.eclipseSdkExe.exists() + } + + doLast { + File dotLock = new File(config.eclipseSdkDir, ".lock") + if (!dotLock.exists()) { + dotLock.getParentFile().mkdirs(); + dotLock.createNewFile() + } + FileOutputStream fos = new FileOutputStream(dotLock); + java.nio.channels.FileLock lock = fos.getChannel().lock(); + try { + doDownloadEclipseSdk(project, config) + } finally { + lock.release(); + fos.close(); + } + } + } + } + + void doDownloadEclipseSdk(Project project, Config config) { + config.eclipseSdkDir.mkdirs() + if (Constants.getOs() == "win32") { + File targetZip = new File(config.eclipseSdkDir, "eclipse-sdk.zip") + project.ant.get(src: Constants.eclipseSdkDownloadUrl, dest: targetZip) + project.ant.unzip(src: targetZip, dest: targetZip.parentFile) + } else { + File targetTar = new File(config.eclipseSdkDir, "eclipse-sdk.tar.gz") + project.ant.get(src: Constants.eclipseSdkDownloadUrl, dest: targetTar) + project.ant.untar(src: targetTar, dest: targetTar.parentFile, compression: "gzip") + } + project.logger.info("Set ${config.eclipseSdkExe} executable") + config.eclipseSdkExe.setExecutable(true) + } + + void installTargetPlatformTask(Project project, Config config) { + + project.task("installTargetPlatform", dependsOn: 'downloadEclipseSdk') { + group = Constants.gradleTaskGroupName + description "Installs an Eclipse SDK along with a set of additional plugins to a local Eclipse installation located in the target platform location. To modify the used Eclipse version add -Peclipse.version=[37|42|43|44] parameter" + + outputs.upToDateWhen { + config.mavenizedTargetPlatformDir.exists() && config.mavenizedTargetPlatformDir.list().length > 0 + } + + doLast { + File dotLock = new File(config.eclipseSdkDir, ".lock") + if (!dotLock.exists()) { + dotLock.createNewFile() + } + FileOutputStream fos = new FileOutputStream(dotLock); + java.nio.channels.FileLock lock = fos.getChannel().lock(); + try { + doInstallTargetPlatform(project, config) + } finally { + lock.release(); + fos.close(); + } + } + } + + project.task("uninstallTargetPlatform") { + group = Constants.gradleTaskGroupName + doLast { + File targetPlatformDirectory = config.containerDir + if(!targetPlatformDirectory.exists()){ + logger.info("Directory '${config.containerDir}' does not exist. Nothing to uninstall.") + return + } + + logger.info("Deleting target platform directory '${config.containerDir}'.") + def success = targetPlatformDirectory.deleteDir() + if (!success) { + throw new RuntimeException("Deleting target platform directory '${targetPlatformDirectory}' did not succeed.") + } + } + } + } + + void doInstallTargetPlatform(Project project, Config config) { + if (!config.targetPlatformDir.exists() || !config.targetPlatformDir.list().length == 0) { + project.exec { + standardOutput = new ByteArrayOutputStream() + errorOutput = new ByteArrayOutputStream() + + commandLine(config.eclipseSdkExe.path, + '-application', 'org.eclipse.equinox.p2.director', + '-repository', Constants.getEclipseReleaseUpdateSite(config.getEclipseVersion()), + '-installIU', 'org.eclipse.sdk.ide/' + Constants.getEclipseReleaseBuildVersion(config.getEclipseVersion()), + '-tag', '1-Initial-State', + '-destination', config.targetPlatformDir.path, + '-profile', 'SDKProfile', + '-bundlepool', config.targetPlatformDir.path, + '-p2.os', Constants.os, + '-p2.ws', Constants.ws, + '-p2.arch', Constants.arch, + '-roaming', + '-nosplash') + } + project.exec { + standardOutput = new ByteArrayOutputStream() + errorOutput = new ByteArrayOutputStream() + + commandLine(config.eclipseSdkExe.path, + '-application', 'org.eclipse.equinox.p2.director', + '-repository', "http://dev1.gradle.org:8000/eclipse/update-site/mirror/groovy-eclipse/e${config.getEclipseVersion()},${Constants.getEclipseReleaseUpdateSite(config.getEclipseVersion())},http://dev1.gradle.org:8000/eclipse/update-site/mirror/orbit", + '-installIU', 'org.junit', + '-tag', '2-Additional-Features', + '-destination', config.targetPlatformDir.path, + '-profile', 'SDKProfile', + '-nosplash') + } + + // finally create the maven repository based on the target platform + def mavenDeployer = new Deployer(config.mavenizedTargetPlatformDir.toURI().toURL().toString()) + def source = new EclipseSource() + source.url = config.mavenizedTargetPlatformDir.toURI().toURL().toString() + new SimpleDeployer(config.targetPlatformDir, Constants.mavenEclipsPluginGroupName, mavenDeployer).deploy(Arrays.asList(source)) + def versionFile = new File(config.targetPlatformDir, 'version') + versionFile.createNewFile() + versionFile.text = config.eclipseVersion + } + } +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/BundlePlugin.groovy b/buildSrc/src/main/groovy/eclipsebuild/BundlePlugin.groovy new file mode 100644 index 000000000..59c47a42e --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/BundlePlugin.groovy @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild +import java.util.List; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.Copy; +import org.mozilla.classfile.SuperBlock; + +import aQute.bnd.maven.support.Maven; + +/** + * Gradle plugin for building Eclipse bundles + *

+ * Applies the java plugin by default. + *

+ * Makes the os, ws and arch variables available for the project (via project.ext) for building against platform-dependent + * dependencies; for example

 compile "eclipse:org.eclipse.swt.${project.ext.ws}.${project.ext.os}.${project.ext.arch}:+"
+ *

+ * To make the compilation work, the compileJava task depends on the establishment of the target platform. + *

+ * To construct the output jar just like Eclipse PDE the plugin loads the contents of the build.properties + * file and brings it in sync with the jar's content. + *

+ * The updateLibs assigns takes all project dependencies defined in the bundled scope, and copies + * them (including transitives) into the lib folder and modifies the bundle manifest to export all packages from the libraries. + */ +class BundlePlugin implements Plugin { + + @Override + public void apply(Project project) { + configureProject(project) + addUpdateLibsTasks(project) + } + + void configureProject(Project project) { + // apply the java plugin + project.plugins.apply('java') + + // make new variables for the build.gradle file e.g. for platform-dependent dependencies + project.ext.ECLIPSE_OS = Constants.os; + project.ext.ECLIPSE_WS = Constants.ws; + project.ext.ECLIPSE_ARCH = Constants.arch; + project.ext.ECLIPSE_VERSION = Config.on(project).eclipseVersion + + // add new configuration scope + project.configurations.create('bundled') + project.configurations.create('bundledSource') + project.configurations { compile.extendsFrom bundled } + + // make the most basic task depend on the target platform assembling + project.tasks.compileJava.dependsOn ":installTargetPlatform" + + // Make sure the manifest ifile exists + assert project.file('build.properties').exists() + + // Use the same MANIFEST.MF file as it is in the project except the Bundle-Version + PluginUtils.updatePluginManifest(project) + + // Parse build.properties and sync it with output jar + PluginUtils.syncJarContentWithBuildProperties(project) + + // assemble task does't change anything outside the buildDir folder + project.tasks.assemble.outputs.dir project.buildDir + } + + void addUpdateLibsTasks(Project project) { + project.task('updateLibs', dependsOn: ['copyLibs', 'updateManifest']){ + group = Constants.gradleTaskGroupName + description = '''Copies the bundled dependencies into the project's lib folder and updates the manifest file''' + } + + project.task('copyLibs', dependsOn: [ + project.configurations.bundled, + project.configurations.bundledSource, + ":installTargetPlatform" + ], type: Copy) { + group = Constants.gradleTaskGroupName + description = 'Copies the bundled dependencies into the lib folder' + + def libDir = project.file('lib') + + // before the update delete all the libraries that are currently in the lib folder + doFirst { + libDir.listFiles().each { File f -> + if (f.toString().endsWith('.jar')) { + logger.info("Deleting ${f.name}") + f.delete() + } + } + } + + // copy the dependencies to the 'libs' folder + into libDir + from project.configurations.bundled + from project.configurations.bundledSource + } + + project.task('updateManifest', dependsOn: project.configurations.bundled) { + group = Constants.gradleTaskGroupName + description = 'Updates the manifest file with all the dependencies of the Tooling API' + + // check the existence of the manifest file in configuration-time + assert project.file('META-INF/MANIFEST.MF').exists() + + doLast { + // don't write anything if there is no bundled dependency + if (project.configurations.bundled.dependencies.isEmpty()) { + return + } + + File manifest = project.file('META-INF/MANIFEST.MF') + List lines = manifest.readLines() + int i = 0; + + manifest.withPrintWriter { out -> + // copy file upto line with 'Bundle-ClassPath: .' + while (i < lines.size() && !lines[i].startsWith('Bundle-ClassPath: .,')) { + out.println(lines[i]) + i++ + } + + out.print 'Bundle-ClassPath: .,' + + // add a sorted list of jar file names under the Bundle-Classpath section + boolean comma = false + def bundledConfig = project.configurations.bundled as List + bundledConfig.sort { it.name }.each { File jarFile -> + if (jarFile.toString().endsWith('.jar')) { + if (comma) { + out.println(',') + } else { + out.println() + } + String name = jarFile.getName() + out.print(" lib/$name") + comma = true + } + } + out.println() + + // skip lines up to 'Export-Package: ' + while (i < lines.size() && !lines[i].startsWith('Export-Package: ')) { + i++ + } + + // copy the remaining lines + while (i < lines.size()) { + out.println lines[i] + i++ + } + } + + // update the .classpath file + def classpathFile = project.file('.classpath') + def classpathXml = new XmlParser().parse(classpathFile) + // delete all nodes pointing to the lib folder + classpathXml.findAll { it.name().equals('classpathentry') && it.@path.startsWith('lib/') }.each { classpathXml.remove(it) } + // re-create the deleted nodes with the 'sourcepath' attribute + project.configurations.bundled.sort { it.name }.each { File jarFile -> + def name = jarFile.getName() + def nameWithoutExtension = name.substring(0, name.lastIndexOf('.')) + new Node(classpathXml, 'classpathentry', ['exported' : 'true', 'kind' : 'lib', 'path' : "lib/$name", 'sourcepath' : "lib/${nameWithoutExtension}-sources.jar"]) + } + new XmlNodePrinter(new PrintWriter(new FileWriter(classpathFile))).print(classpathXml) + } + + } + } +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/Config.groovy b/buildSrc/src/main/groovy/eclipsebuild/Config.groovy new file mode 100644 index 000000000..58d06e356 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/Config.groovy @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.api.Project + + +class Config { + + Project project + + static Config on(Project project) { + return new Config(project); + } + + public Config(Project project) { + this.project = project + } + + String getEclipseVersion() { + String version = project.hasProperty("eclipse.version") ? project.property("eclipse.version") : project.rootProject.eclipseBuild.eclipseVersion + if (version == null) { + throw new RuntimeException('No Eclipse version specified') + } + else { + version + } + } + + File getContainerDir() { + if (project.hasProperty("containerDir")) { + return new File(project.property("containerDir")) + } + else { + return new File(System.getProperty("user.home"), ".tooling/eclipse/targetPlatforms") + } + } + + File getEclipseSdkDir() { + project.file("${getContainerDir()}/eclipse-sdk"); + } + + File getEclipseSdkExe() { + project.file("${getEclipseSdkDir()}/${Constants.eclipseExePath}") + } + + File getTargetPlatformDir() { + return project.file("${getContainerDir()}/${getEclipseVersion()}/target-platform"); + } + + File getMavenizedTargetPlatformDir() { + return project.file("${getContainerDir()}/${getEclipseVersion()}/mavenized-target-platform"); + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/Constants.groovy b/buildSrc/src/main/groovy/eclipsebuild/Constants.groovy new file mode 100644 index 000000000..4b2cf53a0 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/Constants.groovy @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.internal.os.OperatingSystem + + +/** + * Constant store for the Eclipse build + */ +class Constants { + + /** + * @return The name of the group where all tasks defined in this project should show upon the execution of + * gradle tasks. + */ + static String getGradleTaskGroupName() { + return "Eclipse Plugin Build" + } + + /** + * Eclipse runtime abbreviation of the operating system. + * http://help.eclipse.org/indigo/topic/org.eclipse.platform.doc.isv/reference/misc/runtime-options.html + * + * @return The operating system: 'linux', 'win32' or 'macosx' + */ + static String getOs() { + OperatingSystem os = OperatingSystem.current() + os.isLinux() ? 'linux' : os.isWindows() ? 'win32' : os.isMacOsX() ? 'macosx': null + } + + /** + * Eclipse runtime abbreviation of the windowing system. + * http://help.eclipse.org/indigo/topic/org.eclipse.platform.doc.isv/reference/misc/runtime-options.html + * + * @return The windowing system: 'gtk', 'win32' or 'cocoa' + */ + static String getWs() { + OperatingSystem os = OperatingSystem.current() + os.isLinux() ? 'gtk' : os.isWindows() ? 'win32' : os.isMacOsX() ? 'cocoa' : null + } + + /** + * Eclipse runtime abbreviation of the architecture. + * http://help.eclipse.org/indigo/topic/org.eclipse.platform.doc.isv/reference/misc/runtime-options.html + * + * @return The architecture: x86, x86_64 or ppc + */ + static String getArch() { + System.getProperty("os.arch").contains("64") ? "x86_64" : "x86" + } + + /** + * @return The list of Eclipse versions supported by this Eclipse build. Possible values: '37', '42', '43' or '44', + */ + static List getAcceptedEclipseVersions() { + return Arrays.asList("36", "37", "42", "43", "44", "45" ); + } + + /** + * @return The group ID for referencing eclipse bundles in the local Maven repository. + */ + static String getMavenEclipsPluginGroupName() { + return "eclipse" + } + + /** + * Returns an Eclipse release repository update site URL for a given version of Eclipse. + * @param version the version of Eclipse (from {@link #getAcceptedEclipseVersions}) + * @return The update site URL as String + */ + static String getEclipseReleaseUpdateSite(String version) { + switch (version) { + case "36": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-helios/"; + case "37": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-indigo/"; + case "42": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-juno/"; + case "43": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-kepler/"; + case "44": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-luna/"; + case "45": return "http://dev1.gradle.org:8000/eclipse/update-site/mirror/release-mars/"; + default : throw new RuntimeException("Not supported eclipse version: ${version}") + } + } + + /** + * Returns the concrete Eclipse build version for a given Eclipse version. + * @param version the version of Eclipse (from {@link #getAcceptedEclipseVersions}) + * @return The concrete Eclipse build version + */ + static String getEclipseReleaseBuildVersion(String version) { + switch (version) { + case "36": return "3.6.2.M20110210-1200"; + case "37": return "3.7.2.M20120208-0800"; + case "42": return "4.2.2.M20130204-1200"; + case "43": return "4.3.2.M20140221-1700"; + case "44": return "4.4.2.M20150204-1700"; + case "45": return "4.5.0.I20150203-1300"; + default : throw new RuntimeException("Not supported eclipse version: ${version}") + } + } + + /** + * @return The subpath of the Eclipse executable for the current platform. + */ + static String getEclipseExePath() { + OperatingSystem os = OperatingSystem.current() + os.isLinux() ? "eclipse/eclipse" : + os.isWindows() ? "eclipse/eclipse.exe" : + os.isMacOsX() ? "eclipse/Eclipse.app/Contents/MacOS/eclipse" : + null + } + + /** + * @return A URL which always redirect to a mirror from where and Eclipse SDK can be downloaded. + */ + static URL getEclipseSdkDownloadUrl() { + def archInUrl = getArch() == "x86_64" ? "-x86_64" : ""; + if (getOs() == "win32") { + return new URL("http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops4/R-4.4.2-201502041700/eclipse-SDK-4.4.2-win32${archInUrl}.zip&r=1") + } + else { + return new URL("http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops4/R-4.4.2-201502041700/eclipse-SDK-4.4.2-${getOs()}-${getWs()}${archInUrl}.tar.gz&r=1"); + } + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/FeaturePlugin.groovy b/buildSrc/src/main/groovy/eclipsebuild/FeaturePlugin.groovy new file mode 100644 index 000000000..b4e2336cb --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/FeaturePlugin.groovy @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +/** + * Gradle plugin for building Eclipse features. + *

+ * A feature project can specify its feature.xml with a very simple build script: + *

+ apply plugin: eclipsebuild.Feature
+
+ feature {
+   featureXml = file('feature.xml')
+ }
+ 
+ *

+ * Only thing this plugin does is basic validation if the feature.xml file and the build.properties file exists and + * It add everything to the plugin jar what is defined in the build.properties (just line in {@link EclipsePlugin#syncJarContentWithBuildProperties(Project)}) + */ +class FeaturePlugin implements Plugin { + + static class Extension { + File featureXml + } + + @Override + public void apply(Project project) { + configureProject(project) + } + + void configureProject(Project project) { + // Add a 'feature' extension to configure the location of the descriptor + project.extensions.create('feature', Extension) + // Quick solution to make the 'jar' task available + // TODO: remove this and implement a custom jar task + project.plugins.apply('java') + + // make sure the descriptors exist + assert project.file('build.properties').exists() + assert project.file('META-INF/MANIFEST.MF').exists() + + // sync jar content with the build.properties file + PluginUtils.syncJarContentWithBuildProperties(project) + + // assemble task does't change anything outside the buildDir folder + project.tasks.assemble.outputs.dir project.buildDir + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/PluginUtils.groovy b/buildSrc/src/main/groovy/eclipsebuild/PluginUtils.groovy new file mode 100644 index 000000000..ee5272328 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/PluginUtils.groovy @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.api.Project + +/** + * Static helper functions which can be used different places around the Eclipse plugin build. + */ +public class PluginUtils { + + /** + * Configure the project manifest file to use the META-INF/MANIFEST.MF file as a base of the output manifest instead + * of generating one. In addition it sets the Bundle-Version attribute to the project version. + * + * @param project The project to configure + */ + static void updatePluginManifest(Project project) { + project.jar { + manifest { + attributes 'Bundle-Version' : project.version + from('META-INF/MANIFEST.MF') { + eachEntry { entry -> + if (entry.key == 'Bundle-Version') { + entry.value = project.version + } + } + } + } + } + } + + /** + * Parse the build.properties file from the project and set the projec output jar to have the same entries when created. + * + * @param project The project to configure + */ + static void syncJarContentWithBuildProperties(Project project) { + // load the content of the build.properties file + def buildProperties = new Properties() + File buildPropertiesFile = project.file('build.properties') + def fos = new FileInputStream(buildPropertiesFile) + buildProperties.load(fos) + fos.close() + + + // parse the content + Set effectiveResources = new LinkedHashSet() + if (buildProperties) { + def virtualResources = ['.'] + buildProperties.'bin.includes'?.split(',').each { relPath -> + if(!(relPath in virtualResources)) { + effectiveResources.add(relPath) + } + } + } + + // configure the content of the jar file based on the file content + for (String location in effectiveResources) { + File resource = project.file(location) + if (resource.isDirectory()) { + project.jar { + from(location, {into(location)}) + } + } else { + project.jar { + from location + // modify the feature's version to the project version + if (resource.getName().equals("feature.xml")) { + filter(org.apache.tools.ant.filters.ReplaceTokens, tokens:['0.0.1.featureversion' :'"' + project.version + '"'], beginToken: '"', endToken: '"') + } + } + } + } + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/SimpleDeployer.groovy b/buildSrc/src/main/groovy/eclipsebuild/SimpleDeployer.groovy new file mode 100644 index 000000000..aeec47fc8 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/SimpleDeployer.groovy @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.apache.commons.codec.digest.DigestUtils +import org.akhikhl.unpuzzle.utils.IConsole +import org.akhikhl.unpuzzle.utils.SysConsole +import org.akhikhl.unpuzzle.eclipse2maven.EclipseSource +import org.akhikhl.unpuzzle.eclipse2maven.Version +import org.akhikhl.unpuzzle.osgi2maven.Pom +import org.akhikhl.unpuzzle.osgi2maven.Bundle2Pom +import org.akhikhl.unpuzzle.osgi2maven.DependencyBundle +import org.akhikhl.unpuzzle.osgi2maven.Deployer + +/** + * Copyright (c) 2014 Andrey Hihlovskiy + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + */ + +/** + * Converts an Eclipse plugin folder to a Maven repository. + *

+ * Note: this class has been taken from {@code org.akhikhl.unpuzzle:unpuzzle-plugin:0.0.17} + * and modified. + */ +final class SimpleDeployer { + + private final File targetDir + private final String eclipseGroup + private final Deployer mavenDeployer + private final IConsole console + private final String installGroupPath + private final String installGroupChecksum + private Map artifacts = [:] + private Map artifactsNl = [:] + private Map artifactFiles = [:] + private Map sourceFiles = [:] + + SimpleDeployer(File targetDir, String eclipseGroup, Deployer mavenDeployer, IConsole console = null) { + this.targetDir = targetDir + this.eclipseGroup = eclipseGroup + this.mavenDeployer = mavenDeployer + this.console = console ?: new SysConsole() + installGroupPath = mavenDeployer.repositoryUrl.toString() + '/' + (eclipseGroup ? eclipseGroup.replace('.', '/') : '') + installGroupChecksum = DigestUtils.md5Hex(installGroupPath) + } + + private void collectArtifactsInFolder(EclipseSource source, artifactsSourceDir) { + def processFile = { File file -> + console.info("Collecting artifacts: ${file.name}") + try { + Bundle2Pom reader = new Bundle2Pom(group: eclipseGroup, dependencyGroup: eclipseGroup) + Pom pom = reader.convert(file) + def source_match = pom.artifact =~ /(.*)\.source/ + if(source_match) { + def artifact = source_match[0][1] + sourceFiles["${artifact}:${pom.version}"] = file + } else if(!source.sourcesOnly) { + def nl_match = pom.artifact =~ /(.*)\.nl_(.*)/ + if(nl_match) { + def artifact = nl_match[0][1] + def language = nl_match[0][2] + if(!artifactsNl[language]) + artifactsNl[language] = [:] + artifactsNl[language][artifact] = pom + } else if(!source.languagePacksOnly) { + if(!artifacts.containsKey(pom.artifact)) + artifacts[pom.artifact] = [] + artifacts[pom.artifact].add pom + } + artifactFiles["${pom.artifact}:${pom.version}"] = file + } + } catch (Exception e) { + console.info("Error while mavenizing ${file}") + e.printStackTrace() + } + } + console.startProgress("Reading bundles in $artifactsSourceDir") + try { + artifactsSourceDir.eachDir processFile + artifactsSourceDir.eachFileMatch ~/.*\.jar/, processFile + } finally { + console.endProgress() + } + } + + void deploy(List sources) { + for(EclipseSource source in sources) { + File unpackDir = targetDir + boolean packageInstalled = false + + if(!packageInstalled) { + File pluginFolder = new File(unpackDir, 'plugins') + if (!pluginFolder.exists()) { + pluginFolder = unpackDir + } + collectArtifactsInFolder(source, pluginFolder) + } + } + + fixDependencies() + + console.startProgress('Deploying artifacts') + try { + artifacts.each { name, artifactVersions -> + artifactVersions.each { pom -> + mavenDeployer.deployBundle pom, artifactFiles["${pom.artifact}:${pom.version}"], sourceFile: sourceFiles["${pom.artifact}:${pom.version}"] + } + } + artifactsNl.each { language, map_nl -> + map_nl.each { artifactName, pom -> + mavenDeployer.deployBundle pom, artifactFiles["${pom.artifact}:${pom.version}"] + } + } + } finally { + console.endProgress() + } + } + + private void fixDependencies() { + console.startProgress('Fixing dependencies') + try { + artifacts.each { name, artifactVersions -> + console.info("Fixing dependencies: $name") + artifactVersions.each { pom -> + pom.dependencyBundles.removeAll { reqBundle -> + if(!artifacts[reqBundle.name.trim()]) { + console.info("Warning: artifact dependency $pom.group:$pom.artifact:$pom.version -> $reqBundle.name could not be resolved.") + return true + } + return false + } + pom.dependencyBundles.each { reqBundle -> + def resolvedVersions = artifacts[reqBundle.name.trim()] + if(resolvedVersions.size() == 1) + reqBundle.version = resolvedVersions[0].version + else if(!resolvedVersions.find { it -> it.version == reqBundle.version.trim() }) { + def compare = { a, b -> new Version(a).compare(new Version(b)) } + resolvedVersions = resolvedVersions.sort(compare) + int i = Collections.binarySearch resolvedVersions, reqBundle.version.trim(), compare as java.util.Comparator + if(i < 0) + i = -i - 1 + if(i > resolvedVersions.size() - 1) + i = resolvedVersions.size() - 1 + def c = resolvedVersions[i] + def depsStr = resolvedVersions.collect({ p -> "$p.group:$p.artifact:$p.version" }).join(', ') + console.info("Warning: resolved ambiguous dependency: $pom.group:$pom.artifact:$pom.version -> $reqBundle.name:$reqBundle.version, chosen $c.group:$c.artifact:$c.version from [$depsStr].") + reqBundle.version = c.version + } + } + artifactsNl.each { language, map_nl -> + def pom_nl = map_nl[pom.artifact] + if(pom_nl) + pom.dependencyBundles.each { dep_bundle -> + def dep_pom_nl = map_nl[dep_bundle.name] + if(dep_pom_nl) { + pom_nl.dependencyBundles.add new DependencyBundle(name: dep_pom_nl.artifact, version: dep_pom_nl.version, visibility: dep_bundle.visibility, resolution: dep_bundle.resolution) + } + } + } + } + } + } finally { + console.endProgress() + } + } + +} + diff --git a/buildSrc/src/main/groovy/eclipsebuild/TestBundlePlugin.groovy b/buildSrc/src/main/groovy/eclipsebuild/TestBundlePlugin.groovy new file mode 100644 index 000000000..33f983df0 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/TestBundlePlugin.groovy @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.tasks.testing.Test + +import eclipsebuild.testing.EclipseTestExecuter; +import eclipsebuild.testing.EclipseTestExtension; + +import javax.inject.Inject + +/** + * Gradle plugin to build Eclipse test bundles. + */ +class TestBundlePlugin implements Plugin { + + public final FileResolver fileResolver + + @Inject + public TestBundlePlugin(FileResolver fileResolver) { + this.fileResolver = fileResolver + } + + @Override + public void apply(Project project) { + configureProject(project) + createEclipseTestTask(project) + } + + void configureProject(Project project) { + project.extensions.create('eclipseTest', EclipseTestExtension) + project.getPlugins().apply(eclipsebuild.BundlePlugin) + } + + void createEclipseTestTask(Project project) { + Config config = Config.on(project) + def eclipseTest = project.task('eclipseTest', type: Test) { + group = Constants.gradleTaskGroupName + description = 'Installs all dependencies into a fresh Eclipse, runs the IDE an executes the test classes with the PDE Test Runner' + + // configure the test runner to execute all classes from the proejct + testExecuter = new EclipseTestExecuter(project) + testClassesDir = project.sourceSets['main'].output.classesDir + classpath = project.sourceSets.main.output + project.sourceSets.test.output + testSrcDirs = [] + reports.html.destination = new File("${project.reporting.baseDir}/eclipseTest") + + // set some system properties for the test Eclipse + systemProperty('osgi.requiredJavaVersion','1.7') + systemProperty('eclipse.pde.launch','true') + systemProperty('eclipse.p2.data.area','@config.dir/p2') + + // The input of the task is the dependant project tasks' jar + // the output is the folders additional plugins dir and the testing Eclipse folder + def testDistributionDir = project.file("$project.buildDir/eclipseTest/eclipse") + def additionalPluginsDir = project.file("$project.buildDir/eclipseTest/additions") + outputs.dir testDistributionDir + outputs.dir additionalPluginsDir + + // the eclipseTask input is the output jars from the dependent projects hence we have wait until the project + // is evaluated before we cen set the input files. + project.afterEvaluate { + for (tc in project.configurations.compile.dependencies.withType(ProjectDependency)*.dependencyProject.tasks) { + def taskHandler = tc.findByPath("jar") + if(taskHandler != null) inputs.files taskHandler.outputs.files + } + } + + doFirst { + + // Before testing, create a fresh eclipse IDE with all dependent plugins installed. + // First delete the test eclipse distribution and the original plugins. + testDistributionDir.deleteDir() + additionalPluginsDir.deleteDir() + + // Copy the target platform to the test distribution folder. + copyTargetPlatformToBuildFolder(project, config, testDistributionDir) + + // Publish the dependencies' output jars into a P2 repo in the additions folder. + publishDependenciesIntoTemporaryRepo(project, config, additionalPluginsDir) + + // Install all elements from the generated P2 repo into the test Eclipse distributin. + installDepedenciesIntoTargetPlatform(project, config, additionalPluginsDir, testDistributionDir) + } + } + + // Make sure that every time the testing is running the 'eclipseTest' task is also gets executed. + eclipseTest.dependsOn 'test' + eclipseTest.dependsOn 'jar' + project.tasks.check.dependsOn eclipseTest + } + + void copyTargetPlatformToBuildFolder(Project project, Config config, File distro) { + project.copy { + from config.targetPlatformDir + into distro + } + } + + void publishDependenciesIntoTemporaryRepo(Project project, Config config, File additionalPluginsDir) { + // take all direct dependencies and and publish their jar archive to the build folder (eclipsetest/additions + // subfolder) as a mini P2 update site + + for (ProjectDependency dep : project.configurations.compile.dependencies.withType(ProjectDependency)) { + Project p = dep.dependencyProject + project.exec { + commandLine(config.eclipseSdkExe, + "-application", "org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher", + "-metadataRepository", "file:${additionalPluginsDir.path}/${p.name}", + "-artifactRepository", "file:${additionalPluginsDir.path}/${p.name}", + "-bundles", p.tasks.jar.outputs.files.singleFile.path, + "-publishArtifacts", + "-nosplash") + } + } + + // and do the same with the current plugin + project.exec { + commandLine(config.eclipseSdkExe, + "-application", "org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher", + "-metadataRepository", "file:${additionalPluginsDir.path}/${project.name}", + "-artifactRepository", "file:${additionalPluginsDir.path}/${project.name}", + "-bundles", project.jar.outputs.files.singleFile.path, + "-publishArtifacts", + "-nosplash") + } + } + + void installDepedenciesIntoTargetPlatform(Project project, Config config, File additionalPluginsDir, File testDistributionDir) { + // take the mini P2 update sites from the build folder and install their content into the test Eclipse distro + for (ProjectDependency dep : project.configurations.compile.dependencies.withType(ProjectDependency)) { + Project p = dep.dependencyProject + project.exec { + commandLine(config.eclipseSdkExe, + '-application', 'org.eclipse.equinox.p2.director', + '-repository', "file:${additionalPluginsDir.path}/${p.name}", + '-installIU', p.name, + '-destination', testDistributionDir, + '-profile', 'SDKProfile', + '-p2.os', Constants.os, + '-p2.ws', Constants.ws, + '-p2.arch', Constants.arch, + '-roaming', + '-nosplash') + } + } + + // do the same with the current project + project.exec { + commandLine(config.eclipseSdkExe, + '-application', 'org.eclipse.equinox.p2.director', + '-repository', "file:${additionalPluginsDir.path}/${project.name}", + '-installIU', project.name, + '-destination', testDistributionDir, + '-profile', 'SDKProfile', + '-p2.os', Constants.os, + '-p2.ws', Constants.ws, + '-p2.arch', Constants.arch, + '-roaming', + '-nosplash') + } + } +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/UpdateSitePlugin.groovy b/buildSrc/src/main/groovy/eclipsebuild/UpdateSitePlugin.groovy new file mode 100644 index 000000000..c2a38f5e7 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/UpdateSitePlugin.groovy @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ProjectDependency; + +/** + * Gradle plugin for building Eclipse update sites. + *

+ * An example for a valid DSL: + *

+ * apply plugin: eclipsebuild.UpdateSite
+ *
+ * updateSite {
+ *   siteDescriptor = file("category.xml")
+ * }
+ * 
+ * Where the category.xml file exists in the project. + *

+ * The main tasks contributed by this plugin are responsible to generate an Eclipse Update site. They are attached to + * the 'assemble' task. When executed, all project dependency jars are copied to the build folder, signed and published + * to the buildDir/repository folder. + */ +class UpdateSitePlugin implements Plugin { + + static class Extension { + File siteDescriptor + } + + @Override + public void apply(Project project) { + configureProject(project) + addCopyBundlesTask(project) + addSignBundlesTask(project) + addCreateP2RepoTask(project) + } + + void configureProject(Project project) { + project.plugins.apply('java') + project.extensions.create('updateSite', Extension) + } + + void addCopyBundlesTask(Project project) { + def copyPluginsAndFeaturesTask = project.task("copyBundles") { + description = "Copy update site bundles to the build folder before update site creation" + group = Constants.gradleTaskGroupName + + // the copy's input is the project dependencies' outputs + project.afterEvaluate { + for (tc in project.configurations.compile.dependencies.withType(ProjectDependency)*.dependencyProject.tasks) { + def taskHandler = tc.findByPath("jar") + if(taskHandler != null) inputs.files taskHandler.outputs.files + } + } + + // project outputs + def bundlesDir = new File(project.buildDir, 'unsigned-bundles') + def pluginsDir = new File(bundlesDir, 'plugins') + def featuresDir = new File(bundlesDir, 'features') + outputs.dir bundlesDir + outputs.dir pluginsDir + outputs.dir featuresDir + + doLast { + // delete old content + bundlesDir.deleteDir() + + for (ProjectDependency projectDep : project.configurations.compile.dependencies.withType(ProjectDependency)) { + def dep = projectDep.dependencyProject + // copy the output jar for each java plugin dependency + if (dep.plugins.hasPlugin(BundlePlugin)) { + project.logger.debug("Copy project ${dep.name} plugin jar ${dep.tasks.jar.outputs.files.singleFile} to ${pluginsDir}") + project.copy { + def depJar = dep.tasks.jar.outputs.files.singleFile + from depJar + into pluginsDir + } + } + + if (dep.plugins.hasPlugin(FeaturePlugin)) { + project.logger.debug("Copy project ${dep.name} feature jar ${dep.tasks.jar.outputs.files.singleFile} to ${featuresDir}") + project.copy { + def depJar = dep.tasks.jar.outputs.files.singleFile + from depJar + into featuresDir + } + } + } + } + } + copyPluginsAndFeaturesTask.dependsOn 'jar' + } + + void addSignBundlesTask(Project project) { + project.task("signBundles", dependsOn: 'copyBundles') { + group = Constants.gradleTaskGroupName + description = "Sign bundles before generating update site" + + // task input is the copyBundles task output + inputs.files project.tasks.copyBundles.outputs.files + + // task output is the the plugins and features direcory + def unsignedRootDir = new File(project.buildDir, 'unsigned-bundles') + def signedRootDir = new File(project.buildDir, 'signed-bundles') + def unsignedPluginsDir = new File(unsignedRootDir, 'plugins') + def unsignedFeaturesDir = new File(unsignedRootDir, 'features') + def signedPluginsDir = new File(signedRootDir, 'plugins') + def signedFeaturesDir = new File(signedRootDir, 'features') + + outputs.dir unsignedRootDir + outputs.dir signedPluginsDir + outputs.dir signedFeaturesDir + outputs.dir unsignedPluginsDir + outputs.dir unsignedFeaturesDir + + doLast { + signedPluginsDir.deleteDir() + signedFeaturesDir.deleteDir() + signedPluginsDir.mkdirs() + signedFeaturesDir.mkdirs() + + File targetDir = signedPluginsDir + def signBundle = { + if (it.name.endsWith(".jar")) { + ant.signjar( + verbose: 'true', + destDir: targetDir, + alias: 'EclipsePlugins', + jar: it, + keystore: project.findProject(':').file('gradle/config/signing/DevKeystore.ks'), + storepass: 'tooling', + keypass: 'tooling', + sigalg: 'SHA1withDSA', + digestalg: 'SHA1', + preservelastmodified: 'true') + } + else { + logger.warn("Signing should point only to jar files but found ${it.path}") + } + } + + unsignedPluginsDir.listFiles().each signBundle + targetDir = signedFeaturesDir + unsignedFeaturesDir.listFiles().each signBundle + } + } + } + + void addCreateP2RepoTask(Project project) { + // task output is a repository + + def createP2RepositoryTask = project.task("createP2Repository", dependsOn: ['copyBundles', 'signBundles', ":installTargetPlatform"]) { + group Constants.gradleTaskGroupName + description 'Generates P2 repository with selected bundles and features.' + + // task input is the signbundles output + inputs.files project.tasks.signBundles.outputs.files + + def repoDir = new File(project.buildDir, 'repository') + outputs.dir repoDir + + doLast { + // always generate a new update site + def deleted = repoDir.deleteDir() + + def unsignedRootDir = new File(project.buildDir, 'unsigned-bundles') + def signedRootDir = new File(project.buildDir, 'signed-bundles') + + // TODO refactor config object to a parameter (like in the other plugins) + def version = new File(Config.on(project).targetPlatformDir, "version").text + def rootDir = version == '3.7.2' ? unsignedRootDir : signedRootDir + + // publish artifacts in the update site + project.exec { + commandLine(Config.on(project).eclipseSdkExe, + '-nosplash', + '-application', 'org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher', + '-metadataRepository', repoDir.toURI().toURL(), + '-artifactRepository', repoDir.toURI().toURL(), + '-source', rootDir, + '-compress', + '-publishArtifacts', + '-configs', "ANY") + } + + // publish the P2 category defined in the site.xml + project.exec { + commandLine(Config.on(project).eclipseSdkExe, + '-nosplash', + '-application', 'org.eclipse.equinox.p2.publisher.CategoryPublisher', + '-metadataRepository', repoDir.toURI().toURL(), + '-categoryDefinition', project.updateSite.siteDescriptor.toURI().toURL(), + '-compress') + } + } + } + + project.tasks.assemble.dependsOn createP2RepositoryTask + project.tasks.assemble.outputs.dir project.buildDir + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/testing/EclipsePluginTestClassScanner.java b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipsePluginTestClassScanner.java new file mode 100644 index 000000000..22214aade --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipsePluginTestClassScanner.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild.testing; + +import java.io.File; + +import org.gradle.api.file.EmptyFileVisitor; +import org.gradle.api.file.FileTree; +import org.gradle.api.file.FileVisitDetails; +import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo; +import org.gradle.api.internal.tasks.testing.TestClassProcessor; +import org.gradle.api.internal.tasks.testing.TestClassRunInfo; + +public final class EclipsePluginTestClassScanner implements Runnable { + + private final FileTree candidateClassFiles; + private final TestClassProcessor testClassProcessor; + + public EclipsePluginTestClassScanner(FileTree candidateClassFiles, TestClassProcessor testClassProcessor) { + this.candidateClassFiles = candidateClassFiles; + this.testClassProcessor = testClassProcessor; + } + + @Override + public void run() { + this.candidateClassFiles.visit(new ClassFileVisitor() { + @Override + public void visitClassFile(FileVisitDetails fileDetails) { + String className = fileDetails.getRelativePath().getPathString().replaceAll("\\.class", "").replace('/', '.'); + TestClassRunInfo testClass = new DefaultTestClassRunInfo(className); + EclipsePluginTestClassScanner.this.testClassProcessor.processTestClass(testClass); + } + }); + } + + private abstract class ClassFileVisitor extends EmptyFileVisitor { + @Override + public void visitFile(FileVisitDetails fileDetails) { + final File file = fileDetails.getFile(); + + if (file.getAbsolutePath().endsWith(".class") && !file.getAbsolutePath().contains("$")) { + visitClassFile(fileDetails); + } + } + + public abstract void visitClassFile(FileVisitDetails fileDetails); + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExecuter.java b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExecuter.java new file mode 100644 index 000000000..8c1ef79ba --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExecuter.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild.testing; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.internal.junit.model.ITestRunListener2; +import org.eclipse.jdt.internal.junit.model.RemoteTestRunnerClient; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.FileTree; +import org.gradle.api.internal.file.FileResolver; +import org.gradle.api.internal.tasks.testing.TestClassProcessor; +import org.gradle.api.internal.tasks.testing.TestClassRunInfo; +import org.gradle.api.internal.tasks.testing.TestCompleteEvent; +import org.gradle.api.internal.tasks.testing.TestDescriptorInternal; +import org.gradle.api.internal.tasks.testing.TestResultProcessor; +import org.gradle.api.internal.tasks.testing.TestStartEvent; +import org.gradle.api.internal.tasks.testing.detection.TestExecuter; +import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector; +import org.gradle.api.internal.tasks.testing.processors.TestMainAction; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.tasks.testing.Test; +import org.gradle.api.tasks.testing.TestOutputEvent; +import org.gradle.internal.TrueTimeProvider; +import org.gradle.process.ExecResult; +import org.gradle.process.internal.DefaultJavaExecAction; +import org.gradle.process.internal.JavaExecAction; + +import eclipsebuild.Constants; +import eclipsebuild.TestBundlePlugin; + +public final class EclipseTestExecuter implements TestExecuter { + + private static final Logger LOGGER = Logging.getLogger(EclipseTestExecuter.class); + + private final Project project; + + public EclipseTestExecuter(Project project) { + this.project = project; + } + + @Override + public void execute(Test test, TestResultProcessor testResultProcessor) { + LOGGER.info("Executing tests in Eclipse"); + + int pdeTestPort = new PDETestPortLocator().locatePDETestPortNumber(); + if (pdeTestPort == -1) { + throw new GradleException("Cannot allocate port for PDE test run"); + } + LOGGER.info("Will use port {} to communicate with Eclipse.", pdeTestPort); + + runPDETestsInEclipse(test, testResultProcessor, pdeTestPort); + } + + private EclipseTestExtension getExtension(Test testTask) { + return (EclipseTestExtension) testTask.getProject().getExtensions().findByName("eclipseTest"); + } + + private void runPDETestsInEclipse(final Test testTask, final TestResultProcessor testResultProcessor, + final int pdeTestPort) { + ExecutorService threadPool = Executors.newFixedThreadPool(2); + File runDir = new File(testTask.getProject().getBuildDir(), testTask.getName()); + + File testEclipseDir = new File(this.project.property("buildDir") + "/eclipseTest/eclipse"); + + // File configIniFile = getInputs().getFiles().getSingleFile(); + File configIniFile = new File(testEclipseDir, "configuration/config.ini"); + assert configIniFile.exists(); + + File runPluginsDir = new File(testEclipseDir, "plugins"); + LOGGER.info("Eclipse test directory is {}", runPluginsDir.getPath()); + File equinoxLauncherFile = getEquinoxLauncherFile(testEclipseDir); + LOGGER.info("equinox launcher file {}", equinoxLauncherFile); + + final JavaExecAction javaExecHandleBuilder = new DefaultJavaExecAction(getFileResolver(testTask)); + javaExecHandleBuilder.setClasspath(this.project.files(equinoxLauncherFile)); + javaExecHandleBuilder.setMain("org.eclipse.equinox.launcher.Main"); + List programArgs = new ArrayList(); + + programArgs.add("-os"); + programArgs.add(Constants.getOs()); + programArgs.add("-ws"); + programArgs.add(Constants.getWs()); + programArgs.add("-arch"); + programArgs.add(Constants.getArch()); + + if (getExtension(testTask).isConsoleLog()) { + programArgs.add("-consoleLog"); + } + File optionsFile = getExtension(testTask).getOptionsFile(); + if (optionsFile != null) { + programArgs.add("-debug"); + programArgs.add(optionsFile.getAbsolutePath()); + } + programArgs.add("-version"); + programArgs.add("4"); + programArgs.add("-port"); + programArgs.add(Integer.toString(pdeTestPort)); + programArgs.add("-testLoaderClass"); + programArgs.add("org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader"); + programArgs.add("-loaderpluginname"); + programArgs.add("org.eclipse.jdt.junit4.runtime"); + programArgs.add("-classNames"); + for (String clzName : collectTestNames(testTask)) { + programArgs.add(clzName); + } + programArgs.add("-application"); + programArgs.add(getExtension(testTask).getApplicationName()); + programArgs.add("-product org.eclipse.platform.ide"); + // alternatively can use URI for -data and -configuration (file:///path/to/dir/) + programArgs.add("-data"); + programArgs.add(runDir.getAbsolutePath()); + programArgs.add("-configuration"); + programArgs.add(configIniFile.getParentFile().getAbsolutePath()); + + programArgs.add("-testpluginname"); + String fragmentHost = getExtension(testTask).getFragmentHost(); + if (fragmentHost != null) { + programArgs.add(fragmentHost); + } else { + programArgs.add(this.project.getName()); + } + + javaExecHandleBuilder.setArgs(programArgs); + javaExecHandleBuilder.setSystemProperties(testTask.getSystemProperties()); + javaExecHandleBuilder.setEnvironment(testTask.getEnvironment()); + + // TODO this should be specified when creating the task (to allow overrid in build script) + List jvmArgs = new ArrayList(); + jvmArgs.add("-XX:MaxPermSize=256m"); + jvmArgs.add("-Xms40m"); + jvmArgs.add("-Xmx1024m"); + + // uncomment to debug spawned Eclipse instance + // jvmArgs.add("-Xdebug"); + // jvmArgs.add("-Xrunjdwp:transport=dt_socket,address=8998,server=y"); + + if (Constants.getOs().equals("macosx")) { + jvmArgs.add("-XstartOnFirstThread"); + } + javaExecHandleBuilder.setJvmArgs(jvmArgs); + javaExecHandleBuilder.setWorkingDir(this.project.getBuildDir()); + + final CountDownLatch latch = new CountDownLatch(1); + Future eclipseJob = threadPool.submit(new Runnable() { + @Override + public void run() { + try { + ExecResult execResult = javaExecHandleBuilder.execute(); + execResult.assertNormalExitValue(); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + latch.countDown(); + } + } + }); + // TODO + final String suiteName = this.project.getName(); + Future testCollectorJob = threadPool.submit(new Runnable() { + @Override + public void run() { + EclipseTestListener pdeTestListener = new EclipseTestListener(testResultProcessor, suiteName, this); + new RemoteTestRunnerClient().startListening(new ITestRunListener2[] { pdeTestListener }, pdeTestPort); + LOGGER.info("Listening on port " + pdeTestPort + " for test suite " + suiteName + " results ..."); + synchronized (this) { + try { + wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } finally { + latch.countDown(); + } + } + } + }); + try { + latch.await(getExtension(testTask).getTestTimeoutSeconds(), TimeUnit.SECONDS); + // short chance to do cleanup + eclipseJob.get(15, TimeUnit.SECONDS); + testCollectorJob.get(15, TimeUnit.SECONDS); + } catch (Exception e) { + throw new GradleException("Test execution failed", e); + } + } + + private File getEquinoxLauncherFile(File testEclipseDir) { + File[] plugins = new File(testEclipseDir, "plugins").listFiles(); + for (File plugin : plugins) { + if (plugin.getName().startsWith("org.eclipse.equinox.launcher_")) { + return plugin; + } + } + return null; + } + + private FileResolver getFileResolver(Test testTask) { + return testTask.getProject().getPlugins().findPlugin(TestBundlePlugin.class).fileResolver; + } + + private List collectTestNames(Test testTask) { + ClassNameCollectingProcessor processor = new ClassNameCollectingProcessor(); + Runnable detector; + final FileTree testClassFiles = testTask.getCandidateClassFiles(); + if (testTask.isScanForTestClasses()) { + TestFrameworkDetector testFrameworkDetector = testTask.getTestFramework().getDetector(); + testFrameworkDetector.setTestClassesDirectory(testTask.getTestClassesDir()); + testFrameworkDetector.setTestClasspath(testTask.getClasspath()); + detector = new EclipsePluginTestClassScanner(testClassFiles, processor); + } else { + detector = new EclipsePluginTestClassScanner(testClassFiles, processor); + } + new TestMainAction(detector, processor, new NoOpTestResultProcessor(), new TrueTimeProvider()).run(); + LOGGER.info("collected test class names: {}", processor.classNames); + return processor.classNames; + } + + public static final class NoOpTestResultProcessor implements TestResultProcessor { + + @Override + public void started(TestDescriptorInternal testDescriptorInternal, TestStartEvent testStartEvent) { + } + + @Override + public void completed(Object o, TestCompleteEvent testCompleteEvent) { + } + + @Override + public void output(Object o, TestOutputEvent testOutputEvent) { + } + + @Override + public void failure(Object o, Throwable throwable) { + } + } + + private class ClassNameCollectingProcessor implements TestClassProcessor { + public List classNames = new ArrayList(); + + @Override + public void startProcessing(TestResultProcessor testResultProcessor) { + // no-op + } + + @Override + public void processTestClass(TestClassRunInfo testClassRunInfo) { + this.classNames.add(testClassRunInfo.getTestClassName()); + } + + @Override + public void stop() { + // no-op + } + } +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExtension.java b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExtension.java new file mode 100644 index 000000000..ed613ac4d --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestExtension.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild.testing; + +import org.gradle.api.internal.file.FileResolver; + +import javax.inject.Inject; +import java.io.File; + +public class EclipseTestExtension { + + private String fragmentHost; + + /** + * Application launched in Eclipse. + * {@code org.eclipse.pde.junit.runtime.coretestapplication} can be used to run non-UI tests. + */ + private String applicationName = "org.eclipse.pde.junit.runtime.uitestapplication"; + + private File optionsFile; + + /** Boolean toggle to control whether to show Eclipse log or not. */ + private boolean consoleLog; + + private long testTimeoutSeconds = 60 * 60L; + + @Inject + public FileResolver getFileResolver() { + throw new UnsupportedOperationException(); + } + + public String getApplicationName() { + return this.applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public File getOptionsFile() { + return this.optionsFile; + } + + public void setOptionsFile(File optionsFile) { + this.optionsFile = optionsFile; + } + + public boolean isConsoleLog() { + return this.consoleLog; + } + + public void setConsoleLog(boolean consoleLog) { + this.consoleLog = consoleLog; + } + + public long getTestTimeoutSeconds() { + return this.testTimeoutSeconds; + } + + public void setTestTimeoutSeconds(long testTimeoutSeconds) { + this.testTimeoutSeconds = testTimeoutSeconds; + } + + public String getFragmentHost() { + return this.fragmentHost; + } + + public void setFragmentHost(String fragmentHost) { + this.fragmentHost = fragmentHost; + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestListener.java b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestListener.java new file mode 100644 index 000000000..3aeeb3013 --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/testing/EclipseTestListener.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild.testing; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.internal.junit.model.ITestRunListener2; +import org.gradle.api.GradleException; +import org.gradle.api.internal.tasks.testing.DefaultTestClassDescriptor; +import org.gradle.api.internal.tasks.testing.DefaultTestMethodDescriptor; +import org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent; +import org.gradle.api.internal.tasks.testing.DefaultTestSuiteDescriptor; +import org.gradle.api.internal.tasks.testing.TestCompleteEvent; +import org.gradle.api.internal.tasks.testing.TestDescriptorInternal; +import org.gradle.api.internal.tasks.testing.TestResultProcessor; +import org.gradle.api.internal.tasks.testing.TestStartEvent; +import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor; +import org.gradle.api.tasks.testing.TestOutputEvent; +import org.gradle.api.tasks.testing.TestResult; + +public final class EclipseTestListener implements ITestRunListener2 { + private static final Pattern ECLIPSE_TEST_NAME = Pattern.compile("(.*)\\((.*)\\)"); + + /** + * Test identifier prefix for ignored tests. + */ + public static final String IGNORED_TEST_PREFIX = "@Ignore: "; //$NON-NLS-1$ + + /** + * Test identifier prefix for tests with assumption failures. + */ + public static final String ASSUMPTION_FAILED_TEST_PREFIX = "@AssumptionFailure: "; //$NON-NLS-1$ + + private final TestResultProcessor testResultProcessor; + private final String suiteName; + private final Object waitMonitor; + private TestDescriptorInternal currentTestSuite; + private TestDescriptorInternal currentTestClass; + private TestDescriptorInternal currentTestMethod; + private org.gradle.api.tasks.testing.TestResult.ResultType currentResult; + + public EclipseTestListener(TestResultProcessor testResultProcessor, String suite, Object waitMonitor) { + this.testResultProcessor = new AttachParentTestResultProcessor(testResultProcessor); + this.waitMonitor = waitMonitor; + this.suiteName = suite; + } + + @Override + public synchronized void testRunStarted(int testCount) { + this.currentTestSuite = new DefaultTestSuiteDescriptor("root", this.suiteName); + this.testResultProcessor.started(this.currentTestSuite, new TestStartEvent(System.currentTimeMillis())); + } + + @Override + public synchronized void testRunEnded(long elapsedTime) { + // System.out.println("Test Run Ended - " + (failed() ? "FAILED" : "PASSED") + + // " - Total: " + totalNumberOfTests + // + " (Errors: " + numberOfTestsWithError + // + ", Failed: " + numberOfTestsFailed + // + ", Passed: " + numberOfTestsPassed + "), duration " + elapsedTime + "ms." + " id: " + + // currentSuite.getId()); + + this.testResultProcessor.completed(this.currentTestSuite.getId(), new TestCompleteEvent(System.currentTimeMillis())); + synchronized (this.waitMonitor) { + this.waitMonitor.notifyAll(); + } + } + + @Override + public synchronized void testRunStopped(long elapsedTime) { + // System.out.println("Test Run Stopped"); + // TODO report failure when stopped? + testRunEnded(elapsedTime); + } + + @Override + public synchronized void testRunTerminated() { + // System.out.println("Test Run Terminated"); + // TODO report failure when terminated? + testRunEnded(0); + } + + @Override + public synchronized void testStarted(String testId, String testName) { + // TODO need idGenerator + String testClass = testName; + String testMethod = testName; + Matcher matcher = ECLIPSE_TEST_NAME.matcher(testName); + if (matcher.matches()) { + testClass = matcher.group(2); + testMethod = matcher.group(1); + } + + this.currentTestClass = new DefaultTestClassDescriptor(testId + " class"/* + * idGenerator.generateId( + * ) + */, testClass); + this.currentTestMethod = new DefaultTestMethodDescriptor(testId/* idGenerator.generateId() */, testClass, testMethod); + this.currentResult = org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS; + try { + this.testResultProcessor.started(this.currentTestClass, new TestStartEvent(System.currentTimeMillis())); + this.testResultProcessor.started(this.currentTestMethod, new TestStartEvent(System.currentTimeMillis())); + } catch (IllegalArgumentException iae) { + iae.printStackTrace(); + } + } + + @Override + public synchronized void testEnded(String testId, String testName) { + if (testName.startsWith(IGNORED_TEST_PREFIX)) { + if (this.currentResult == org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS) { + this.currentResult = TestResult.ResultType.SKIPPED; + } else { + throw new GradleException("Failing ignored test is suspicious: " + testId + ", " + testName); + } + } + this.testResultProcessor.completed(this.currentTestMethod.getId(), new TestCompleteEvent(System.currentTimeMillis(), this.currentResult)); + this.testResultProcessor.completed(this.currentTestClass.getId(), new TestCompleteEvent(System.currentTimeMillis())); + } + + @Override + public synchronized void testFailed(int status, String testId, String testName, String trace, String expected, String actual) { + String statusMessage = String.valueOf(status); + + System.out.println(" Test - " + testName + " - status: " + statusMessage + ", trace: " + trace + ", expected: " + expected + ", actual: " + actual + " id: " + + this.currentTestClass.getId()); + if (status == ITestRunListener2.STATUS_OK) { + statusMessage = "OK"; + this.currentResult = org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS; + } else if (status == ITestRunListener2.STATUS_FAILURE) { + statusMessage = "FAILED"; + this.currentResult = org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE; + } else if (status == ITestRunListener2.STATUS_ERROR) { + statusMessage = "ERROR"; + this.currentResult = org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE; + } else { + throw new GradleException("Unknown status for test execution " + status + ", " + testId + ", " + testName); + } + + if (this.currentTestMethod == null) { + System.out.println("Test failure without current test method: " + testName + " - status: " + statusMessage + ", trace: " + trace + ", expected: " + expected + + ", actual: " + actual + " id: " + testId); + return; + } + this.testResultProcessor.output(this.currentTestMethod.getId(), new DefaultTestOutputEvent(TestOutputEvent.Destination.StdOut, "Expected: " + expected + ", actual: " + actual)); + this.testResultProcessor.failure(this.currentTestMethod.getId(), new FailureThrowableStub(trace)); + } + + @Override + public synchronized void testReran(String testId, String testClass, String testName, int status, String trace, String expected, String actual) { + throw new UnsupportedOperationException("Unexpected call to testReran when running tests in Eclipse."); + } + + @Override + public synchronized void testTreeEntry(String description) { + // System.out.println("Test Tree Entry - Description: " + description); + } + + public static final class FailureThrowableStub extends Exception { + + private static final long serialVersionUID = 1L; + private final String trace; + + public FailureThrowableStub(String trace) { + super(); + this.trace = trace; + } + + @Override + public void printStackTrace(PrintStream s) { + s.println(this.trace); + } + + @Override + public void printStackTrace(PrintWriter s) { + s.println(this.trace); + } + + } + +} diff --git a/buildSrc/src/main/groovy/eclipsebuild/testing/PDETestPortLocator.java b/buildSrc/src/main/groovy/eclipsebuild/testing/PDETestPortLocator.java new file mode 100644 index 000000000..da72fa54a --- /dev/null +++ b/buildSrc/src/main/groovy/eclipsebuild/testing/PDETestPortLocator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package eclipsebuild.testing; + +import java.io.IOException; +import java.net.ServerSocket; + +public final class PDETestPortLocator { + + public int locatePDETestPortNumber() { + ServerSocket socket = null; + try { + socket = new ServerSocket(0); + return socket.getLocalPort(); + } catch (IOException e) { + // ignore + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // ignore + } + } + } + return -1; + } + +} diff --git a/com.gradleware.tooling.eclipse.branding/.classpath b/com.gradleware.tooling.eclipse.branding/.classpath new file mode 100644 index 000000000..32a9aa470 --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/com.gradleware.tooling.eclipse.branding/.project b/com.gradleware.tooling.eclipse.branding/.project new file mode 100644 index 000000000..5d0b626ee --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/.project @@ -0,0 +1,28 @@ + + + com.gradleware.tooling.eclipse.branding + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/com.gradleware.tooling.eclipse.branding/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.branding/META-INF/MANIFEST.MF new file mode 100644 index 000000000..36e26ef64 --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Buildship, Eclipse Plug-ins for Gradle +Bundle-SymbolicName: com.gradleware.tooling.eclipse.branding +Bundle-Version: 1.0.0 +Bundle-Vendor: Gradle Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-ClassPath: . diff --git a/com.gradleware.tooling.eclipse.branding/about.ini b/com.gradleware.tooling.eclipse.branding/about.ini new file mode 100644 index 000000000..756ba4822 --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/about.ini @@ -0,0 +1,7 @@ +# Icon on the about page +featureImage=icons/gradle-icon-32x32.png + +# Text on the about page +aboutText= Buildship: Eclipse Plug-ins for Gradle, provided as part of the Gradle Platform.\n\n\ +Copyright (c) 2015 Gradle Inc.\n\ +For more information, visit our website http://www.gradle.org. diff --git a/com.gradleware.tooling.eclipse.branding/build.gradle b/com.gradleware.tooling.eclipse.branding/build.gradle new file mode 100644 index 000000000..f748be578 --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/build.gradle @@ -0,0 +1 @@ +apply plugin: eclipsebuild.BundlePlugin diff --git a/com.gradleware.tooling.eclipse.branding/build.properties b/com.gradleware.tooling.eclipse.branding/build.properties new file mode 100644 index 000000000..e03098cbc --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/build.properties @@ -0,0 +1,6 @@ +source.. = src/main/java/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + icons/,\ + about.ini diff --git a/com.gradleware.tooling.eclipse.branding/icons/gradle-icon-32x32.png b/com.gradleware.tooling.eclipse.branding/icons/gradle-icon-32x32.png new file mode 100644 index 000000000..3dd8a7e7b Binary files /dev/null and b/com.gradleware.tooling.eclipse.branding/icons/gradle-icon-32x32.png differ diff --git a/com.gradleware.tooling.eclipse.branding/src/main/java/README.txt b/com.gradleware.tooling.eclipse.branding/src/main/java/README.txt new file mode 100644 index 000000000..36ca42ff2 --- /dev/null +++ b/com.gradleware.tooling.eclipse.branding/src/main/java/README.txt @@ -0,0 +1,3 @@ +Eclipse requires a valid reference to a source folder, even if there are no sources. + +Do not remove this file or Git will stop tracking this directory. \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/.classpath b/com.gradleware.tooling.eclipse.core.test/.classpath new file mode 100644 index 000000000..91de35154 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/.classpath @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.core.test/.project b/com.gradleware.tooling.eclipse.core.test/.project new file mode 100644 index 000000000..b4f16a67d --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/.project @@ -0,0 +1,40 @@ + + + com.gradleware.tooling.eclipse.core.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.groovy.core.groovyNature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + + + 1365114673930 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-target + + + + diff --git a/com.gradleware.tooling.eclipse.core.test/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.core.test/META-INF/MANIFEST.MF new file mode 100644 index 000000000..238b5e983 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Buildship, Eclipse Plug-ins for Gradle - Core Test +Bundle-SymbolicName: com.gradleware.tooling.eclipse.core.test;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Vendor: Gradle Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Fragment-Host: com.gradleware.tooling.eclipse.core +Require-Bundle: org.junit +Bundle-ClassPath: ., + lib/cglib-nodep-3.1.jar, + lib/groovy-all-2.0.5.jar, + lib/guava-18.0.jar, + lib/hamcrest-core-1.3.jar, + lib/junit-dep-4.10.jar, + lib/slf4j-api-1.7.10.jar, + lib/slf4j-simple-1.7.10.jar, + lib/spock-core-0.7-groovy-2.0.jar, + lib/testing-junit-0.3.jar diff --git a/com.gradleware.tooling.eclipse.core.test/build.gradle b/com.gradleware.tooling.eclipse.core.test/build.gradle new file mode 100644 index 000000000..8fd24ab04 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/build.gradle @@ -0,0 +1,30 @@ +apply plugin: eclipsebuild.TestBundlePlugin +apply plugin: 'groovy' + +dependencies { + compile project(':com.gradleware.tooling.eclipse.core') + + bundled "org.spockframework:spock-core:$spockLibVersion" + bundled "cglib:cglib-nodep:$cglibLibVersion" + bundled "com.gradleware.tooling:testing-junit:$commonsLibVersion" + bundled "org.slf4j:slf4j-simple:$slf4jLibVersion" +} + +eclipseTest { + fragmentHost 'com.gradleware.tooling.eclipse.core' + applicationName 'org.eclipse.pde.junit.runtime.coretestapplication' + optionsFile rootProject.project(':com.gradleware.tooling.eclipse.core').file('.options') +} + +// append the sources of each first-level dependency and its transitive dependencies of +// the 'bundled' configuration to the 'bundledSource' configuration +configurations.bundled.resolvedConfiguration.firstLevelModuleDependencies.each { dep -> + addSourcesRecursively(dep) +} + +def addSourcesRecursively(dep) { + dependencies { + bundledSource group: dep.moduleGroup, name: dep.moduleName, version: dep.moduleVersion, classifier: 'sources' + } + dep.children.each { addSourcesRecursively(it) } +} diff --git a/com.gradleware.tooling.eclipse.core.test/build.properties b/com.gradleware.tooling.eclipse.core.test/build.properties new file mode 100644 index 000000000..e57182328 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/main/groovy/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + lib/ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1-sources.jar new file mode 100644 index 000000000..f2a7080fa Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1.jar b/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1.jar new file mode 100644 index 000000000..c0ac12145 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/cglib-nodep-3.1.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5-sources.jar new file mode 100644 index 000000000..e3bb8d1fb Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5.jar b/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5.jar new file mode 100644 index 000000000..76aec578b Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/groovy-all-2.0.5.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0-sources.jar new file mode 100644 index 000000000..d97cc501b Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0.jar b/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0.jar new file mode 100644 index 000000000..8f89e4901 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/guava-18.0.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3-sources.jar new file mode 100644 index 000000000..c3c110b4d Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3.jar b/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3.jar new file mode 100644 index 000000000..9d5fe16e3 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/hamcrest-core-1.3.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10-sources.jar new file mode 100644 index 000000000..bfcb68742 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10.jar b/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10.jar new file mode 100644 index 000000000..32209cb13 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/junit-dep-4.10.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10-sources.jar new file mode 100644 index 000000000..9e214e964 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10.jar b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10.jar new file mode 100644 index 000000000..744e9ec5b Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-api-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10-sources.jar new file mode 100644 index 000000000..2e9e57d86 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10.jar b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10.jar new file mode 100644 index 000000000..b40be298f Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/slf4j-simple-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0-sources.jar new file mode 100644 index 000000000..1b7b17e35 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0.jar b/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0.jar new file mode 100644 index 000000000..317c587c5 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/spock-core-0.7-groovy-2.0.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3-sources.jar b/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3-sources.jar new file mode 100644 index 000000000..866d49ea8 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3.jar b/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3.jar new file mode 100644 index 000000000..e5fb2e604 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core.test/lib/testing-junit-0.3.jar differ diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/.gitignore b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/.gitignore new file mode 100644 index 000000000..8fc28968a --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/.gitignore @@ -0,0 +1,4 @@ +.project +.classpath +.gradle +.settings \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/build.gradle b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/build.gradle new file mode 100644 index 000000000..4aa8739df --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/build.gradle @@ -0,0 +1,2 @@ +apply plugin: 'java' + diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/src/main/java/ApiInterface.java b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/src/main/java/ApiInterface.java new file mode 100644 index 000000000..92b08de2f --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/api/src/main/java/ApiInterface.java @@ -0,0 +1,5 @@ + + +public interface ApiInterface { + void foo(); +} diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/build.gradle b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/build.gradle new file mode 100644 index 000000000..52672fd4e --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/build.gradle @@ -0,0 +1,3 @@ +subprojects { + repositories { jcenter() } +} \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/build.gradle b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/build.gradle new file mode 100644 index 000000000..bf5ec7288 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/build.gradle @@ -0,0 +1,6 @@ +apply plugin: 'java' + +dependencies { + compile project(":api") + compile "org.apache.commons:commons-lang3:3.3.2" +} \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/src/main/java/ClientImplementation.java b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/src/main/java/ClientImplementation.java new file mode 100644 index 000000000..f69b00168 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/client/src/main/java/ClientImplementation.java @@ -0,0 +1,11 @@ +import org.apache.commons.lang3.StringUtils; + + +public class ClientImplementation implements ApiInterface { + + @Override + public void foo() { + System.out.println(StringUtils.abbreviate("Deoxyribonucleic", 6)); + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/build.gradle b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/build.gradle new file mode 100644 index 000000000..b830c0fa9 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/build.gradle @@ -0,0 +1,5 @@ +apply plugin: 'java' + +dependencies { + compile project(":impl:client") +} \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/src/main/java/ExtClientImplementation.java b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/src/main/java/ExtClientImplementation.java new file mode 100644 index 000000000..7d4221741 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/impl/extclient/src/main/java/ExtClientImplementation.java @@ -0,0 +1,9 @@ + +public final class ExtClientImplementation extends ClientImplementation { + + @Override + public void foo() { + System.out.println("baz"); + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/settings.gradle b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/settings.gradle new file mode 100644 index 000000000..40360d7d4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/resources/sample-projects/simple-multimodule/settings.gradle @@ -0,0 +1 @@ +include "api", "impl:client", "impl:extclient" \ No newline at end of file diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/CorePluginTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/CorePluginTest.groovy new file mode 100644 index 000000000..6e117f044 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/CorePluginTest.groovy @@ -0,0 +1,21 @@ +package com.gradleware.tooling.eclipse.core + +import static org.junit.Assert.assertNotNull; +import spock.lang.Specification; + + +class CorePluginTest extends Specification { + + def "Services exposed from core plugin are available"() { + expect: + CorePlugin.getInstance() != null + CorePlugin.gradleLaunchConfigurationManager() != null + CorePlugin.logger() != null + CorePlugin.modelRepositoryProvider() != null + CorePlugin.processStreamsProvider() != null + CorePlugin.projectConfigurationManager() != null + CorePlugin.publishedGradleVersions() !=null + CorePlugin.workbenchOperations() != null + CorePlugin.workspaceOperations() != null + } +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationManagerTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationManagerTest.groovy new file mode 100644 index 000000000..ee2aa3fb9 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationManagerTest.groovy @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.configuration.internal + +import com.google.common.collect.ImmutableList +import com.gradleware.tooling.eclipse.core.CorePlugin +import com.gradleware.tooling.eclipse.core.GradleNature +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfigurationManager +import com.gradleware.tooling.eclipse.core.projectimport.ProjectImportConfiguration +import com.gradleware.tooling.eclipse.core.projectimport.ProjectImportJob +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations +import com.gradleware.tooling.junit.TestFile +import com.gradleware.tooling.toolingclient.GradleDistribution +import com.gradleware.tooling.toolingmodel.Path +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes +import org.eclipse.core.resources.IProject +import org.eclipse.core.runtime.NullProgressMonitor +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Shared +import spock.lang.Specification + +class ProjectConfigurationManagerTest extends Specification { + + @Shared + ProjectConfigurationManager configurationManager = CorePlugin.projectConfigurationManager() + + @Shared + WorkspaceOperations workspaceOperations = CorePlugin.workspaceOperations(); + + @Rule + TemporaryFolder tempFolder + + def cleanup() { + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + } + + def "no Gradle root project configurations available when there are no projects"() { + setup: + Set rootProjectConfigurations = configurationManager.getRootProjectConfigurations() + assert rootProjectConfigurations == [] as Set + } + + def "no Gradle root project configurations available when there are no Eclipse projects with Gradle nature"() { + given: + File projectDir = tempFolder.root + workspaceOperations.createProject("sample-project", projectDir, ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + when: + Set rootProjectConfigurations = configurationManager.getRootProjectConfigurations() + + then: + assert rootProjectConfigurations == [] as Set + } + + def "no Gradle root project configurations available when there are no open Eclipse projects with Gradle nature"() { + given: + File projectDir = tempFolder.root + IProject project = workspaceOperations.createProject("sample-project", projectDir, ImmutableList.of(), Arrays.asList(GradleNature.ID), new NullProgressMonitor()) + project.close(new NullProgressMonitor()) + + when: + Set rootProjectConfigurations = configurationManager.getRootProjectConfigurations() + + then: + assert rootProjectConfigurations == [] as Set + } + + def "one Gradle root project configuration when one Gradle multi-project build is imported"() { + setup: + def rootDir = new TestFile(tempFolder.newFolder()) + rootDir.create { + file('settings.gradle').text = ''' + rootProject.name = 'project one' + include 'sub1' + include 'sub2' + ''' + sub1 { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + sub2 { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + } + + def importConfigurationOne = new ProjectImportConfiguration() + importConfigurationOne.projectDir = new File(rootDir.absolutePath) + importConfigurationOne.gradleDistribution = GradleDistributionWrapper.from(GradleDistributionWrapper.DistributionType.WRAPPER, null) + + new ProjectImportJob(importConfigurationOne).importProject(new NullProgressMonitor()) + + when: + Set rootProjectConfigurations = configurationManager.getRootProjectConfigurations() + + then: + rootProjectConfigurations == [ + ProjectConfiguration.from( + new FixedRequestAttributes(rootDir, + null, + GradleDistribution.fromBuild(), + null, + ImmutableList.of(), + ImmutableList.of()), + Path.from(':'), rootDir)] as Set + } + + def "two Gradle root project configurations when two Gradle multi-project builds are imported"() { + setup: + def rootDirOne = new TestFile(tempFolder.newFolder()) + rootDirOne.create { + file('settings.gradle').text = ''' + rootProject.name = 'project one' + include 'sub1' + include 'sub2' + ''' + sub1 { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + sub2 { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + } + + def rootDirTwo = new TestFile(tempFolder.newFolder()) + rootDirTwo.create { + file('settings.gradle').text = ''' + rootProject.name = 'project two' + include 'alpha' + include 'beta' + ''' + alpha { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + beta { + file('build.gradle').text = ''' + apply plugin: 'java' + ''' + } + } + + def importConfigurationOne = new ProjectImportConfiguration() + importConfigurationOne.projectDir = new File(rootDirOne.absolutePath) + importConfigurationOne.gradleDistribution = GradleDistributionWrapper.from(GradleDistributionWrapper.DistributionType.WRAPPER, null) + + def importConfigurationTwo = new ProjectImportConfiguration() + importConfigurationTwo.projectDir = new File(rootDirTwo.absolutePath) + importConfigurationTwo.gradleDistribution = GradleDistributionWrapper.from(GradleDistributionWrapper.DistributionType.VERSION, '1.12') + + new ProjectImportJob(importConfigurationOne).importProject(new NullProgressMonitor()) + new ProjectImportJob(importConfigurationTwo).importProject(new NullProgressMonitor()) + + when: + Set rootProjectConfigurations = configurationManager.getRootProjectConfigurations() + + then: + rootProjectConfigurations == [ + ProjectConfiguration.from( + new FixedRequestAttributes(rootDirOne, + null, + GradleDistribution.fromBuild(), + null, + ImmutableList.of(), + ImmutableList.of()), + Path.from(':'), rootDirOne), + ProjectConfiguration.from( + new FixedRequestAttributes(rootDirTwo, + null, + GradleDistribution.forVersion('1.12'), + null, + ImmutableList.of(), + ImmutableList.of()), + Path.from(':'), rootDirTwo)] as Set + } + + def "error thrown when projects of same multi-project build have different shared project configurations"() { + given: + File rootProjectDir = tempFolder.newFolder() + + // create root project and use Gradle version 2.0 in the persisted configuration + IProject rootProject = workspaceOperations.createProject("root-project", rootProjectDir, ImmutableList.of(), Arrays.asList(GradleNature.ID), new NullProgressMonitor()) + def requestAttributes = new FixedRequestAttributes(rootProjectDir, null, GradleDistribution.forVersion("2.0"), null, + ImmutableList.copyOf("-Xmx256M"), ImmutableList.copyOf("foo")) + def projectConfiguration = ProjectConfiguration.from(requestAttributes, Path.from(":"), rootProjectDir) + configurationManager.saveProjectConfiguration(projectConfiguration, rootProject) + + // create child project and use Gradle version 1.0 in the persisted configuration + File childProjectDir = tempFolder.newFolder() + IProject childProject = workspaceOperations.createProject("child-project", childProjectDir, ImmutableList.of(), Arrays.asList(GradleNature.ID), new NullProgressMonitor()) + def childRequestAttributes = new FixedRequestAttributes(rootProjectDir, null, GradleDistribution.forVersion("1.0"), null, + ImmutableList.copyOf("-Xmx256M"), ImmutableList.copyOf("foo")) + def childProjectConfiguration = ProjectConfiguration.from(childRequestAttributes, Path.from(":child"), childProjectDir) + configurationManager.saveProjectConfiguration(childProjectConfiguration, childProject) + + when: + configurationManager.getRootProjectConfigurations() + + then: + thrown(GradlePluginsRuntimeException) + } + + def "save and read project with full configuration"() { + given: + File projectDir = tempFolder.root + IProject project = workspaceOperations.createProject("sample-project", projectDir, ImmutableList.of(), Arrays.asList(GradleNature.ID), new NullProgressMonitor()) + + def requestAttributes = new FixedRequestAttributes(projectDir, tempFolder.newFolder(), GradleDistribution.forVersion("1.12"), tempFolder.newFolder(), + ImmutableList.copyOf("-Xmx256M"), ImmutableList.copyOf("foo")) + def projectConfiguration = ProjectConfiguration.from(requestAttributes, Path.from(":"), projectDir) + + when: + configurationManager.saveProjectConfiguration(projectConfiguration, project) + + then: + configurationManager.readProjectConfiguration(project) == projectConfiguration + } + + def "save and read project with minimal configuration"() { + given: + File projectDir = tempFolder.newFolder() + IProject project = workspaceOperations.createProject("sample-project", projectDir, ImmutableList.of(), Arrays.asList(GradleNature.ID), new NullProgressMonitor()) + + def attributes = new FixedRequestAttributes(projectDir, null, GradleDistribution.fromBuild(), null, + ImmutableList.of(), ImmutableList.of()) + def projectConfiguration = ProjectConfiguration.from(attributes, Path.from(":"), projectDir) + + when: + configurationManager.saveProjectConfiguration(projectConfiguration, project) + + then: + configurationManager.readProjectConfiguration(project) == projectConfiguration + } + + private static File createDir(File parent, String dir) { + def sub1 = new File(parent, dir) + sub1.mkdir() + sub1 + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManagerTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManagerTest.groovy new file mode 100644 index 000000000..fddcc1b72 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManagerTest.groovy @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch + +import com.gradleware.tooling.toolingclient.GradleDistribution +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchManager; + +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper.DistributionType; +import com.gradleware.tooling.eclipse.core.launch.internal.DefaultGradleLaunchConfigurationManager; + +import spock.lang.Specification; + +class GradleLaunchConfigurationManagerTest extends Specification { + + def validAttribute = GradleRunConfigurationAttributes.with( ['clean'], "/home/user/workspace/project", GradleDistributionWrapper.from(DistributionType.VERSION, "2.3").toGradleDistribution(), "/.gradle", "/.java", ["-ea"], ["-q"], true) + def manager = new DefaultGradleLaunchConfigurationManager() + + def setup() { + DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations().each { it.delete() } + assert DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations().size() == 0 + } + + def cleanup() { + DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations().each { it.delete() } + } + + // partition : null or not null + + def "Storing a new attribute produces a new launch configuration instance"() { + when: + ILaunchConfiguration config = manager.getOrCreateRunConfiguration(validAttribute) + + then: + config != null + config.getName().contains("clean") + config.getAttributes().values().size() >= 8 + } + + def "Can't create a run configuration from null object"() { + when: + def config = manager.getOrCreateRunConfiguration(null) + + then: + thrown(NullPointerException) + } + + def "Creating run configuration twice from same object finds creates only one element"() { + // persistence contains only one config. objects same name are the same + } + + def "Two different attributes create two run configurations"() { + when: + def config1 = manager.getOrCreateRunConfiguration(validAttribute) + def config2 = manager.getOrCreateRunConfiguration(validAttribute) + + then: + DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations().size() == 1 + config1 == config2 + } + + def "Run configurations are considered equal if working directory and tasks are equal"() { + setup: + // only difference: argument -i or -d + def atr = GradleRunConfigurationAttributes.with( ['build'], "/home/user/workspace/project-p", GradleDistribution.forVersion('2.0'), null, null, [], [], false) + def atrPrime = GradleRunConfigurationAttributes.with( ['build'], "/home/user/workspace/project-p", GradleDistribution.forVersion('2.1'), null, null, [], [], false) + + when: + ILaunchConfiguration config1 = manager.getOrCreateRunConfiguration(atr) + ILaunchConfiguration config2 = manager.getOrCreateRunConfiguration(atrPrime) + + then: + DebugPlugin.getDefault().getLaunchManager().getLaunchConfigurations().size() == 1 + config1 == config2 + } + + def "Can't save attribute if launch manager is not able to retrieve configurations"() { + setup: + ILaunchManager launchManager = Mock(ILaunchManager) + ILaunchConfigurationType type = Mock(ILaunchConfigurationType) + def configurationManager = new DefaultGradleLaunchConfigurationManager(launchManager) + launchManager.getLaunchConfigurations() >> {} + launchManager.getLaunchConfigurationType(_) >> { return type } + launchManager.getLaunchConfigurations(_) >> { throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "error")) } + + when: + configurationManager.getOrCreateRunConfiguration(validAttribute) + + then: + thrown(GradlePluginsRuntimeException) + } + + def "If can't save attributes then a runtime exception is thrown"() { + setup: + ILaunchManager launchManager = Mock(ILaunchManager) + ILaunchConfigurationType type = Mock(ILaunchConfigurationType) + def configurationManager = new DefaultGradleLaunchConfigurationManager(launchManager) + launchManager.getLaunchConfigurations() >> {} + launchManager.getLaunchConfigurationType(_) >> { return type } + launchManager.getLaunchConfigurations(_) >> { [] } + type.newInstance(_,_) >> { throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "error")) } + + when: + configurationManager.getOrCreateRunConfiguration(validAttribute) + + then: + thrown(GradlePluginsRuntimeException) + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributesTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributesTest.groovy new file mode 100644 index 000000000..ba0551609 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributesTest.groovy @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch + +import java.util.List; + +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchManager; + +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper.DistributionType; +import com.gradleware.tooling.toolingclient.GradleDistribution; + +import spock.lang.Shared; +import spock.lang.Specification; + + +class GradleRunConfigurationAttributesTest extends Specification { + + // list ov valid attributes + @Shared def atr = [ + 'tasks' : ['clean'], + 'workingDir' : "/home/user/workspace", + 'gradleDistr' : GradleDistributionWrapper.from(DistributionType.WRAPPER, null).toGradleDistribution(), + 'gradleHome' : "/.gradle", + 'javaHome' : "/.java", + 'arguments' : ["-q"], + 'jvmArguments' : ["-ea"], + 'visualize' : true + ] + + + def "Can create a new valid instance"() { + when: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, atr.javaHome, atr.jvmArguments, atr.arguments, atr.visualize) + + then: + // not null + configuration != null + // check non-calculated values + configuration.getTasks() == atr.tasks + configuration.getWorkingDirExpression() == atr.workingDir + configuration.getGradleDistribution() == atr.gradleDistr + configuration.getGradleUserHomeExpression() == atr.gradleHome + configuration.getJavaHomeExpression() == atr.javaHome + configuration.getJvmArguments() == atr.jvmArguments + configuration.getArguments() == atr.arguments + configuration.isVisualizeTestProgress() == atr.visualize + // check calculated value + configuration.getArgumentExpressions() == atr.arguments + configuration.getJvmArgumentExpressions() == atr.jvmArguments + configuration.getWorkingDir().getAbsolutePath() == new File(atr.workingDir).getAbsolutePath() + configuration.getGradleUserHome().getAbsolutePath() == new File(atr.gradleHome).getAbsolutePath() + configuration.getJavaHome().getAbsolutePath() == new File(atr.javaHome).getAbsolutePath() + } + + + + def "Can create a new valid instance with valid null arguments"() { + when: + def configuration = GradleRunConfigurationAttributes.with(tasks, workingDir, gradleDistr, gradleHome, javaHome, jvmArguments, arguments, visualize) + + then: + configuration != null + gradleHome != null || configuration.getGradleUserHome() == null + javaHome != null || configuration.getJavaHome() == null + + + where: + tasks | workingDir | gradleDistr | gradleHome | javaHome | jvmArguments | arguments | visualize + atr.tasks | atr.workingDir | atr.gradleDistr | null | atr.javaHome | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | atr.gradleHome | null | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | null | null | atr.jvmArguments | atr.arguments | atr.visualize + } + + def "Creation fails when null argument passed"() { + when: + GradleRunConfigurationAttributes.with(tasks, workingDir, gradleDistr, gradleHome, javaHome, jvmArguments, arguments, visualize) + + then: + thrown(RuntimeException) + + where: + tasks | workingDir | gradleDistr | gradleHome | javaHome | jvmArguments | arguments | visualize + null | atr.workingDir | atr.gradleDistr | atr.gradleHome | atr.javaHome | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | null | atr.gradleDistr | atr.gradleHome | atr.javaHome | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | atr.gradleHome | atr.javaHome | null | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | atr.gradleHome | atr.javaHome | atr.jvmArguments | null | atr.visualize + } + + def "Expressions can be resolved in the parameters"() { + when: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, '${workspace_loc}/working_dir', atr.gradleDistr, '${workspace_loc}/gradle_user_home', '${workspace_loc}/java_home', atr.jvmArguments, atr.arguments, atr.visualize) + + then: + configuration.getWorkingDir().getPath().endsWith("working_dir") + !(configuration.getWorkingDir().getPath().contains('$')) + configuration.getGradleUserHome().getPath().endsWith("gradle_user_home") + !(configuration.getGradleUserHome().getPath().contains('$')) + configuration.getJavaHome().getPath().endsWith("java_home") + !(configuration.getJavaHome().getPath().contains('$')) + } + + def "Unresolvable expressions in Java home results in runtime exception"() { + setup: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, '${nonexistingvariable}/java_home', atr.jvmArguments, atr.arguments, atr.visualize) + + when: + configuration.getJavaHome() + + then: + thrown(GradlePluginsRuntimeException) + + } + + def "Unresolvable expressions in Gradle user home results in runtime exception"() { + setup: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, '${nonexistingvariable}/gradle_user_home', atr.javaHome, atr.jvmArguments, atr.arguments, atr.visualize) + + when: + configuration.getGradleUserHome() + + then: + thrown(GradlePluginsRuntimeException) + + } + + def "Unresolvable expressions in working directory results in runtime exception"() { + setup: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, '${nonexistingvariable}/working_dir', atr.gradleDistr, atr.gradleHome, atr.javaHome, atr.jvmArguments, atr.arguments, atr.visualize) + + when: + configuration.getWorkingDir() + + then: + thrown(GradlePluginsRuntimeException) + } + + def "Unresolvable expressions in arguments results in runtime exception"() { + setup: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, atr.javaHome, atr.jvmArguments, ['${nonexistingvariable}/arguments'], atr.visualize) + + when: + configuration.getArguments() + + then: + thrown(GradlePluginsRuntimeException) + } + + def "Unresolvable expressions in jvm arguments results in runtime exception"() { + setup: + def configuration = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, atr.javaHome, ['${nonexistingvariable}/jvmarguments'], atr.arguments, atr.visualize) + + when: + configuration.getJvmArguments() + + then: + thrown(GradlePluginsRuntimeException) + } + + + + def "All configuration can be saved to Eclipse settings"() { + setup: + def launchManager = DebugPlugin.getDefault().getLaunchManager(); + def type = launchManager.getLaunchConfigurationType(GradleRunConfigurationDelegate.ID); + def eclipseConfig = type.newInstance(null, "launch-config-name") + + when: + assert eclipseConfig.getAttributes().isEmpty() + def gradleConfig = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, atr.javaHome, atr.jvmArguments, atr.arguments, atr.visualize) + gradleConfig.apply(eclipseConfig) + + then: + eclipseConfig.getAttributes().size() == atr.size() + } + + def "All valid configuration settings can be stored and retrieved"() { + setup: + def launchManager = DebugPlugin.getDefault().getLaunchManager(); + def type = launchManager.getLaunchConfigurationType(GradleRunConfigurationDelegate.ID); + def eclipseConfig = type.newInstance(null, "launch-config-name") + + when: + def gradleConfig1 = GradleRunConfigurationAttributes.with(tasks, workingDir, gradleDistr, gradleHome, javaHome, jvmArguments, arguments, visualize) + gradleConfig1.apply(eclipseConfig) + def gradleConfig2 = GradleRunConfigurationAttributes.from(eclipseConfig) + + then: + gradleConfig1.getTasks() == gradleConfig2.getTasks() + gradleConfig1.getWorkingDirExpression() == gradleConfig2.getWorkingDirExpression() + gradleConfig1.getGradleDistribution() == gradleConfig2.getGradleDistribution() + gradleConfig1.getGradleUserHomeExpression() == gradleConfig2.getGradleUserHomeExpression() + gradleConfig1.getJavaHomeExpression() == gradleConfig2.getJavaHomeExpression() + gradleConfig1.getJvmArguments() == gradleConfig2.getJvmArguments() + gradleConfig1.getArguments() == gradleConfig2.getArguments() + gradleConfig1.isVisualizeTestProgress() == gradleConfig2.isVisualizeTestProgress() + + where: + tasks | workingDir | gradleDistr | gradleHome | javaHome | jvmArguments | arguments | visualize + atr.tasks | atr.workingDir | atr.gradleDistr | atr.gradleHome | atr.javaHome | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | null | atr.javaHome | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | atr.gradleHome | null | atr.jvmArguments | atr.arguments | atr.visualize + atr.tasks | atr.workingDir | atr.gradleDistr | null | null | atr.jvmArguments | atr.arguments | atr.visualize + } + + + def "Saved Configuration attributes has same unique attributes"() { + setup: + def launchManager = DebugPlugin.getDefault().getLaunchManager(); + def type = launchManager.getLaunchConfigurationType(GradleRunConfigurationDelegate.ID); + def eclipseConfig = type.newInstance(null, "launch-config-name") + + when: + def gradleConfig = GradleRunConfigurationAttributes.with(atr.tasks, atr.workingDir, atr.gradleDistr, atr.gradleHome, atr.javaHome, atr.jvmArguments, atr.arguments, atr.visualize) + gradleConfig.apply(eclipseConfig) + + then: + gradleConfig.hasSameUniqueAttributes(eclipseConfig) + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounterTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounterTest.groovy new file mode 100644 index 000000000..20b5823cf --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounterTest.groovy @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal + +import spock.lang.Specification; + + +class TestCounterTest extends Specification { + + def "Counters starts from zero"() { + setup: + def TestCounter counter = new TestCounter() + + expect: + counter.getStartedCount() == 0 + counter.getFinishedCount() == 0 + counter.getFailureCount() == 0 + counter.getErrorCount() == 0 + counter.getIgnoredCount() == 0 + } + + def "Increment started"() { + setup: + def TestCounter counter = new TestCounter() + + when: + counter.incrementStarted() + + then: + counter.getStartedCount() == 1 + counter.getFinishedCount() == 0 + counter.getFailureCount() == 0 + counter.getErrorCount() == 0 + counter.getIgnoredCount() == 0 + } + + def "Increment success"() { + setup: + def TestCounter counter = new TestCounter() + + when: + counter.incrementSuccess() + + then: + counter.getStartedCount() == 0 + counter.getFinishedCount() == 1 + counter.getFailureCount() == 0 + counter.getErrorCount() == 0 + counter.getIgnoredCount() == 0 + } + + def "Increment failure"() { + setup: + def TestCounter counter = new TestCounter() + + when: + counter.incrementFailure() + + then: + counter.getStartedCount() == 0 + counter.getFinishedCount() == 1 + counter.getFailureCount() == 1 + counter.getErrorCount() == 0 + counter.getIgnoredCount() == 0 + } + + def "Increment ignored"() { + setup: + def TestCounter counter = new TestCounter() + + when: + counter.incrementIgnored() + + then: + counter.getStartedCount() == 0 + counter.getFinishedCount() == 1 + counter.getFailureCount() == 0 + counter.getErrorCount() == 0 + counter.getIgnoredCount() == 1 + } +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTrackerTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTrackerTest.groovy new file mode 100644 index 000000000..06cdd9607 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTrackerTest.groovy @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal + +import com.gradleware.tooling.eclipse.core.testprogress.internal.TestRunSessionStateTracker.TestRunSessionState +import spock.lang.Specification + +class TestRunSessionStateTrackerTest extends Specification { + + def "Initial state is not running"() { + setup: + def stateContainer = new TestRunSessionStateTracker(); + + expect: + stateContainer.currentState == TestRunSessionState.UNKNOWN + !stateContainer.isRunning() + } + + def "State can be changed to running"() { + setup: + def stateContainer = new TestRunSessionStateTracker(); + + when: + stateContainer.sessionStarted() + + then: + stateContainer.currentState == TestRunSessionState.RUNNING + stateContainer.isRunning() + } + + def "State can be changed to finished"() { + setup: + def stateContainer = new TestRunSessionStateTracker(); + + when: + stateContainer.sessionFinished() + + then: + stateContainer.currentState == TestRunSessionState.FINISHED + !stateContainer.isRunning() + } + +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainerTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainerTest.groovy new file mode 100644 index 000000000..84ec4f853 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainerTest.groovy @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal +import org.eclipse.jdt.internal.junit.model.ITestSessionListener +import org.eclipse.jdt.internal.junit.model.TestCaseElement +import org.eclipse.jdt.internal.junit.model.TestElement.Status +import org.eclipse.jdt.internal.junit.model.TestSuiteElement +import spock.lang.Specification + +@SuppressWarnings("restriction") +class TestSessionListenerContainerTest extends Specification { + + TestSessionListenerContainer container = new TestSessionListenerContainer() + TestSuiteElement testSuite = new TestSuiteElement(null, "id_1", "mocked_test_suite_1", 1) + TestCaseElement testCase = new TestCaseElement(testSuite, "id_2", "mocked_test_case_1") + + def "Listeners receive events"() { + setup: + ITestSessionListener listener = Mock(ITestSessionListener) + container.add(listener) + + when: + container.notifySessionStarted() + then: + 1 * listener.sessionStarted() + + when: + container.notifySessionEnded(1000) + then: + 1 * listener.sessionEnded(1000) + + + when: + container.notifyTestingBegins() + then: + 1 * listener.runningBegins() + + when: + container.notifySuiteStarted(testSuite) + then: + 1 * listener.testAdded(testSuite) + + when: + container.notifySuiteFinished(testSuite, Status.OK, null, null, null) + then: + 1 * listener.testFailed(testSuite,Status.OK,_,_,_) + + when: + container.notifyTestStarted(testCase) + then: + 1 * listener.testStarted(testCase) + + when: + container.notifyTestEnded(testCase) + then: + 1 * listener.testEnded(testCase) + } + + def "Can add and remove listeners"() { + TestSessionListenerContainer container = new TestSessionListenerContainer() + ITestSessionListener listener = Mock(ITestSessionListener) + + when: + container.notifySessionStarted() + then: + 0 * listener.sessionStarted() + + when: + container.add(listener) + container.notifySessionStarted() + then: + 1 * listener.sessionStarted() + + when: + container.remove(listener) + container.notifySessionStarted() + then: + 0 * listener.sessionStarted() + } +} diff --git a/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/workspace/internal/WorkspaceOperationsTest.groovy b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/workspace/internal/WorkspaceOperationsTest.groovy new file mode 100644 index 000000000..39905f260 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core.test/src/main/groovy/com/gradleware/tooling/eclipse/core/workspace/internal/WorkspaceOperationsTest.groovy @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace.internal + +import com.google.common.collect.ImmutableList +import com.gradleware.tooling.eclipse.core.CorePlugin +import com.gradleware.tooling.eclipse.core.GradleNature +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration +import com.gradleware.tooling.eclipse.core.workspace.ClasspathDefinition +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations +import com.gradleware.tooling.toolingclient.GradleDistribution +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes +import org.eclipse.core.resources.IMarker +import org.eclipse.core.resources.IProject +import org.eclipse.core.resources.IResource +import org.eclipse.core.runtime.NullProgressMonitor +import org.eclipse.core.runtime.Path +import org.eclipse.jdt.core.IClasspathEntry +import org.eclipse.jdt.core.IJavaProject +import org.eclipse.jdt.launching.JavaRuntime +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Shared +import spock.lang.Specification + +class WorkspaceOperationsTest extends Specification { + + @Shared + WorkspaceOperations workspaceOperations = CorePlugin.workspaceOperations(); + + @Rule + TemporaryFolder tempFolder + + def cleanup() { + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + } + + def "can create a new project"() { + setup: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + def projectFolder = tempFolder.newFolder("sample-project-folder") + + when: + IProject project = workspaceOperations.createProject("sample-project", projectFolder, ImmutableList.of(), ImmutableList.of(GradleNature.ID), null) + + then: + project.exists() + project.isAccessible() + project.hasNature(GradleNature.ID) + project.getDescription().natureIds.length == 1 + } + + def "Project can be created without any natures"() { + setup: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + def projectFolder = tempFolder.newFolder("sample-project-folder") + + when: + IProject project = workspaceOperations.createProject("sample-project", projectFolder, ImmutableList.of(), ImmutableList.of(), null) + + then: + project.getDescription().natureIds.length == 0 + } + + def "Project can be created with multiple natures"() { + setup: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + def projectFolder = tempFolder.newFolder("sample-project-folder") + + when: + IProject project = workspaceOperations.createProject("sample-project", projectFolder, ImmutableList.of(), ImmutableList.of("dummy-nature-1", "dummy-nature-2"), new NullProgressMonitor()) + + then: + project.getDescription().natureIds.length == 2 + project.getDescription().natureIds[0] == "dummy-nature-1" + project.getDescription().natureIds[1] == "dummy-nature-2" + + } + + def "Importing nonexisting folder fails"() { + when: + workspaceOperations.createProject("projectname", new File("path-to-nonexisting-folder"), ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + then: + thrown(IllegalArgumentException.class) + } + + def "Importing a file is considered invalid"() { + when: + workspaceOperations.createProject("projectname", tempFolder.newFile("filename"), ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + then: + thrown(IllegalArgumentException) + } + + def "Importing project with name existing already in workspace fails"() { + setup: + def projectFolder = tempFolder.newFolder("projectname") + + when: + workspaceOperations.createProject("projectname", projectFolder, ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + workspaceOperations.createProject("projectname", projectFolder, ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + then: + thrown(GradlePluginsRuntimeException.class) + } + + def "Project name can't be empty when created"() { + setup: + def projectFolder = tempFolder.newFolder("projectname") + + when: + workspaceOperations.createProject("", projectFolder, ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + then: + thrown(IllegalArgumentException) + } + + def "Project name can't be null when created"() { + setup: + def projectFolder = tempFolder.newFolder("projectname") + + when: + workspaceOperations.createProject(null, projectFolder, ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + + then: + thrown(NullPointerException) + } + + /////////////////////////////////// + // tests for deleteAllProjects() // + /////////////////////////////////// + + def "Delete succeeds even the workspace is empty"() { + when: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + + then: + workspaceOperations.allProjects.empty + } + + def "A project can be deleted"() { + setup: + workspaceOperations.createProject("sample-project", tempFolder.newFolder(), ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + assert workspaceOperations.allProjects.size() == 1 + + when: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + + then: + assert workspaceOperations.allProjects.size() == 0 + } + + def "Closed projects can be deleted"() { + setup: + IProject project = workspaceOperations.createProject("sample-project", tempFolder.newFolder(), ImmutableList.of(), ImmutableList.of(), new NullProgressMonitor()) + project.close(null) + + when: + workspaceOperations.deleteAllProjects(new NullProgressMonitor()) + + then: + assert workspaceOperations.allProjects.size() == 0 + } + + ///////////////////////// + // tests for refresh() // + ///////////////////////// + + def "Refresh project calls for resource refresh"() { + setup: + IProject project = Mock(IProject) + project.isAccessible() >> true + + when: + workspaceOperations.refresh(project, new NullProgressMonitor()) + + then: + 1 * project.refreshLocal(_, _) + } + + def "Non-accessible cannot be refreshed"() { + setup: + IProject project = Mock(IProject) + project.isAccessible() >> false + + when: + workspaceOperations.refresh(project, new NullProgressMonitor()) + + then: + thrown(IllegalArgumentException) + } + + def "Null project can't be refreshed"() { + when: + workspaceOperations.refresh(null, new NullProgressMonitor()) + + then: + thrown(NullPointerException) + } + + /////////////////////////////////// + // tests for createJavaProject() // + /////////////////////////////////// + + def "A Java project can be created"() { + setup: + def rootFolder = tempFolder.newFolder() + IProject project = workspaceOperations.createProject("sample-project", rootFolder, ImmutableList.of(), ImmutableList.of("dummy-project"), new NullProgressMonitor()) + def attributes = new FixedRequestAttributes(rootFolder, null, GradleDistribution.fromBuild(), null, ImmutableList. of(), ImmutableList. of()) + ProjectConfiguration projectConfiguration = ProjectConfiguration.from(attributes, com.gradleware.tooling.toolingmodel.Path.from(':'), rootFolder); + CorePlugin.projectConfigurationManager().saveProjectConfiguration(projectConfiguration, project); + + when: + ClasspathDefinition classpath = new ClasspathDefinition(ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), JavaRuntime.getDefaultJREContainerEntry().getPath()) + IJavaProject javaProject = workspaceOperations.createJavaProject(project, classpath, new NullProgressMonitor()) + + then: + javaProject != null + javaProject.getProject() == project + } + + def "A Java project can't be created from null project"() { + setup: + ClasspathDefinition classpath = new ClasspathDefinition(ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), JavaRuntime.getDefaultJREContainerEntry().getPath()) + + when: + workspaceOperations.createJavaProject(null, classpath, new NullProgressMonitor()) + + then: + thrown(NullPointerException) + } + + def "A Java project can't be created from not accessible project"() { + setup: + IProject project = Mock(IProject) + project.isAccessible() >> false + ClasspathDefinition classpath = new ClasspathDefinition(ImmutableList.of(), ImmutableList.of(), ImmutableList.of(), JavaRuntime.getDefaultJREContainerEntry().getPath()) + + when: + workspaceOperations.createJavaProject(project, classpath, new NullProgressMonitor()) + + then: + thrown(IllegalArgumentException) + } + + def "A Java classpath can't be null"() { + setup: + IProject project = workspaceOperations.createProject("sample-project", tempFolder.newFolder(), ImmutableList.of(), ImmutableList.of("dummy-project"), new NullProgressMonitor()) + + when: + workspaceOperations.createJavaProject(project, null, new NullProgressMonitor()) + + then: + thrown(NullPointerException) + } +} diff --git a/com.gradleware.tooling.eclipse.core/.classpath b/com.gradleware.tooling.eclipse.core/.classpath new file mode 100644 index 000000000..a1f09d153 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.core/.project b/com.gradleware.tooling.eclipse.core/.project new file mode 100644 index 000000000..e77dc691b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/.project @@ -0,0 +1,39 @@ + + + com.gradleware.tooling.eclipse.core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + + + + 1365114646088 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-target + + + + diff --git a/com.gradleware.tooling.eclipse.core/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.core/META-INF/MANIFEST.MF new file mode 100644 index 000000000..7d84b2a8e --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/META-INF/MANIFEST.MF @@ -0,0 +1,151 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Buildship, Eclipse Plug-ins for Gradle - Core +Bundle-SymbolicName: com.gradleware.tooling.eclipse.core;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Vendor: Gradle Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Activator: com.gradleware.tooling.eclipse.core.CorePlugin +Require-Bundle: org.eclipse.core.runtime;visibility:=reexport, + org.eclipse.core.resources;visibility:=reexport, + org.eclipse.core.variables;visibility:=reexport, + org.eclipse.core.filesystem;visibility:=reexport, + org.eclipse.jdt.core;visibility:=reexport, + org.eclipse.jdt.junit.core;visibility:=reexport, + org.eclipse.jdt.launching, + org.eclipse.debug.core +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: ., + lib/gradle-tooling-api-2.4-20150310170129+0000.jar, + lib/gson-2.2.4.jar, + lib/guava-18.0.jar, + lib/slf4j-api-1.7.10.jar, + lib/slf4j-simple-1.7.10.jar, + lib/toolingclient-0.3.jar, + lib/toolingmodel-0.3.jar, + lib/toolingutils-0.3.jar +Export-Package: com.google.common.annotations, + com.google.common.base, + com.google.common.base.internal, + com.google.common.cache, + com.google.common.collect, + com.google.common.escape, + com.google.common.eventbus, + com.google.common.hash, + com.google.common.html, + com.google.common.io, + com.google.common.math, + com.google.common.net, + com.google.common.primitives, + com.google.common.reflect, + com.google.common.util.concurrent, + com.google.common.xml, + com.google.gson, + com.google.gson.annotations, + com.google.gson.internal, + com.google.gson.internal.bind, + com.google.gson.reflect, + com.google.gson.stream, + com.google.thirdparty.publicsuffix, + com.gradleware.tooling.eclipse.core, + com.gradleware.tooling.eclipse.core.i18n, + com.gradleware.tooling.eclipse.core.gradle, + com.gradleware.tooling.eclipse.core.configuration, + com.gradleware.tooling.eclipse.core.console, + com.gradleware.tooling.eclipse.core.launch, + com.gradleware.tooling.eclipse.core.model, + com.gradleware.tooling.eclipse.core.projectimport, + com.gradleware.tooling.eclipse.core.testprogress, + com.gradleware.tooling.eclipse.core.workspace, + com.gradleware.tooling.eclipse.core.workbench, + com.gradleware.tooling.eclipse.core.util.collections, + com.gradleware.tooling.eclipse.core.util.file, + com.gradleware.tooling.eclipse.core.util.logging, + com.gradleware.tooling.eclipse.core.util.progress, + com.gradleware.tooling.eclipse.core.util.variable, + com.gradleware.tooling.toolingclient, + com.gradleware.tooling.toolingmodel, + com.gradleware.tooling.toolingmodel.buildaction, + com.gradleware.tooling.toolingmodel.repository, + com.gradleware.tooling.toolingmodel.util, + com.gradleware.tooling.toolingutils, + com.gradleware.tooling.toolingutils.binding, + com.gradleware.tooling.toolingutils.distribution, + org.gradle, + org.gradle.api, + org.gradle.api.artifacts, + org.gradle.api.artifacts.component, + org.gradle.api.artifacts.dsl, + org.gradle.api.artifacts.query, + org.gradle.api.artifacts.repositories, + org.gradle.api.artifacts.result, + org.gradle.api.component, + org.gradle.api.credentials, + org.gradle.api.execution, + org.gradle.api.file, + org.gradle.api.initialization, + org.gradle.api.initialization.dsl, + org.gradle.api.internal, + org.gradle.api.internal.classpath, + org.gradle.api.internal.file, + org.gradle.api.internal.file.pattern, + org.gradle.api.invocation, + org.gradle.api.logging, + org.gradle.api.plugins, + org.gradle.api.resources, + org.gradle.api.specs, + org.gradle.api.specs.internal, + org.gradle.api.tasks, + org.gradle.api.tasks.util, + org.gradle.api.tasks.util.internal, + org.gradle.groovy.scripts, + org.gradle.initialization, + org.gradle.initialization.layout, + org.gradle.internal, + org.gradle.internal.classloader, + org.gradle.internal.classpath, + org.gradle.internal.concurrent, + org.gradle.internal.exceptions, + org.gradle.internal.hash, + org.gradle.internal.jvm, + org.gradle.internal.progress, + org.gradle.internal.reflect, + org.gradle.internal.resource, + org.gradle.internal.service, + org.gradle.internal.typeconversion, + org.gradle.logging, + org.gradle.logging.internal, + org.gradle.messaging.dispatch, + org.gradle.process, + org.gradle.process.internal, + org.gradle.tooling, + org.gradle.tooling.exceptions, + org.gradle.tooling.internal.adapter, + org.gradle.tooling.internal.build, + org.gradle.tooling.internal.consumer, + org.gradle.tooling.internal.consumer.async, + org.gradle.tooling.internal.consumer.connection, + org.gradle.tooling.internal.consumer.converters, + org.gradle.tooling.internal.consumer.loader, + org.gradle.tooling.internal.consumer.parameters, + org.gradle.tooling.internal.consumer.versioning, + org.gradle.tooling.internal.gradle, + org.gradle.tooling.internal.protocol, + org.gradle.tooling.internal.protocol.eclipse, + org.gradle.tooling.internal.protocol.exceptions, + org.gradle.tooling.model, + org.gradle.tooling.model.build, + org.gradle.tooling.model.eclipse, + org.gradle.tooling.model.gradle, + org.gradle.tooling.model.idea, + org.gradle.tooling.model.internal, + org.gradle.tooling.model.internal.outcomes, + org.gradle.tooling.provider.model, + org.gradle.tooling.provider.model.internal, + org.gradle.util, + org.gradle.util.internal, + org.gradle.wrapper, + org.slf4j, + org.slf4j.helpers, + org.slf4j.impl, + org.slf4j.spi diff --git a/com.gradleware.tooling.eclipse.core/build.gradle b/com.gradleware.tooling.eclipse.core/build.gradle new file mode 100644 index 000000000..c1d7f4964 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/build.gradle @@ -0,0 +1,28 @@ +apply plugin: eclipsebuild.BundlePlugin + +dependencies { + compile "eclipse:org.eclipse.core.runtime:+" + compile "eclipse:org.eclipse.core.resources:+" + compile "eclipse:org.eclipse.core.variables:+" + compile "eclipse:org.eclipse.core.filesystem:+" + compile "eclipse:org.eclipse.jdt.core:+" + compile "eclipse:org.eclipse.jdt.junit.core:+" + compile "eclipse:org.eclipse.jdt.launching:+" + compile "eclipse:org.eclipse.debug.core:+" + + bundled "com.gradleware.tooling:toolingmodel:$commonsLibVersion" + bundled "org.slf4j:slf4j-simple:$slf4jLibVersion" +} + +// append the sources of each first-level dependency and its transitive dependencies of +// the 'bundled' configuration to the 'bundledSource' configuration +configurations.bundled.resolvedConfiguration.firstLevelModuleDependencies.each { dep -> + addSourcesRecursively(dep) +} + +def addSourcesRecursively(dep) { + dependencies { + bundledSource group: dep.moduleGroup, name: dep.moduleName, version: dep.moduleVersion, classifier: 'sources' + } + dep.children.each { addSourcesRecursively(it) } +} diff --git a/com.gradleware.tooling.eclipse.core/build.properties b/com.gradleware.tooling.eclipse.core/build.properties new file mode 100644 index 000000000..2d44fc80b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/build.properties @@ -0,0 +1,8 @@ +source.. = src/main/java/,\ + src/main/resources/ +output.. = bin/ +bin.includes = META-INF/,\ + lib/,\ + .,\ + plugin.xml + diff --git a/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000-sources.jar b/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000-sources.jar new file mode 100644 index 000000000..3d32b719a Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000.jar b/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000.jar new file mode 100644 index 000000000..6a6498755 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/gradle-tooling-api-2.4-20150310170129+0000.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4-sources.jar b/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4-sources.jar new file mode 100644 index 000000000..74d3cc5e5 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4.jar b/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4.jar new file mode 100644 index 000000000..75fe27c54 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/gson-2.2.4.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/guava-18.0-sources.jar b/com.gradleware.tooling.eclipse.core/lib/guava-18.0-sources.jar new file mode 100644 index 000000000..d97cc501b Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/guava-18.0-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/guava-18.0.jar b/com.gradleware.tooling.eclipse.core/lib/guava-18.0.jar new file mode 100644 index 000000000..8f89e4901 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/guava-18.0.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10-sources.jar new file mode 100644 index 000000000..9e214e964 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10.jar b/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10.jar new file mode 100644 index 000000000..744e9ec5b Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/slf4j-api-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10-sources.jar new file mode 100644 index 000000000..2e9e57d86 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10.jar b/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10.jar new file mode 100644 index 000000000..b40be298f Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/slf4j-simple-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3-sources.jar b/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3-sources.jar new file mode 100644 index 000000000..1db85f9de Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3.jar b/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3.jar new file mode 100644 index 000000000..94f581fd6 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingclient-0.3.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3-sources.jar b/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3-sources.jar new file mode 100644 index 000000000..7a3d1aae8 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3.jar b/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3.jar new file mode 100644 index 000000000..b1f727a99 Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingmodel-0.3.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3-sources.jar b/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3-sources.jar new file mode 100644 index 000000000..32cb6c02c Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3.jar b/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3.jar new file mode 100644 index 000000000..1b28a39ce Binary files /dev/null and b/com.gradleware.tooling.eclipse.core/lib/toolingutils-0.3.jar differ diff --git a/com.gradleware.tooling.eclipse.core/plugin.xml b/com.gradleware.tooling.eclipse.core/plugin.xml new file mode 100644 index 000000000..c9dee59d4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/plugin.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/CorePlugin.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/CorePlugin.java new file mode 100644 index 000000000..a19584a8d --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/CorePlugin.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.eclipse.core.runtime.Plugin; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceRegistration; +import org.osgi.util.tracker.ServiceTracker; + +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfigurationManager; +import com.gradleware.tooling.eclipse.core.configuration.internal.DefaultProjectConfigurationManager; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; +import com.gradleware.tooling.eclipse.core.console.internal.StdProcessStreamsProvider; +import com.gradleware.tooling.eclipse.core.launch.GradleLaunchConfigurationManager; +import com.gradleware.tooling.eclipse.core.launch.internal.DefaultGradleLaunchConfigurationManager; +import com.gradleware.tooling.eclipse.core.util.logging.EclipseLogger; +import com.gradleware.tooling.eclipse.core.workbench.WorkbenchOperations; +import com.gradleware.tooling.eclipse.core.workbench.internal.EmptyWorkbenchOperations; +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations; +import com.gradleware.tooling.eclipse.core.workspace.internal.DefaultWorkspaceOperations; +import com.gradleware.tooling.toolingclient.ToolingClient; +import com.gradleware.tooling.toolingmodel.repository.Environment; +import com.gradleware.tooling.toolingmodel.repository.ModelRepositoryProvider; +import com.gradleware.tooling.toolingmodel.repository.internal.DefaultModelRepositoryProvider; +import com.gradleware.tooling.toolingutils.distribution.PublishedGradleVersions; + +/** + * The plug-in runtime class for the Gradle integration plugin containing the non-UI elements. + *

+ * This class is automatically instantiated by the Eclipse runtime and wired through the + * Bundle-Activator entry in the META-INF/MANIFEST.MF file. The registered + * instance can be obtained during runtime through the {@link CorePlugin#getInstance()} method. + *

+ * Moreover, this is the entry point for accessing associated services: + *

+ *

+ * The {@link #start(BundleContext)} and {@link #stop(BundleContext)} methods' responsibility is to + * assign and free the managed services along the plugin runtime lifecycle. + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public final class CorePlugin extends Plugin { + + public static final String PLUGIN_ID = "com.gradleware.tooling.eclipse.core"; + + private static CorePlugin plugin; + + // do not use generics-aware signature since this causes compilation troubles (JDK, Spock) + // search the web for -target jsr14 to find out more about this obscurity + private ServiceRegistration loggerService; + private ServiceRegistration publishedGradleVersionsService; + private ServiceRegistration modelRepositoryProviderService; + private ServiceRegistration workspaceOperationsService; + private ServiceRegistration projectConfigurationManagerService; + private ServiceRegistration processStreamsProviderService; + private ServiceRegistration gradleLaunchConfigurationService; + private ServiceRegistration workbenchOperationsService; + + // service tracker for each service to allow to register other service implementations of the + // same type but with higher prioritization, useful for testing + private ServiceTracker loggerServiceTracker; + private ServiceTracker publishedGradleVersionsServiceTracker; + private ServiceTracker modelRepositoryProviderServiceTracker; + private ServiceTracker workspaceOperationsServiceTracker; + private ServiceTracker projectConfigurationManagerServiceTracker; + private ServiceTracker processStreamsProviderServiceTracker; + private ServiceTracker gradleLaunchConfigurationServiceTracker; + private ServiceTracker workbenchOperationsServiceTracker; + + @Override + public void start(BundleContext bundleContext) throws Exception { + super.start(bundleContext); + registerServices(bundleContext); + plugin = this; + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + unregisterServices(); + super.stop(context); + } + + private void registerServices(BundleContext context) { + // store services with low ranking such that they can be overridden + // during testing or the like + Dictionary preferences = new Hashtable(); + preferences.put(Constants.SERVICE_RANKING, 1); + + // initialize service trackers before the services are created + this.loggerServiceTracker = createServiceTracker(context, Logger.class); + this.publishedGradleVersionsServiceTracker = createServiceTracker(context, PublishedGradleVersions.class); + this.modelRepositoryProviderServiceTracker = createServiceTracker(context, ModelRepositoryProvider.class); + this.workspaceOperationsServiceTracker = createServiceTracker(context, WorkspaceOperations.class); + this.projectConfigurationManagerServiceTracker = createServiceTracker(context, ProjectConfigurationManager.class); + this.processStreamsProviderServiceTracker = createServiceTracker(context, ProcessStreamsProvider.class); + this.gradleLaunchConfigurationServiceTracker = createServiceTracker(context, GradleLaunchConfigurationManager.class); + this.workbenchOperationsServiceTracker = createServiceTracker(context, WorkbenchOperations.class); + + // register all services + this.loggerService = registerService(context, Logger.class, createLogger(), preferences); + this.publishedGradleVersionsService = registerService(context, PublishedGradleVersions.class, createPublishedGradleVersions(), preferences); + this.modelRepositoryProviderService = registerService(context, ModelRepositoryProvider.class, createModelRepositoryProvider(), preferences); + this.workspaceOperationsService = registerService(context, WorkspaceOperations.class, createWorkspaceOperations(), preferences); + this.projectConfigurationManagerService = registerService(context, ProjectConfigurationManager.class, createProjectConfigurationManager(), preferences); + this.processStreamsProviderService = registerService(context, ProcessStreamsProvider.class, createProcessStreamsProvider(), preferences); + this.gradleLaunchConfigurationService = registerService(context, GradleLaunchConfigurationManager.class, createGradleLaunchConfigurationManager(), preferences); + this.workbenchOperationsService = registerService(context, WorkbenchOperations.class, createWorkbenchOperations(), preferences); + } + + private ServiceTracker createServiceTracker(BundleContext context, Class clazz) { + ServiceTracker serviceTracker = new ServiceTracker(context, clazz.getName(), null); + serviceTracker.open(); + return serviceTracker; + } + + private ServiceRegistration registerService(BundleContext context, Class clazz, T service, Dictionary properties) { + return context.registerService(clazz.getName(), service, properties); + } + + private EclipseLogger createLogger() { + return new EclipseLogger(getLog(), PLUGIN_ID); + } + + private PublishedGradleVersions createPublishedGradleVersions() { + return PublishedGradleVersions.create(true); + } + + private ModelRepositoryProvider createModelRepositoryProvider() { + ToolingClient toolingClient = ToolingClient.newClient(); + return new DefaultModelRepositoryProvider(toolingClient, Environment.ECLIPSE); + } + + private WorkspaceOperations createWorkspaceOperations() { + return new DefaultWorkspaceOperations(); + } + + private ProjectConfigurationManager createProjectConfigurationManager() { + WorkspaceOperations workspaceOperations = (WorkspaceOperations) this.workspaceOperationsServiceTracker.getService(); + return new DefaultProjectConfigurationManager(workspaceOperations); + } + + private ProcessStreamsProvider createProcessStreamsProvider() { + return new StdProcessStreamsProvider(); + } + + private GradleLaunchConfigurationManager createGradleLaunchConfigurationManager() { + return new DefaultGradleLaunchConfigurationManager(); + } + + private WorkbenchOperations createWorkbenchOperations() { + return new EmptyWorkbenchOperations(); + } + + private void unregisterServices() { + this.workbenchOperationsService.unregister(); + this.gradleLaunchConfigurationService.unregister(); + this.processStreamsProviderService.unregister(); + this.projectConfigurationManagerService.unregister(); + this.workspaceOperationsService.unregister(); + this.modelRepositoryProviderService.unregister(); + this.publishedGradleVersionsService.unregister(); + this.loggerService.unregister(); + + this.workbenchOperationsServiceTracker.close(); + this.gradleLaunchConfigurationServiceTracker.close(); + this.processStreamsProviderServiceTracker.close(); + this.projectConfigurationManagerServiceTracker.close(); + this.workspaceOperationsServiceTracker.close(); + this.modelRepositoryProviderServiceTracker.close(); + this.publishedGradleVersionsServiceTracker.close(); + this.loggerServiceTracker.close(); + } + + public static CorePlugin getInstance() { + return plugin; + } + + public static Logger logger() { + return (Logger) getInstance().loggerServiceTracker.getService(); + } + + public static PublishedGradleVersions publishedGradleVersions() { + return (PublishedGradleVersions) getInstance().publishedGradleVersionsServiceTracker.getService(); + } + + public static ModelRepositoryProvider modelRepositoryProvider() { + return (ModelRepositoryProvider) getInstance().modelRepositoryProviderServiceTracker.getService(); + } + + public static WorkspaceOperations workspaceOperations() { + return (WorkspaceOperations) getInstance().workspaceOperationsServiceTracker.getService(); + } + + public static ProjectConfigurationManager projectConfigurationManager() { + return (ProjectConfigurationManager) getInstance().projectConfigurationManagerServiceTracker.getService(); + } + + public static ProcessStreamsProvider processStreamsProvider() { + return (ProcessStreamsProvider) getInstance().processStreamsProviderServiceTracker.getService(); + } + + public static GradleLaunchConfigurationManager gradleLaunchConfigurationManager() { + return (GradleLaunchConfigurationManager) getInstance().gradleLaunchConfigurationServiceTracker.getService(); + } + + public static WorkbenchOperations workbenchOperations() { + return (WorkbenchOperations) getInstance().workbenchOperationsServiceTracker.getService(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradleNature.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradleNature.java new file mode 100644 index 000000000..d99464d30 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradleNature.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.runtime.CoreException; + +/** + * Gradle nature definition registered via the org.eclipse.core.resources.natures + * extension point in the plugin.xml. + */ +public final class GradleNature implements IProjectNature { + + // the nature ID has to be in the following format: ${PLUGIN_ID}.${NATURE_ID} + // the nature id is defined in the external (xml) file specified in the plugin.xml + public static final String ID = CorePlugin.PLUGIN_ID + ".nature"; + + private IProject project; + + @Override + public void configure() { + } + + @Override + public void deconfigure() { + } + + @Override + public IProject getProject() { + return this.project; + } + + @Override + public void setProject(IProject project) { + this.project = project; + } + + /** + * Determines if the specified project has the Gradle nature applied. + * + * @param project the project to verify + * @return {@code true} iff the specified project has the Gradle nature applied + */ + public static boolean isPresentOn(IProject project) { + // abort if the project is closed since we cannot investigate closed projects + if (!project.isOpen()) { + String message = String.format("Cannot investigate Gradle nature on closed project %s.", project); + CorePlugin.logger().error(message); + throw new GradlePluginsRuntimeException(message); + } + + // check if the Gradle nature is applied + try { + return project.hasNature(ID); + } catch (CoreException e) { + String message = String.format("Cannot check for Gradle nature on project %s.", project); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradlePluginsRuntimeException.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradlePluginsRuntimeException.java new file mode 100644 index 000000000..1ce0fc0e7 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/GradlePluginsRuntimeException.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core; + +/** + * Base class for all custom unchecked exception types thrown by the Gradle integration plugins. + */ +public class GradlePluginsRuntimeException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Creates a new instance. + * + * @param message the detail message + */ + public GradlePluginsRuntimeException(String message) { + super(message); + } + + /** + * Creates a new instance. + * + * @param message the detail message + * @param cause the cause + */ + public GradlePluginsRuntimeException(String message, Exception cause) { + super(message, cause); + } + + /** + * Creates a new instance. + * + * @param cause the cause + */ + public GradlePluginsRuntimeException(Exception cause) { + super(cause); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/Logger.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/Logger.java new file mode 100644 index 000000000..19d1ad6d8 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/Logger.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core; + +/** + * Simplifying abstraction over Eclipse's logging ({@link org.eclipse.core.runtime.ILog}) interface. + *

+ * By using this interface, we can log like this: + * + *

+ * try {
+ *   ...
+ * } catch (Exception e) {
+ *   CorePlugin.logger().error(e);
+ * }o
+ * 
+ * + * Instead of doing this: + * + *
+ * try {
+ *   ...
+ * } catch (Exception e) {
+ *   CorePlugin.getInstance().getLog().log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Error occurred", e));
+ * }
+ * 
+ *

+ */ +public interface Logger { + + /** + * Logs an entry with {@link org.eclipse.core.runtime.IStatus#INFO} severity in Eclipse's log. + * + * @param message the information to log + */ + void info(String message); + + /** + * Logs an entry with {@link org.eclipse.core.runtime.IStatus#WARNING} severity in Eclipse's log. + * + * @param message the warning to log + */ + void warn(String message); + + /** + * Logs an entry with {@link org.eclipse.core.runtime.IStatus#ERROR} severity in Eclipse's log. + * + * @param message the warning to log + */ + void error(String message); + + /** + * Logs an entry with {@link org.eclipse.core.runtime.IStatus#ERROR} severity in Eclipse's log. + * + * @param message the error to log + * @param t the underlying cause + */ + void error(String message, Throwable t); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfiguration.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfiguration.java new file mode 100644 index 000000000..b1969ae21 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfiguration.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.configuration; + +import java.io.File; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.gradleware.tooling.toolingmodel.OmniEclipseProject; +import com.gradleware.tooling.toolingmodel.Path; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; + +/** + * Describes the Gradle-specific configuration of an Eclipse project. + */ +public final class ProjectConfiguration { + + private final FixedRequestAttributes requestAttributes; + private final Path projectPath; + private final File projectDir; + + private ProjectConfiguration(FixedRequestAttributes requestAttributes, Path projectPath, File projectDir) { + this.requestAttributes = Preconditions.checkNotNull(requestAttributes); + this.projectPath = Preconditions.checkNotNull(projectPath); + this.projectDir = Preconditions.checkNotNull(projectDir); + } + + /** + * Returns the request attributes that are used to connect to the Gradle project. + * + * @return the request attributes used to connect to the Gradle project, never null + */ + public FixedRequestAttributes getRequestAttributes() { + return this.requestAttributes; + } + + /** + * Returns the path of the Gradle project. + * + * @return the path of the Gradle project, never null + */ + public Path getProjectPath() { + return this.projectPath; + } + + /** + * Returns the location of the Gradle project. + * + * @return the location of the Gradle project, never null + */ + public File getProjectDir() { + return this.projectDir; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + ProjectConfiguration that = (ProjectConfiguration) other; + return Objects.equal(this.requestAttributes, that.requestAttributes) && Objects.equal(this.projectDir, that.projectDir) + && Objects.equal(this.projectPath, that.projectPath); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.requestAttributes, this.projectDir, this.projectPath); + } + + /** + * Creates a new instance. + * + * @param requestAttributes the connection aspects of the configuration + * @param project the project aspects of the configuration + * @return the new instance + */ + public static ProjectConfiguration from(FixedRequestAttributes requestAttributes, OmniEclipseProject project) { + return from(requestAttributes, project.getPath(), project.getProjectDirectory()); + } + + /** + * Creates a new instance. + * + * @param requestAttributes the connection aspects of the configuration + * @param projectPath the path of the Gradle project + * @param projectDir the location of the Gradle project + * @return the new instance + */ + public static ProjectConfiguration from(FixedRequestAttributes requestAttributes, Path projectPath, File projectDir) { + return new ProjectConfiguration(requestAttributes, projectPath, projectDir); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfigurationManager.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfigurationManager.java new file mode 100644 index 000000000..8ba258dc4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/ProjectConfigurationManager.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.configuration; + +import org.eclipse.core.resources.IProject; + +import com.google.common.collect.ImmutableSet; + +/** + * Manages the persisted configuration of Gradle projects in the Eclipse workspace. + */ +public interface ProjectConfigurationManager { + + /** + * Returns the unique set of {@link ProjectConfiguration} roots found in the workspace. + * + * @return the unique set of {@code ProjectConfiguration} roots + */ + ImmutableSet getRootProjectConfigurations(); + + /** + * Returns the complete set of {@link ProjectConfiguration} instances found in the workspace. + * + * @return the complete set of {@code ProjectConfiguration} instances + */ + ImmutableSet getAllProjectConfigurations(); + + /** + * Saves the given Gradle project configuration in the Eclipse project's .settings + * folder. + * + * @param projectConfiguration the Gradle configuration to persist + * @param workspaceProject the Eclipse project for which to persist the Gradle configuration + */ + void saveProjectConfiguration(ProjectConfiguration projectConfiguration, IProject workspaceProject); + + /** + * Reads the Gradle project configuration from the Eclipse project's .settings folder. + * + * @param workspaceProject the Eclipse project from which to read the Gradle configuration + * @return the persisted Gradle configuration + */ + ProjectConfiguration readProjectConfiguration(IProject workspaceProject); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/DefaultProjectConfigurationManager.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/DefaultProjectConfigurationManager.java new file mode 100644 index 000000000..bb2762495 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/DefaultProjectConfigurationManager.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.configuration.internal; + +import java.io.File; +import java.util.Map; + +import org.eclipse.core.resources.IProject; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradleNature; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfigurationManager; +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations; +import com.gradleware.tooling.toolingmodel.Path; + +/** + * Manages the persistence and querying of information related to {@code ProjectConfiguration}s. + */ +public final class DefaultProjectConfigurationManager implements ProjectConfigurationManager { + + private final WorkspaceOperations workspaceOperations; + private final ProjectConfigurationPersistence projectConfigurationPersistence; + + public DefaultProjectConfigurationManager(WorkspaceOperations workspaceOperations) { + this.workspaceOperations = workspaceOperations; + this.projectConfigurationPersistence = new ProjectConfigurationPersistence(); + } + + @Override + public ImmutableSet getRootProjectConfigurations() { + // collect all Gradle root project configurations in the workspace by asking each Eclipse + // project with a Gradle nature for the Gradle root project it belongs to + ImmutableSet.Builder rootConfigurations = ImmutableSet.builder(); + for (IProject workspaceProject : this.workspaceOperations.getAllProjects()) { + if (workspaceProject.isOpen() && GradleNature.isPresentOn(workspaceProject)) { + // calculate the root configuration to which the current configuration belongs + ProjectConfiguration projectConfiguration = this.projectConfigurationPersistence.readProjectConfiguration(workspaceProject); + File rootProjectDir = projectConfiguration.getRequestAttributes().getProjectDir(); + ProjectConfiguration rootProjectConfiguration = ProjectConfiguration.from(projectConfiguration.getRequestAttributes(), Path.from(":"), rootProjectDir); + rootConfigurations.add(rootProjectConfiguration); + } + } + + // make sure there are no projects that point to the same root project but with a different + // configuration (different java home, etc.) + // if such an inconsistent state is detected, it means that the Gradle configurations were + // changed/corrupted manually + Map rootProjectDirs = Maps.newHashMap(); + for (ProjectConfiguration rootProjectConfiguration : rootConfigurations.build()) { + String rootProjectDirPath = rootProjectConfiguration.getProjectDir().getPath(); + if (!rootProjectDirs.containsKey(rootProjectDirPath)) { + rootProjectDirs.put(rootProjectDirPath, rootProjectConfiguration); + } else { + String message = String.format("Inconsistent Gradle project configuration for project at %s.", rootProjectDirPath); + CorePlugin.logger().error(message); + throw new GradlePluginsRuntimeException(message); + } + } + + // return the validated, unique set of root project configurations + return rootConfigurations.build(); + } + + @Override + public ImmutableSet getAllProjectConfigurations() { + // collect all the Gradle project configurations in the workspace + ImmutableSet.Builder allConfigurations = ImmutableSet.builder(); + for (IProject workspaceProject : this.workspaceOperations.getAllProjects()) { + if (workspaceProject.isOpen() && GradleNature.isPresentOn(workspaceProject)) { + ProjectConfiguration projectConfiguration = this.projectConfigurationPersistence.readProjectConfiguration(workspaceProject); + allConfigurations.add(projectConfiguration); + } + } + + // return the complete set of project configurations + return allConfigurations.build(); + } + + @Override + public void saveProjectConfiguration(ProjectConfiguration projectConfiguration, IProject workspaceProject) { + this.projectConfigurationPersistence.saveProjectConfiguration(projectConfiguration, workspaceProject); + } + + @Override + public ProjectConfiguration readProjectConfiguration(IProject workspaceProject) { + return this.projectConfigurationPersistence.readProjectConfiguration(workspaceProject); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationPersistence.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationPersistence.java new file mode 100644 index 000000000..869d14678 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/configuration/internal/ProjectConfigurationPersistence.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.configuration.internal; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; + +import org.eclipse.core.resources.IProject; + +import com.google.common.base.Charsets; +import com.google.common.collect.Maps; +import com.google.common.io.CharSource; +import com.google.common.io.Files; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.util.collections.CollectionsUtils; +import com.gradleware.tooling.eclipse.core.util.file.FileUtils; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionSerializer; +import com.gradleware.tooling.toolingmodel.Path; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; + +/** + * Manages reading and writing of the Gradle-specific configuration of an Eclipse project. + */ +final class ProjectConfigurationPersistence { + + private static final String PROJECT_PATH = "project_path"; + private static final String PROJECT_DIR = "project_dir"; + private static final String CONNECTION_PROJECT_DIR = "connection_project_dir"; + private static final String CONNECTION_GRADLE_USER_HOME = "connection_gradle_user_home"; + private static final String CONNECTION_GRADLE_DISTRIBUTION = "connection_gradle_distribution"; + private static final String CONNECTION_JAVA_HOME = "connection_java_home"; + private static final String CONNECTION_JVM_ARGUMENTS = "connection_jvm_arguments"; + private static final String CONNECTION_ARGUMENTS = "connection_arguments"; + + /** + * Saves the given Gradle project configuration in the Eclipse project's .settings + * folder. + * + * @param projectConfiguration the Gradle configuration to persist + * @param workspaceProject the Eclipse project for which to persist the Gradle configuration + */ + public void saveProjectConfiguration(ProjectConfiguration projectConfiguration, IProject workspaceProject) { + Map projectConfig = Maps.newLinkedHashMap(); + projectConfig.put(PROJECT_PATH, projectConfiguration.getProjectPath().getPath()); + projectConfig.put(PROJECT_DIR, projectConfiguration.getProjectDir().getAbsolutePath()); + projectConfig.put(CONNECTION_PROJECT_DIR, projectConfiguration.getRequestAttributes().getProjectDir().getAbsolutePath()); + projectConfig.put(CONNECTION_GRADLE_USER_HOME, FileUtils.getAbsolutePath(projectConfiguration.getRequestAttributes().getGradleUserHome()).orNull()); + projectConfig.put(CONNECTION_GRADLE_DISTRIBUTION, GradleDistributionSerializer.INSTANCE.serializeToString(projectConfiguration.getRequestAttributes().getGradleDistribution())); + projectConfig.put(CONNECTION_JAVA_HOME, FileUtils.getAbsolutePath(projectConfiguration.getRequestAttributes().getJavaHome()).orNull()); + projectConfig.put(CONNECTION_JVM_ARGUMENTS, CollectionsUtils.joinWithSpace(projectConfiguration.getRequestAttributes().getJvmArguments())); + projectConfig.put(CONNECTION_ARGUMENTS, CollectionsUtils.joinWithSpace(projectConfiguration.getRequestAttributes().getArguments())); + + Map config = Maps.newLinkedHashMap(); + config.put("1.0", projectConfig); + + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); + String json = gson.toJson(config, createMapTypeToken()); + + try { + File configFile = createConfigFile(workspaceProject); + CharSource.wrap(json).copyTo(Files.asCharSink(configFile, Charsets.UTF_8)); + } catch (IOException e) { + String message = String.format("Cannot persist Gradle configuration for project %s.", workspaceProject.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + + /** + * Reads the Gradle project configuration from the Eclipse project's .settings folder. + * + * @param workspaceProject the Eclipse project from which to read the Gradle configuration + * @return the persisted Gradle configuration + */ + public ProjectConfiguration readProjectConfiguration(IProject workspaceProject) { + String json; + try { + File configFile = createConfigFile(workspaceProject); + json = Files.toString(configFile, Charsets.UTF_8); + } catch (IOException e) { + String message = String.format("Cannot read Gradle configuration for project %s.", workspaceProject.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + + Gson gson = new GsonBuilder().create(); + Map config = gson.fromJson(json, createMapTypeToken()); + Map projectConfig = getProjectConfigForVersion(config); + + FixedRequestAttributes requestAttributes = new FixedRequestAttributes(new File(projectConfig.get(CONNECTION_PROJECT_DIR)), FileUtils.getAbsoluteFile( + projectConfig.get(CONNECTION_GRADLE_USER_HOME)).orNull(), GradleDistributionSerializer.INSTANCE.deserializeFromString(projectConfig + .get(CONNECTION_GRADLE_DISTRIBUTION)), FileUtils.getAbsoluteFile(projectConfig.get(CONNECTION_JAVA_HOME)).orNull(), CollectionsUtils.splitBySpace(projectConfig + .get(CONNECTION_JVM_ARGUMENTS)), CollectionsUtils.splitBySpace(projectConfig.get(CONNECTION_ARGUMENTS))); + return ProjectConfiguration.from(requestAttributes, Path.from(projectConfig.get(PROJECT_PATH)), new File(projectConfig.get(PROJECT_DIR))); + } + + private File createConfigFile(IProject workspaceProject) throws IOException { + File file = new File(workspaceProject.getLocation().toFile(), ".settings/gradle.prefs"); + Files.createParentDirs(file); + return file; + } + + @SuppressWarnings("serial") + private Type createMapTypeToken() { + return new TypeToken>() { + }.getType(); + } + + @SuppressWarnings("unchecked") + private Map getProjectConfigForVersion(Map config) { + return (Map) config.get("1.0"); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessDescription.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessDescription.java new file mode 100644 index 000000000..69c259947 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessDescription.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.console; + +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.debug.core.ILaunch; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +/** + * Describes a process. + *

+ * Each process description has a name and optionally an {@link ILaunch} instance and a {@link Job} + * instance which are performing the actual execution. + */ +public final class ProcessDescription { + + private final String name; + private final Optional launch; + private final Optional job; + + private ProcessDescription(String name, Optional launch, Optional job) { + this.name = name; + this.launch = launch; + this.job = job; + } + + public String getName() { + return this.name; + } + + public Optional getLaunch() { + return this.launch; + } + + public Optional getJob() { + return this.job; + } + + /** + * Creates a new instance. + * + * @param name a human-readable name of the process + * @return the new instance + */ + public static ProcessDescription with(String name) { + return with(name, null, null); + } + + /** + * Creates a new instance. + * + * @param name a human-readable name of the process + * @param launch the {@code ILaunch} instance of this process + * @param job the {@code Job} instances in which the {@code ILaunch} instance is run + * @return the new instance + */ + public static ProcessDescription with(String name, ILaunch launch, Job job) { + return new ProcessDescription(Preconditions.checkNotNull(name), Optional.fromNullable(launch), Optional.fromNullable(job)); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreams.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreams.java new file mode 100644 index 000000000..b85f90ae4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreams.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.console; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Contains the typical input/output streams that are part of invoking an external process. + */ +public interface ProcessStreams { + + /** + * Returns the default output stream. + * + * @return the default output stream + */ + OutputStream getOutput(); + + /** + * Returns the error stream. + * + * @return the error stream + */ + OutputStream getError(); + + /** + * Returns the input stream. + * + * @return the input stream + */ + InputStream getInput(); + + /** + * Closes all the contained streams. + */ + void close(); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreamsProvider.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreamsProvider.java new file mode 100644 index 000000000..6f6cbf73c --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/ProcessStreamsProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.console; + +/** + * Provider interface to obtain {@link ProcessStreams} instances. + */ +public interface ProcessStreamsProvider { + + /** + * Returns a singleton {@link ProcessStreams} instance suited for background processes. + * + * @return the instance suitable for background processes + */ + ProcessStreams getBackgroundJobProcessStreams(); + + /** + * Creates a new {@link ProcessStreams} instance. + * + * @param processDescription the backing process + * @return the new instance + */ + ProcessStreams createProcessStreams(ProcessDescription processDescription); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/internal/StdProcessStreamsProvider.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/internal/StdProcessStreamsProvider.java new file mode 100644 index 000000000..afbd6d372 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/console/internal/StdProcessStreamsProvider.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.console.internal; + +import java.io.InputStream; +import java.io.OutputStream; + +import com.gradleware.tooling.eclipse.core.console.ProcessDescription; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; + +/** + * Default implementation of {@link ProcessStreamsProvider} that provides {@link System#out}, + * {@link System#err}, and {@link System#in}. + *

+ * This implementation is useful in non-UI test scenarios. + */ +public final class StdProcessStreamsProvider implements ProcessStreamsProvider { + + private final ProcessStreams stdStreams = new ProcessStreams() { + + @Override + public InputStream getInput() { + return System.in; + } + + @Override + public OutputStream getOutput() { + return System.out; + } + + @Override + public OutputStream getError() { + return System.err; + } + + @Override + public void close() { + // do nothing since we never want to close the std streams + } + + }; + + @Override + public ProcessStreams getBackgroundJobProcessStreams() { + return this.stdStreams; + } + + @Override + public ProcessStreams createProcessStreams(ProcessDescription processDescription) { + return this.stdStreams; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleConnectionValidators.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleConnectionValidators.java new file mode 100644 index 000000000..4a6fe74a4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleConnectionValidators.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.gradle; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.gradleware.tooling.eclipse.core.i18n.CoreMessages; +import com.gradleware.tooling.toolingutils.binding.Validator; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * Factory class for {@link Validator} instances that validate Gradle connections attributes. + */ +public final class GradleConnectionValidators { + + private GradleConnectionValidators() { + } + + public static Validator requiredDirectoryValidator(final String prefix) { + return new Validator() { + + @Override + public Optional validate(File file) { + if (file == null) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeSpecified, prefix)); + } else if (!file.exists()) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_DoesNotExist, prefix)); + } else if (!file.isDirectory()) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeDirectory, prefix)); + } else { + return Optional.absent(); + } + } + }; + } + + public static Validator optionalDirectoryValidator(final String prefix) { + return new Validator() { + + @Override + public Optional validate(File file) { + if (file == null) { + return Optional.absent(); + } else if (!file.exists()) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_DoesNotExist, prefix)); + } else if (!file.isDirectory()) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeDirectory, prefix)); + } else { + return Optional.absent(); + } + } + }; + } + + public static Validator gradleDistributionValidator() { + return new Validator() { + + @Override + public Optional validate(GradleDistributionWrapper gradleDistribution) { + GradleDistributionWrapper.DistributionType type = gradleDistribution.getType(); + String configuration = gradleDistribution.getConfiguration(); + + if (GradleDistributionWrapper.DistributionType.LOCAL_INSTALLATION == type) { + if (Strings.isNullOrEmpty(configuration)) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeSpecified, CoreMessages.GradleDistribution_Label_LocalInstallationDirectory)); + } else if (!new File(configuration).exists()) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_DoesNotExist, CoreMessages.GradleDistribution_Label_LocalInstallationDirectory)); + } else { + return Optional.absent(); + } + } else if (GradleDistributionWrapper.DistributionType.REMOTE_DISTRIBUTION == type) { + if (Strings.isNullOrEmpty(configuration)) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeSpecified, CoreMessages.GradleDistribution_Label_RemoteDistributionUri)); + } else if (!isValidURI(configuration)) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_IsNotValid, CoreMessages.GradleDistribution_Label_RemoteDistributionUri)); + } else { + return Optional.absent(); + } + } else if (GradleDistributionWrapper.DistributionType.VERSION == type) { + if (Strings.isNullOrEmpty(configuration)) { + return Optional.of(String.format(CoreMessages.ErrorMessage_0_MustBeSpecified, CoreMessages.GradleDistribution_Label_SpecificGradleVersion)); + } else { + return Optional.absent(); + } + } else { + return Optional.absent(); + } + } + + private boolean isValidURI(String configuration) { + try { + new URI(configuration); + return true; + } catch (URISyntaxException e) { + return false; + } + } + }; + } + + public static Validator nullValidator() { + return new Validator() { + + @Override + public Optional validate(T value) { + return Optional.absent(); + } + + }; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionFormatter.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionFormatter.java new file mode 100644 index 000000000..f5d4f69ab --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionFormatter.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.gradle; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.i18n.CoreMessages; +import com.gradleware.tooling.toolingclient.GradleDistribution; + +/** + * Formats a {@link GradleDistribution} to a human-readable display string. + */ +public final class GradleDistributionFormatter { + + /** + * Converts the given {@link GradleDistribution} to a human-readable {@link String}. + * + * @param gradleDistribution the Gradle distribution to stringify + * @return the resulting string + */ + public static String toString(GradleDistribution gradleDistribution) { + Preconditions.checkNotNull(gradleDistribution); + + GradleDistributionWrapper gradleDistributionWrapper = GradleDistributionWrapper.from(gradleDistribution); + return toString(gradleDistributionWrapper); + } + + /** + * Converts the given {@link GradleDistributionWrapper} to a human-readable {@link String}. + * + * @param gradleDistributionWrapper the Gradle distribution to stringify + * @return the resulting string + */ + public static String toString(GradleDistributionWrapper gradleDistributionWrapper) { + Preconditions.checkNotNull(gradleDistributionWrapper); + + switch (gradleDistributionWrapper.getType()) { + case WRAPPER: + return CoreMessages.GradleDistribution_Value_UseGradleWrapper; + case LOCAL_INSTALLATION: + return String.format(CoreMessages.GradleDistribution_Value_UseLocalInstallation_0, gradleDistributionWrapper.getConfiguration()); + case REMOTE_DISTRIBUTION: + return String.format(CoreMessages.GradleDistribution_Value_UseRemoteDistribution_0, gradleDistributionWrapper.getConfiguration()); + case VERSION: + return String.format(CoreMessages.GradleDistribution_Value_UseGradleVersion_0, gradleDistributionWrapper.getConfiguration()); + default: + throw new GradlePluginsRuntimeException("Unrecognized Gradle distribution type: " + gradleDistributionWrapper.getType()); //$NON-NLS-1$ + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionSerializer.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionSerializer.java new file mode 100644 index 000000000..b13afdd0b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionSerializer.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.gradle; + +import java.io.File; +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.toolingclient.GradleDistribution; + +/** + * Serializes / deserializes a {@link GradleDistribution} to / from a {@link String}. + */ +public final class GradleDistributionSerializer { + + public static final GradleDistributionSerializer INSTANCE = new GradleDistributionSerializer(); + + private GradleDistributionSerializer() { + } + + /** + * Serializes the given Gradle distribution to its String representation. + * + * @param distribution the distribution to serialize + * @return the serialized distribution + */ + public String serializeToString(GradleDistribution distribution) { + Preconditions.checkNotNull(distribution); + + try { + Field localInstallationDirField = GradleDistribution.class.getDeclaredField("localInstallationDir"); + localInstallationDirField.setAccessible(true); + File localInstallationDir = (File) localInstallationDirField.get(distribution); + if (localInstallationDir != null) { + return String.format("GRADLE_DISTRIBUTION(LOCAL_INSTALLATION(%s))", localInstallationDir.getAbsolutePath()); + } + + Field remoteDistributionUriField = GradleDistribution.class.getDeclaredField("remoteDistributionUri"); + remoteDistributionUriField.setAccessible(true); + URI remoteDistributionUri = (URI) remoteDistributionUriField.get(distribution); + if (remoteDistributionUri != null) { + return String.format("GRADLE_DISTRIBUTION(REMOTE_DISTRIBUTION(%s))", remoteDistributionUri.toString()); + } + + Field versionField = GradleDistribution.class.getDeclaredField("version"); + versionField.setAccessible(true); + String version = (String) versionField.get(distribution); + if (version != null) { + return String.format("GRADLE_DISTRIBUTION(VERSION(%s))", version); + } + + return String.valueOf("GRADLE_DISTRIBUTION(WRAPPER)"); + } catch (Exception e) { + String message = String.format("Cannot serialize Gradle distribution '%s.'", distribution); + throw new RuntimeException(message, e); + } + } + + /** + * Deserializes the Gradle distribution from the the given String representation. + * + * @param distributionString the serialized distribution + * @return the deserialized distribution + */ + public GradleDistribution deserializeFromString(String distributionString) { + Preconditions.checkNotNull(distributionString); + + String localInstallationPrefix = "GRADLE_DISTRIBUTION(LOCAL_INSTALLATION("; + if (distributionString.startsWith(localInstallationPrefix) && distributionString.endsWith("))")) { + String localInstallationDir = distributionString.substring(localInstallationPrefix.length(), distributionString.length() - 2); + return GradleDistribution.forLocalInstallation(new File(localInstallationDir)); + } + + String remoteDistributionPrefix = "GRADLE_DISTRIBUTION(REMOTE_DISTRIBUTION("; + if (distributionString.startsWith(remoteDistributionPrefix) && distributionString.endsWith("))")) { + String remoteDistributionUri = distributionString.substring(remoteDistributionPrefix.length(), distributionString.length() - 2); + return GradleDistribution.forRemoteDistribution(createURI(remoteDistributionUri)); + } + + String versionPrefix = "GRADLE_DISTRIBUTION(VERSION("; + if (distributionString.startsWith(versionPrefix) && distributionString.endsWith("))")) { + String version = distributionString.substring(versionPrefix.length(), distributionString.length() - 2); + return GradleDistribution.forVersion(version); + } + + String wrapperString = "GRADLE_DISTRIBUTION(WRAPPER)"; + if (distributionString.equals(wrapperString)) { + return GradleDistribution.fromBuild(); + } + + String message = String.format("Cannot deserialize Gradle distribution string '%s.'", distributionString); + throw new RuntimeException(message); + } + + private URI createURI(String uri) { + try { + return new URI(uri); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage()); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionWrapper.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionWrapper.java new file mode 100644 index 000000000..ef7841386 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/GradleDistributionWrapper.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.gradle; + +import java.io.File; +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.toolingclient.GradleDistribution; + +/** + * Wraps the type of Gradle distribution and its configurations, e.g. using a fixed Gradle version + * (type) in version 2.1 (configuration). + */ +public final class GradleDistributionWrapper { + + private final DistributionType type; + private final String configuration; + + private GradleDistributionWrapper(DistributionType type, String configuration) { + this.type = Preconditions.checkNotNull(type); + this.configuration = configuration; + } + + public DistributionType getType() { + return this.type; + } + + public String getConfiguration() { + return this.configuration; + } + + public GradleDistribution toGradleDistribution() { + if (this.type == DistributionType.LOCAL_INSTALLATION) { + return GradleDistribution.forLocalInstallation(new File(this.configuration)); + } else if (this.type == DistributionType.REMOTE_DISTRIBUTION) { + return GradleDistribution.forRemoteDistribution(createURI(this.configuration)); + } else if (this.type == DistributionType.VERSION) { + return GradleDistribution.forVersion(this.configuration); + } else { + return GradleDistribution.fromBuild(); + } + } + + private URI createURI(String path) { + try { + return new URI(path); + } catch (URISyntaxException e) { + throw new RuntimeException(e.getMessage()); + } + } + + public static GradleDistributionWrapper from(DistributionType type, String configuration) { + return new GradleDistributionWrapper(type, configuration); + } + + public static GradleDistributionWrapper from(GradleDistribution distribution) { + Preconditions.checkNotNull(distribution); + + try { + Field localInstallationDirField = GradleDistribution.class.getDeclaredField("localInstallationDir"); + localInstallationDirField.setAccessible(true); + File localInstallationDir = (File) localInstallationDirField.get(distribution); + if (localInstallationDir != null) { + return from(DistributionType.LOCAL_INSTALLATION, localInstallationDir.getAbsolutePath()); + } + + Field remoteDistributionUriField = GradleDistribution.class.getDeclaredField("remoteDistributionUri"); + remoteDistributionUriField.setAccessible(true); + URI remoteDistributionUri = (URI) remoteDistributionUriField.get(distribution); + if (remoteDistributionUri != null) { + return from(DistributionType.REMOTE_DISTRIBUTION, remoteDistributionUri.toString()); + } + + Field versionField = GradleDistribution.class.getDeclaredField("version"); + versionField.setAccessible(true); + String version = (String) versionField.get(distribution); + if (version != null) { + return from(DistributionType.VERSION, version); + } + + return from(DistributionType.WRAPPER, null); + } catch (Exception e) { + String message = String.format("Cannot serialize Gradle distribution '%s.'", distribution); + throw new RuntimeException(message, e); + } + } + + /** + * Enumerates the different types of Gradle distributions. + */ + public enum DistributionType { + WRAPPER, LOCAL_INSTALLATION, REMOTE_DISTRIBUTION, VERSION + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/Specs.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/Specs.java new file mode 100644 index 000000000..8f2554317 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/gradle/Specs.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.gradle; + +import org.gradle.api.specs.Spec; + +import com.gradleware.tooling.toolingmodel.OmniEclipseProject; +import com.gradleware.tooling.toolingmodel.OmniGradleProject; +import com.gradleware.tooling.toolingmodel.Path; + +/** + * Contains useful {@code Spec} implementations. + */ +public final class Specs { + + private Specs() { + } + + /** + * Returns a spec that matches if the the project path of a {@code OmniEclipseProject} instance + * matches the given project path. + * + * @param projectPath the project path to match + * @return the spec + */ + public static Spec eclipseProjectMatchesProjectPath(final Path projectPath) { + return new Spec() { + + @Override + public boolean isSatisfiedBy(OmniEclipseProject candidate) { + return candidate.getPath().equals(projectPath); + } + }; + } + + /** + * Returns a spec that matches if the the project path of a {@code OmniGradleProject} instance + * matches the given project path. + * + * @param projectPath the project path to match + * @return the spec + */ + public static Spec gradleProjectMatchesProjectPath(final Path projectPath) { + return new Spec() { + + @Override + public boolean isSatisfiedBy(OmniGradleProject candidate) { + return candidate.getPath().equals(projectPath); + } + }; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.java new file mode 100644 index 000000000..98673a484 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.i18n; + +import org.eclipse.osgi.util.NLS; + +/** + * Lists the i18n resource keys for the core messages. + */ +public final class CoreMessages extends NLS { + + private static final String BUNDLE_NAME = "com.gradleware.tooling.eclipse.core.i18n.CoreMessages"; //$NON-NLS-1$ + + public static String GradleDistribution_Label_GradleWrapper; + public static String GradleDistribution_Label_LocalInstallationDirectory; + public static String GradleDistribution_Label_RemoteDistributionUri; + public static String GradleDistribution_Label_SpecificGradleVersion; + + public static String GradleDistribution_Value_UseGradleWrapper; + public static String GradleDistribution_Value_UseLocalInstallation_0; + public static String GradleDistribution_Value_UseRemoteDistribution_0; + public static String GradleDistribution_Value_UseGradleVersion_0; + + public static String ProgressVisualization_Label_VisualizeTestProgress; + + public static String RunConfiguration_Label_GradleTasks; + public static String RunConfiguration_Label_WorkingDirectory; + public static String RunConfiguration_Label_GradleDistribution; + public static String RunConfiguration_Label_GradleUserHome; + public static String RunConfiguration_Label_JavaHome; + public static String RunConfiguration_Label_JvmArguments; + public static String RunConfiguration_Label_Arguments; + public static String RunConfiguration_Label_ProgressVisualization; + + public static String RunConfiguration_Value_RunDefaultTasks; + + public static String Value_None; + public static String Value_Unknown; + public static String Value_UseGradleDefault; + + public static String ErrorMessage_0_DoesNotExist; + public static String ErrorMessage_0_IsNotValid; + public static String ErrorMessage_0_MustBeSpecified; + public static String ErrorMessage_0_MustBeDirectory; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, CoreMessages.class); + } + + private CoreMessages() { + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManager.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManager.java new file mode 100644 index 000000000..a66669c6b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleLaunchConfigurationManager.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch; + +import org.eclipse.debug.core.ILaunchConfiguration; + +/** + * Manages the interactions with the Gradle {@link ILaunchConfiguration} instances. + */ +public interface GradleLaunchConfigurationManager { + + /** + * Returns either a new Gradle {@link ILaunchConfiguration} instance or an existing one, + * depending on whether there is already a Gradle run configuration for the given set of + * attributes or not. + * + * @param configurationAttributes the run configuration attributes + * @return the new or reused Gradle run configuration + */ + ILaunchConfiguration getOrCreateRunConfiguration(GradleRunConfigurationAttributes configurationAttributes); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributes.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributes.java new file mode 100644 index 000000000..12dbadd64 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationAttributes.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch; + +import java.io.File; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionSerializer; +import com.gradleware.tooling.eclipse.core.util.file.FileUtils; +import com.gradleware.tooling.eclipse.core.util.variable.ExpressionUtils; +import com.gradleware.tooling.toolingclient.GradleDistribution; + +/** + * Contains the attributes that describe a Gradle run configuration. + */ +public final class GradleRunConfigurationAttributes { + + // keys used when setting/getting attributes from an ILaunchConfiguration instance + private static final String TASKS = "tasks"; + private static final String WORKING_DIR = "working_dir"; + private static final String GRADLE_DISTRIBUTION = "gradle_distribution"; + private static final String GRADLE_USER_HOME = "gradle_user_home"; + private static final String JAVA_HOME = "java_home"; + private static final String JVM_ARGUMENTS = "jvm_arguments"; + private static final String ARGUMENTS = "arguments"; + private static final String VISUALIZE_TEST_PROGRESS = "visualize_test_progress"; + + private final ImmutableList tasks; + private final String workingDirExpression; + private final GradleDistribution gradleDistribution; + private final String gradleUserHomeExpression; + private final String javaHomeExpression; + private final ImmutableList jvmArgumentExpressions; + private final ImmutableList argumentExpressions; + private final boolean visualizeTestProgress; + + /** + * Creates a new instance. + * + * @param tasks the Gradle tasks to launch + * @param workingDirExpression the expression resolving to the working directory from which to + * launch the Gradle tasks, never null + * @param gradleDistribution the Gradle distribution to use + * @param gradleUserHomeExpression the expression resolving to the Gradle user home to use, can + * be null + * @param javaHomeExpression the expression resolving to the Java home to use, can be null + * @param jvmArgumentExpressions the expressions resolving to the JVM arguments to apply + * @param argumentExpressions the expressions resolving to the arguments to apply + */ + private GradleRunConfigurationAttributes(List tasks, String workingDirExpression, GradleDistribution gradleDistribution, String gradleUserHomeExpression, + String javaHomeExpression, List jvmArgumentExpressions, List argumentExpressions, boolean visualizeTestProgress) { + this.tasks = ImmutableList.copyOf(tasks); + this.workingDirExpression = Preconditions.checkNotNull(workingDirExpression); + this.gradleDistribution = Preconditions.checkNotNull(gradleDistribution); + this.gradleUserHomeExpression = gradleUserHomeExpression; + this.javaHomeExpression = javaHomeExpression; + this.jvmArgumentExpressions = ImmutableList.copyOf(jvmArgumentExpressions); + this.argumentExpressions = ImmutableList.copyOf(argumentExpressions); + this.visualizeTestProgress = visualizeTestProgress; + } + + public ImmutableList getTasks() { + return this.tasks; + } + + public String getWorkingDirExpression() { + return this.workingDirExpression; + } + + public File getWorkingDir() { + try { + String location = ExpressionUtils.decode(this.workingDirExpression); + return new File(location).getAbsoluteFile(); + } catch (CoreException e) { + String message = String.format("Cannot resolve working directory expression %s.", this.workingDirExpression); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + } + + public GradleDistribution getGradleDistribution() { + return this.gradleDistribution; + } + + public String getGradleUserHomeExpression() { + return this.gradleUserHomeExpression; + } + + public File getGradleUserHome() { + try { + String location = ExpressionUtils.decode(this.gradleUserHomeExpression); + return FileUtils.getAbsoluteFile(location).orNull(); + } catch (CoreException e) { + String message = String.format("Cannot resolve Gradle user home directory expression %s.", this.gradleUserHomeExpression); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + } + + public String getJavaHomeExpression() { + return this.javaHomeExpression; + } + + public File getJavaHome() { + try { + String location = ExpressionUtils.decode(this.javaHomeExpression); + return FileUtils.getAbsoluteFile(location).orNull(); + } catch (CoreException e) { + String message = String.format("Cannot resolve Java home directory expression %s.", this.javaHomeExpression); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + } + + public ImmutableList getJvmArgumentExpressions() { + return this.jvmArgumentExpressions; + } + + public ImmutableList getJvmArguments() { + return FluentIterable.from(this.jvmArgumentExpressions).transform(new Function() { + + @Override + public String apply(String input) { + try { + return ExpressionUtils.decode(input); + } catch (CoreException e) { + String message = String.format("Cannot resolve JVM argument expression %s.", input); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + } + }).toList(); + } + + public ImmutableList getArgumentExpressions() { + return this.argumentExpressions; + } + + public ImmutableList getArguments() { + return FluentIterable.from(this.argumentExpressions).transform(new Function() { + + @Override + public String apply(String input) { + try { + return ExpressionUtils.decode(input); + } catch (CoreException e) { + String message = String.format("Cannot resolve argument expression %s.", input); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + } + }).toList(); + } + + public boolean isVisualizeTestProgress() { + return this.visualizeTestProgress; + } + + public boolean hasSameUniqueAttributes(ILaunchConfiguration launchConfiguration) { + // reuse an existing run configuration if the working directory and the tasks are the same, + // regardless of the other settings of the launch configuration + try { + return this.tasks.equals(launchConfiguration.getAttribute(TASKS, ImmutableList. of())) + && this.workingDirExpression.equals(launchConfiguration.getAttribute(WORKING_DIR, "")); + } catch (CoreException e) { + String message = String.format("Cannot read Gradle launch configuration %s.", launchConfiguration); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + + public void apply(ILaunchConfigurationWorkingCopy launchConfiguration) { + applyTasks(this.tasks, launchConfiguration); + applyWorkingDirExpression(this.workingDirExpression, launchConfiguration); + applyGradleDistribution(this.gradleDistribution, launchConfiguration); + applyGradleUserHomeExpression(this.gradleUserHomeExpression, launchConfiguration); + applyJavaHomeExpression(this.javaHomeExpression, launchConfiguration); + applyJvmArgumentExpressions(this.jvmArgumentExpressions, launchConfiguration); + applyArgumentExpressions(this.argumentExpressions, launchConfiguration); + applyVisualizeTestProgress(this.visualizeTestProgress, launchConfiguration); + } + + public static void applyTasks(List tasks, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(TASKS, tasks); + } + + public static void applyWorkingDirExpression(String workingDirExpression, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(WORKING_DIR, Preconditions.checkNotNull(workingDirExpression)); + } + + public static void applyGradleDistribution(GradleDistribution gradleDistribution, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(GRADLE_DISTRIBUTION, Preconditions.checkNotNull(GradleDistributionSerializer.INSTANCE.serializeToString(gradleDistribution))); + } + + public static void applyGradleUserHomeExpression(String gradleUserHomeExpression, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(GRADLE_USER_HOME, gradleUserHomeExpression); + } + + public static void applyJavaHomeExpression(String javaHomeExpression, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(JAVA_HOME, javaHomeExpression); + } + + public static void applyJvmArgumentExpressions(List jvmArgumentsExpression, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(JVM_ARGUMENTS, jvmArgumentsExpression); + } + + public static void applyArgumentExpressions(List argumentsExpression, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(ARGUMENTS, argumentsExpression); + } + + public static void applyVisualizeTestProgress(boolean visualizeTestProgress, ILaunchConfigurationWorkingCopy launchConfiguration) { + launchConfiguration.setAttribute(VISUALIZE_TEST_PROGRESS, visualizeTestProgress); + } + + public static GradleRunConfigurationAttributes with(List tasks, String workingDirExpression, GradleDistribution gradleDistribution, String gradleUserHomeExpression, + String javaHomeExpression, List jvmArgumentExpressions, List argumentExpressions, boolean visualizeTestProgress) { + return new GradleRunConfigurationAttributes(tasks, workingDirExpression, gradleDistribution, gradleUserHomeExpression, javaHomeExpression, jvmArgumentExpressions, + argumentExpressions, visualizeTestProgress); + } + + @SuppressWarnings("unchecked") + public static GradleRunConfigurationAttributes from(ILaunchConfiguration launchConfiguration) { + List tasks; + try { + tasks = launchConfiguration.getAttribute(TASKS, ImmutableList. of()); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", TASKS); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + String workingDirExpression; + try { + workingDirExpression = launchConfiguration.getAttribute(WORKING_DIR, ""); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", WORKING_DIR); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + GradleDistribution gradleDistribution; + try { + String serialized = launchConfiguration.getAttribute(GRADLE_DISTRIBUTION, (String) null); + gradleDistribution = serialized != null ? GradleDistributionSerializer.INSTANCE.deserializeFromString(serialized) : GradleDistribution.fromBuild(); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", GRADLE_DISTRIBUTION); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + String gradleUserHomeExpression; + try { + gradleUserHomeExpression = launchConfiguration.getAttribute(GRADLE_USER_HOME, (String) null); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", GRADLE_USER_HOME); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + String javaHomeExpression; + try { + javaHomeExpression = launchConfiguration.getAttribute(JAVA_HOME, (String) null); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", JAVA_HOME); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + List jvmArgumentExpressions; + try { + jvmArgumentExpressions = launchConfiguration.getAttribute(JVM_ARGUMENTS, ImmutableList. of()); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", JVM_ARGUMENTS); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + List argumentExpressions; + try { + argumentExpressions = launchConfiguration.getAttribute(ARGUMENTS, ImmutableList. of()); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", ARGUMENTS); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + boolean visualizeTestProgress; + try { + visualizeTestProgress = launchConfiguration.getAttribute(VISUALIZE_TEST_PROGRESS, true); + } catch (CoreException e) { + String message = String.format("Cannot read launch configuration attribute '%s'.", VISUALIZE_TEST_PROGRESS); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message); + } + + return with(tasks, workingDirExpression, gradleDistribution, gradleUserHomeExpression, javaHomeExpression, jvmArgumentExpressions, argumentExpressions, + visualizeTestProgress); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationDelegate.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationDelegate.java new file mode 100644 index 000000000..9c66071c7 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/GradleRunConfigurationDelegate.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.model.LaunchConfigurationDelegate; + +import com.gradleware.tooling.eclipse.core.CorePlugin; + +/** + * Execute Gradle tasks from the run configurations. + *

+ * The delegate invokes the {@link RunGradleConfigurationDelegateJob} job to do the actual execution + * and waits until it finishes. It also propagates the cancellation to that job. + */ +public final class GradleRunConfigurationDelegate extends LaunchConfigurationDelegate { + + // configuration type id declared in the plugin.xml + public static final String ID = "com.gradleware.tooling.eclipse.core.launch.runconfiguration"; + + @Override + public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) { + monitor.beginTask("Launch Gradle tasks", IProgressMonitor.UNKNOWN); + try { + // schedule the task + final CountDownLatch latch = new CountDownLatch(1); + RunGradleConfigurationDelegateJob job = new RunGradleConfigurationDelegateJob(launch, configuration); + job.addJobChangeListener(new JobChangeAdapter() { + + @Override + public void done(IJobChangeEvent event) { + latch.countDown(); + } + }); + job.schedule(); + + // block until the task execution job has finished successfully or failed, + // periodically check if this launch has been cancelled, and if so, cancel + // the task execution job + try { + boolean cancelRequested = false; + while (!latch.await(500, TimeUnit.MILLISECONDS)) { + // regularly check if the job was cancelled + // until the job is either finished or failed + if (monitor.isCanceled() && !cancelRequested) { + // cancel the job only once + job.cancel(); + cancelRequested = true; + } + } + } catch (InterruptedException e) { + CorePlugin.logger().error("Failed to launch Gradle tasks.", e); + } + } finally { + monitor.done(); + + // explicitly remove the launch since the code in DebugPlugin never removes the launch + // (we depend on the removal event being sent in the 'close console actions' since no + // other events of interest to us get ever fired) + DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/RunGradleConfigurationDelegateJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/RunGradleConfigurationDelegateJob.java new file mode 100644 index 000000000..41deb712e --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/RunGradleConfigurationDelegateJob.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.text.DateFormat; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.BuildException; +import org.gradle.tooling.TestProgressEvent; +import org.gradle.tooling.TestProgressListener; + +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.console.ProcessDescription; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionFormatter; +import com.gradleware.tooling.eclipse.core.i18n.CoreMessages; +import com.gradleware.tooling.eclipse.core.testprogress.GradleTestRunSession; +import com.gradleware.tooling.eclipse.core.testprogress.GradleTestRunSessionFactory; +import com.gradleware.tooling.eclipse.core.util.collections.CollectionsUtils; +import com.gradleware.tooling.eclipse.core.util.file.FileUtils; +import com.gradleware.tooling.eclipse.core.util.progress.DelegatingProgressListener; +import com.gradleware.tooling.eclipse.core.util.progress.ToolingApiJob; +import com.gradleware.tooling.toolingclient.BuildLaunchRequest; +import com.gradleware.tooling.toolingclient.GradleDistribution; +import com.gradleware.tooling.toolingclient.LaunchableConfig; +import com.gradleware.tooling.toolingclient.ToolingClient; + +/** + * Runs the given {@link ILaunch} instance. + */ +public final class RunGradleConfigurationDelegateJob extends ToolingApiJob { + + // todo (etst) close streams when done + + private final ILaunch launch; + private final ILaunchConfiguration launchConfiguration; + + public RunGradleConfigurationDelegateJob(ILaunch launch, ILaunchConfiguration launchConfiguration) { + super("Launching Gradle tasks"); + + this.launch = Preconditions.checkNotNull(launch); + this.launchConfiguration = Preconditions.checkNotNull(launchConfiguration); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + runLaunchConfiguration(monitor); + return Status.OK_STATUS; + } catch (BuildCancelledException e) { + // if the job was cancelled by the user, do not show an error dialog + CorePlugin.logger().info(e.getMessage()); + return Status.CANCEL_STATUS; + } catch (BuildException e) { + // return only a warning if there was a problem while running the Gradle build since the + // error is also visible in the Gradle console + return new Status(IStatus.WARNING, CorePlugin.PLUGIN_ID, "Gradle build failure during task execution.", e); + } catch (Exception e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Launching the Gradle tasks failed.", e); + } finally { + monitor.done(); + } + } + + @SuppressWarnings("unchecked") + public void runLaunchConfiguration(IProgressMonitor monitor) { + // derive all build launch settings from the launch configuration + GradleRunConfigurationAttributes configurationAttributes = GradleRunConfigurationAttributes.from(this.launchConfiguration); + List tasks = configurationAttributes.getTasks(); + File workingDir = configurationAttributes.getWorkingDir(); + GradleDistribution gradleDistribution = configurationAttributes.getGradleDistribution(); + File gradleUserHome = configurationAttributes.getGradleUserHome(); + File javaHome = configurationAttributes.getJavaHome(); + ImmutableList jvmArguments = configurationAttributes.getJvmArguments(); + ImmutableList arguments = configurationAttributes.getArguments(); + + // start tracking progress + monitor.beginTask(String.format("Launch Gradle tasks %s", tasks), IProgressMonitor.UNKNOWN); + + // configure the request with the build launch settings derived from the launch + // configuration + BuildLaunchRequest request = ToolingClient.newClient().newBuildLaunchRequest(LaunchableConfig.forTasks(tasks)); + request.projectDir(workingDir); + request.gradleDistribution(gradleDistribution); + request.gradleUserHomeDir(gradleUserHome); + request.javaHomeDir(javaHome); + request.jvmArguments(jvmArguments.toArray(new String[jvmArguments.size()])); + request.arguments(arguments.toArray(new String[arguments.size()])); + + // configure the request with the transient request attributes + String processName = createProcessName(tasks, workingDir); + ProcessDescription processDescription = ProcessDescription.with(processName, this.launch, this); + ProcessStreams processStreams = CorePlugin.processStreamsProvider().createProcessStreams(processDescription); + request.standardOutput(processStreams.getOutput()); + request.standardError(processStreams.getError()); + request.standardInput(processStreams.getInput()); + request.progressListeners(new DelegatingProgressListener(monitor)); + request.cancellationToken(getToken()); + + // print the applied run configuration settings at the beginning of the console output + writeRunConfigurationDescription(configurationAttributes, processStreams.getOutput()); + + // attach a test progress listener if the run configuration has the test progress + // visualization enabled + Optional testProgressListener = createTestProgressListenerIfEnabled(configurationAttributes); + if (testProgressListener.isPresent()) { + request.testProgressListeners(testProgressListener.get()); + } + + // launch the build (optionally with test execution progress being tracked) + if (testProgressListener.isPresent()) { + testProgressListener.get().start(); + } + try { + request.executeAndWait(); + } finally { + if (testProgressListener.isPresent()) { + testProgressListener.get().finish(); + } + } + } + + private String createProcessName(List tasks, File workingDir) { + return String.format("%s [Gradle Project] %s in %s (%s)", this.launchConfiguration.getName(), Joiner.on(' ').join(tasks), workingDir.getAbsolutePath(), DateFormat + .getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(new Date())); + } + + private void writeRunConfigurationDescription(GradleRunConfigurationAttributes runConfiguration, OutputStream output) { + OutputStreamWriter writer = new OutputStreamWriter(output); + try { + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_GradleTasks, toNonEmpty(runConfiguration.getTasks(), CoreMessages.RunConfiguration_Value_RunDefaultTasks))); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_WorkingDirectory, FileUtils.getAbsolutePath(runConfiguration.getWorkingDir()).get())); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_GradleDistribution, GradleDistributionFormatter.toString(runConfiguration.getGradleDistribution()))); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_GradleUserHome, toNonEmpty(runConfiguration.getGradleUserHome(), CoreMessages.Value_UseGradleDefault))); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_JavaHome, toNonEmpty(runConfiguration.getJavaHome(), CoreMessages.Value_UseGradleDefault))); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_JvmArguments, toNonEmpty(runConfiguration.getJvmArguments(), CoreMessages.Value_UseGradleDefault))); + writer.write(String.format("%s: %s%n", CoreMessages.RunConfiguration_Label_Arguments, toNonEmpty(runConfiguration.getArguments(), CoreMessages.Value_None))); + writer.write('\n'); + writer.flush(); + } catch (IOException e) { + String message = String.format("Cannot write run configuration description to Gradle console."); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + + private String toNonEmpty(File fileValue, String defaultMessage) { + String string = FileUtils.getAbsolutePath(fileValue).orNull(); + return string != null ? string : defaultMessage; + } + + private String toNonEmpty(List stringValues, String defaultMessage) { + String string = Strings.emptyToNull(CollectionsUtils.joinWithSpace(stringValues)); + return string != null ? string : defaultMessage; + } + + private Optional createTestProgressListenerIfEnabled(GradleRunConfigurationAttributes configurationAttributes) { + if (configurationAttributes.isVisualizeTestProgress()) { + Optional workspaceProject = findJavaProjectInWorkspace(configurationAttributes); + GradleTestRunSessionForwardingTestProgressListener testProgressListener = new GradleTestRunSessionForwardingTestProgressListener(this.launch, workspaceProject); + return Optional.of(testProgressListener); + } else { + return Optional.absent(); + } + } + + private Optional findJavaProjectInWorkspace(GradleRunConfigurationAttributes configuration) { + final File workingDirectory = configuration.getWorkingDir(); + Optional javaProject = FluentIterable.from(CorePlugin.workspaceOperations().getAllProjects()).firstMatch(new Predicate() { + + @Override + public boolean apply(IProject project) { + try { + return project.isOpen() && project.hasNature(JavaCore.NATURE_ID) && project.getLocation().toFile().equals(workingDirectory); + } catch (Exception e) { + return false; + } + } + }); + + return Optional.fromNullable(JavaCore.create(javaProject.orNull())); + } + + /** + * {@code TestProgressListener} that forwards all test progress events to a {@code GradleTestRunSession}. + */ + public static final class GradleTestRunSessionForwardingTestProgressListener implements TestProgressListener { + + private final GradleTestRunSession session; + private final AtomicBoolean firstInvocation; + + public GradleTestRunSessionForwardingTestProgressListener(ILaunch launch, Optional workspaceProject) { + this.session = GradleTestRunSessionFactory.newSession(launch, workspaceProject.orNull()); + this.firstInvocation = new AtomicBoolean(true); + } + + public void start() { + this.session.start(); + } + + public void finish() { + this.session.finish(); + } + + @Override + public void statusChanged(TestProgressEvent event) { + // activate the Test View the first time we receive a test progress event + if (this.firstInvocation.getAndSet(false)) { + CorePlugin.workbenchOperations().activateTestRunnerView(); + } + + this.session.process(event); + } + + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/internal/DefaultGradleLaunchConfigurationManager.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/internal/DefaultGradleLaunchConfigurationManager.java new file mode 100644 index 000000000..13263d863 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/launch/internal/DefaultGradleLaunchConfigurationManager.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.launch.internal; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.launch.GradleLaunchConfigurationManager; +import com.gradleware.tooling.eclipse.core.launch.GradleRunConfigurationAttributes; +import com.gradleware.tooling.eclipse.core.launch.GradleRunConfigurationDelegate; +import com.gradleware.tooling.eclipse.core.util.collections.CollectionsUtils; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; + +/** + * Default implementation of the {@link GradleLaunchConfigurationManager} interface. + */ +public final class DefaultGradleLaunchConfigurationManager implements GradleLaunchConfigurationManager { + + private final ILaunchManager launchManager; + + public DefaultGradleLaunchConfigurationManager() { + this(DebugPlugin.getDefault().getLaunchManager()); + } + + public DefaultGradleLaunchConfigurationManager(ILaunchManager launchManager) { + this.launchManager = Preconditions.checkNotNull(launchManager); + } + + @Override + public ILaunchConfiguration getOrCreateRunConfiguration(GradleRunConfigurationAttributes configurationAttributes) { + Preconditions.checkNotNull(configurationAttributes); + Optional launchConfiguration = findLaunchConfiguration(configurationAttributes); + return launchConfiguration.isPresent() ? launchConfiguration.get() : createLaunchConfiguration(configurationAttributes); + } + + private Optional findLaunchConfiguration(GradleRunConfigurationAttributes configurationAttributes) { + for (ILaunchConfiguration launchConfiguration : getGradleLaunchConfigurations()) { + if (configurationAttributes.hasSameUniqueAttributes(launchConfiguration)) { + return Optional.of(launchConfiguration); + } + } + return Optional.absent(); + } + + private ILaunchConfiguration createLaunchConfiguration(GradleRunConfigurationAttributes configurationAttributes) { + // derive the name of the launch configuration from the configuration attributes + // since the launch configuration name must not contain ':', we replace all ':' with '.' + String taskNamesOrDefault = configurationAttributes.getTasks().isEmpty() ? "(default tasks)" : CollectionsUtils.joinWithSpace(configurationAttributes.getTasks()); + String rawLaunchConfigurationName = String.format("%s - %s", configurationAttributes.getWorkingDir().getName(), taskNamesOrDefault); + String launchConfigurationName = this.launchManager.generateLaunchConfigurationName(rawLaunchConfigurationName.replace(':', '.')); + ILaunchConfigurationType launchConfigurationType = this.launchManager.getLaunchConfigurationType(GradleRunConfigurationDelegate.ID); + + try { + // create new launch configuration instance + ILaunchConfigurationWorkingCopy launchConfiguration = launchConfigurationType.newInstance(null, launchConfigurationName); + + // configure the launch configuration + configurationAttributes.apply(launchConfiguration); + + // persist the launch configuration and return it + return launchConfiguration.doSave(); + } catch (Exception e) { + String message = String.format("Cannot create Gradle launch configuration %s.", launchConfigurationName); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(e); + } + } + + private ILaunchConfiguration[] getGradleLaunchConfigurations() { + ILaunchConfigurationType launchConfigurationType = this.launchManager.getLaunchConfigurationType(GradleRunConfigurationDelegate.ID); + + try { + return this.launchManager.getLaunchConfigurations(launchConfigurationType); + } catch (Exception e) { + String message = "Cannot get Gradle launch configurations."; + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildJob.java new file mode 100644 index 000000000..6eb62a042 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildJob.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.model; + +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.ProgressListener; +import org.gradle.tooling.TestProgressListener; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; +import com.gradleware.tooling.eclipse.core.util.progress.DelegatingProgressListener; +import com.gradleware.tooling.eclipse.core.util.progress.ToolingApiJob; +import com.gradleware.tooling.toolingclient.Consumer; +import com.gradleware.tooling.toolingmodel.OmniEclipseGradleBuild; +import com.gradleware.tooling.toolingmodel.repository.FetchStrategy; +import com.gradleware.tooling.toolingmodel.repository.ModelRepository; +import com.gradleware.tooling.toolingmodel.repository.ModelRepositoryProvider; +import com.gradleware.tooling.toolingmodel.repository.TransientRequestAttributes; + +/** + * Loads the {@link OmniEclipseGradleBuild} model for a given {@link ProjectConfiguration} instance. + */ +public final class LoadEclipseGradleBuildJob extends ToolingApiJob { + + private final ModelRepositoryProvider modelRepositoryProvider; + private final ProcessStreamsProvider processStreamsProvider; + private final FetchStrategy modelFetchStrategy; + private final ProjectConfiguration configuration; + private final Consumer> postProcessor; + + public LoadEclipseGradleBuildJob(ModelRepositoryProvider modelRepositoryProvider, ProcessStreamsProvider processStreamsProvider, FetchStrategy modelFetchStrategy, + ProjectConfiguration configuration, Consumer> postProcessor) { + super(String.format("Loading tasks of project located at %s", configuration.getProjectDir().getAbsolutePath())); + this.modelRepositoryProvider = Preconditions.checkNotNull(modelRepositoryProvider); + this.processStreamsProvider = Preconditions.checkNotNull(processStreamsProvider); + this.modelFetchStrategy = Preconditions.checkNotNull(modelFetchStrategy); + this.configuration = Preconditions.checkNotNull(configuration); + this.postProcessor = Preconditions.checkNotNull(postProcessor); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + // load model + try { + OmniEclipseGradleBuild eclipseGradleBuild = loadTasksOfEclipseProject(monitor); + this.postProcessor.accept(Optional.of(eclipseGradleBuild)); + return Status.OK_STATUS; + } catch (BuildCancelledException e) { + // if the job was cancelled by the user, do not show an error dialog + CorePlugin.logger().info(e.getMessage()); + this.postProcessor.accept(Optional. absent()); + return Status.CANCEL_STATUS; + } catch (Exception e) { + this.postProcessor.accept(Optional. absent()); + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + String.format("Loading the tasks of the project located at %s failed.", this.configuration.getProjectDir().getName()), e); + } + } finally { + monitor.done(); + } + } + + public OmniEclipseGradleBuild loadTasksOfEclipseProject(IProgressMonitor monitor) { + monitor.beginTask(String.format("Load tasks of project located at %s", this.configuration.getProjectDir().getName()), 1); + return fetchEclipseGradleBuild(new SubProgressMonitor(monitor, 1)); + } + + private OmniEclipseGradleBuild fetchEclipseGradleBuild(IProgressMonitor monitor) { + monitor.beginTask("Load Eclipse Project", IProgressMonitor.UNKNOWN); + try { + ProcessStreams stream = this.processStreamsProvider.getBackgroundJobProcessStreams(); + List listeners = ImmutableList. of(new DelegatingProgressListener(monitor)); + TransientRequestAttributes transientAttributes = new TransientRequestAttributes(false, stream.getOutput(), stream.getError(), null, listeners, ImmutableList. of(), getToken()); + ModelRepository repository = this.modelRepositoryProvider.getModelRepository(this.configuration.getRequestAttributes()); + return repository.fetchEclipseGradleBuild(transientAttributes, this.modelFetchStrategy); + } finally { + monitor.done(); + } + } + + @Override + public boolean belongsTo(Object family) { + // associate with a family so we can cancel all builds of + // this type at once through the Eclipse progress manager + return LoadEclipseGradleBuildJob.class.getName().equals(family); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildsJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildsJob.java new file mode 100644 index 000000000..d20200a38 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/model/LoadEclipseGradleBuildsJob.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.model; + +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; +import com.gradleware.tooling.toolingclient.Consumer; +import com.gradleware.tooling.toolingmodel.OmniEclipseGradleBuild; +import com.gradleware.tooling.toolingmodel.repository.FetchStrategy; +import com.gradleware.tooling.toolingmodel.repository.ModelRepositoryProvider; + +/** + * Loads the {@link OmniEclipseGradleBuild} models for all given {@link ProjectConfiguration} + * instances. Each model is loaded in parallel in a separate job. Canceling this job will cancel all + * these jobs. + * + * It is ensured that only one instance of this job can run at any given time. + */ +public final class LoadEclipseGradleBuildsJob extends Job { + + private final ModelRepositoryProvider modelRepositoryProvider; + private final ProcessStreamsProvider processStreamsProvider; + private final FetchStrategy modelFetchStrategy; + private final ImmutableSet configurations; + private final Consumer> postProcessor; + + public LoadEclipseGradleBuildsJob(ModelRepositoryProvider modelRepositoryProvider, ProcessStreamsProvider processStreamsProvider, FetchStrategy modelFetchStrategy, + Set configurations, Consumer> postProcessor) { + super("Loading tasks of all projects"); + this.modelRepositoryProvider = Preconditions.checkNotNull(modelRepositoryProvider); + this.processStreamsProvider = Preconditions.checkNotNull(processStreamsProvider); + this.modelFetchStrategy = Preconditions.checkNotNull(modelFetchStrategy); + this.configurations = ImmutableSet.copyOf(configurations); + this.postProcessor = Preconditions.checkNotNull(postProcessor); + } + + @Override + protected IStatus run(final IProgressMonitor monitor) { + monitor.beginTask("Load tasks of all projects", this.configurations.size()); + try { + // prepare a job listener that is notified for each job that has + // finished and then counts down the latch + final CountDownLatch latch = new CountDownLatch(this.configurations.size()); + JobChangeAdapter jobListener = new JobChangeAdapter() { + + @Override + public void done(IJobChangeEvent event) { + latch.countDown(); + monitor.worked(1); + } + }; + + // in parallel, run a job for each project configuration to load + for (ProjectConfiguration configuration : this.configurations) { + LoadEclipseGradleBuildJob loadProjectJob = new LoadEclipseGradleBuildJob(this.modelRepositoryProvider, this.processStreamsProvider, this.modelFetchStrategy, + configuration, this.postProcessor); + loadProjectJob.addJobChangeListener(jobListener); + loadProjectJob.schedule(); + } + + // block until all project load jobs have finished successfully or failed, + // canceling this job will trigger the cancellation of all jobs scheduled by this job + try { + latch.await(); + } catch (InterruptedException e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Loading the tasks of all projects failed.", e); + } + + // everything went well + return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS; + } finally { + monitor.done(); + } + } + + @Override + protected void canceling() { + // cancel all running LoadEclipseGradleBuildJob instances, + // assuming all these 'child' jobs have been scheduled by this 'parent' job + Job.getJobManager().cancel(LoadEclipseGradleBuildJob.class.getName()); + } + + @Override + public boolean belongsTo(Object family) { + return getJobFamilyName().equals(family); + } + + @Override + public boolean shouldRun() { + // if another job of this type is already scheduled, then + // we see 2 jobs by that name in the job manager + // (the current job gets registered before shouldRun() is called) + return Job.getJobManager().find(getJobFamilyName()).length <= 1; + } + + private String getJobFamilyName() { + return LoadEclipseGradleBuildsJob.class.getName(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportConfiguration.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportConfiguration.java new file mode 100644 index 000000000..1ec5b6d92 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportConfiguration.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.projectimport; + +import java.io.File; +import java.util.List; + +import com.gradleware.tooling.eclipse.core.util.collections.CollectionsUtils; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper; +import com.gradleware.tooling.toolingclient.GradleDistribution; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; +import com.gradleware.tooling.toolingutils.binding.Property; +import com.gradleware.tooling.toolingutils.binding.Validator; +import com.gradleware.tooling.toolingutils.binding.Validators; + +/** + * Serves as the data model of the project import wizard. + */ +public final class ProjectImportConfiguration { + + private final Property projectDir; + private final Property gradleDistribution; + private final Property gradleUserHome; + private final Property javaHome; + private final Property jvmArguments; + private final Property arguments; + + public ProjectImportConfiguration() { + this(Validators. noOp(), Validators. noOp(), Validators. noOp(), Validators. noOp(), Validators. noOp(), Validators + . noOp()); + } + + public ProjectImportConfiguration(Validator projectDirValidator, Validator gradleDistributionValidator, + Validator gradleUserHomeValidator, Validator javaHomeValidator, Validator jvmArgumentsValidator, Validator argumentsValidator) { + this.projectDir = Property.create(projectDirValidator); + this.gradleDistribution = Property.create(gradleDistributionValidator); + this.gradleUserHome = Property.create(gradleUserHomeValidator); + this.javaHome = Property.create(javaHomeValidator); + this.jvmArguments = Property.create(jvmArgumentsValidator); + this.arguments = Property.create(argumentsValidator); + } + + public Property getProjectDir() { + return this.projectDir; + } + + public void setProjectDir(File projectDir) { + this.projectDir.setValue(projectDir); + } + + public Property getGradleDistribution() { + return this.gradleDistribution; + } + + public void setGradleDistribution(GradleDistributionWrapper gradleDistribution) { + this.gradleDistribution.setValue(gradleDistribution); + } + + public Property getGradleUserHome() { + return this.gradleUserHome; + } + + public void setGradleUserHome(File gradleUserHome) { + this.gradleUserHome.setValue(gradleUserHome); + } + + public Property getJavaHome() { + return this.javaHome; + } + + public void setJavaHome(File javaHome) { + this.javaHome.setValue(javaHome); + } + + public Property getJvmArguments() { + return this.jvmArguments; + } + + public void setJvmArguments(String jvmArguments) { + this.jvmArguments.setValue(jvmArguments); + } + + public Property getArguments() { + return this.arguments; + } + + public void setArguments(String arguments) { + this.arguments.setValue(arguments); + } + + public FixedRequestAttributes toFixedAttributes() { + File projectDir = getProjectDir().getValue(); + GradleDistribution gradleDistribution = getGradleDistribution().getValue().toGradleDistribution(); + File gradleUserHome = getGradleUserHome().getValue(); + File javaHome = getJavaHome().getValue(); + List jvmArguments = CollectionsUtils.splitBySpace(getJvmArguments().getValue()); + List arguments = CollectionsUtils.splitBySpace(getArguments().getValue()); + + return new FixedRequestAttributes(projectDir, gradleUserHome, gradleDistribution, javaHome, jvmArguments, arguments); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportJob.java new file mode 100644 index 000000000..9e18dd69b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectImportJob.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.projectimport; + +import java.io.File; +import java.util.List; + +import com.gradleware.tooling.eclipse.core.gradle.Specs; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.launching.JavaRuntime; +import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.ProgressListener; +import org.gradle.tooling.TestProgressListener; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradleNature; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.util.progress.DelegatingProgressListener; +import com.gradleware.tooling.eclipse.core.util.progress.ToolingApiWorkspaceJob; +import com.gradleware.tooling.eclipse.core.workspace.ClasspathDefinition; +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations; +import com.gradleware.tooling.toolingmodel.OmniEclipseGradleBuild; +import com.gradleware.tooling.toolingmodel.OmniEclipseProject; +import com.gradleware.tooling.toolingmodel.OmniEclipseProjectDependency; +import com.gradleware.tooling.toolingmodel.OmniEclipseSourceDirectory; +import com.gradleware.tooling.toolingmodel.OmniExternalDependency; +import com.gradleware.tooling.toolingmodel.repository.FetchStrategy; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; +import com.gradleware.tooling.toolingmodel.repository.ModelRepository; +import com.gradleware.tooling.toolingmodel.repository.TransientRequestAttributes; +import com.gradleware.tooling.toolingmodel.util.Pair; + +/** + * Imports a Gradle project into Eclipse using the project import coordinates given by a {@code ProjectImportConfiguration} instance. + */ +public final class ProjectImportJob extends ToolingApiWorkspaceJob { + + private final FixedRequestAttributes fixedAttributes; + + public ProjectImportJob(ProjectImportConfiguration configuration) { + super("Importing project"); + + this.fixedAttributes = configuration.toFixedAttributes(); + + // explicitly show a dialog with the progress while the import is in process + setUser(true); + } + + @Override + public IStatus runInWorkspace(IProgressMonitor monitor) { + try { + importProject(monitor); + return Status.OK_STATUS; + } catch (BuildCancelledException e) { + // if the job was cancelled by the user, do not show an error dialog + CorePlugin.logger().info(e.getMessage()); + return Status.CANCEL_STATUS; + } catch (Exception e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Importing the project failed.", e); + } finally { + monitor.done(); + } + } + + public void importProject(IProgressMonitor monitor) { + monitor.beginTask("Import Gradle Project", 100); + + OmniEclipseGradleBuild eclipseGradleBuild = fetchEclipseGradleBuild(new SubProgressMonitor(monitor, 50)); + OmniEclipseProject rootProject = eclipseGradleBuild.getRootEclipseProject(); + List allProjects = rootProject.getAll(); + for (OmniEclipseProject project : allProjects) { + importProject(project, rootProject, new SubProgressMonitor(monitor, 50 / allProjects.size())); + } + } + + private OmniEclipseGradleBuild fetchEclipseGradleBuild(IProgressMonitor monitor) { + monitor.beginTask("Load Eclipse Project", IProgressMonitor.UNKNOWN); + try { + ProcessStreams streams = CorePlugin.processStreamsProvider().getBackgroundJobProcessStreams(); + List listeners = ImmutableList.of(new DelegatingProgressListener(monitor)); + TransientRequestAttributes transientAttributes = new TransientRequestAttributes(false, streams.getOutput(), streams.getError(), null, listeners, ImmutableList.of(), getToken()); + ModelRepository repository = CorePlugin.modelRepositoryProvider().getModelRepository(this.fixedAttributes); + return repository.fetchEclipseGradleBuild(transientAttributes, FetchStrategy.FORCE_RELOAD); + } finally { + monitor.done(); + } + } + + private void importProject(OmniEclipseProject project, OmniEclipseProject rootProject, IProgressMonitor monitor) { + monitor.beginTask("Import project " + project.getName(), 3); + try { + WorkspaceOperations workspaceOperations = CorePlugin.workspaceOperations(); + + // create a new project in the Eclipse workspace for the current Gradle project + IProject workspaceProject = workspaceOperations.createProject(project.getName(), project.getProjectDirectory(), collectChildProjectLocations(project), + ImmutableList.of(GradleNature.ID), new SubProgressMonitor(monitor, 1)); + + // persist the Gradle-specific configuration in the Eclipse project's .settings folder + ProjectConfiguration projectConfiguration = ProjectConfiguration.from(this.fixedAttributes, project); + CorePlugin.projectConfigurationManager().saveProjectConfiguration(projectConfiguration, workspaceProject); + + // if the current Gradle project is a Java project, configure the Java nature, + // the classpath, and the source paths + if (isJavaProject(project)) { + ClasspathDefinition classpath = collectClasspath(project, rootProject); + workspaceOperations.createJavaProject(workspaceProject, classpath, new SubProgressMonitor(monitor, 1)); + workspaceOperations.refresh(workspaceProject, new SubProgressMonitor(monitor, 1)); + } + } finally { + monitor.done(); + } + } + + private boolean isJavaProject(OmniEclipseProject modelProject) { + return !modelProject.getSourceDirectories().isEmpty(); + } + + private List collectChildProjectLocations(OmniEclipseProject project) { + return FluentIterable.from(project.getChildren()).transform(new Function() { + + @Override + public File apply(OmniEclipseProject project) { + return project.getProjectDirectory(); + } + }).toList(); + } + + private ClasspathDefinition collectClasspath(OmniEclipseProject project, OmniEclipseProject rootProject) { + List> jars = collectJarDependencies(project); + List projects = collectProjectDependencies(project, rootProject); + List sources = collectSourceDirectories(project); + IPath jre = collectDefaultJreLocation(); + return new ClasspathDefinition(jars, projects, sources, jre); + } + + private ImmutableList> collectJarDependencies(OmniEclipseProject project) { + return FluentIterable.from(project.getExternalDependencies()).transform(new Function>() { + + @Override + public Pair apply(OmniExternalDependency dependency) { + IPath jar = Path.fromOSString(dependency.getFile().getAbsolutePath()); + IPath sourceJar = dependency.getSource() != null ? Path.fromOSString(dependency.getSource().getAbsolutePath()) : null; + return new Pair(jar, sourceJar); + } + }).toList(); + } + + private ImmutableList collectProjectDependencies(OmniEclipseProject project, final OmniEclipseProject rootProject) { + return FluentIterable.from(project.getProjectDependencies()).transform(new Function() { + + @Override + public IPath apply(OmniEclipseProjectDependency dependency) { + OmniEclipseProject dependentProject = rootProject.tryFind(Specs.eclipseProjectMatchesProjectPath(dependency.getTargetProjectPath())).get(); + return new Path("/" + dependentProject.getName()); + } + }).toList(); + } + + private ImmutableList collectSourceDirectories(OmniEclipseProject project) { + return FluentIterable.from(project.getSourceDirectories()).transform(new Function() { + + @Override + public String apply(OmniEclipseSourceDirectory directory) { + return directory.getPath(); + } + }).toList(); + } + + private IPath collectDefaultJreLocation() { + return JavaRuntime.getDefaultJREContainerEntry().getPath(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectPreviewJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectPreviewJob.java new file mode 100644 index 000000000..adb0e219e --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/projectimport/ProjectPreviewJob.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.projectimport; + +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.gradle.tooling.BuildCancelledException; +import org.gradle.tooling.ProgressListener; +import org.gradle.tooling.TestProgressListener; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.FutureCallback; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.util.progress.ToolingApiJob; +import com.gradleware.tooling.toolingmodel.OmniBuildEnvironment; +import com.gradleware.tooling.toolingmodel.OmniGradleBuildStructure; +import com.gradleware.tooling.toolingmodel.repository.FetchStrategy; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; +import com.gradleware.tooling.toolingmodel.repository.ModelRepository; +import com.gradleware.tooling.toolingmodel.repository.TransientRequestAttributes; +import com.gradleware.tooling.toolingmodel.util.Pair; + +/** + * A job that fetches the models required for the project import preview. + */ +public final class ProjectPreviewJob extends ToolingApiJob { + + private final FixedRequestAttributes fixedAttributes; + private final TransientRequestAttributes transientAttributes; + + private Optional> result; + + public ProjectPreviewJob(ProjectImportConfiguration configuration, List listeners, + final FutureCallback>> resultHandler) { + super("Loading project preview"); + + this.fixedAttributes = configuration.toFixedAttributes(); + ProcessStreams stream = CorePlugin.processStreamsProvider().getBackgroundJobProcessStreams(); + this.transientAttributes = new TransientRequestAttributes(false, stream.getOutput(), stream.getError(), null, listeners, ImmutableList. of(), getToken()); + + this.result = null; + + addJobChangeListener(new JobChangeAdapter() { + + @Override + public void done(IJobChangeEvent event) { + if (event.getResult().isOK()) { + resultHandler.onSuccess(ProjectPreviewJob.this.result); + } else { + resultHandler.onFailure(event.getResult().getException()); + } + } + }); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + this.result = previewProject(monitor); + return Status.OK_STATUS; + } catch (BuildCancelledException e) { + // if the job was cancelled by the user, do not show an error dialog + CorePlugin.logger().info(e.getMessage()); + this.result = Optional.absent(); + return Status.CANCEL_STATUS; + } catch (Exception e) { + this.result = null; + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Loading the project preview failed.", e); + } finally { + monitor.done(); + } + } + + public Optional> previewProject(IProgressMonitor monitor) { + monitor.beginTask("Load project preview", 10); + + OmniBuildEnvironment buildEnvironment = fetchBuildEnvironment(new SubProgressMonitor(monitor, 2)); + OmniGradleBuildStructure gradleBuildStructure = fetchGradleBuildStructure(new SubProgressMonitor(monitor, 8)); + return Optional.of(new Pair(buildEnvironment, gradleBuildStructure)); + } + + private OmniBuildEnvironment fetchBuildEnvironment(IProgressMonitor monitor) { + monitor.beginTask("Load Gradle Build Environment", IProgressMonitor.UNKNOWN); + try { + ModelRepository repository = CorePlugin.modelRepositoryProvider().getModelRepository(this.fixedAttributes); + return repository.fetchBuildEnvironment(this.transientAttributes, FetchStrategy.FORCE_RELOAD); + } finally { + monitor.done(); + } + } + + private OmniGradleBuildStructure fetchGradleBuildStructure(IProgressMonitor monitor) { + monitor.beginTask("Load Gradle Project Structure", IProgressMonitor.UNKNOWN); + try { + ModelRepository repository = CorePlugin.modelRepositoryProvider().getModelRepository(this.fixedAttributes); + return repository.fetchGradleBuildStructure(this.transientAttributes, FetchStrategy.FORCE_RELOAD); + } finally { + monitor.done(); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSession.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSession.java new file mode 100644 index 000000000..49352e0a4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSession.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress; + +import org.gradle.tooling.TestProgressEvent; + +/** + * Decorates a {@code TestRunSession} that can be started, finished, and fed with {@code TestProgressEvent} instances that mark test execution progress. + */ +public interface GradleTestRunSession { + + /** + * Notifies the underlying {@code TestRunSession} that a new Gradle build with potential test execution has started. + */ + void start(); + + /** + * Notifies the underlying {@code TestRunSession} that the previously started Gradle build has finished. + */ + void finish(); + + /** + * Converts and forwards the given test progress event to the underlying {@code TestRunSession} instance. + * + * @param event the event to process + */ + void process(TestProgressEvent event); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSessionFactory.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSessionFactory.java new file mode 100644 index 000000000..11e987f72 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/GradleTestRunSessionFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress; + +import com.gradleware.tooling.eclipse.core.testprogress.internal.DefaultGradleTestRunSession; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.jdt.core.IJavaProject; + +/** + * Factory to create {@link GradleTestRunSession} instances. + */ +public final class GradleTestRunSessionFactory { + + private GradleTestRunSessionFactory() { + } + + /** + * Creates a new test session and associates it with the given Eclipse Java project. + * + * @param launch the launch instance as part of which the tests are executed in Gradle + * @param project the associated Java project, can be null + * @return the new instance + */ + public static GradleTestRunSession newSession(ILaunch launch, IJavaProject project) { + return new DefaultGradleTestRunSession(launch, project); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/DefaultGradleTestRunSession.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/DefaultGradleTestRunSession.java new file mode 100644 index 000000000..a4d9bfa59 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/DefaultGradleTestRunSession.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.testprogress.GradleTestRunSession; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.internal.junit.JUnitCorePlugin; +import org.eclipse.jdt.internal.junit.model.ITestSessionListener; +import org.eclipse.jdt.internal.junit.model.TestCaseElement; +import org.eclipse.jdt.internal.junit.model.TestElement.Status; +import org.eclipse.jdt.internal.junit.model.TestRunSession; +import org.eclipse.jdt.internal.junit.model.TestSuiteElement; +import org.gradle.tooling.TestDescriptor; +import org.gradle.tooling.TestFailedEvent; +import org.gradle.tooling.TestFailure; +import org.gradle.tooling.TestProgressEvent; +import org.gradle.tooling.TestSkippedEvent; +import org.gradle.tooling.TestStartedEvent; +import org.gradle.tooling.TestSucceededEvent; +import org.gradle.tooling.TestSuccess; +import org.gradle.tooling.TestSuiteFailedEvent; +import org.gradle.tooling.TestSuiteSkippedEvent; +import org.gradle.tooling.TestSuiteStartedEvent; +import org.gradle.tooling.TestSuiteSucceededEvent; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; +import java.util.Map; + +/** + * Default implementation of the {@code GradleTestRunSession} interface. Converts and delegates all received + * test progress information in a format understood by the Eclipse Test Runner infrastructure. + */ +@SuppressWarnings("restriction") +public final class DefaultGradleTestRunSession extends TestRunSession implements GradleTestRunSession { + + private final TestSessionListenerContainer testSessionListeners; + private final TestRunSessionStateTracker testSessionState; + private final TestCounter testCounter; + private final Map testSuites; + private final Map testCases; + + public DefaultGradleTestRunSession(ILaunch launch, IJavaProject project) { + super(launch, project, 9999); + this.testSessionListeners = new TestSessionListenerContainer(); + this.testSessionState = new TestRunSessionStateTracker(); + this.testCounter = new TestCounter(); + this.testSuites = Maps.newHashMap(); + this.testCases = Maps.newHashMap(); + } + + @Override + public void start() { + JUnitCorePlugin.getModel().addTestRunSession(this); + JUnitCorePlugin.getModel().start(); + this.testSessionState.sessionStarted(); + this.testSessionListeners.notifySessionStarted(); + this.testSessionListeners.notifyTestingBegins(); + } + + @Override + public void finish() { + this.testSessionState.sessionFinished(); + this.testSessionListeners.notifySessionEnded(this.testSessionState.getDuration()); + } + + @Override + public void process(TestProgressEvent event) { + //CHECKSTYLE:OFF, required due to false negative in Checkstyle + // suites + if (event instanceof TestSuiteStartedEvent) { + suiteStarted((TestSuiteStartedEvent) event); + } else if (event instanceof TestSuiteSucceededEvent) { + suiteSucceeded((TestSuiteSucceededEvent) event); + } else if (event instanceof TestSuiteFailedEvent) { + suiteFailed((TestSuiteFailedEvent) event); + } else if (event instanceof TestSuiteSkippedEvent) { + suiteSkipped((TestSuiteSkippedEvent) event); + } + + // tests + else if (event instanceof TestStartedEvent) { + testStarted((TestStartedEvent) event); + } else if (event instanceof TestSucceededEvent) { + testSucceeded((TestSucceededEvent) event); + } else if (event instanceof TestFailedEvent) { + testFailed((TestFailedEvent) event); + } else if (event instanceof TestSkippedEvent) { + testSkipped((TestSkippedEvent) event); + } + //CHECKSTYLE:ON + } + + private void suiteStarted(TestSuiteStartedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + Optional parentCandidate = Optional.fromNullable(descriptor.getParent()); + TestSuiteElement parent = parentCandidate.isPresent() ? this.testSuites.get(parentCandidate.get()) : getTestRoot(); + TestSuiteElement testSuite = new TestSuiteElement(parent, String.valueOf(System.identityHashCode(descriptor)), descriptor.getName(), 0); + testSuite.setStatus(Status.RUNNING); + this.testSuites.put(descriptor, testSuite); + this.testSessionListeners.notifySuiteStarted(testSuite); + } + + private void suiteSucceeded(TestSuiteSucceededEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestSuiteElement testSuite = this.testSuites.get(descriptor); + if (testSuite == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test suite %s.", descriptor.getName())); + } + + TestSuccess result = event.getResult(); + long elapsedTimeMilliseconds = result.getEndTime() - result.getStartTime(); + testSuite.setElapsedTimeInSeconds(elapsedTimeMilliseconds / 1000d); + testSuite.setStatus(Status.OK); + this.testSessionListeners.notifySuiteFinished(testSuite, Status.OK, null, null, null); + } + + private void suiteFailed(TestSuiteFailedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestSuiteElement testSuite = this.testSuites.get(descriptor); + if (testSuite == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test suite %s.", descriptor.getName())); + } + + TestFailure result = event.getResult(); + String trace = toString(result.getExceptions()); + long elapsedTimeMilliseconds = result.getEndTime() - result.getStartTime(); + testSuite.setElapsedTimeInSeconds(elapsedTimeMilliseconds / 1000d); + testSuite.setStatus(Status.FAILURE, trace, null, null); + this.testSessionListeners.notifySuiteFinished(testSuite, Status.FAILURE, trace, null, null); + } + + private void suiteSkipped(TestSuiteSkippedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestSuiteElement testSuite = this.testSuites.get(descriptor); + if (testSuite == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test suite %s.", descriptor.getName())); + } + + testSuite.setStatus(Status.NOT_RUN); + this.testSessionListeners.notifySuiteFinished(testSuite, Status.NOT_RUN, null, null, null); + } + + private void testStarted(TestStartedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestSuiteElement parentSuite = this.testSuites.get(descriptor.getParent()); + if (parentSuite == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find parent for test %s.", descriptor.getName())); + } + + TestCaseElement testCase = new TestCaseElement(parentSuite, String.valueOf(System.identityHashCode(descriptor)), descriptor.getName() + "(" + descriptor.getClassName() + ")"); + testCase.setStatus(Status.RUNNING); + this.testCases.put(descriptor, testCase); + this.testSessionListeners.notifyTestStarted(testCase); + this.testCounter.incrementStarted(); + } + + private void testSucceeded(TestSucceededEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestCaseElement testCase = this.testCases.get(descriptor); + if (testCase == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test %s.", descriptor.getName())); + } + + TestSuccess result = event.getResult(); + long elapsedTimeMilliseconds = result.getEndTime() - result.getStartTime(); + testCase.setElapsedTimeInSeconds(elapsedTimeMilliseconds / 1000d); + testCase.setStatus(Status.OK); + this.testSessionListeners.notifyTestEnded(testCase); + this.testCounter.incrementSuccess(); + } + + private void testFailed(TestFailedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestCaseElement testCase = this.testCases.get(descriptor); + if (testCase == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test %s.", descriptor.getName())); + } + + TestFailure result = event.getResult(); + String trace = toString(result.getExceptions()); + long elapsedTimeMilliseconds = result.getEndTime() - result.getStartTime(); + testCase.setElapsedTimeInSeconds(elapsedTimeMilliseconds / 1000d); + testCase.setStatus(Status.FAILURE, trace, null, null); + this.testSessionListeners.notifyTestEnded(testCase); + this.testCounter.incrementFailure(); + } + + private void testSkipped(TestSkippedEvent event) { + Preconditions.checkNotNull(event); + Preconditions.checkNotNull(event.getDescriptor()); + + TestDescriptor descriptor = event.getDescriptor(); + TestCaseElement testCase = this.testCases.get(descriptor); + if (testCase == null) { + throw new GradlePluginsRuntimeException(String.format("Cannot find test %s.", descriptor.getName())); + } + + testCase.setStatus(Status.NOT_RUN); + this.testSessionListeners.notifyTestEnded(testCase); + this.testCounter.incrementIgnored(); + } + + @Override + public void addTestSessionListener(ITestSessionListener listener) { + // the constructor of the superclass tries to add something + // at that time the listeners field is still null + if (this.testSessionListeners != null) { + this.testSessionListeners.add(listener); + } + } + + @Override + public void removeTestSessionListener(ITestSessionListener listener) { + this.testSessionListeners.remove(listener); + } + + @Override + public int getTotalCount() { + // since tests are added dynamically while Gradle executes tests, the total number + // of tests is the number of tests that have been started so far + return this.testCounter.getStartedCount(); + } + + @Override + public int getStartedCount() { + // to have some progression in the ui, we display the number of started tasks as + // the number of finished tasks + return this.testCounter.getFinishedCount(); + } + + @Override + public int getFailureCount() { + return this.testCounter.getFailureCount(); + } + + @Override + public int getErrorCount() { + return this.testCounter.getErrorCount(); + } + + @Override + public int getIgnoredCount() { + return this.testCounter.getIgnoredCount(); + } + + @Override + public boolean isRunning() { + return this.testSessionState.isRunning(); + } + + @Override + public boolean isStopped() { + return this.testSessionState.isStopped(); + } + + @Override + public synchronized void swapIn() { + } + + @Override + public void swapOut() { + } + + private static String toString(List exceptions) { + StringWriter stackTrace = new StringWriter(); + PrintWriter writer = new PrintWriter(stackTrace); + for (Throwable t : exceptions) { + t.printStackTrace(writer); + } + return stackTrace.toString(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounter.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounter.java new file mode 100644 index 000000000..44b54bdba --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestCounter.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal; + +/** + * Keeps track of the total number of actual tests that been started, succeeded, failed, skipped. + */ +final class TestCounter { + + int started; + int success; + int failure; + int error; + int ignored; + + int getStartedCount() { + return this.started; + } + + public int getFinishedCount() { + return this.success + this.failure + this.error + this.ignored; + } + + int getFailureCount() { + return this.failure; + } + + int getErrorCount() { + return this.error; + } + + int getIgnoredCount() { + return this.ignored; + } + + void incrementStarted() { + this.started++; + } + + void incrementSuccess() { + this.success++; + } + + void incrementFailure() { + this.failure++; + } + + void incrementIgnored() { + this.ignored++; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTracker.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTracker.java new file mode 100644 index 000000000..f4862b184 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestRunSessionStateTracker.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal; + +/** + * Keeps track of the current state of a test run session. + */ +final class TestRunSessionStateTracker { + + /** + * Enumerates the different states a test runner session can be in. + */ + enum TestRunSessionState { + UNKNOWN, RUNNING, STOPPED, FINISHED + } + + private TestRunSessionState currentState; + private long startTime; + private long endTime; + + TestRunSessionStateTracker() { + this.currentState = TestRunSessionState.UNKNOWN; + this.startTime = -1; + this.endTime = -1; + } + + TestRunSessionState getCurrentState() { + return this.currentState; + } + + void sessionStarted() { + this.startTime = System.currentTimeMillis(); + this.currentState = TestRunSessionState.RUNNING; + } + + void sessionFinished() { + this.endTime = System.currentTimeMillis(); + this.currentState = TestRunSessionState.FINISHED; + } + + boolean isRunning() { + return this.currentState == TestRunSessionState.RUNNING; + } + + boolean isStopped() { + return this.currentState == TestRunSessionState.STOPPED; + } + + long getDuration() { + return this.endTime - this.startTime; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainer.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainer.java new file mode 100644 index 000000000..67140607b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/testprogress/internal/TestSessionListenerContainer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.testprogress.internal; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.internal.junit.model.ITestSessionListener; +import org.eclipse.jdt.internal.junit.model.TestCaseElement; +import org.eclipse.jdt.internal.junit.model.TestElement; + +/** + * Notifies all registered {@code ITestSessionListener} instances about the test execution progress. This implementation hides the intricacies of how certain test (suite) states + * can be triggered in the Eclipse Test Runner. + */ +@SuppressWarnings("restriction") +final class TestSessionListenerContainer { + + private final List listeners = new CopyOnWriteArrayList(); + + void notifySessionStarted() { + for (ITestSessionListener listener : this.listeners) { + listener.sessionStarted(); + } + } + + void notifySessionEnded(long elapsedTimeInSeconds) { + for (ITestSessionListener listener : this.listeners) { + listener.sessionEnded(elapsedTimeInSeconds); + } + } + + void notifyTestingBegins() { + for (ITestSessionListener listener : this.listeners) { + listener.runningBegins(); + } + } + + void notifySuiteStarted(TestElement element) { + waitForUiThreadToFinishUpdatingTestView(); + for (ITestSessionListener listener : this.listeners) { + // there is no API to explicitly start a test suite, but adding the test suite suffices + listener.testAdded(element); + } + } + + void notifySuiteFinished(TestElement element, TestElement.Status status, String trace, String expected, String actual) { + for (ITestSessionListener listener : this.listeners) { + // if an OK status is passed and the [trace, expected, actual] parameters are null the + // suite will be actually marked as success + listener.testFailed(element, status, trace, expected, actual); + } + } + + void notifyTestStarted(TestCaseElement element) { + waitForUiThreadToFinishUpdatingTestView(); + for (ITestSessionListener listener : this.listeners) { + // starting the test will implicitly add the the test + listener.testStarted(element); + } + } + + void notifyTestEnded(TestCaseElement element) { + for (ITestSessionListener listener : this.listeners) { + listener.testEnded(element); + } + } + + void add(ITestSessionListener listener) { + this.listeners.add(listener); + } + + void remove(ITestSessionListener listener) { + this.listeners.remove(listener); + } + + private static void waitForUiThreadToFinishUpdatingTestView() { + // the test view gets updated in the (separate) UI thread, this can lead to + // race conditions with some suites/tests not being added to the view + // briefly sleeping acts as a current work-around + try { + TimeUnit.MILLISECONDS.sleep(50); + } catch (InterruptedException e) { + // ignore + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/collections/CollectionsUtils.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/collections/CollectionsUtils.java new file mode 100644 index 000000000..65d66fcb3 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/collections/CollectionsUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.collections; + +import java.util.List; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +/** + * Contains helper methods related to Collections operations. + */ +public final class CollectionsUtils { + + private static final char SPACE = ' '; + + private CollectionsUtils() { + } + + /** + * Splits the given string for each space that is found. + * + * @param string the string to split, can be null + * @return the split string with each segment being an element of the returned list, never null + */ + public static ImmutableList splitBySpace(String string) { + return Strings.isNullOrEmpty(string) ? ImmutableList. of() : ImmutableList.copyOf(Splitter.on(SPACE).omitEmptyStrings().splitToList(string)); + } + + /** + * Splits the given string for each space that is found. + * + * @param elements the elements to join, must not be null + * @return the joined elements returned as a single string with each element separated by a + * space + */ + public static String joinWithSpace(List elements) { + return Joiner.on(SPACE).join(elements); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/file/FileUtils.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/file/FileUtils.java new file mode 100644 index 000000000..e2ed127e0 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/file/FileUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.file; + +import java.io.File; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; + +/** + * Contains helper methods related to file operations. + */ +public final class FileUtils { + + private FileUtils() { + } + + /** + * Derives a {@code File} instance with absolute path from the specified path. + * + * @param path the relative or absolute path of the {@code File} instance to derive + * @return the absolute {@code File} if the path is not {@code null} or empty, otherwise + * {@link Optional#absent()} + */ + public static Optional getAbsoluteFile(String path) { + if (Strings.isNullOrEmpty(path)) { + return Optional.absent(); + } else { + return Optional.of(new File(path.trim()).getAbsoluteFile()); + } + } + + /** + * Derives the absolute path from the specified {@code File} instance. + * + * @param file the file from which to get the absolute path + * @return the absolute path if the file is not {@code null}, otherwise + * {@link Optional#absent()} + */ + public static Optional getAbsolutePath(File file) { + if (file == null) { + return Optional.absent(); + } else { + return Optional.of(file.getAbsolutePath()); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/logging/EclipseLogger.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/logging/EclipseLogger.java new file mode 100644 index 000000000..8e22d36de --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/logging/EclipseLogger.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.logging; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +import com.gradleware.tooling.eclipse.core.Logger; + +/** + * Logs to the Eclipse logging infrastructure. + */ +public final class EclipseLogger implements Logger { + + private final ILog log; + private final String pluginId; + + public EclipseLogger(ILog log, String pluginId) { + this.log = log; + this.pluginId = pluginId; + } + + @Override + public void info(String message) { + this.log.log(new Status(IStatus.INFO, this.pluginId, message)); + } + + @Override + public void warn(String message) { + this.log.log(new Status(IStatus.WARNING, this.pluginId, message)); + } + + @Override + public void error(String message) { + this.log.log(new Status(IStatus.ERROR, this.pluginId, message)); + } + + @Override + public void error(String message, Throwable t) { + this.log.log(new Status(IStatus.ERROR, this.pluginId, message, t)); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/DelegatingProgressListener.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/DelegatingProgressListener.java new file mode 100644 index 000000000..dc7837efc --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/DelegatingProgressListener.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.progress; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.gradle.tooling.ProgressEvent; +import org.gradle.tooling.ProgressListener; + +/** + * {@link ProgressListener} implementation which delegates all Gradle {@link ProgressEvent}s to a + * target Eclipse monitor. + *

+ * While there is no target progress monitor set, the most recent progress message is preserved and + * automatically assigned once a monitor is set. + * + * @see IProgressMonitor + */ +public final class DelegatingProgressListener implements ProgressListener { + + public final Object LOCK = new Object(); + + private IProgressMonitor monitor; + private String lastMessage; + + /** + * Creates a new progress listener without an initial target Eclipse monitor. + */ + public DelegatingProgressListener() { + this.monitor = null; + } + + /** + * Creates a new progress listener with the given Eclipse target monitor. + * + * @param monitor the Eclipse target monitor to delegate to + */ + public DelegatingProgressListener(IProgressMonitor monitor) { + this.monitor = monitor; + } + + /** + * Sets the given Eclipse target monitor. + * + * @param monitor the Eclipse target monitor to delegate to + */ + public void setMonitor(IProgressMonitor monitor) { + synchronized (this.LOCK) { + this.monitor = monitor; + if (this.monitor != null) { + this.monitor.subTask(this.lastMessage); + } + } + } + + /** + * Delegates the event to the current Eclipse target monitor. + * + * @param event the event to delegate + */ + @Override + public void statusChanged(ProgressEvent event) { + synchronized (this.LOCK) { + if (this.monitor != null) { + // if the monitor is present, then create a sub task for each progress event + this.monitor.subTask(event.getDescription()); + } else { + // if a monitor is not present, preserve the last event + this.lastMessage = event.getDescription(); + } + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiJob.java new file mode 100644 index 000000000..9ef6a2807 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiJob.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.progress; + +import org.eclipse.core.runtime.jobs.Job; +import org.gradle.tooling.CancellationToken; +import org.gradle.tooling.CancellationTokenSource; +import org.gradle.tooling.GradleConnector; + +/** + * Base class for cancellable jobs that invoke the Gradle Tooling API. + */ +public abstract class ToolingApiJob extends Job { + + private final CancellationTokenSource tokenSource; + + /** + * Creates a new job with the specified name. The job name is a human-readable value that is + * displayed to users. The name does not need to be unique, but it must not be {@code null}. A + * token for Gradle build cancellation is created. + * + * @param name the name of the job + */ + protected ToolingApiJob(String name) { + super(name); + this.tokenSource = GradleConnector.newCancellationTokenSource(); + } + + protected CancellationToken getToken() { + return this.tokenSource.token(); + } + + @Override + protected void canceling() { + this.tokenSource.cancel(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiWorkspaceJob.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiWorkspaceJob.java new file mode 100644 index 000000000..226a5857d --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/ToolingApiWorkspaceJob.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.progress; + +import org.eclipse.core.resources.WorkspaceJob; +import org.gradle.tooling.CancellationToken; +import org.gradle.tooling.CancellationTokenSource; +import org.gradle.tooling.GradleConnector; + +/** + * Base class for cancellable workspace jobs that invoke the Gradle Tooling API. + */ +public abstract class ToolingApiWorkspaceJob extends WorkspaceJob { + + private final CancellationTokenSource tokenSource; + + /** + * Creates a new job with the specified name. The job name is a human-readable value that is + * displayed to users. The name does not need to be unique, but it must not be {@code null}. A + * token for Gradle build cancellation is created. + * + * @param name the name of the job + */ + protected ToolingApiWorkspaceJob(String name) { + super(name); + this.tokenSource = GradleConnector.newCancellationTokenSource(); + } + + protected CancellationToken getToken() { + return this.tokenSource.token(); + } + + @Override + protected void canceling() { + this.tokenSource.cancel(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/UniqueJobRule.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/UniqueJobRule.java new file mode 100644 index 000000000..37ab9273b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/progress/UniqueJobRule.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.progress; + +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * Simple {@link ISchedulingRule} implementation forcing only one job instance to run at once among + * all jobs with the associated rule instance applied. + */ +public final class UniqueJobRule implements ISchedulingRule { + + private UniqueJobRule() { + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return rule == this; + } + + /** + * Creates a new instance. + * + * @return the new instance + */ + public static ISchedulingRule newInstance() { + return new UniqueJobRule(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/variable/ExpressionUtils.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/variable/ExpressionUtils.java new file mode 100644 index 000000000..e58f1d4eb --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/util/variable/ExpressionUtils.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.util.variable; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.variables.IStringVariableManager; +import org.eclipse.core.variables.VariablesPlugin; + +/** + * Contains helper methods related to variable expressions. + */ +public final class ExpressionUtils { + + private static final String WORKSPACE_LOC_VARIABLE = "workspace_loc"; + + private ExpressionUtils() { + } + + /** + * Encodes the workspace location of the given Eclipse project as a variable expression. + * + * @param project the project name to encode, must not be null + * @return the project reference as a variable expression, never null + */ + public static String encodeWorkspaceLocation(IProject project) { + return getStringVariableManager().generateVariableExpression(WORKSPACE_LOC_VARIABLE, project.getFullPath().toString()); //$NON-NLS-1$ + } + + /** + * Decodes the given expression. + * + * @param expression the expression to decode, can be null + * @return the resolved expression, can be null + */ + public static String decode(String expression) throws CoreException { + return expression != null ? getStringVariableManager().performStringSubstitution(expression) : null; + } + + private static IStringVariableManager getStringVariableManager() { + return VariablesPlugin.getDefault().getStringVariableManager(); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/WorkbenchOperations.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/WorkbenchOperations.java new file mode 100644 index 000000000..d85904171 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/WorkbenchOperations.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workbench; + +/** + * Provides operations related to the Eclipse workbench. + */ +public interface WorkbenchOperations { + + /** + * Attempts to activate the Eclipse Test Runner View. + */ + void activateTestRunnerView(); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/internal/EmptyWorkbenchOperations.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/internal/EmptyWorkbenchOperations.java new file mode 100644 index 000000000..9d7783d21 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workbench/internal/EmptyWorkbenchOperations.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workbench.internal; + +import com.gradleware.tooling.eclipse.core.workbench.WorkbenchOperations; + +/** + * Empty implementation of the {@link WorkbenchOperations} interface since the core plugin does + * not know about the workbench which is a part of the UI. + */ +public final class EmptyWorkbenchOperations implements WorkbenchOperations { + + @Override + public void activateTestRunnerView() { + // do nothing + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/ClasspathDefinition.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/ClasspathDefinition.java new file mode 100644 index 000000000..df3cab305 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/ClasspathDefinition.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace; + +import java.util.List; + +import org.eclipse.core.runtime.IPath; + +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.toolingmodel.util.Pair; + +/** + * Value holder for defining the sources and classpath of a Java project. + */ +public final class ClasspathDefinition { + + /** + * Id and string representation of the path where all Gradle projects store their external + * dependencies. This path is added during the project import and the + * {@code org.eclipse.jdt.core.classpathContainerInitializer} extension populates it with the + * actual external (source and binary) jars. + */ + public static final String GRADLE_CLASSPATH_CONTAINER_ID = "com.gradleware.tooling.eclipse.core.gradleclasspathcontainer"; + + private final ImmutableList> externalDependencies; + private final ImmutableList projectDependencies; + private final ImmutableList sourceDirectories; + private final IPath jrePath; + + /** + * Creates a new instance. + * + * @param externalDependencies the path for the jar files to have on the classpath, the first + * item of each {@link Pair} points to the binary, the second to the sources jar + * @param projectDependencies the paths to the dependent local projects + * @param sourceDirectories the paths of the source folders relative ot the project + * @param jrePath the path to the Java runtime to include as a library in the project + */ + public ClasspathDefinition(List> externalDependencies, List projectDependencies, List sourceDirectories, IPath jrePath) { + this.externalDependencies = ImmutableList.copyOf(externalDependencies); + this.projectDependencies = ImmutableList.copyOf(projectDependencies); + this.sourceDirectories = ImmutableList.copyOf(sourceDirectories); + this.jrePath = jrePath; + } + + /** + * Returns the external dependencies of a given project. + * + * @return the path for the jar files to have on the classpath, the first item of each + * {@link Pair} points to the binary, the second to the sources jar + */ + public List> getExternalDependencies() { + return this.externalDependencies; + } + + /** + * Returns the project dependencies of a given project. + * + * @return the paths to the dependent local projects + */ + public List getProjectDependencies() { + return this.projectDependencies; + } + + /** + * Returns the source directories of a given project. + * + * @return the paths of the source folders relative ot the project + */ + public List getSourceDirectories() { + return this.sourceDirectories; + } + + /** + * Returns the path to the JRE to include as a library of a given project. + * + * @return the path to the Java runtime to include as a library in the project + */ + public IPath getJrePath() { + return this.jrePath; + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/WorkspaceOperations.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/WorkspaceOperations.java new file mode 100644 index 000000000..548685b54 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/WorkspaceOperations.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace; + +import java.io.File; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; + +/** + * Provides operations related to querying and modifying the Eclipse elements that exist in a + * workspace. + */ +public interface WorkspaceOperations { + + /** + * Returns all of the workspace's projects. Open and closed projects are included. + * + * @return all projects of the workspace + */ + ImmutableList getAllProjects(); + + /** + * Returns the workspace's project with the given name, if it exists. Open and closed projects + * are included. + * + * @param name the name of the project to find + * @return the matching project, otherwise {@link Optional#absent()} + */ + Optional findProjectByName(String name); + + /** + * Removes all of the workspace's projects. + * + * @param monitor the monitor to report progress on + * @throws com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException thrown if any of the deletions fails + */ + void deleteAllProjects(IProgressMonitor monitor); + + /** + * Creates a new {@link IProject} in the workspace using the specified name and location. The + * location must exist and no project with the specified name must currently exist in the + * workspace. The new project gets the specified natures applied. Those child projects whose + * specified locations are nested under this project's location are hidden through a resource + * filter. + * + * @param name the unique name of the project to create + * @param location the location of the project to import + * @param childProjectLocations the location of the child projects + * @param natureIds the nature ids to associate with the project + * @param monitor the monitor to report progress on + * @return the created project + * @throws com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException thrown if the project creation fails + */ + IProject createProject(String name, File location, List childProjectLocations, List natureIds, IProgressMonitor monitor); + + /** + * Configures an existing {@link IProject} to be a {@link IJavaProject}. + * + * @param project the project to turn into a Java project + * @param classpath the classpath definition of the project + * @param monitor the monitor to report progress on + * @return the created Java project + */ + IJavaProject createJavaProject(IProject project, ClasspathDefinition classpath, IProgressMonitor monitor); + + /** + * Refreshes project content to get it in sync with the file system. + *

+ * Useful to avoid having out-of-sync warnings showing up in the IDE. + * + * @param project the project to be refreshed + * @param monitor the monitor to report progress on + */ + void refresh(IProject project, IProgressMonitor monitor); + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/DefaultWorkspaceOperations.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/DefaultWorkspaceOperations.java new file mode 100644 index 000000000..d2029174e --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/DefaultWorkspaceOperations.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace.internal; + +import java.io.File; +import java.util.List; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.workspace.ClasspathDefinition; +import com.gradleware.tooling.eclipse.core.workspace.WorkspaceOperations; + +/** + * Default implementation of the {@link WorkspaceOperations} interface. + */ +public final class DefaultWorkspaceOperations implements WorkspaceOperations { + + @Override + public ImmutableList getAllProjects() { + return ImmutableList.copyOf(ResourcesPlugin.getWorkspace().getRoot().getProjects()); + } + + @Override + public Optional findProjectByName(final String name) { + return FluentIterable.from(getAllProjects()).firstMatch(new Predicate() { + + @Override + public boolean apply(IProject project) { + return project.getName().equals(name); + } + }); + } + + @Override + public void deleteAllProjects(IProgressMonitor monitor) { + monitor = MoreObjects.firstNonNull(monitor, new NullProgressMonitor()); + monitor.beginTask("Delete all Eclipse projects from workspace", 100); + try { + List allProjects = getAllProjects(); + for (IProject project : allProjects) { + try { + project.delete(true, new SubProgressMonitor(monitor, 100 / allProjects.size())); + } catch (Exception e) { + String message = String.format("Cannot delete project %s.", project); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + } finally { + monitor.done(); + } + } + + @Override + public IProject createProject(String name, File location, List childProjectLocations, List natureIds, IProgressMonitor monitor) { + // validate arguments + Preconditions.checkNotNull(name); + Preconditions.checkNotNull(location); + Preconditions.checkNotNull(natureIds); + Preconditions.checkArgument(!name.isEmpty(), "Project name must not be empty."); + Preconditions.checkArgument(location.exists(), String.format("Project location %s must exist.", location)); + Preconditions.checkArgument(location.isDirectory(), String.format("Project location %s must be a directory.", location)); + + monitor = MoreObjects.firstNonNull(monitor, new NullProgressMonitor()); + monitor.beginTask(String.format("Create Eclipse project %s", name), 4); + try { + // make sure no project with the specified name already exists + Preconditions.checkState(!findProjectByName(name).isPresent(), String.format("Workspace already contains project with name %s.", name)); + monitor.worked(1); + + // get an IProject instance and create the project + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IProjectDescription desc = workspace.newProjectDescription(name); + desc.setLocation(Path.fromOSString(location.getPath())); + desc.setNatureIds(natureIds.toArray(new String[natureIds.size()])); + IProject project = workspace.getRoot().getProject(name); + project.create(desc, new SubProgressMonitor(monitor, 1)); + + // attach filters to the project to hide the sub-projects of this project + ResourceFilter.attachFilters(project, childProjectLocations, new SubProgressMonitor(monitor, 1)); + + // open the project and return it + project.open(new SubProgressMonitor(monitor, 1)); + return project; + } catch (Exception e) { + String message = String.format("Cannot create Eclipse project %s.", name); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + + @Override + public IJavaProject createJavaProject(IProject project, ClasspathDefinition classpath, IProgressMonitor monitor) { + // validate arguments + Preconditions.checkNotNull(project); + Preconditions.checkNotNull(classpath); + Preconditions.checkArgument(project.isAccessible(), "Project must be open."); + + monitor = MoreObjects.firstNonNull(monitor, new NullProgressMonitor()); + monitor.beginTask(String.format("Create Eclipse Java project %s", project.getName()), 17); + try { + // add Java nature + addNature(project, JavaCore.NATURE_ID, new SubProgressMonitor(monitor, 2)); + + // create the Eclipse Java project from the plain Eclipse project + IJavaProject javaProject = JavaCore.create(project); + monitor.worked(5); + + // set up resources (sources and classpath) + setSourcesAndClasspathOnProject(javaProject, classpath, new SubProgressMonitor(monitor, 5)); + + // set up output location + IFolder outputFolder = createOutputFolder(project, new SubProgressMonitor(monitor, 1)); + javaProject.setOutputLocation(outputFolder.getFullPath(), new SubProgressMonitor(monitor, 1)); + + // avoid out-of-sync messages when the content of the .gradle folder changes upon running a Gradle build + markDotGradleFolderAsDerived(project, new SubProgressMonitor(monitor, 1)); + + // save the project configuration + javaProject.save(new SubProgressMonitor(monitor, 2), true); + + // return the created Java project + return javaProject; + } catch (Exception e) { + String message = String.format("Cannot create Eclipse Java project %s.", project.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + + private void addNature(IProject project, String natureId, IProgressMonitor monitor) { + monitor.beginTask(String.format("Add Java nature to Eclipse project %s", project.getName()), 1); + try { + // get the description + IProjectDescription description = project.getDescription(); + + // abort if the project already has the nature applied + List currentNatureIds = ImmutableList.copyOf(description.getNatureIds()); + if (currentNatureIds.contains(natureId)) { + return; + } + + // add the Gradle nature to the project + ImmutableList newIds = ImmutableList. builder().addAll(currentNatureIds).add(natureId).build(); + description.setNatureIds(newIds.toArray(new String[newIds.size()])); + + // save the updated description + project.setDescription(description, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + String message = String.format("Cannot add Java nature to Eclipse project %s.", project.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + + private IFolder createOutputFolder(IProject project, IProgressMonitor monitor) { + monitor.beginTask(String.format("Create output folder for Eclipse project %s", project.getName()), 1); + try { + IFolder outputFolder = project.getFolder("bin"); + if (!outputFolder.exists()) { + outputFolder.create(true, true, new SubProgressMonitor(monitor, 1)); + } + return outputFolder; + } catch (Exception e) { + String message = String.format("Cannot create output folder for Eclipse project %s.", project.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + + private void markDotGradleFolderAsDerived(IProject project, IProgressMonitor monitor) throws CoreException { + monitor.beginTask(String.format("Mark .gradle folder as derived for Eclipse project %s", project.getName()), 1); + try { + IFolder dotGradleFolder = project.getFolder(".gradle"); + if (dotGradleFolder.exists()) { + dotGradleFolder.setDerived(true, new SubProgressMonitor(monitor, 1)); + } + } finally { + monitor.done(); + } + } + + private void setSourcesAndClasspathOnProject(IJavaProject javaProject, ClasspathDefinition classpath, IProgressMonitor monitor) { + monitor.beginTask(String.format("Configure sources and classpath for Eclipse project %s", javaProject.getProject().getName()), 12); + try { + // create a new holder for all classpath entries + Builder entries = ImmutableList.builder(); + + // add the library with the JRE dependencies + entries.add(JavaCore.newContainerEntry(classpath.getJrePath())); + monitor.worked(1); + + // add classpath definition of where to store the external dependencies, the classpath + // will be populated lazily by the org.eclipse.jdt.core.classpathContainerInitializer + // extension point (see GradleClasspathContainerInitializer) + entries.add(createClasspathContainerForExternalDependencies()); + monitor.worked(1); + + // add project dependencies + entries.addAll(collectProjectDependencies(classpath)); + monitor.worked(1); + + // add source directories; create the directory if it doesn't exist + entries.addAll(collectSourceDirectories(classpath, javaProject)); + monitor.worked(1); + + // assign the whole classpath at once to the project + List entriesArray = entries.build(); + javaProject.setRawClasspath(entriesArray.toArray(new IClasspathEntry[entriesArray.size()]), new SubProgressMonitor(monitor, 8)); + } catch (Exception e) { + String message = String.format("Cannot configure sources and classpath for Eclipse project %s.", javaProject.getProject().getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + + private IClasspathEntry createClasspathContainerForExternalDependencies() throws JavaModelException { + // http://www-01.ibm.com/support/knowledgecenter/SSZND2_6.0.0/org.eclipse.jdt.doc.isv/guide/jdt_api_classpath.htm?cp=SSZND2_6.0.0%2F3-1-1-0-0-2 + Path containerPath = new Path(ClasspathDefinition.GRADLE_CLASSPATH_CONTAINER_ID); + return JavaCore.newContainerEntry(containerPath, true); + } + + private List collectProjectDependencies(ClasspathDefinition classpath) { + return FluentIterable.from(classpath.getProjectDependencies()).transform(new Function() { + + @Override + public IClasspathEntry apply(IPath dependency) { + return JavaCore.newProjectEntry(dependency, true); + } + }).toList(); + } + + private List collectSourceDirectories(ClasspathDefinition classpath, final IJavaProject javaProject) { + return FluentIterable.from(classpath.getSourceDirectories()).transform(new Function() { + + @Override + public IClasspathEntry apply(String directory) { + IFolder sourceDirectory = javaProject.getProject().getFolder(Path.fromOSString(directory)); + ensureFolderHierarchyExists(sourceDirectory); + IPackageFragmentRoot root = javaProject.getPackageFragmentRoot(sourceDirectory); + return JavaCore.newSourceEntry(root.getPath()); + } + }).toList(); + } + + private void ensureFolderHierarchyExists(IFolder folder) { + if (!folder.exists()) { + if (folder.getParent() instanceof IFolder) { + ensureFolderHierarchyExists((IFolder) folder.getParent()); + } + + try { + folder.create(true, true, null); + } catch (CoreException e) { + String message = String.format("Cannot create folder %s.", folder); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + } + + @Override + public void refresh(IProject project, IProgressMonitor monitor) { + // validate arguments + Preconditions.checkNotNull(project); + Preconditions.checkArgument(project.isAccessible(), "Project must be open."); + + monitor = MoreObjects.firstNonNull(monitor, new NullProgressMonitor()); + monitor.beginTask(String.format("Refresh Eclipse project %s", project.getName()), 1); + try { + project.refreshLocal(IProject.DEPTH_INFINITE, new SubProgressMonitor(monitor, 1)); + } catch (Exception e) { + String message = String.format("Cannot refresh Eclipse project %s.", project.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } finally { + monitor.done(); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/GradleClasspathContainerInitializer.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/GradleClasspathContainerInitializer.java new file mode 100644 index 000000000..d5f79cb09 --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/GradleClasspathContainerInitializer.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace.internal; + +import java.util.List; + +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.ClasspathContainerInitializer; +import org.eclipse.jdt.core.IClasspathContainer; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.gradle.tooling.CancellationToken; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProgressListener; +import org.gradle.tooling.TestProgressListener; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.configuration.ProjectConfiguration; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.gradle.Specs; +import com.gradleware.tooling.eclipse.core.workspace.ClasspathDefinition; +import com.gradleware.tooling.toolingmodel.OmniEclipseGradleBuild; +import com.gradleware.tooling.toolingmodel.OmniEclipseProject; +import com.gradleware.tooling.toolingmodel.OmniExternalDependency; +import com.gradleware.tooling.toolingmodel.repository.FetchStrategy; +import com.gradleware.tooling.toolingmodel.repository.ModelRepository; +import com.gradleware.tooling.toolingmodel.repository.TransientRequestAttributes; + +/** + * Initializes the classpath of each Eclipse workspace project that has a Gradle nature with the + * external dependencies of the underlying Gradle project. + *

+ * When this initializer is invoked, it looks up the {@link OmniEclipseProject} for the given + * Eclipse workspace project, takes all the found external Jar dependencies, and assigns them to the + * {@link ClasspathDefinition#GRADLE_CLASSPATH_CONTAINER_ID} classpath container. + *

+ * This initializer is assigned to the projects via the + * {@code org.eclipse.jdt.core.classpathContainerInitializer} extension point. + *

+ * The initialization is scheduled as a job, to not block the IDE upon startup. + */ +public final class GradleClasspathContainerInitializer extends ClasspathContainerInitializer { + + /** + * Looks up the {@link OmniEclipseProject} for the target project, takes all external Jar + * dependencies and assigns them to the classpath container with id + * {@link ClasspathDefinition#GRADLE_CLASSPATH_CONTAINER_ID}. + */ + @Override + public void initialize(final IPath containerPath, final IJavaProject project) throws CoreException { + new Job("Initialize Gradle classpath for project '" + project.getElementName() + "'") { + + // todo (etst) review job creation + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + internalInitialize(containerPath, project); + return Status.OK_STATUS; + } catch (Exception e) { + // TODO (donat) add a marker describing the problem + String message = String.format("Failed to initialize Gradle classpath for project %s.", project.getProject()); + CorePlugin.logger().error(message, e); + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, message, e); + } + } + }.schedule(); + } + + private void internalInitialize(IPath containerPath, IJavaProject project) throws JavaModelException { + Optional eclipseProject = findEclipseProject(project.getProject()); + if (eclipseProject.isPresent()) { + ImmutableList externalDependencies = collectExternalDependencies(eclipseProject.get()); + setClasspathContainer(externalDependencies, containerPath, project); + project.save(new NullProgressMonitor(), true); + } else { + throw new GradlePluginsRuntimeException(String.format("Cannot find Eclipse project model for project %s.", project.getProject())); + } + } + + private Optional findEclipseProject(IProject project) { + ProjectConfiguration configuration = CorePlugin.projectConfigurationManager().readProjectConfiguration(project); + OmniEclipseGradleBuild eclipseGradleBuild = fetchEclipseGradleBuild(configuration.getRequestAttributes()); + return eclipseGradleBuild.getRootEclipseProject().tryFind(Specs.eclipseProjectMatchesProjectPath(configuration.getProjectPath())); + } + + private OmniEclipseGradleBuild fetchEclipseGradleBuild(FixedRequestAttributes fixedRequestAttributes) { + ProcessStreams streams = CorePlugin.processStreamsProvider().getBackgroundJobProcessStreams(); + List noProgressListeners = ImmutableList.of(); + List noTestProgressListeners = ImmutableList.of(); + CancellationToken cancellationToken = GradleConnector.newCancellationTokenSource().token(); + TransientRequestAttributes transientAttributes = new TransientRequestAttributes(false, streams.getOutput(), streams.getError(), null, noProgressListeners, + noTestProgressListeners, cancellationToken); + ModelRepository repository = CorePlugin.modelRepositoryProvider().getModelRepository(fixedRequestAttributes); + return repository.fetchEclipseGradleBuild(transientAttributes, FetchStrategy.LOAD_IF_NOT_CACHED); + } + + private ImmutableList collectExternalDependencies(OmniEclipseProject project) { + return FluentIterable.from(project.getExternalDependencies()).transform(new Function() { + + @Override + public IClasspathEntry apply(OmniExternalDependency dependency) { + IPath jar = org.eclipse.core.runtime.Path.fromOSString(dependency.getFile().getAbsolutePath()); + IPath sourceJar = dependency.getSource() != null ? org.eclipse.core.runtime.Path.fromOSString(dependency.getSource().getAbsolutePath()) : null; + return JavaCore.newLibraryEntry(jar, sourceJar, null, true); + } + }).toList(); + } + + private void setClasspathContainer(List classpathEntries, IPath containerPath, IJavaProject project) throws JavaModelException { + org.eclipse.core.runtime.Path classpathContainerPath = new org.eclipse.core.runtime.Path(ClasspathDefinition.GRADLE_CLASSPATH_CONTAINER_ID); + IClasspathContainer classpathContainer = new ExternalDependenciesClasspathContainer(classpathContainerPath, classpathEntries); + JavaCore.setClasspathContainer(containerPath, new IJavaProject[] { project }, new IClasspathContainer[] { classpathContainer }, null); + } + + /** + * {@code IClasspathContainer} to describe the external dependencies. + */ + private static final class ExternalDependenciesClasspathContainer implements IClasspathContainer { + + private final org.eclipse.core.runtime.Path path; + private final IClasspathEntry[] classpathEntries; + + private ExternalDependenciesClasspathContainer(org.eclipse.core.runtime.Path path, List classpathEntries) { + this.path = path; + this.classpathEntries = Iterables.toArray(classpathEntries, IClasspathEntry.class); + } + + @Override + public IPath getPath() { + return this.path; + } + + @Override + public IClasspathEntry[] getClasspathEntries() { + return this.classpathEntries; + } + + @Override + public int getKind() { + return IClasspathContainer.K_APPLICATION; + } + + @Override + public String getDescription() { + return "External Dependencies"; + } + + } +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/ResourceFilter.java b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/ResourceFilter.java new file mode 100644 index 000000000..5985b739b --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/java/com/gradleware/tooling/eclipse/core/workspace/internal/ResourceFilter.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.core.workspace.internal; + +import java.io.File; +import java.util.List; + +import org.eclipse.core.resources.FileInfoMatcherDescription; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceFilterDescription; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; + +/** + * Provides resource filtering on {@link IProject} instances. + */ +final class ResourceFilter { + + // to create filters we reuse the constants and classes from the org.eclipse.core.resources + // plug-in. The IDs are not exposed as a class, consequently we redefine them in this class. + // documentation: + // http://help.eclipse.org/luna/topic/org.eclipse.platform.doc.isv/guide/resInt_filters.htm + + // resource filter matcher id + private static final String MATCHER_ID = "org.eclipse.core.resources.regexFilterMatcher"; + + // id to merge the resource filters as a single OR statement + private static final String OR_ID = "org.eclipse.ui.ide.orFilterMatcher"; + + private ResourceFilter() { + } + + /** + * Attaches resource filters on the specified project to hide any of the given child locations. + * + * @param project the project for which to create resource filters + * @param childLocations the child locations + * @param monitor the monitor to report progress on + */ + public static void attachFilters(IProject project, List childLocations, IProgressMonitor monitor) { + monitor = MoreObjects.firstNonNull(monitor, new NullProgressMonitor()); + List filters = createFilters(project, childLocations); + setFilters(project, filters, monitor); + } + + private static List createFilters(IProject project, List children) { + ImmutableList.Builder filters = ImmutableList.builder(); + IPath projectLocation = project.getLocation(); + for (File child : children) { + IPath childLocation = new Path(child.getAbsolutePath()); + if (projectLocation.isPrefixOf(childLocation)) { + filters.add(new FileInfoMatcherDescription(MATCHER_ID, childLocation.makeRelativeTo(projectLocation).toPortableString())); + } + } + return filters.build(); + } + + private static void setFilters(IProject project, List filters, IProgressMonitor monitor) { + monitor.beginTask(String.format("Set resource filters for project %s", project), 2); + try { + // get all current filters + IResourceFilterDescription[] currentFilters; + try { + currentFilters = project.getFilters(); + } catch (CoreException e) { + String message = String.format("Cannot retrieve current resource filters for project %s.", project.getName()); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + + // delete all current filters + for (IResourceFilterDescription filter : currentFilters) { + try { + filter.delete(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + String message = String.format("Cannot delete current resource filter %s.", filter); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + + // create the specified filters + if (!filters.isEmpty()) { + try { + project.createFilter(IResourceFilterDescription.EXCLUDE_ALL | IResourceFilterDescription.FOLDERS | IResourceFilterDescription.INHERITABLE, + createCompositeFilter(filters), IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 1)); + } catch (CoreException e) { + String message = String.format("Cannot create new resource filters for project %s.", project); + CorePlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + } finally { + monitor.done(); + } + } + + private static FileInfoMatcherDescription createCompositeFilter(List filters) { + return new FileInfoMatcherDescription(OR_ID, filters.toArray(new FileInfoMatcherDescription[filters.size()])); + } + +} diff --git a/com.gradleware.tooling.eclipse.core/src/main/resources/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.properties b/com.gradleware.tooling.eclipse.core/src/main/resources/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.properties new file mode 100644 index 000000000..8f7b287fb --- /dev/null +++ b/com.gradleware.tooling.eclipse.core/src/main/resources/com/gradleware/tooling/eclipse/core/i18n/CoreMessages.properties @@ -0,0 +1,42 @@ +# +# Copyright (c) 2015 the original author or authors. +# 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: +# Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation +# + +GradleDistribution_Label_GradleWrapper=Gradle wrapper +GradleDistribution_Label_LocalInstallationDirectory=Local installation directory +GradleDistribution_Label_RemoteDistributionUri=Remote distribution location +GradleDistribution_Label_SpecificGradleVersion=Specific Gradle version + +GradleDistribution_Value_UseGradleWrapper=Gradle wrapper from target build +GradleDistribution_Value_UseLocalInstallation_0=Local installation at %s +GradleDistribution_Value_UseRemoteDistribution_0=Remote distribution from %s +GradleDistribution_Value_UseGradleVersion_0=Specific Gradle version %s + +ProgressVisualization_Label_VisualizeTestProgress=Visualize Test Progress + +RunConfiguration_Label_GradleTasks=Gradle Tasks +RunConfiguration_Label_WorkingDirectory=Working Directory +RunConfiguration_Label_GradleDistribution=Gradle Distribution +RunConfiguration_Label_GradleUserHome=Gradle User Home +RunConfiguration_Label_JavaHome=Java Home +RunConfiguration_Label_JvmArguments=JVM Arguments +RunConfiguration_Label_Arguments=Program Arguments +RunConfiguration_Label_ProgressVisualization=Progress Visualization + +RunConfiguration_Value_RunDefaultTasks= + +Value_None= +Value_Unknown= +Value_UseGradleDefault= + +ErrorMessage_0_DoesNotExist=%s does not exist. +ErrorMessage_0_IsNotValid=%s is not valid. +ErrorMessage_0_MustBeSpecified=%s must be specified. +ErrorMessage_0_MustBeDirectory=%s must be a directory. diff --git a/com.gradleware.tooling.eclipse.feature/.project b/com.gradleware.tooling.eclipse.feature/.project new file mode 100644 index 000000000..5c3d940ae --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/.project @@ -0,0 +1,17 @@ + + + com.gradleware.tooling.eclipse.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/com.gradleware.tooling.eclipse.feature/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.feature/META-INF/MANIFEST.MF new file mode 100644 index 000000000..59499bce4 --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/META-INF/MANIFEST.MF @@ -0,0 +1,2 @@ +Manifest-Version: 1.0 + diff --git a/com.gradleware.tooling.eclipse.feature/build.gradle b/com.gradleware.tooling.eclipse.feature/build.gradle new file mode 100644 index 000000000..7c6c60625 --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/build.gradle @@ -0,0 +1,5 @@ +apply plugin: eclipsebuild.FeaturePlugin + +feature { + featureXml = file('feature.xml') +} diff --git a/com.gradleware.tooling.eclipse.feature/build.properties b/com.gradleware.tooling.eclipse.feature/build.properties new file mode 100644 index 000000000..c7874f8a3 --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/build.properties @@ -0,0 +1,7 @@ +bin.includes = feature.xml,\ + feature.properties,\ + license.txt +src.includes = feature.xml,\ + feature.properties,\ + license.txt,\ + build.properties diff --git a/com.gradleware.tooling.eclipse.feature/feature.properties b/com.gradleware.tooling.eclipse.feature/feature.properties new file mode 100644 index 000000000..7e03bed75 --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/feature.properties @@ -0,0 +1,25 @@ +# "featureName" property - the name of the feature +featureName=\ +Buildship: Eclipse Plug-ins for Gradle + +# "providerName" property - the name of the company providing the feature +providerName=\ +Gradle Inc. + +# "description" property - description of the feature +description=\ +Buildship: Eclipse Plug-ins for Gradle, provided as part of the Gradle Platform.\n\n\ +Copyright (c) 2015 Gradle Inc.\n\ +For more information, visit our website http://www.gradle.org. + +# "copyright" property - the copyright notice +copyright=\ +Copyright (c) 2015 Gradle Inc. + +# "license" property - the license notice +license=\ +Eclipse Public License - v 1.0 + +# "licenseUrl" property - the hosted license notice +licenseUrl=\ +license.txt diff --git a/com.gradleware.tooling.eclipse.feature/feature.xml b/com.gradleware.tooling.eclipse.feature/feature.xml new file mode 100644 index 000000000..b6bf81dba --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/feature.xml @@ -0,0 +1,43 @@ + + + + + + %description + + + + %copyright + + + + %license + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.feature/license.txt b/com.gradleware.tooling.eclipse.feature/license.txt new file mode 100644 index 000000000..79e486c3d --- /dev/null +++ b/com.gradleware.tooling.eclipse.feature/license.txt @@ -0,0 +1,70 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and +iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of the Program. +Contributors may not remove or alter any copyright notices contained within the Program. + +Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. diff --git a/com.gradleware.tooling.eclipse.site/.project b/com.gradleware.tooling.eclipse.site/.project new file mode 100644 index 000000000..93fd27426 --- /dev/null +++ b/com.gradleware.tooling.eclipse.site/.project @@ -0,0 +1,17 @@ + + + com.gradleware.tooling.eclipse.site + + + + + + org.eclipse.pde.UpdateSiteBuilder + + + + + + org.eclipse.pde.UpdateSiteNature + + diff --git a/com.gradleware.tooling.eclipse.site/build.gradle b/com.gradleware.tooling.eclipse.site/build.gradle new file mode 100644 index 000000000..130a53656 --- /dev/null +++ b/com.gradleware.tooling.eclipse.site/build.gradle @@ -0,0 +1,10 @@ +apply plugin: eclipsebuild.UpdateSitePlugin + +updateSite { siteDescriptor = file("category.xml") } + +dependencies { + compile project(':com.gradleware.tooling.eclipse.core') + compile project(':com.gradleware.tooling.eclipse.ui') + compile project(':com.gradleware.tooling.eclipse.branding') + compile project(':com.gradleware.tooling.eclipse.feature') +} diff --git a/com.gradleware.tooling.eclipse.site/category.xml b/com.gradleware.tooling.eclipse.site/category.xml new file mode 100644 index 000000000..0d4993f3d --- /dev/null +++ b/com.gradleware.tooling.eclipse.site/category.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/com.gradleware.tooling.eclipse.ui.test/.classpath b/com.gradleware.tooling.eclipse.ui.test/.classpath new file mode 100644 index 000000000..c24a0bbea --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.ui.test/.project b/com.gradleware.tooling.eclipse.ui.test/.project new file mode 100644 index 000000000..c6d52cfeb --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/.project @@ -0,0 +1,40 @@ + + + com.gradleware.tooling.eclipse.ui.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.jdt.groovy.core.groovyNature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + + + 1365114673930 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-target + + + + diff --git a/com.gradleware.tooling.eclipse.ui.test/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.ui.test/META-INF/MANIFEST.MF new file mode 100644 index 000000000..bb815020c --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Buildship, Eclipse Plug-ins for Gradle - UI Test +Bundle-SymbolicName: com.gradleware.tooling.eclipse.ui.test;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Vendor: Gradle Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Fragment-Host: com.gradleware.tooling.eclipse.ui +Require-Bundle: org.junit +Bundle-ClassPath: ., + lib/groovy-all-2.0.5.jar, + lib/guava-18.0.jar, + lib/hamcrest-core-1.3.jar, + lib/junit-dep-4.10.jar, + lib/slf4j-api-1.7.10.jar, + lib/slf4j-simple-1.7.10.jar, + lib/spock-core-0.7-groovy-2.0.jar, + lib/testing-junit-0.3.jar diff --git a/com.gradleware.tooling.eclipse.ui.test/build.gradle b/com.gradleware.tooling.eclipse.ui.test/build.gradle new file mode 100644 index 000000000..67aa20d80 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/build.gradle @@ -0,0 +1,32 @@ +apply plugin: eclipsebuild.TestBundlePlugin +apply plugin: 'groovy' + +dependencies { + compile project(':com.gradleware.tooling.eclipse.core') + compile project(':com.gradleware.tooling.eclipse.ui') + + bundled "org.spockframework:spock-core:$spockLibVersion" + bundled "com.gradleware.tooling:testing-junit:$commonsLibVersion" + bundled "org.slf4j:slf4j-simple:$slf4jLibVersion" +} + +eclipseTest { + applicationName 'org.eclipse.pde.junit.runtime.uitestapplication' + fragmentHost 'com.gradleware.tooling.eclipse.ui' + optionsFile rootProject.project(':com.gradleware.tooling.eclipse.core').file('.options') + consoleLog = true +} + +// append the sources of each first-level dependency and its transitive dependencies of +// the 'bundled' configuration to the 'bundledSource' configuration +configurations.bundled.resolvedConfiguration.firstLevelModuleDependencies.each { dep -> + addSourcesRecursively(dep) +} + +def addSourcesRecursively(dep) { + dependencies { + bundledSource group: dep.moduleGroup, name: dep.moduleName, version: dep.moduleVersion, classifier: 'sources' + } + dep.children.each { addSourcesRecursively(it) } +} + diff --git a/com.gradleware.tooling.eclipse.ui.test/build.properties b/com.gradleware.tooling.eclipse.ui.test/build.properties new file mode 100644 index 000000000..1247f5832 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/main/groovy/ +output.. = bin/ +bin.includes = META-INF/,\ + lib/,\ + . diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5-sources.jar new file mode 100644 index 000000000..e3bb8d1fb Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5.jar b/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5.jar new file mode 100644 index 000000000..76aec578b Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/groovy-all-2.0.5.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0-sources.jar new file mode 100644 index 000000000..d97cc501b Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0.jar b/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0.jar new file mode 100644 index 000000000..8f89e4901 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/guava-18.0.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3-sources.jar new file mode 100644 index 000000000..c3c110b4d Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3.jar b/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3.jar new file mode 100644 index 000000000..9d5fe16e3 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/hamcrest-core-1.3.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10-sources.jar new file mode 100644 index 000000000..bfcb68742 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10.jar b/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10.jar new file mode 100644 index 000000000..32209cb13 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/junit-dep-4.10.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10-sources.jar new file mode 100644 index 000000000..9e214e964 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10.jar b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10.jar new file mode 100644 index 000000000..744e9ec5b Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-api-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10-sources.jar new file mode 100644 index 000000000..2e9e57d86 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10.jar b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10.jar new file mode 100644 index 000000000..b40be298f Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/slf4j-simple-1.7.10.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0-sources.jar new file mode 100644 index 000000000..1b7b17e35 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0.jar b/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0.jar new file mode 100644 index 000000000..317c587c5 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/spock-core-0.7-groovy-2.0.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3-sources.jar b/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3-sources.jar new file mode 100644 index 000000000..866d49ea8 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3-sources.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3.jar b/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3.jar new file mode 100644 index 000000000..e5fb2e604 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui.test/lib/testing-junit-0.3.jar differ diff --git a/com.gradleware.tooling.eclipse.ui.test/src/main/groovy/com/gradleware/tooling/eclipse/ui/EclipseUiTest.groovy b/com.gradleware.tooling.eclipse.ui.test/src/main/groovy/com/gradleware/tooling/eclipse/ui/EclipseUiTest.groovy new file mode 100644 index 000000000..543f6f824 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui.test/src/main/groovy/com/gradleware/tooling/eclipse/ui/EclipseUiTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui + +import org.eclipse.swt.widgets.Display; + +import spock.lang.Specification; + +class EclipseUiTest extends Specification { + + def "Ui tests can run as Spock tets"() { + expect: + Display.getDefault() != null + } +} diff --git a/com.gradleware.tooling.eclipse.ui/.classpath b/com.gradleware.tooling.eclipse.ui/.classpath new file mode 100644 index 000000000..d0f902807 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.ui/.project b/com.gradleware.tooling.eclipse.ui/.project new file mode 100644 index 000000000..f024e911e --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/.project @@ -0,0 +1,44 @@ + + + com.gradleware.tooling.eclipse.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + + + 1376416703537 + + 10 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-target + + + + diff --git a/com.gradleware.tooling.eclipse.ui/META-INF/MANIFEST.MF b/com.gradleware.tooling.eclipse.ui/META-INF/MANIFEST.MF new file mode 100644 index 000000000..8fcc29a7c --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/META-INF/MANIFEST.MF @@ -0,0 +1,19 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Buildship, Eclipse Plug-ins for Gradle - UI +Bundle-SymbolicName: com.gradleware.tooling.eclipse.ui;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Vendor: Gradle Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.6 +Bundle-Activator: com.gradleware.tooling.eclipse.ui.UiPlugin +Require-Bundle: com.gradleware.tooling.eclipse.core, + org.eclipse.help, + org.eclipse.ui, + org.eclipse.ui.ide, + org.eclipse.ui.console, + org.eclipse.ui.editors, + org.eclipse.ui.views, + org.eclipse.debug.ui, + org.eclipse.jdt.ui +Bundle-ActivationPolicy: lazy +Bundle-ClassPath: . diff --git a/com.gradleware.tooling.eclipse.ui/build.gradle b/com.gradleware.tooling.eclipse.ui/build.gradle new file mode 100644 index 000000000..7a52cf502 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/build.gradle @@ -0,0 +1,15 @@ +apply plugin: eclipsebuild.BundlePlugin + +dependencies { + compile project(':com.gradleware.tooling.eclipse.core') + compile "eclipse:org.eclipse.help:+" + compile "eclipse:org.eclipse.ui:+" + compile "eclipse:org.eclipse.ui.ide:+" + compile "eclipse:org.eclipse.ui.console:+" + compile "eclipse:org.eclipse.ui.editors:+" + compile "eclipse:org.eclipse.ui.views:+" + compile "eclipse:org.eclipse.debug.ui:+" + compile "eclipse:org.eclipse.jdt.ui:+" + compile "eclipse:org.eclipse.swt.${ECLIPSE_WS}.${ECLIPSE_OS}.${ECLIPSE_ARCH}:+" +} + diff --git a/com.gradleware.tooling.eclipse.ui/build.properties b/com.gradleware.tooling.eclipse.ui/build.properties new file mode 100644 index 000000000..9594980eb --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/build.properties @@ -0,0 +1,9 @@ +source.. = src/main/java/,\ + src/main/resources/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + icons/,\ + plugin.xml,\ + help/ + diff --git a/com.gradleware.tooling.eclipse.ui/help/help-contexts.xml b/com.gradleware.tooling.eclipse.ui/help/help-contexts.xml new file mode 100644 index 000000000..ca35e4aca --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/help/help-contexts.xml @@ -0,0 +1,6 @@ + + + The Gradle Project Import Wizard allows you to import existing Gradle projects into the Eclipse workspace. + + + diff --git a/com.gradleware.tooling.eclipse.ui/help/html/project-import.html b/com.gradleware.tooling.eclipse.ui/help/html/project-import.html new file mode 100644 index 000000000..3e5529f5c --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/help/html/project-import.html @@ -0,0 +1,7 @@ +

Importing a Gradle Project

+ +

Specify the root directory of the Gradle project to import, + select the Gradle distribution to use when interacting with the Gradle project, + specify optional advanced configuration options, + preview the import, + and finally trigger the full import into the Eclipse workspace.

diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/cancel_build_execution.png b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/cancel_build_execution.png new file mode 100644 index 000000000..e0122f22e Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/cancel_build_execution.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/refresh.gif b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/refresh.gif new file mode 100644 index 000000000..478cff5fb Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/refresh.gif differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_all_consoles.png b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_all_consoles.png new file mode 100644 index 000000000..e968f58ec Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_all_consoles.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_console.png b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_console.png new file mode 100644 index 000000000..d1cd8b620 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/remove_console.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/run.png b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/run.png new file mode 100644 index 000000000..051c86caf Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/dlcl16/run.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/cancel_build_execution.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/cancel_build_execution.png new file mode 100644 index 000000000..3544673b6 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/cancel_build_execution.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/link_to_selection.gif b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/link_to_selection.gif new file mode 100644 index 000000000..870934b69 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/link_to_selection.gif differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/refresh.gif b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/refresh.gif new file mode 100644 index 000000000..3ca04d06f Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/refresh.gif differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_all_consoles.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_all_consoles.png new file mode 100644 index 000000000..5b5869dc7 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_all_consoles.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_console.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_console.png new file mode 100644 index 000000000..38ab8a480 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/remove_console.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run.png new file mode 100644 index 000000000..08571c1f2 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run_tasks.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run_tasks.png new file mode 100644 index 000000000..14668af4c Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/run_tasks.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_type.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_type.png new file mode 100644 index 000000000..4f5113ec2 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_type.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_visibility.png b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_visibility.png new file mode 100644 index 000000000..5a75bc4ec Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/elcl16/sort_by_visibility.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/etool16/import_wiz.png b/com.gradleware.tooling.eclipse.ui/icons/full/etool16/import_wiz.png new file mode 100644 index 000000000..628f06f98 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/etool16/import_wiz.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/eview16/arguments.gif b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/arguments.gif new file mode 100644 index 000000000..e35f594d8 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/arguments.gif differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/eview16/gradle_distribution.png b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/gradle_distribution.png new file mode 100644 index 000000000..628f06f98 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/gradle_distribution.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/eview16/tasks.png b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/tasks.png new file mode 100644 index 000000000..23dd06268 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/eview16/tasks.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/obj16/gradle_logo.png b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/gradle_logo.png new file mode 100644 index 000000000..628f06f98 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/gradle_logo.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/obj16/launch_config.png b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/launch_config.png new file mode 100644 index 000000000..628f06f98 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/launch_config.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/obj16/task.png b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/task.png new file mode 100644 index 000000000..14668af4c Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/obj16/task.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_private.png b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_private.png new file mode 100644 index 000000000..f38098df0 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_private.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_project.png b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_project.png new file mode 100644 index 000000000..9b276be70 Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_project.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_selector.png b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_selector.png new file mode 100644 index 000000000..b8bfce09d Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/ovr16/task_selector.png differ diff --git a/com.gradleware.tooling.eclipse.ui/icons/full/wizban/import_wiz.png b/com.gradleware.tooling.eclipse.ui/icons/full/wizban/import_wiz.png new file mode 100644 index 000000000..3071bdf5f Binary files /dev/null and b/com.gradleware.tooling.eclipse.ui/icons/full/wizban/import_wiz.png differ diff --git a/com.gradleware.tooling.eclipse.ui/plugin.xml b/com.gradleware.tooling.eclipse.ui/plugin.xml new file mode 100644 index 000000000..08ce98e88 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/plugin.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + Import a Gradle project in the local file system. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/HelpContext.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/HelpContext.java new file mode 100644 index 000000000..8ed945ed6 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/HelpContext.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +/** + * Help context registered via the org.eclipse.help.contexts extension point in the + * plugin.xml. + */ +public final class HelpContext { + + // the help context ID has to be in the following format: ${PLUGIN_ID}.${CONTEXT_ID} + // the context id is defined in the external (xml) file specified in the plugin.xml + public static final String PROJECT_IMPORT = UiPlugin.PLUGIN_ID + ".projectimport"; //$NON-NLS-1$ + + private HelpContext() { + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImage.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImage.java new file mode 100644 index 000000000..3dd49fce0 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +/** + * Describes an image of the plugin. The image can be materialized for different + * {@link PluginImage.ImageState} values. + */ +public interface PluginImage { + + PluginImageWithState withState(ImageState state); + + /** + * Enumerates the different states for which an image can have a (possibly different) + * representation. + */ + enum ImageState { + + ENABLED, DISABLED + + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImageWithState.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImageWithState.java new file mode 100644 index 000000000..80f44f235 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImageWithState.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +import java.util.List; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; + +/** + * Describes an image of the plugin in a given state (enabled, disabled, etc.). + */ +public interface PluginImageWithState { + + String getKey(); + + Image getImage(); + + ImageDescriptor getImageDescriptor(); + + Image getOverlayImage(List overlayImages); + + ImageDescriptor getOverlayImageDescriptor(List overlayImages); + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImages.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImages.java new file mode 100644 index 000000000..97421f681 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/PluginImages.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.gradleware.tooling.eclipse.ui.util.image.ImageUtils; + +/** + * Enumerates all the images used in this plugin. Uses the {@link ImageRegistry} provided by the + * {@link UiPlugin} for storage and access. + */ +public enum PluginImages implements PluginImage { + + // @formatter:off + TASK(ImmutableMap.of(ImageState.ENABLED, "icons/full/obj16/task.png")), + OVERLAY_PROJECT_TASK(ImmutableMap.of(ImageState.ENABLED, "icons/full/ovr16/task_project.png")), + OVERLAY_TASK_SELECTOR(ImmutableMap.of(ImageState.ENABLED, "icons/full/ovr16/task_selector.png")), + OVERLAY_PRIVATE_TASK(ImmutableMap.of(ImageState.ENABLED, "icons/full/ovr16/task_private.png")), + SORT_BY_TYPE(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/sort_by_type.png")), + SORT_BY_VISIBILITY(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/sort_by_visibility.png")), + RUN_TASKS(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/run_tasks.png")), + REFRESH(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/refresh.gif", ImageState.DISABLED, "icons/full/dlcl16/refresh.gif")), + LINK_TO_SELECTION(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/link_to_selection.gif")), + REMOVE_CONSOLE(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/remove_console.png", ImageState.DISABLED, "icons/full/dlcl16/remove_console.png")), + REMOVE_ALL_CONSOLES(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/remove_all_consoles.png", ImageState.DISABLED, "icons/full/dlcl16/remove_all_consoles.png")), + CANCEL_TASK_EXECUTION(ImmutableMap.of(ImageState.ENABLED, "icons/full/elcl16/cancel_build_execution.png", ImageState.DISABLED, "icons/full/dlcl16/cancel_build_execution.png")), + RUN_CONFIG_GRADLE_DISTRIBUTION(ImmutableMap.of(ImageState.ENABLED, "icons/full/eview16/gradle_distribution.png")), + RUN_CONFIG_ARGUMENTS(ImmutableMap.of(ImageState.ENABLED, "icons/full/eview16/arguments.gif")); + // @formatter:on + + private final ImmutableMap images; + + PluginImages(ImmutableMap images) { + this.images = images; + } + + public void register() { + for (Map.Entry entry : this.images.entrySet()) { + ImageState state = entry.getKey(); + PluginImageWithState imageWithState = withState(state); + ImageDescriptor imageDescriptor = ImageUtils.findImageDescriptor(UiPlugin.getInstance().getBundle(), entry.getValue()); + getImageRegistry().put(imageWithState.getKey(), imageDescriptor); + } + } + + @Override + public PluginImageWithState withState(final ImageState state) { + return new PluginImageWithState() { + + @Override + public String getKey() { + return String.format("%s.%s", name(), state); + } + + @Override + public Image getImage() { + Image image = getImageRegistry().get(getKey()); + if (image == null) { + throw new IllegalArgumentException(String.format("Image %s in state %s not available in UiPlugin image registry.", name(), state)); + } + + return image; + } + + @Override + public ImageDescriptor getImageDescriptor() { + ImageDescriptor image = getImageRegistry().getDescriptor(getKey()); + if (image == null) { + throw new IllegalArgumentException(String.format("Image descriptor %s in state %s not available in UiPlugin image registry.", name(), state)); + } + + return image; + } + + @Override + public Image getOverlayImage(List overlayImages) { + ImmutableList imageKeys = getImageKeys(overlayImages); + return ImageUtils.getOverlayImage(getKey(), imageKeys, getImageRegistry()); + } + + @Override + public ImageDescriptor getOverlayImageDescriptor(List overlayImages) { + ImmutableList imageKeys = getImageKeys(overlayImages); + return ImageUtils.getOverlayImageDescriptor(getKey(), imageKeys, getImageRegistry()); + } + + private ImmutableList getImageKeys(List overlayImages) { + return FluentIterable.from(overlayImages).transform(new Function() { + + @Override + public String apply(PluginImageWithState imageWithState) { + return imageWithState.getKey(); + } + }).toList(); + } + + }; + } + + private ImageRegistry getImageRegistry() { + return UiPlugin.getInstance().getImageRegistry(); + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPlugin.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPlugin.java new file mode 100644 index 000000000..539bd6800 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPlugin.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +import java.util.Dictionary; +import java.util.Hashtable; + +import com.gradleware.tooling.eclipse.core.workbench.WorkbenchOperations; +import com.gradleware.tooling.eclipse.ui.workbench.DefaultWorkbenchOperations; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; + +import com.gradleware.tooling.eclipse.core.Logger; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; +import com.gradleware.tooling.eclipse.core.util.logging.EclipseLogger; +import com.gradleware.tooling.eclipse.ui.console.ConsoleProcessStreamsProvider; + +/** + * The plug-in runtime class for the Gradle integration plug-in containing the UI-related elements. + *

+ * This class is automatically instantiated by the Eclipse runtime and wired through the + * Bundle-Activator entry in the META-INF/MANIFEST.MF file. The registered + * instance can be obtained during runtime through the {@link UiPlugin#getInstance()} method. + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public final class UiPlugin extends AbstractUIPlugin { + + public static final String PLUGIN_ID = "com.gradleware.tooling.eclipse.ui"; //$NON-NLS-1$ + + private static UiPlugin plugin; + + // do not use generics-aware signature since this causes compilation troubles (JDK, Spock) + // search for -target jsr14 to find out more about this obscurity + private ServiceRegistration loggerService; + private ServiceRegistration processStreamsProviderService; + private ServiceRegistration workbenchOperationsService; + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + registerServices(context); + plugin = this; + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + unregisterServices(); + super.stop(context); + } + + private void registerServices(BundleContext context) { + // store services with low ranking such that they can be overridden + // during testing or the like + Dictionary preferences = new Hashtable(); + preferences.put(Constants.SERVICE_RANKING, 1); + + Dictionary priorityPreferences = new Hashtable(); + priorityPreferences.put(Constants.SERVICE_RANKING, 2); + + // register all services (override the ProcessStreamsProvider registered in the core plugin) + this.loggerService = registerService(context, Logger.class, createLogger(), preferences); + this.processStreamsProviderService = registerService(context, ProcessStreamsProvider.class, createConsoleProcessStreamsProvider(), priorityPreferences); + this.workbenchOperationsService = registerService(context, WorkbenchOperations.class, createWorkbenchOperations(), priorityPreferences); + } + + private ServiceRegistration registerService(BundleContext context, Class clazz, T service, Dictionary properties) { + return context.registerService(clazz.getName(), service, properties); + } + + private EclipseLogger createLogger() { + return new EclipseLogger(getLog(), PLUGIN_ID); + } + + private ProcessStreamsProvider createConsoleProcessStreamsProvider() { + return new ConsoleProcessStreamsProvider(); + } + + private WorkbenchOperations createWorkbenchOperations() { + return new DefaultWorkbenchOperations(); + } + + private void unregisterServices() { + this.workbenchOperationsService.unregister(); + this.processStreamsProviderService.unregister(); + this.loggerService.unregister(); + } + + public static UiPlugin getInstance() { + return plugin; + } + + public static Logger logger() { + return getService(getInstance().loggerService.getReference()); + } + + private static T getService(ServiceReference reference) { + return (T) reference.getBundle().getBundleContext().getService(reference); + } + + @Override + protected void initializeImageRegistry(ImageRegistry imageRegistry) { + for (PluginImages pluginImage : PluginImages.values()) { + pluginImage.register(); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPluginConstants.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPluginConstants.java new file mode 100644 index 000000000..22e780a9a --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/UiPluginConstants.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui; + +/** + * Enumerates some of the values defined in the plugin.xml. + *

+ * Every time the content of the plugin.xml file changes, the values in this class must be + * aligned accordingly. + */ +public final class UiPluginConstants { + + /** + * The context ID associated with the task view. This context is activated when the task view is + * in focus. + */ + public static final String UI_TASKVIEW_CONTEXT_ID = "com.gradleware.tooling.eclipse.ui.contexts.taskview"; + + /** + * The id of the command to refresh the task view. + */ + public static final String REFRESH_TASKVIEW_COMMAND_ID = "com.gradleware.tooling.eclipse.ui.commands.refreshtaskview"; + + /** + * The id of the command to run the selected tasks. + */ + public static final String RUN_TASKS_COMMAND_ID = "com.gradleware.tooling.eclipse.ui.commands.runtasks"; + + /** + * The id of the command to run the default tasks of the selected project. + */ + public static final String RUN_DEFAULT_TASKS_COMMAND_ID = "com.gradleware.tooling.eclipse.ui.commands.rundefaulttasks"; + + /** + * The id of the command to open the run configuration for the selected tasks. + */ + public static final String OPEN_RUN_CONFIGURATION_COMMAND_ID = "com.gradleware.tooling.eclipse.ui.commands.openrunconfiguration"; + + /** + * The id of the Run launch group provided by Eclipse core. + */ + public static final String RUN_LAUNCH_GROUP_ID = "org.eclipse.debug.ui.launchGroup.run"; + + + /** + * The id of the command to open the build script for the selected project. + */ + public static final String OPEN_BUILD_SCRIPT_COMMAND_ID = "com.gradleware.tooling.eclipse.ui.commands.openbuildscript"; + + private UiPluginConstants() { + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/CancelBuildExecutionAction.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/CancelBuildExecutionAction.java new file mode 100644 index 000000000..f97876be8 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/CancelBuildExecutionAction.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; +import org.eclipse.jface.action.Action; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.ui.PluginImage.ImageState; +import com.gradleware.tooling.eclipse.ui.PluginImages; + +/** + * Cancel the build execution attached to the given {@link GradleConsole} instance. + */ +public final class CancelBuildExecutionAction extends Action { + + private final GradleConsole gradleConsole; + + public CancelBuildExecutionAction(GradleConsole gradleConsole) { + this.gradleConsole = Preconditions.checkNotNull(gradleConsole); + + setToolTipText(ConsoleMessages.Action_CancelBuildExecution_Tooltip); + setImageDescriptor(PluginImages.CANCEL_TASK_EXECUTION.withState(ImageState.ENABLED).getImageDescriptor()); + setDisabledImageDescriptor(PluginImages.CANCEL_TASK_EXECUTION.withState(ImageState.DISABLED).getImageDescriptor()); + + registerJobChangeListener(); + } + + private void registerJobChangeListener() { + Optional job = this.gradleConsole.getProcessDescription().getJob(); + if (job.isPresent()) { + job.get().addJobChangeListener(new JobChangeAdapter() { + + @Override + public void done(IJobChangeEvent event) { + CancelBuildExecutionAction.this.setEnabled(false); + } + }); + setEnabled(job.get().getState() != Job.NONE); + } else { + // if no job is associated with the console, never enable this action + setEnabled(false); + } + } + + @Override + public void run() { + this.gradleConsole.getProcessDescription().getJob().get().cancel(); + } + + public void dispose(){ + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleMessages.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleMessages.java new file mode 100644 index 000000000..7815030d0 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleMessages.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import org.eclipse.osgi.util.NLS; + +/** + * Lists the i18n resource keys for the console messages. + */ +public final class ConsoleMessages extends NLS { + + private static final String BUNDLE_NAME = "com.gradleware.tooling.eclipse.ui.console.ConsoleMessages"; //$NON-NLS-1$ + + public static String Background_Console_Title; + + public static String Action_CancelBuildExecution_Tooltip; + public static String Action_RemoveTerminatedConsole_Tooltip; + public static String Action_RemoveAllTerminatedConsoles_Tooltip; + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, ConsoleMessages.class); + } + + private ConsoleMessages() { + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleProcessStreamsProvider.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleProcessStreamsProvider.java new file mode 100644 index 000000000..f103fbb74 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/ConsoleProcessStreamsProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import com.google.common.base.Preconditions; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; + +import com.gradleware.tooling.eclipse.core.console.ProcessDescription; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.core.console.ProcessStreamsProvider; + +/** + * Provider of {@link ProcessStreams} instances that are backed by console pages of the Eclipse + * Console view. + */ +public final class ConsoleProcessStreamsProvider implements ProcessStreamsProvider { + + /** + * Returns the same instance for each invocation. + * + * @return the instance suitable for background jobs + */ + @Override + public synchronized ProcessStreams getBackgroundJobProcessStreams() { + // static inner class will be loaded lazily upon first access + return BackgroundJobProcessStream.INSTANCE; + } + + /** + * Returns a new instance for each invocation. + * + * @param processDescription the backing process + * @return the new instance + */ + @Override + public ProcessStreams createProcessStreams(ProcessDescription processDescription) { + Preconditions.checkNotNull(processDescription); + return createAndRegisterNewConsole(processDescription); + } + + private static GradleConsole createAndRegisterNewConsole(ProcessDescription processDescription) { + // creates a new console and adds it to the Eclipse Console view + GradleConsole gradleConsole = new GradleConsole(processDescription); + ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] { gradleConsole }); + return gradleConsole; + } + + /** + * Holds a {@code GradleConsole} instance. The instance held by this inner class is not created until first accessed. + */ + private static final class BackgroundJobProcessStream { + + private static final GradleConsole INSTANCE = createAndRegisterNewConsole(ProcessDescription.with(ConsoleMessages.Background_Console_Title)); + + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsole.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsole.java new file mode 100644 index 000000000..586962606 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsole.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.console.IOConsole; +import org.eclipse.ui.console.IOConsoleInputStream; +import org.eclipse.ui.console.IOConsoleOutputStream; + +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.console.ProcessDescription; +import com.gradleware.tooling.eclipse.core.console.ProcessStreams; +import com.gradleware.tooling.eclipse.ui.PluginImages; +import com.gradleware.tooling.eclipse.ui.UiPlugin; + +/** + * Provides a console to display the output of interacting with Gradle. + * + * Note that once a console is removed, all open streams managed by the console will be closed + * automatically, thus there is no need for us to close these streams explicitly here. + */ +public final class GradleConsole extends IOConsole implements ProcessStreams { + + private final ProcessDescription processDescription; + private final IOConsoleOutputStream outputStream; + private final IOConsoleOutputStream errorStream; + private final IOConsoleInputStream inputStream; + + public GradleConsole(ProcessDescription processDescription) { + super(processDescription.getName(), PluginImages.TASK.withState(PluginImages.ImageState.ENABLED).getImageDescriptor()); + + this.processDescription = processDescription; + this.outputStream = newOutputStream(); + this.errorStream = newOutputStream(); + this.inputStream = super.getInputStream(); + + // set proper colors on output/error streams (needs to happen in the UI thread) + Display.getDefault().asyncExec(new Runnable() { + + @Override + public void run() { + Color outputColor = Display.getDefault().getSystemColor(SWT.COLOR_BLACK); + GradleConsole.this.outputStream.setColor(outputColor); + + Color errorColor = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED); + GradleConsole.this.errorStream.setColor(errorColor); + } + }); + } + + public ProcessDescription getProcessDescription() { + return this.processDescription; + } + + public boolean isTerminated() { + Optional launch = this.processDescription.getLaunch(); + return launch.isPresent() && launchFinished(launch.get()); + } + + private boolean launchFinished(ILaunch launch) { + // a launch is considered finished, if it is not registered anymore + // (all other ways to determine the state of the launch did not work for us) + return !ImmutableList.copyOf(DebugPlugin.getDefault().getLaunchManager().getLaunches()).contains(launch); + } + + public boolean isCloseable() { + return this.processDescription.getLaunch().isPresent(); + } + + @Override + public OutputStream getOutput() { + return this.outputStream; + } + + @Override + public OutputStream getError() { + return this.errorStream; + } + + @Override + public InputStream getInput() { + return this.inputStream; + } + + @Override + public void close() { + Exception e = null; + + try { + this.outputStream.flush(); + this.outputStream.close(); + } catch (IOException ioe) { + e = ioe; + } + try { + this.errorStream.flush(); + this.errorStream.close(); + } catch (IOException ioe) { + e = ioe; + } + try { + this.inputStream.close(); + } catch (IOException ioe) { + e = ioe; + } + + if (e != null) { + String message = String.format("Cannot close streams of console %s.", getName()); //$NON-NLS-1$ + UiPlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsolePageParticipant.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsolePageParticipant.java new file mode 100644 index 000000000..d5b2bf223 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/GradleConsolePageParticipant.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.ui.console.IConsole; +import org.eclipse.ui.console.IConsoleConstants; +import org.eclipse.ui.console.IConsolePageParticipant; +import org.eclipse.ui.part.IPageBookViewPage; + +/** + * Contributes actions to {@link GradleConsole} instances at the time a new console is initialized. + */ +public final class GradleConsolePageParticipant implements IConsolePageParticipant { + + private CancelBuildExecutionAction cancelBuildExecutionAction; + private RemoveTerminatedGradleConsoleAction removeConsoleAction; + private RemoveAllTerminatedConsolesAction removeAllConsolesAction; + + /** + * {@inheritDoc} + * + * Adds custom toolbar items to {@link GradleConsole} instances. + */ + @Override + public void init(IPageBookViewPage page, IConsole console) { + if (console instanceof GradleConsole) { + GradleConsole gradleConsole = (GradleConsole) console; + if (gradleConsole.isCloseable()) { + addActionsToToolbar(page.getSite().getActionBars().getToolBarManager(), gradleConsole); + } + } + } + + private void addActionsToToolbar(IToolBarManager toolBarManager, GradleConsole gradleConsole) { + this.cancelBuildExecutionAction = new CancelBuildExecutionAction(gradleConsole); + this.removeConsoleAction = new RemoveTerminatedGradleConsoleAction(gradleConsole); + this.removeAllConsolesAction = new RemoveAllTerminatedConsolesAction(gradleConsole); + + toolBarManager.appendToGroup(IConsoleConstants.LAUNCH_GROUP, this.cancelBuildExecutionAction); + toolBarManager.appendToGroup(IConsoleConstants.LAUNCH_GROUP, this.removeConsoleAction); + toolBarManager.appendToGroup(IConsoleConstants.LAUNCH_GROUP, this.removeAllConsolesAction); + } + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class adapter) { + return null; + } + + @Override + public void activated() { + // do nothing + } + + @Override + public void deactivated() { + // do nothing + } + + @Override + public void dispose() { + if (this.cancelBuildExecutionAction != null) { + this.cancelBuildExecutionAction.dispose(); + this.cancelBuildExecutionAction = null; + } + if (this.removeConsoleAction != null) { + this.removeConsoleAction.dispose(); + this.removeConsoleAction = null; + } + if (this.removeAllConsolesAction != null) { + this.removeAllConsolesAction.dispose(); + this.removeAllConsolesAction = null; + } + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveAllTerminatedConsolesAction.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveAllTerminatedConsolesAction.java new file mode 100644 index 000000000..441bfba83 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveAllTerminatedConsolesAction.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import com.google.common.base.Preconditions; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.jface.action.Action; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsoleManager; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.ui.PluginImage.ImageState; +import com.gradleware.tooling.eclipse.ui.PluginImages; + +/** + * Removes all finished {@link ILaunch} instances associated with a {@link GradleConsole} instance. + * The action is only enabled if at least one console can be removed. + */ +public final class RemoveAllTerminatedConsolesAction extends Action implements ILaunchesListener2 { + + private final GradleConsole gradleConsole; + + public RemoveAllTerminatedConsolesAction(GradleConsole gradleConsole) { + this.gradleConsole = Preconditions.checkNotNull(gradleConsole); + + setToolTipText(ConsoleMessages.Action_RemoveAllTerminatedConsoles_Tooltip); + setImageDescriptor(PluginImages.REMOVE_ALL_CONSOLES.withState(ImageState.ENABLED).getImageDescriptor()); + setDisabledImageDescriptor(PluginImages.REMOVE_ALL_CONSOLES.withState(ImageState.DISABLED).getImageDescriptor()); + + DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); + + update(); + } + + private void update() { + setEnabled(this.gradleConsole.isCloseable() && this.gradleConsole.isTerminated()); + } + + private ImmutableList getTerminatedConsoles() { + IConsoleManager consoleManager = ConsolePlugin.getDefault().getConsoleManager(); + return FluentIterable.of(consoleManager.getConsoles()).filter(GradleConsole.class).filter(new Predicate() { + + @Override + public boolean apply(GradleConsole console) { + return console.isCloseable() && console.isTerminated(); + } + }).toList(); + } + + @Override + public void run() { + ImmutableList terminatedConsoles = getTerminatedConsoles(); + ConsolePlugin.getDefault().getConsoleManager().removeConsoles(terminatedConsoles.toArray(new GradleConsole[terminatedConsoles.size()])); + } + + @Override + public void launchesAdded(ILaunch[] launches) { + } + + @Override + public void launchesChanged(ILaunch[] launches) { + } + + @Override + public void launchesTerminated(ILaunch[] launches) { + } + + @Override + public void launchesRemoved(ILaunch[] launches) { + update(); + } + + public void dispose() { + DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this); + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveTerminatedGradleConsoleAction.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveTerminatedGradleConsoleAction.java new file mode 100644 index 000000000..3489fb11a --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/console/RemoveTerminatedGradleConsoleAction.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.console; + +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.jface.action.Action; +import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.ui.PluginImage.ImageState; +import com.gradleware.tooling.eclipse.ui.PluginImages; + +/** + * Removes the finished {@link ILaunch} instance associated with a given {@link GradleConsole} + * instance. The action is only enabled if the launch instance has terminated. + * + * Note: the implementation is somewhat along the lines of + * {@code org.eclipse.debug.internal.ui.views.console.ConsoleRemoveLaunchAction}. + */ +public final class RemoveTerminatedGradleConsoleAction extends Action implements ILaunchesListener2 { + + private final GradleConsole gradleConsole; + + public RemoveTerminatedGradleConsoleAction(GradleConsole gradleConsole) { + this.gradleConsole = Preconditions.checkNotNull(gradleConsole); + + setToolTipText(ConsoleMessages.Action_RemoveTerminatedConsole_Tooltip); + setImageDescriptor(PluginImages.REMOVE_CONSOLE.withState(ImageState.ENABLED).getImageDescriptor()); + setDisabledImageDescriptor(PluginImages.REMOVE_CONSOLE.withState(ImageState.DISABLED).getImageDescriptor()); + + DebugPlugin.getDefault().getLaunchManager().addLaunchListener(this); + + update(); + } + + private void update() { + setEnabled(this.gradleConsole.isCloseable() && this.gradleConsole.isTerminated()); + } + + @Override + public void run() { + ConsolePlugin.getDefault().getConsoleManager().removeConsoles(new IConsole[] { this.gradleConsole }); + } + + @Override + public void launchesAdded(ILaunch[] launches) { + } + + @Override + public void launchesChanged(ILaunch[] launches) { + } + + @Override + public void launchesTerminated(ILaunch[] launches) { + } + + @Override + public void launchesRemoved(ILaunch[] launches) { + update(); + } + + public void dispose() { + DebugPlugin.getDefault().getLaunchManager().removeLaunchListener(this); + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionEnablingSelectionChangedListener.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionEnablingSelectionChangedListener.java new file mode 100644 index 000000000..c71b07991 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionEnablingSelectionChangedListener.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import java.util.List; + +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreeViewer; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +/** + * Enables {@link SelectionSpecificAction} instances, after querying each action if it should be + * enabled for the current selection. + * + * @see SelectionSpecificAction#isEnabledFor(NodeSelection) + */ +public final class ActionEnablingSelectionChangedListener implements ISelectionChangedListener { + + private final TreeViewer treeViewer; + private final ImmutableList actions; + + public ActionEnablingSelectionChangedListener(TreeViewer treeViewer, List actions) { + this.treeViewer = Preconditions.checkNotNull(treeViewer); + this.actions = ImmutableList.copyOf(actions); + + // initialize the actions based on the current selection + handleSelection(NodeSelection.from(this.treeViewer.getSelection())); + } + + @Override + public void selectionChanged(SelectionChangedEvent event) { + NodeSelection selection = NodeSelection.from(this.treeViewer.getSelection()); + handleSelection(selection); + } + + private void handleSelection(NodeSelection selection) { + for (SelectionSpecificAction action : this.actions) { + action.setEnabled(action.isEnabledFor(selection)); + } + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionShowingContextMenuListener.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionShowingContextMenuListener.java new file mode 100644 index 000000000..a750c79c3 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ActionShowingContextMenuListener.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import java.util.List; + +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.viewers.TreeViewer; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +/** + * Adds {@link SelectionSpecificAction} instances as menu items to the context menu of a given + * {@link TreeViewer} instance, after querying each action if it should be shown for the current + * selection. + * + * @see SelectionSpecificAction#isVisibleFor(NodeSelection) + */ +public final class ActionShowingContextMenuListener implements IMenuListener { + + private final TreeViewer treeViewer; + private final ImmutableList actions; + + public ActionShowingContextMenuListener(TreeViewer treeViewer, List actions) { + this.treeViewer = Preconditions.checkNotNull(treeViewer); + this.actions = ImmutableList.copyOf(actions); + } + + @Override + public void menuAboutToShow(IMenuManager manager) { + NodeSelection selection = NodeSelection.from(this.treeViewer.getSelection()); + handleSelection(manager, selection); + } + + private void handleSelection(IMenuManager manager, NodeSelection selection) { + for (SelectionSpecificAction action : this.actions) { + if (action.isVisibleFor(selection)) { + manager.add(action); + } + } + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/CommandBackedAction.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/CommandBackedAction.java new file mode 100644 index 000000000..c8ad0cc50 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/CommandBackedAction.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.swt.widgets.Event; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.handlers.IHandlerService; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.ui.UiPlugin; + +/** + * Base class for {@link Action} instances that invoke a {@link org.eclipse.core.commands.Command} + * instance via its command id when the action is triggered. + */ +public abstract class CommandBackedAction extends Action { + + private final String commandId; + + protected CommandBackedAction(String commandId) { + this(commandId, IAction.AS_UNSPECIFIED); + } + + protected CommandBackedAction(String commandId, int style) { + super(null, style); + this.commandId = Preconditions.checkNotNull(commandId); + } + + @Override + public void runWithEvent(Event event) { + try { + getHandlerService().executeCommand(this.commandId, event); + } catch (Exception e) { + String message = String.format("Cannot execute command for action '%s'.", getText()); + UiPlugin.logger().error(message, e); + throw new GradlePluginsRuntimeException(message, e); + } + } + + @SuppressWarnings("cast") + private IHandlerService getHandlerService() { + return (IHandlerService) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getService(IHandlerService.class); + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ContextActivatingViewPartListener.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ContextActivatingViewPartListener.java new file mode 100644 index 000000000..2756cec53 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/ContextActivatingViewPartListener.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import org.eclipse.ui.IPartListener2; +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPartReference; +import org.eclipse.ui.contexts.IContextActivation; +import org.eclipse.ui.contexts.IContextService; + +import com.google.common.base.Preconditions; +import com.gradleware.tooling.eclipse.ui.util.selection.SelectionUtils; + +/** + * Dynamically activates the context with the specified id whenever the currently active + * {@link IWorkbenchPartReference} belongs to the specified {@link IViewPart}. + */ +public final class ContextActivatingViewPartListener implements IPartListener2 { + + private final IViewPart viewPart; + private final String contextId; + private final IContextService contextService; + private IContextActivation activation; + + @SuppressWarnings("cast") + public ContextActivatingViewPartListener(String contextId, IViewPart viewPart) { + this.viewPart = Preconditions.checkNotNull(viewPart); + this.contextId = Preconditions.checkNotNull(contextId); + this.contextService = (IContextService) viewPart.getSite().getService(IContextService.class); + this.activation = null; + } + + @Override + public void partActivated(IWorkbenchPartReference partReference) { + if (SelectionUtils.belongsToViewPart(partReference, this.viewPart)) { + this.activation = this.contextService.activateContext(this.contextId); + } + } + + @Override + public void partDeactivated(IWorkbenchPartReference partReference) { + if (SelectionUtils.belongsToViewPart(partReference, this.viewPart)) { + this.contextService.deactivateContext(this.activation); + } + } + + @Override + public void partOpened(IWorkbenchPartReference partRef) { + } + + @Override + public void partClosed(IWorkbenchPartReference partRef) { + } + + @Override + public void partVisible(IWorkbenchPartReference partRef) { + } + + @Override + public void partHidden(IWorkbenchPartReference partRef) { + } + + @Override + public void partBroughtToTop(IWorkbenchPartReference partRef) { + } + + @Override + public void partInputChanged(IWorkbenchPartReference partRef) { + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/NodeSelection.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/NodeSelection.java new file mode 100644 index 000000000..55a80c254 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/NodeSelection.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import java.util.List; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; + +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +/** + * Provides information about a given set of selected nodes. + */ +public final class NodeSelection { + + private static final NodeSelection EMPTY = new NodeSelection(ImmutableList.of()); + + private final ImmutableList nodes; + + private NodeSelection(List nodes) { + this.nodes = ImmutableList.copyOf(nodes); + } + + public boolean isEmpty() { + return this.nodes.isEmpty(); + } + + public boolean isSingleSelection() { + return this.nodes.size() == 1; + } + + public T getFirstNode(Class expectedType) { + if (isEmpty()) { + throw new IllegalStateException("Selection is empty."); + } else { + return expectedType.cast(this.nodes.get(0)); + } + } + + public Object getFirstNode() { + if (isEmpty()) { + throw new IllegalStateException("Selection is empty."); + } else { + return this.nodes.get(0); + } + } + + public ImmutableList getNodes() { + return this.nodes; + } + + /** + * Returns a list of all nodes. + * + * @param expectedType the expected type of the nodes + * @return the list of all nodes + * @throws ClassCastException thrown if a node is not of the expected type + */ + public ImmutableList getNodes(final Class expectedType) { + return FluentIterable.from(this.nodes).transform(new Function() { + + @Override + public T apply(Object input) { + return expectedType.cast(input); + } + }).toList(); + } + + /** + * Checks whether all nodes are of the given type. + * + * @param expectedType the expected type of the nodes + * @return {@code true} if all nodes match the type + */ + public boolean hasAllNodesOfType(Class expectedType) { + return allMatch(Predicates.instanceOf(expectedType)); + } + + /** + * Checks whether all nodes of this node selection meet the specified criteria. + * + * @param predicate the criteria to match + * @return {@code true} if all nodes match the criteria + */ + public boolean allMatch(Predicate predicate) { + return FluentIterable.from(this.nodes).allMatch(predicate); + } + + /** + * Merges this node selection with the given node selection by removing those nodes that are not + * part of the new selection and by adding those nodes that are only in the new selection. The + * merge result is returned as a new {@code NodeSelection} instance, while this node selection + * is not modified. + * + * @param newSelection the node selection to merge with + * @return a new instance containing the merge result + */ + public NodeSelection mergeWith(NodeSelection newSelection) { + // short-circuit if the new selection is empty + if (newSelection.isEmpty()) { + return NodeSelection.empty(); + } + + List result = Lists.newArrayList(this.nodes); + + // remove those nodes that are not in the new selection anymore + result.retainAll(newSelection.getNodes()); + + // add those nodes that are new in the new selection + ImmutableList newlySelected = removeAll(newSelection.getNodes(), result); + result.addAll(newlySelected); + + return new NodeSelection(result); + } + + private ImmutableList removeAll(List toRemoveFrom, final List elementsToRemove) { + return FluentIterable.from(toRemoveFrom).filter(new Predicate() { + + @Override + public boolean apply(Object node) { + return !elementsToRemove.contains(node); + } + }).toList(); + } + + /** + * Creates a new instance representing the empty selection. + * + * @return the new instance + */ + public static NodeSelection empty() { + return EMPTY; + } + + /** + * Creates a new instance reflecting the given {@link IStructuredSelection} instance. + * + * @param selection the selection from which to create the new instance + * @return the new instance + */ + public static NodeSelection from(IStructuredSelection selection) { + return selection.isEmpty() ? empty() : new NodeSelection(selection.toList()); + } + + /** + * Creates a new instance reflecting the given {@link ISelection} instance. All selection + * sub-types other than the {@link IStructuredSelection} sub-type will always have an empty + * {@link NodeSelection} instance returned. + * + * @param selection the selection from which to create the new instance + * @return the new instance + */ + public static NodeSelection from(ISelection selection) { + return selection instanceof IStructuredSelection ? from((IStructuredSelection) selection) : empty(); + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/QuickSearchManager.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/QuickSearchManager.java new file mode 100644 index 000000000..9e56861d2 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/QuickSearchManager.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +/** + * Custom label control wrapper hooking quick search capability to a {@link Tree}. + * + * When instantiated, it adds a key listener to the tree which performs a search amongst the tree + * items based on the entered search pattern. The matching results can be traversed with the up/down + * keys. + *

+ * The implementation is wrapped around a {@link Label} control that displays the search pattern. + *

+ * If there is nothing entered or the enter/escape key is pressed, the up/down keys regain their + * original functions (e.g. navigating between all nodes). + */ +public final class QuickSearchManager { + + // the target tree on which to support quick searches + private final Tree tree; + + // shows the current search text + private final Label label; + + // holds the current search state + private final QuickSearchState state; + + // updates the search text and selection when a key is pressed while the tree is in focus + private final KeyListener listener; + + public QuickSearchManager(Tree tree, Label label) { + this.tree = Preconditions.checkNotNull(tree); + this.label = Preconditions.checkNotNull(label); + this.state = new QuickSearchState(); + this.listener = new TreeKeyListener(); + + init(); + } + + private void init() { + this.tree.addKeyListener(this.listener); + reset(false); + } + + public void reset() { + reset(true); + } + + private void reset(boolean resetTreeSelection) { + this.state.reset(); + updateLabelText(); + if (resetTreeSelection) { + updateTreeSelection(true); + } + } + + private void handleDownArrow(KeyEvent e) { + // if the search is active, move the selection to the next match + if (isSearchActive()) { + if (this.state.results.length > 0) { + this.state.current++; + this.state.current = this.state.current % this.state.results.length; + + updateTreeSelection(false); + } + + // disable event propagation (don't move the cursor one down) + e.doit = false; + } + } + + private void handleUpArrow(KeyEvent e) { + // if the search is active, move the selection to the previous match + if (isSearchActive()) { + if (this.state.results.length > 0) { + this.state.current--; + if (this.state.current < 0) { + this.state.current += this.state.results.length; + } + + updateTreeSelection(false); + } + + // disable event propagation (don't move the cursor one up) + e.doit = false; + } + } + + private void handleBackspace(KeyEvent e) { + // if the search is active, remove the last character from the search text and update the + // search results + if (isSearchActive()) { + // remove the last character from the search if any + this.state.removeLastCharacterFromSearchText(); + + if (isSearchActive()) { + // update the search text and search results + performSearch(); + + // reflect the search result in the ui + updateLabelText(); + updateTreeSelection(true); + } else { + // clear the search text and search results + reset(false); + } + + // disable event propagation (don't let Eclipse's default key listener take action) + e.doit = false; + } + } + + private void handleMiscKeys(KeyEvent e) { + // if the input is a valid character, append it to the search text and update the search + // results + if (Character.isLetterOrDigit(e.character)) { + // update the state of the search text and perform the search logic + this.state.appendToSearchText(e); + performSearch(); + + // reflect the search result in the ui + updateLabelText(); + updateTreeSelection(true); + + // disable event propagation (don't let Eclipse's default key listener take action) + e.doit = false; + } + } + + private boolean isSearchActive() { + // the search is active if the search text is non-empty + // if the search is not active, the cursors can be used to traverse the tree + // if the search is active, the traversal is bound to the matching tree items + return !Strings.isNullOrEmpty(this.state.searchText); + } + + private void performSearch() { + // iterate through all tree items and find the ones matching the search pattern + final String searchText = QuickSearchManager.this.state.searchText.toUpperCase(); + Predicate predicate = new Predicate() { + + @Override + public boolean apply(TreeItem item) { + return item.getText().toUpperCase().contains(searchText); + } + }; + ImmutableList.Builder hits = ImmutableList.builder(); + filterRecursively(ImmutableList.copyOf(this.tree.getItems()), predicate, hits); + this.state.setResults(hits.build()); + } + + private void filterRecursively(ImmutableList items, Predicate predicate, ImmutableList.Builder hits) { + for (TreeItem item : items) { + if (predicate.apply(item)) { + hits.add(item); + } + + // do not traverse into closed nodes + if (item.getExpanded()) { + TreeItem[] childItems = item.getItems(); + filterRecursively(ImmutableList.copyOf(childItems), predicate, hits); + } + } + } + + private void updateLabelText() { + // update the label to show how many matching elements are there + if (isSearchActive()) { + this.label.setText(String.format("%s (%d matches)", this.state.searchText, this.state.results.length)); + } else { + this.label.setText("Type to search, use arrows to navigate"); + } + } + + private void updateTreeSelection(boolean clearIfNoResults) { + if (this.state.results.length > 0) { + // if there are matching items, select the current one + TreeItem item = this.state.results[this.state.current]; + this.tree.setSelection(item); + this.tree.notifyListeners(SWT.Selection, new Event()); + } else if (clearIfNoResults) { + // if there are no matching items, clear the selection + this.tree.setSelection(new TreeItem[0]); + this.tree.notifyListeners(SWT.Selection, new Event()); + } + + // when we update the selection, we disable the event propagation + // (KeyEvent.doit = false), as a consequence, the selection is not + // propagated properly towards the listeners, thus raise a + // selection event manually + } + + public void dispose() { + if (!this.tree.isDisposed()) { // guard required + this.tree.removeKeyListener(this.listener); + } + } + + /** + * Encapsulates the current state of the quick search. + */ + private final class QuickSearchState { + + // contains the current search text + private String searchText; + + // the tree items that result from applying the current search text + private TreeItem[] results; + + // the index of the current item in the list of matching tree items + private int current; + + private QuickSearchState() { + reset(); + } + + private void reset() { + this.searchText = ""; + this.results = new TreeItem[0]; + this.current = -1; + } + + private void appendToSearchText(KeyEvent e) { + this.searchText += e.character; + } + + private void removeLastCharacterFromSearchText() { + String searchText = this.searchText; + if (searchText.length() > 0) { + this.searchText = searchText.substring(0, searchText.length() - 1); + } + } + + private void setResults(List hits) { + this.results = Iterables.toArray(hits, TreeItem.class); + this.current = this.results.length > 0 ? 0 : -1; + } + + } + + /** + * {code KeyListener} instance that, for each key stroke, updates the quick search accordingly. + */ + private final class TreeKeyListener extends KeyAdapter { + + @Override + public void keyPressed(KeyEvent e) { + switch (e.keyCode) { + case SWT.CR: + reset(false); // keep current selection + break; + case SWT.ESC: + reset(true); // clear current selection + break; + case SWT.ARROW_DOWN: + handleDownArrow(e); + break; + case SWT.ARROW_UP: + handleUpArrow(e); + break; + case SWT.BS: + handleBackspace(e); + break; + default: + handleMiscKeys(e); + break; + } + } + + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionHistoryManager.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionHistoryManager.java new file mode 100644 index 000000000..af132560a --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionHistoryManager.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreeViewer; + +import com.google.common.base.Preconditions; + +/** + * Stores the sequence in which the currently selected nodes were selected in the {@link TreeViewer}. + */ +public final class SelectionHistoryManager { + + private final TreeViewer treeViewer; + private final TreeViewerSelectionListener listener; + private NodeSelection selectionHistory; + + public SelectionHistoryManager(TreeViewer treeViewer) { + this.treeViewer = Preconditions.checkNotNull(treeViewer); + this.listener = new TreeViewerSelectionListener(); + this.selectionHistory = NodeSelection.empty(); + + init(); + } + + private void init() { + this.treeViewer.addSelectionChangedListener(this.listener); + } + + public NodeSelection getSelectionHistory() { + return this.selectionHistory; + } + + private void handleSelection(IStructuredSelection selection) { + NodeSelection nodeSelection = NodeSelection.from(selection); + this.selectionHistory = this.selectionHistory.mergeWith(nodeSelection); + } + + public void dispose() { + this.treeViewer.removeSelectionChangedListener(this.listener); + } + + /** + * {@code ISelectionChangedListener} that, for each selection change in the tree viewer, updates the selection history accordingly. + */ + private final class TreeViewerSelectionListener implements ISelectionChangedListener { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + handleSelection((IStructuredSelection) selection); + } + } + + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionSpecificAction.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionSpecificAction.java new file mode 100644 index 000000000..e832b4a5c --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/generic/SelectionSpecificAction.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.generic; + +import org.eclipse.jface.action.IAction; + +/** + * Describes an {@link IAction} instance that knows when it can be shown and when it can be + * enabled for a given selection state. + */ +public interface SelectionSpecificAction extends IAction { + + /** + * Returns {@code true} if this action should be shown for the given selection. + * + * @param selection the selection from which to make the decision + * @return {@code true} if this action should be shown + */ + boolean isVisibleFor(NodeSelection selection); + + /** + * Returns {@code true} if this action should be enabled for the given selection. + * + * @param selection the selection from which to make the decision + * @return {@code true} if this action should be enabled + */ + boolean isEnabledFor(NodeSelection selection); + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/ArgumentsTab.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/ArgumentsTab.java new file mode 100644 index 000000000..f8858517a --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/ArgumentsTab.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.launch; + +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.debug.ui.StringVariableSelectionDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Text; + +import com.gradleware.tooling.eclipse.core.i18n.CoreMessages; +import com.gradleware.tooling.eclipse.core.launch.GradleRunConfigurationAttributes; +import com.gradleware.tooling.eclipse.core.util.collections.CollectionsUtils; +import com.gradleware.tooling.eclipse.ui.PluginImage.ImageState; +import com.gradleware.tooling.eclipse.ui.PluginImages; + +/** + * Specifies the JVM arguments and program arguments to apply when executing tasks via the run + * configurations. + */ +public final class ArgumentsTab extends AbstractLaunchConfigurationTab { + + private Text argumentsText; + private Text jvmArgumentsText; + + @Override + public String getName() { + return LaunchMessages.Tab_Name_Arguments; + } + + @Override + public Image getImage() { + return PluginImages.RUN_CONFIG_ARGUMENTS.withState(ImageState.ENABLED).getImage(); + } + + @Override + public void createControl(Composite root) { + Composite parent = new Composite(root, SWT.NONE); + GridLayout layout = new GridLayout(1, false); + parent.setLayout(layout); + setControl(parent); + + Group argumentsGroup = createGroup(parent, CoreMessages.RunConfiguration_Label_Arguments + ":"); //$NON-NLS-1$ + createArgumentsSelectionControl(argumentsGroup); + + Group jvmArgumentsGroup = createGroup(parent, CoreMessages.RunConfiguration_Label_JvmArguments + ":"); //$NON-NLS-1$ + createJvmArgumentsSelectionControl(jvmArgumentsGroup); + } + + private Group createGroup(Composite parent, String groupName) { + Group group = new Group(parent, SWT.NONE); + group.setText(groupName); + group.setLayout(new GridLayout()); + group.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + return group; + } + + private void createArgumentsSelectionControl(Composite container) { + this.argumentsText = createTextControl(container); + createVariablesSelectorButton(container, this.argumentsText); + } + + private void createJvmArgumentsSelectionControl(Composite container) { + this.jvmArgumentsText = createTextControl(container); + createVariablesSelectorButton(container, this.jvmArgumentsText); + } + + private Text createTextControl(Composite container) { + Text textControl = new Text(container, SWT.MULTI | SWT.BORDER | SWT.WRAP | SWT.V_SCROLL); + GridData textLayoutData = new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1); + textLayoutData.heightHint = 65; + textControl.setLayoutData(textLayoutData); + textControl.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + updateLaunchConfigurationDialog(); + } + }); + return textControl; + } + + private void createVariablesSelectorButton(Composite container, final Text target) { + Composite buttonContainer = new Composite(container, SWT.NONE); + GridLayout buttonContainerLayout = new GridLayout(1, false); + buttonContainerLayout.marginHeight = 1; + buttonContainerLayout.marginWidth = 0; + buttonContainer.setLayout(buttonContainerLayout); + buttonContainer.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + + Button selectVariableButton = new Button(buttonContainer, SWT.NONE); + selectVariableButton.setText(LaunchMessages.Button_Label_SelectVariables); + selectVariableButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + StringVariableSelectionDialog dialog = new StringVariableSelectionDialog(getShell()); + dialog.open(); + String variable = dialog.getVariableExpression(); + if (variable != null) { + target.insert(variable); + } + } + }); + } + + @Override + public void initializeFrom(ILaunchConfiguration configuration) { + GradleRunConfigurationAttributes configurationAttributes = GradleRunConfigurationAttributes.from(configuration); + this.argumentsText.setText(CollectionsUtils.joinWithSpace(configurationAttributes.getArgumentExpressions())); + this.jvmArgumentsText.setText(CollectionsUtils.joinWithSpace(configurationAttributes.getJvmArgumentExpressions())); + } + + @Override + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + GradleRunConfigurationAttributes.applyArgumentExpressions(CollectionsUtils.splitBySpace(this.argumentsText.getText()), configuration); + GradleRunConfigurationAttributes.applyJvmArgumentExpressions(CollectionsUtils.splitBySpace(this.jvmArgumentsText.getText()), configuration); + } + + @Override + public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { + // leave the controls empty + } + +} diff --git a/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/GradleDistributionTab.java b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/GradleDistributionTab.java new file mode 100644 index 000000000..cb208f659 --- /dev/null +++ b/com.gradleware.tooling.eclipse.ui/src/main/java/com/gradleware/tooling/eclipse/ui/launch/GradleDistributionTab.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2015 the original author or authors. + * 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: + * Etienne Studer & Donát Csikós (Gradle Inc.) - initial API and implementation and initial documentation + */ + +package com.gradleware.tooling.eclipse.ui.launch; + +import java.util.List; + +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Text; +import org.gradle.util.GradleVersion; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.gradleware.tooling.eclipse.core.CorePlugin; +import com.gradleware.tooling.eclipse.core.GradlePluginsRuntimeException; +import com.gradleware.tooling.eclipse.core.gradle.GradleConnectionValidators; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper; +import com.gradleware.tooling.eclipse.core.gradle.GradleDistributionWrapper.DistributionType; +import com.gradleware.tooling.eclipse.core.i18n.CoreMessages; +import com.gradleware.tooling.eclipse.core.launch.GradleRunConfigurationAttributes; +import com.gradleware.tooling.eclipse.ui.PluginImage.ImageState; +import com.gradleware.tooling.eclipse.ui.PluginImages; +import com.gradleware.tooling.eclipse.ui.projectimport.ProjectImportMessages; +import com.gradleware.tooling.eclipse.ui.util.file.DirectoryDialogSelectionListener; +import com.gradleware.tooling.eclipse.ui.util.font.FontUtils; +import com.gradleware.tooling.eclipse.ui.util.selection.Enabler; +import com.gradleware.tooling.eclipse.ui.util.widget.ButtonUtils; +import com.gradleware.tooling.eclipse.ui.util.widget.UiBuilder.UiBuilderFactory; +import com.gradleware.tooling.toolingutils.binding.Validator; +import com.gradleware.tooling.toolingutils.distribution.PublishedGradleVersions; + +/** + * Specifies the Gradle distribution to apply when executing tasks via the run configurations. + */ +public final class GradleDistributionTab extends AbstractLaunchConfigurationTab { + + private final Font defaultFont; + private final UiBuilderFactory builderFactory; + private final Validator gradleDistributionValidator; + private final PublishedGradleVersions publishedGradleVersions; + + private Text localInstallationDirText; + private Text remoteDistributionUriText; + private Combo gradleVersionCombo; + + private Button useGradleWrapperOption; + private Button useLocalInstallationDirOption; + private Button useRemoteDistributionUriOption; + private Button useGradleVersionOption; + + private final SelectionListener optionSelectionChangedListener; + private final ModifyListener textChangedListener; + + // if different configurations are loaded we change radio buttons and text fields. this + // generates several change events which trigger the launch config to update and leads to + // an inconsistent state. to resolve that, we disable the launch config update in when the + // initializeFrom() method is called + private boolean disableUpdateDialog = false; + + public GradleDistributionTab() { + this.defaultFont = FontUtils.getDefaultDialogFont(); + this.builderFactory = new UiBuilderFactory(this.defaultFont); + this.gradleDistributionValidator = GradleConnectionValidators.gradleDistributionValidator(); + this.publishedGradleVersions = CorePlugin.publishedGradleVersions(); + + this.optionSelectionChangedListener = new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + if (!GradleDistributionTab.this.disableUpdateDialog) { + updateLaunchConfigurationDialog(); + } + } + }; + this.textChangedListener = new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + if (!GradleDistributionTab.this.disableUpdateDialog) { + updateLaunchConfigurationDialog(); + } + } + }; + } + + @Override + public String getName() { + return LaunchMessages.Tab_Name_GradleDistribution; + } + + @Override + public Image getImage() { + return PluginImages.RUN_CONFIG_GRADLE_DISTRIBUTION.withState(ImageState.ENABLED).getImage(); + } + + @Override + public void createControl(Composite parent) { + Composite page = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(1, false); + page.setLayout(layout); + setControl(page); + + Group gradleDistributionGroup = createGroup(page, CoreMessages.RunConfiguration_Label_GradleDistribution + ":"); + createGradleDistributionSelectionControl(gradleDistributionGroup); + } + + private Group createGroup(Composite parent, String groupName) { + Group group = new Group(parent, SWT.NONE); + group.setText(groupName); + group.setLayout(new GridLayout(3, false)); + group.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + return group; + } + + private void createGradleDistributionSelectionControl(Group container) { + // first line: gradle wrapper + this.useGradleWrapperOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_GradleWrapper).control(); + this.useGradleWrapperOption.setSelection(true); + + this.builderFactory.span(container); + this.builderFactory.span(container); + + // second line: local installation directory + this.useLocalInstallationDirOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_LocalInstallationDirectory).control(); + this.localInstallationDirText = this.builderFactory.newText(container).alignFillHorizontal().disabled().control(); + Button localInstallationDirBrowseButton = this.builderFactory.newButton(container).alignLeft().disabled().text(ProjectImportMessages.Button_Label_Browse).control(); + localInstallationDirBrowseButton.addSelectionListener(new DirectoryDialogSelectionListener(container.getShell(), this.localInstallationDirText, + CoreMessages.GradleDistribution_Label_LocalInstallationDirectory)); + + // third line: remote distribution installation + this.useRemoteDistributionUriOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_RemoteDistributionUri).control(); + this.remoteDistributionUriText = this.builderFactory.newText(container).alignFillHorizontal().disabled().control(); + this.builderFactory.span(container); + + // fourth line: gradle version + this.useGradleVersionOption = this.builderFactory.newRadio(container).alignLeft().text(CoreMessages.GradleDistribution_Label_SpecificGradleVersion).control(); + this.gradleVersionCombo = this.builderFactory.newCombo(container).alignLeft().disabled().control(); + this.gradleVersionCombo.setSize(150, this.gradleVersionCombo.getSize().y); + this.gradleVersionCombo.setItems(getGradleVersions()); + this.builderFactory.span(container); + + // update launch configuration when the content of the widgets are changing + this.localInstallationDirText.addModifyListener(this.textChangedListener); + this.remoteDistributionUriText.addModifyListener(this.textChangedListener); + this.gradleVersionCombo.addModifyListener(this.textChangedListener); + + // update the enabled/disabled state when the currently selected radio is changing + this.useGradleWrapperOption.addSelectionListener(this.optionSelectionChangedListener); + this.useLocalInstallationDirOption.addSelectionListener(this.optionSelectionChangedListener); + this.useRemoteDistributionUriOption.addSelectionListener(this.optionSelectionChangedListener); + this.useGradleVersionOption.addSelectionListener(this.optionSelectionChangedListener); + + // update the enabled/disabled state when the currently selected radio is changing + new Enabler(this.useGradleWrapperOption).enables(); + new Enabler(this.useLocalInstallationDirOption).enables(this.localInstallationDirText, localInstallationDirBrowseButton); + new Enabler(this.useRemoteDistributionUriOption).enables(this.remoteDistributionUriText); + new Enabler(this.useGradleVersionOption).enables(this.gradleVersionCombo); + } + + private String[] getGradleVersions() { + return FluentIterable.from(this.publishedGradleVersions.getVersions()).transform(new Function() { + + @Override + public String apply(GradleVersion gradleVersion) { + return gradleVersion.getVersion(); + } + }).toArray(String.class); + } + + @Override + public void initializeFrom(ILaunchConfiguration configuration) { + // do not trigger save configuration (through events) when initializing the values + this.disableUpdateDialog = true; + try { + GradleRunConfigurationAttributes configurationAttributes = GradleRunConfigurationAttributes.from(configuration); + setSelection(GradleDistributionWrapper.from(configurationAttributes.getGradleDistribution())); + } finally { + this.disableUpdateDialog = false; + } + } + + private void setSelection(GradleDistributionWrapper wrapper) { + // reset values of controls + this.localInstallationDirText.setText(""); + this.remoteDistributionUriText.setText(""); + if (this.gradleVersionCombo.getItemCount() > 0) { + this.gradleVersionCombo.select(0); + } else { + this.gradleVersionCombo.setText(""); + } + + List