diff options
author | Xavier Ducrohet <xav@android.com> | 2013-05-31 18:31:22 -0700 |
---|---|---|
committer | Xavier Ducrohet <xav@android.com> | 2013-06-27 12:36:58 -0700 |
commit | c2a6c07d08cc39e28a3d4aa63c7c850c700bb831 (patch) | |
tree | a467d9d1450fb6dc7444d7b2582bf4e3251abc25 | |
parent | 504bf5c42ed86a7d053fe6f9e015999ae0d2e3d5 (diff) | |
download | sdk-c2a6c07d08cc39e28a3d4aa63c7c850c700bb831.zip sdk-c2a6c07d08cc39e28a3d4aa63c7c850c700bb831.tar.gz sdk-c2a6c07d08cc39e28a3d4aa63c7c850c700bb831.tar.bz2 |
Improves Gradle export.
Splits figuring out the modules and their props from
the code writing the new files.
The project selection page now uses a ProjectSetupBuilder
and updates it on every project selection or unselection.
This provides real-time feedback on missing dependencies
or broken ones.
Creates a new page to allow confirmation of project
root and list of modules. Will also allow review errors
when some are detected during modules gathering.
A final page, is shown after the export takes place
to review the result.
Change-Id: Ia67acc88f390f661518ed13818f25750be181c78
11 files changed, 1504 insertions, 732 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/BuildFileCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/BuildFileCreator.java index 3b9c39d..d6e2276 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/BuildFileCreator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/BuildFileCreator.java @@ -17,20 +17,17 @@ package com.android.ide.eclipse.adt.internal.wizards.exportgradle; import com.android.SdkConstants; -import com.android.ide.eclipse.adt.AdtConstants; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.annotations.NonNull; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; -import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; import com.android.ide.eclipse.adt.io.IFolderWrapper; import com.android.io.IAbstractFile; import com.android.sdklib.io.FileOp; -import com.android.utils.Pair; import com.android.xml.AndroidManifest; +import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; +import com.google.common.io.Files; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -41,12 +38,8 @@ 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.SubMonitor; 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.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Shell; @@ -60,7 +53,6 @@ import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.regex.Pattern; /** * Creates build.gradle and settings.gradle files for a set of projects. @@ -68,13 +60,13 @@ import java.util.regex.Pattern; * Based on {@link org.eclipse.ant.internal.ui.datatransfer.BuildFileCreator} */ public class BuildFileCreator { - private static final String BUILD_FILE = "build.gradle"; //$NON-NLS-1$ - private static final String SETTINGS_FILE = "settings.gradle"; //$NON-NLS-1$ + static final String BUILD_FILE = "build.gradle"; //$NON-NLS-1$ + static final String SETTINGS_FILE = "settings.gradle"; //$NON-NLS-1$ private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ private static final String GRADLE_WRAPPER_LOCATION = "tools/templates/gradle/wrapper"; //$NON-NLS-1$ static final String PLUGIN_CLASSPATH = - "classpath 'com.android.tools.build:gradle:0.4'"; //$NON-NLS-1$ + "classpath 'com.android.tools.build:gradle:0.4.+'"; //$NON-NLS-1$ static final String MAVEN_REPOSITORY = "mavenCentral()"; //$NON-NLS-1$ private static final String[] GRADLE_WRAPPER_FILES = new String[] { @@ -91,27 +83,24 @@ public class BuildFileCreator { } }; - private StringBuilder buildfile; - private IJavaProject project; - private String projectName; - private String projectRoot; - private final IPath commonRoot; + private final GradleModule mModule; + private final StringBuilder mBuildFile = new StringBuilder(); /** - * Create buildfile for the given projects. + * Create buildfile for the projects. * - * @param projects create buildfiles for these <code>IJavaProject</code> - * objects * @param shell parent instance for dialogs * @return project names for which buildfiles were created * @throws InterruptedException thrown when user cancels task */ - public static List<String> createBuildFiles(Set<IJavaProject> projects, Shell shell, - IProgressMonitor pm) - throws JavaModelException, IOException, CoreException, InterruptedException { + public static void createBuildFiles( + @NonNull ProjectSetupBuilder builder, + @NonNull Shell shell, + @NonNull IProgressMonitor pm) { + File gradleLocation = new File(Sdk.getCurrent().getSdkLocation(), GRADLE_WRAPPER_LOCATION); - List<String> res = new ArrayList<String>(); SubMonitor localmonitor = null; + try { // See if we have a Gradle wrapper in the SDK templates directory. If so, we can copy // it over. @@ -122,42 +111,22 @@ public class BuildFileCreator { } } - Set<IJavaProject> fullProjectSet = Sets.newHashSet(); - - // build a list of all projects that must be included. This is in case - // some dependencies have not been included in the selected projects. We also include - // parent projects so that the full multi-project setup is correct. - // Note that if two projects are selected that are not related, both will be added - // in the same multi-project anyway. - for (IJavaProject javaProject : projects) { - fullProjectSet.add(javaProject); - ProjectState projectState = Sdk.getProjectState(javaProject.getProject()); - - // add dependencies - List<IProject> dependencies = projectState.getFullLibraryProjects(); - for (IProject dependency : dependencies) { - fullProjectSet.add(JavaCore.create(dependency)); - } - - Collection<ProjectState> parents = projectState.getFullParentProjects(); - for (ProjectState parent : parents) { - fullProjectSet.add(JavaCore.create(parent.getProject())); - } - } + Collection<GradleModule> modules = builder.getModules(); + boolean multiModules = modules.size() > 1; // determine files to create/change List<IFile> files = new ArrayList<IFile>(); - // add the build.gradle file for all projects. - for (IJavaProject project : fullProjectSet) { + // add the build.gradle file for all modules. + for (GradleModule module : modules) { // build.gradle file - IFile file = project.getProject().getFile(BuildFileCreator.BUILD_FILE); + IFile file = module.getProject().getFile(BuildFileCreator.BUILD_FILE); files.add(file); } - // get the commonRoot for all projects. If only one project, this returns the path + // get the commonRoot for all modules. If only one module, this returns the path // of the project. - IPath commonRoot = determineCommonRoot(fullProjectSet); + IPath commonRoot = builder.getCommonRoot(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); IPath workspaceLocation = workspaceRoot.getLocation(); @@ -167,8 +136,8 @@ public class BuildFileCreator { File settingsFile = new File(commonRoot.toFile(), SETTINGS_FILE); - // more than one project -> generate settings.gradle - if (fullProjectSet.size() > 1 && rootInWorkspace) { + // more than one modules -> generate settings.gradle + if (multiModules && rootInWorkspace) { // Locate the settings.gradle file and add it to the changed files list IPath settingsGradle = Path.fromOSString(settingsFile.getAbsolutePath()); @@ -193,26 +162,38 @@ public class BuildFileCreator { } } + ExportStatus status = new ExportStatus(); + builder.setStatus(status); + // Trigger checkout of changed files - Set<IFile> confirmedFiles = validateEdit(shell, files); + Set<IFile> confirmedFiles = validateEdit(files, status, shell); + + if (status.hasError()) { + return; + } - // Now iterate over all the projects and generate the build files. + // Now iterate over all the modules and generate the build files. localmonitor = SubMonitor.convert(pm, ExportMessages.PageTitle, confirmedFiles.size()); List<String> projectSettingsPath = Lists.newArrayList(); - for (IJavaProject currentProject : fullProjectSet) { - IFile file = currentProject.getProject().getFile(BuildFileCreator.BUILD_FILE); + for (GradleModule currentModule : modules) { + IProject moduleProject = currentModule.getProject(); + + IFile file = moduleProject.getFile(BuildFileCreator.BUILD_FILE); if (!confirmedFiles.contains(file)) { continue; } localmonitor.setTaskName(NLS.bind(ExportMessages.FileStatusMessage, - currentProject.getProject().getName())); + moduleProject.getName())); - ProjectState projectState = Sdk.getProjectState(currentProject.getProject()); - BuildFileCreator instance = new BuildFileCreator(currentProject, commonRoot, shell); + ProjectState projectState = Sdk.getProjectState(moduleProject); + BuildFileCreator instance = new BuildFileCreator(currentModule, shell); if (projectState != null) { // This is an Android project + if (!multiModules) { + instance.appendBuildScript(); + } instance.appendHeader(projectState.isLibrary()); instance.appendDependencies(); instance.startAndroidTask(projectState); @@ -225,35 +206,58 @@ public class BuildFileCreator { instance.createJavaSourceSets(); } - // Write the build file - String buildfile = instance.buildfile.toString(); - InputStream is = - new ByteArrayInputStream(buildfile.getBytes("UTF-8")); //$NON-NLS-1$ - if (file.exists()) { - file.setContents(is, true, true, null); - } else { - file.create(is, true, null); - } - if (localmonitor.isCanceled()) { - return res; - } - localmonitor.worked(1); - res.add(instance.projectName); + try { + // Write the build file + String buildfile = instance.mBuildFile.toString(); + InputStream is = + new ByteArrayInputStream(buildfile.getBytes("UTF-8")); //$NON-NLS-1$ + if (file.exists()) { + file.setContents(is, true, true, null); + } else { + file.create(is, true, null); + } + } catch (Exception e) { + status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, + file.getLocation().toFile()); + status.setErrorMessage(e.getMessage()); + return; + } + + if (localmonitor.isCanceled()) { + return; + } + localmonitor.worked(1); // get the project path to add it to the settings.gradle. - projectSettingsPath.add(instance.getRelativeGradleProjectPath()); + projectSettingsPath.add(currentModule.getPath()); } // write the settings file. - if (fullProjectSet.size() > 1) { - writeGradleSettingsFile(settingsFile, projectSettingsPath); - writeRootBuildGradle(new File(commonRoot.toFile(), BUILD_FILE)); + if (multiModules) { + try { + writeGradleSettingsFile(settingsFile, projectSettingsPath); + } catch (IOException e) { + status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, settingsFile); + status.setErrorMessage(e.getMessage()); + return; + } + File mainBuildFile = new File(commonRoot.toFile(), BUILD_FILE); + try { + writeRootBuildGradle(mainBuildFile); + } catch (IOException e) { + status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, mainBuildFile); + status.setErrorMessage(e.getMessage()); + return; + } } // finally write the wrapper // TODO check we can based on where it is if (hasGradleWrapper) { - copyGradleWrapper(gradleLocation, commonRoot.toFile()); + copyGradleWrapper(gradleLocation, commonRoot.toFile(), status); + if (status.hasError()) { + return; + } } } finally { @@ -264,19 +268,14 @@ public class BuildFileCreator { pm.done(); } } - return res; } /** - * @param project create buildfile for this project + * @param GradleModule create buildfile for this project * @param shell parent instance for dialogs */ - private BuildFileCreator(IJavaProject project, IPath commonRoot, Shell shell) { - this.project = project; - this.projectName = project.getProject().getName(); - this.buildfile = new StringBuilder(); - this.projectRoot = project.getResource().getLocation().toString(); - this.commonRoot = commonRoot; + private BuildFileCreator(GradleModule module, Shell shell) { + mModule = module; } /** @@ -295,145 +294,71 @@ public class BuildFileCreator { /** * Copy the Gradle wrapper files from one directory to another. */ - private static void copyGradleWrapper(File from, File to) throws IOException { + private static void copyGradleWrapper(File from, File to, ExportStatus status) { for (String file : GRADLE_WRAPPER_FILES) { File dest = new File(to, file); - if (dest.exists()) { - // Don't clobber an existing file. The user may have modified it already. - continue; + try { + File src = new File(from, file); + dest.getParentFile().mkdirs(); + new FileOp().copyFile(src, dest); + dest.setExecutable(src.canExecute()); + status.addFileStatus(ExportStatus.FileStatus.OK, dest); + } catch (IOException e) { + status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, dest); + return; } - File src = new File(from, file); - dest.getParentFile().mkdirs(); - new FileOp().copyFile(src, dest); - dest.setExecutable(src.canExecute()); } } /** - * Finds the common parent directory shared by this project and all its dependencies. - * If there's only one project, returns the single project's folder. + * Outputs boilerplate buildscript information common to all Gradle build files. */ - private static IPath determineCommonRoot(Collection<IJavaProject> projects) { - IPath commonRoot = null; - for (IJavaProject javaProject : projects) { - if (commonRoot == null) { - commonRoot = javaProject.getProject().getLocation(); - } else { - commonRoot = findCommonRoot(commonRoot, javaProject.getProject().getLocation()); - } - } + private void appendBuildScript() { + appendBuildScript(mBuildFile); + } - return commonRoot; + /** + * Outputs boilerplate header information common to all Gradle build files. + */ + private static void appendBuildScript(StringBuilder builder) { + builder.append("buildscript {\n"); //$NON-NLS-1$ + builder.append(" repositories {\n"); //$NON-NLS-1$ + builder.append(" " + MAVEN_REPOSITORY + "\n"); //$NON-NLS-1$ + builder.append(" }\n"); //$NON-NLS-1$ + builder.append(" dependencies {\n"); //$NON-NLS-1$ + builder.append(" " + PLUGIN_CLASSPATH + "\n"); //$NON-NLS-1$ + builder.append(" }\n"); //$NON-NLS-1$ + builder.append("}\n"); //$NON-NLS-1$ } /** * Outputs boilerplate header information common to all Gradle build files. */ private void appendHeader(boolean isLibrary) { - buildfile.append("buildscript {\n"); //$NON-NLS-1$ - buildfile.append(" repositories {\n"); //$NON-NLS-1$ - buildfile.append(" " + MAVEN_REPOSITORY + "\n"); //$NON-NLS-1$ - buildfile.append(" }\n"); //$NON-NLS-1$ - buildfile.append(" dependencies {\n"); //$NON-NLS-1$ - buildfile.append(" " + PLUGIN_CLASSPATH + "\n"); //$NON-NLS-1$ - buildfile.append(" }\n"); //$NON-NLS-1$ - buildfile.append("}\n"); //$NON-NLS-1$ if (isLibrary) { - buildfile.append("apply plugin: 'android-library'\n"); //$NON-NLS-1$ + mBuildFile.append("apply plugin: 'android-library'\n"); //$NON-NLS-1$ } else { - buildfile.append("apply plugin: 'android'\n"); //$NON-NLS-1$ + mBuildFile.append("apply plugin: 'android'\n"); //$NON-NLS-1$ } - buildfile.append("\n"); //$NON-NLS-1$ + mBuildFile.append("\n"); //$NON-NLS-1$ } /** * Outputs a block which sets up library and project dependencies. */ private void appendDependencies() { - if (project == null) { - AdtPlugin.log(IStatus.WARNING, "project is not loaded in workspace"); //$NON-NLS-1$ - return; - } - buildfile.append("dependencies {\n"); //$NON-NLS-1$ + mBuildFile.append("dependencies {\n"); //$NON-NLS-1$ // first the local jars. - buildfile.append(" compile fileTree(dir: 'libs', include: '*.jar')\n"); - - // then the project dependencies. - // First the Android libraries. - ProjectState state = Sdk.getProjectState(project.getProject()); + // TODO: Fix + mBuildFile.append(" compile fileTree(dir: 'libs', include: '*.jar')\n"); //$NON-NLS-1$ - for (LibraryState libState : state.getLibraries()) { - String path = getRelativeGradleProjectPath( - libState.getProjectState().getProject().getLocation(), commonRoot); - - buildfile.append(" compile project('" + path + "')\n"); //$NON-NLS-1$ + for (GradleModule dep : mModule.getDependencies()) { + mBuildFile.append(" compile project('" + dep.getPath() + "')\n"); //$NON-NLS-1$ //$NON-NLS-2$ } - // Next, the other non-Android projects. - for (IClasspathEntry entry : project.readRawClasspath()) { - if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { - IProject cpProject = getNonAndroidProjectByName(entry.getPath().toString()); - if (cpProject != null) { - String path = getRelativeGradleProjectPath( - cpProject.getLocation(), commonRoot); - - buildfile.append(" compile project('" + path + "')\n"); //$NON-NLS-1$ - } - } - } - - buildfile.append("}\n"); //$NON-NLS-1$ - buildfile.append("\n"); //$NON-NLS-1$ - } - - /** - * Given two IPaths, finds the parent directory of both of them. - */ - private static IPath findCommonRoot(IPath path1, IPath path2) { - // TODO: detect paths on different disk drive on Windows! - if (path1 != null && path1.getDevice() != null) { - assert path1.getDevice().equals(path2.getDevice()); - } - IPath result = path1.uptoSegment(0); - - final int count = Math.min(path1.segmentCount(), path2.segmentCount()); - for (int i = 0; i < count; i++) { - if (path1.segment(i).equals(path2.segment(i))) { - result = result.append(Path.SEPARATOR + path2.segment(i)); - } - } - return result; - } - - private String getRelativeGradleProjectPath() { - return getRelativeGradleProjectPath(project.getProject().getLocation(), commonRoot); - } - - /** - * Converts the given path to be relative to the given root path, and converts it to - * Gradle project notation, such as is used in the settings.gradle file. - */ - private String getRelativeGradleProjectPath(IPath path, IPath root) { - IPath relativePath = path.makeRelativeTo(root); - String relativeString = relativePath.toOSString(); - return ":" + relativeString.replaceAll(Pattern.quote(File.separator), ":"); //$NON-NLS-1$ - } - - /** - * Finds the workspace project with the given name - */ - private static IProject getNonAndroidProjectByName(String name) { - try { - IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); - if (project.exists() && !project.hasNature(AdtConstants.NATURE_DEFAULT)) { - return project; - } - } catch (IllegalArgumentException iae) { - } catch (CoreException e) { - } - - return null; + mBuildFile.append("}\n"); //$NON-NLS-1$ + mBuildFile.append("\n"); //$NON-NLS-1$ } /** @@ -442,24 +367,10 @@ public class BuildFileCreator { private void startAndroidTask(ProjectState projectState) { int buildApi = projectState.getTarget().getVersion().getApiLevel(); String toolsVersion = projectState.getTarget().getBuildToolInfo().getRevision().toString(); - buildfile.append("android {\n"); //$NON-NLS-1$ - buildfile.append(" compileSdkVersion " + buildApi + "\n"); //$NON-NLS-1$ - buildfile.append(" buildToolsVersion \"" + toolsVersion + "\"\n"); //$NON-NLS-1$ - buildfile.append("\n"); //$NON-NLS-1$ - } - - /** - * Outputs the defaultConfig block in the Android task. - */ - private void appendDefaultConfig() { - Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(project.getProject()); - int minApi = v.getFirst(); - int targetApi = v.getSecond(); - - buildfile.append(" defaultConfig {\n"); //$NON-NLS-1$ - buildfile.append(" minSdkVersion " + minApi + "\n"); //$NON-NLS-1$ - buildfile.append(" targetSdkVersion " + targetApi + "\n"); //$NON-NLS-1$ - buildfile.append(" }\n"); //$NON-NLS-1$ + mBuildFile.append("android {\n"); //$NON-NLS-1$ + mBuildFile.append(" compileSdkVersion " + buildApi + "\n"); //$NON-NLS-1$ + mBuildFile.append(" buildToolsVersion \"" + toolsVersion + "\"\n"); //$NON-NLS-1$ + mBuildFile.append("\n"); //$NON-NLS-1$ } /** @@ -467,13 +378,13 @@ public class BuildFileCreator { * subdirectories in the project. */ private void createAndroidSourceSets() { - IFolderWrapper projectFolder = new IFolderWrapper(project.getProject()); + IFolderWrapper projectFolder = new IFolderWrapper(mModule.getProject()); IAbstractFile mManifestFile = AndroidManifest.getManifest(projectFolder); if (mManifestFile == null) { return; } List<String> srcDirs = new ArrayList<String>(); - for (IClasspathEntry entry : project.readRawClasspath()) { + for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) { if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE || SdkConstants.FD_GEN_SOURCES.equals(entry.getPath().lastSegment())) { continue; @@ -484,33 +395,33 @@ public class BuildFileCreator { String srcPaths = Joiner.on(",").join(srcDirs); - buildfile.append(" sourceSets {\n"); //$NON-NLS-1$ - buildfile.append(" main {\n"); //$NON-NLS-1$ - buildfile.append(" manifest.srcFile '" + SdkConstants.FN_ANDROID_MANIFEST_XML + "'\n"); //$NON-NLS-1$ - buildfile.append(" java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" aidl.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" renderscript.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" res.srcDirs = ['res']\n"); //$NON-NLS-1$ - buildfile.append(" assets.srcDirs = ['assets']\n"); //$NON-NLS-1$ - buildfile.append(" }\n"); //$NON-NLS-1$ - buildfile.append("\n"); //$NON-NLS-1$ - buildfile.append(" instrumentTest.setRoot('tests')\n"); //$NON-NLS-1$ - buildfile.append(" }\n"); //$NON-NLS-1$ + mBuildFile.append(" sourceSets {\n"); //$NON-NLS-1$ + mBuildFile.append(" main {\n"); //$NON-NLS-1$ + mBuildFile.append(" manifest.srcFile '" + SdkConstants.FN_ANDROID_MANIFEST_XML + "'\n"); //$NON-NLS-1$ + mBuildFile.append(" java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" aidl.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" renderscript.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" res.srcDirs = ['res']\n"); //$NON-NLS-1$ + mBuildFile.append(" assets.srcDirs = ['assets']\n"); //$NON-NLS-1$ + mBuildFile.append(" }\n"); //$NON-NLS-1$ + mBuildFile.append("\n"); //$NON-NLS-1$ + mBuildFile.append(" instrumentTest.setRoot('tests')\n"); //$NON-NLS-1$ + mBuildFile.append(" }\n"); //$NON-NLS-1$ } /** * Outputs the completion of the Android task in the build file. */ private void finishAndroidTask() { - buildfile.append("}\n"); //$NON-NLS-1$ + mBuildFile.append("}\n"); //$NON-NLS-1$ } /** * Outputs a boilerplate header for non-Android projects */ private void appendJavaHeader() { - buildfile.append("apply plugin: 'java'\n"); //$NON-NLS-1$ + mBuildFile.append("apply plugin: 'java'\n"); //$NON-NLS-1$ } /** @@ -518,7 +429,7 @@ public class BuildFileCreator { */ private void createJavaSourceSets() { List<String> dirs = new ArrayList<String>(); - for (IClasspathEntry entry : project.readRawClasspath()) { + for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) { if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { continue; } @@ -528,30 +439,36 @@ public class BuildFileCreator { String srcPaths = Joiner.on(",").join(dirs); - buildfile.append("sourceSets {\n"); //$NON-NLS-1$ - buildfile.append(" main.java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" main.resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ - buildfile.append(" test.java.srcDirs = ['tests/java']\n"); //$NON-NLS-1$ - buildfile.append(" test.resources.srcDirs = ['tests/resources']\n"); //$NON-NLS-1$ - buildfile.append("}\n"); //$NON-NLS-1$ + mBuildFile.append("sourceSets {\n"); //$NON-NLS-1$ + mBuildFile.append(" main.java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" main.resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$ + mBuildFile.append(" test.java.srcDirs = ['tests/java']\n"); //$NON-NLS-1$ + mBuildFile.append(" test.resources.srcDirs = ['tests/resources']\n"); //$NON-NLS-1$ + mBuildFile.append("}\n"); //$NON-NLS-1$ } /** * Merges the new subproject dependencies into the settings.gradle file if it already exists, * and creates one if it does not. + * @throws IOException */ - private static void writeGradleSettingsFile(File settingsFile, List<String> projectPaths) { + private static void writeGradleSettingsFile(File settingsFile, List<String> projectPaths) + throws IOException { StringBuilder contents = new StringBuilder(); for (String path : projectPaths) { contents.append("include '").append(path).append("'\n"); //$NON-NLS-1$ //$NON-NLS-2$ } - AdtPlugin.writeFile(settingsFile, contents.toString()); + Files.write(contents.toString(), settingsFile, Charsets.UTF_8); } - private static void writeRootBuildGradle(File buildFile) { - AdtPlugin.writeFile(buildFile, + private static void writeRootBuildGradle(File buildFile) throws IOException { + StringBuilder sb = new StringBuilder( "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n"); + + appendBuildScript(sb); + + Files.write(sb.toString(), buildFile, Charsets.UTF_8); } /** @@ -564,7 +481,10 @@ public class BuildFileCreator { * @throws CoreException * thrown if project is under version control, but not connected */ - static Set<IFile> validateEdit(Shell shell, List<IFile> files) throws CoreException { + static Set<IFile> validateEdit( + @NonNull List<IFile> files, + @NonNull ExportStatus exportStatus, + @NonNull Shell shell) { Set<IFile> confirmedFiles = new TreeSet<IFile>(FILE_COMPARATOR); if (files.size() == 0) { return confirmedFiles; @@ -576,6 +496,10 @@ public class BuildFileCreator { IStatus statusChild = status.getChildren()[i]; if (statusChild.isOK()) { confirmedFiles.add(files.get(i)); + } else { + exportStatus.addFileStatus( + ExportStatus.FileStatus.VCS_FAILURE, + files.get(i).getLocation().toFile()); } } } else if (status.isOK()) { @@ -593,8 +517,8 @@ public class BuildFileCreator { message.append(statusChild.getMessage() + NEWLINE); } } - throw new CoreException(new Status(IStatus.ERROR, - AdtPlugin.PLUGIN_ID, 0, message.toString(), null)); + String s = message.toString(); + exportStatus.setErrorMessage(s); } return confirmedFiles; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ConfirmationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ConfirmationPage.java new file mode 100644 index 0000000..1f236fb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ConfirmationPage.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.google.common.collect.Lists; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +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.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.ui.model.WorkbenchLabelProvider; + +import java.io.File; +import java.util.Collection; +import java.util.List; + +/** + * Confirmation page to review the actual project export + * list and see warning about existing files. + * + */ +public class ConfirmationPage extends WizardPage { + + private final ProjectSetupBuilder mBuilder; + private TableViewer mTableViewer; + private Label mModuleDescription1; + private Label mModuleDescription2; + private Label mModuleDescription3; + private Label mProjectRootLabel; + private Label mProjectRootWarning; + private List<IJavaProject> mOverrideProjects; + private boolean mOverrideWarning; + private Button mForceOverride; + + public ConfirmationPage(ProjectSetupBuilder builder) { + super("ConfirmationPage"); //$NON-NLS-1$ + mBuilder = builder; + setPageComplete(false); + setTitle(ExportMessages.PageTitle); + setDescription(ExportMessages.PageDescription); + } + + @Override + public void createControl(Composite parent) { + initializeDialogUnits(parent); + GridData data; + + Composite workArea = new Composite(parent, SWT.NONE); + setControl(workArea); + + workArea.setLayout(new GridLayout()); + workArea.setLayoutData(new GridData(GridData.FILL_BOTH + | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); + + Label title = new Label(workArea, SWT.NONE); + title.setText("Please review the export options."); + + Group group = new Group(workArea, SWT.NONE); + group.setText("Project root"); + group.setLayout(new GridLayout()); + group.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + + mProjectRootLabel = new Label(group, SWT.NONE); + mProjectRootLabel.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + + mProjectRootWarning = new Label(group, SWT.NONE); + mProjectRootWarning.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + + Group group2 = new Group(workArea, SWT.NONE); + group2.setText("Exported Modules"); + group2.setLayout(new GridLayout()); + group2.setLayoutData(data = new GridData(SWT.FILL, SWT.FILL, true, true)); + data.heightHint = 300; + + Table table = new Table(group2, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + mTableViewer = new TableViewer(table); + table.setLayout(new TableLayout()); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + mTableViewer.setContentProvider(new IStructuredContentProvider() { + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof ProjectSetupBuilder) { + ProjectSetupBuilder builder = (ProjectSetupBuilder) inputElement; + Collection<GradleModule> modules = builder.getModules(); + Object[] array = new Object[modules.size()]; + int i = 0; + for (GradleModule module : modules) { + array[i++] = module.getJavaProject(); + } + + return array; + } + + return null; + } + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + }); + mTableViewer.setLabelProvider(new WorkbenchLabelProvider() { + @Override + protected String decorateText(String input, Object element) { + if (element instanceof IJavaProject) { + IJavaProject javaProject = (IJavaProject) element; + StringBuilder sb = new StringBuilder(input); + if (!mBuilder.isOriginalProject(javaProject)) { + sb.append('*'); + } + // TODO: decorate icon instead? + if (mOverrideProjects.contains(javaProject)) { + sb.append(" (1 warning)"); + } + + return sb.toString(); + } + + return input; + } + }); + mTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + Object firstElement = selection.getFirstElement(); + if (firstElement instanceof IJavaProject) { + GradleModule module = mBuilder.getModule((IJavaProject) firstElement); + if (mBuilder.getOriginalModules().contains(module)) { + mModuleDescription1.setText("Exported because selected in previous page."); + } else { + List<GradleModule> list = mBuilder.getShortestDependencyTo(module); + StringBuilder sb = new StringBuilder(); + for (GradleModule m : list) { + if (sb.length() > 0) { + sb.append(" > "); + } + sb.append(m.getJavaProject().getProject().getName()); + } + mModuleDescription1.setText("Dependency chain: " + sb); + } + mModuleDescription2.setText("Path: " + module.getPath()); + + if (mOverrideProjects.contains(module.getJavaProject())) { + mModuleDescription3.setText( + "WARNING: build.gradle already exists for this project"); + } else { + mModuleDescription3.setText(""); + } + } else { + mModuleDescription1.setText(""); + mModuleDescription2.setText(""); + mModuleDescription3.setText(""); + } + } + }); + + mModuleDescription1 = new Label(group2, SWT.NONE); + mModuleDescription1.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + mModuleDescription2 = new Label(group2, SWT.NONE); + mModuleDescription2.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + mModuleDescription3 = new Label(group2, SWT.NONE); + mModuleDescription3.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + + mForceOverride = new Button(workArea, SWT.CHECK); + mForceOverride.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false)); + mForceOverride.setText("Force overriding of existing files"); + mForceOverride.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateEnablement(); + } + }); + + setControl(workArea); + Dialog.applyDialogFont(parent); + } + + /** + * Get list of projects which have already a buildfile. + * + * @param javaProjects list of IJavaProject objects + * @return set of project names + */ + private void computeOverride(String commonRoot) { + mOverrideProjects = Lists.newArrayList(); + for (GradleModule module : mBuilder.getModules()) { + if (new File(module.getProject().getLocation().toFile(), + BuildFileCreator.BUILD_FILE).exists()) { + mOverrideProjects.add(module.getJavaProject()); + } + } + + // also check on the root settings.gradle/build.gradle + boolean settingsFile = new File(commonRoot, BuildFileCreator.SETTINGS_FILE).exists(); + boolean buildFile = new File(commonRoot, BuildFileCreator.BUILD_FILE).exists(); + if (settingsFile && buildFile) { + mProjectRootWarning.setText( + "WARNING: build.gradle/settings.gradle already exists at this location."); + } else if (settingsFile) { + mProjectRootWarning.setText( + "WARNING: settings.gradle already exists at this location."); + } else if (buildFile) { + mProjectRootWarning.setText("WARNING: build.gradle already exists at this location."); + } + + mOverrideWarning = mOverrideProjects.size() > 0 || settingsFile || buildFile; + } + + /** + * Enables/disables the finish button on the wizard and displays error messages as needed. + */ + private void updateEnablement() { + if (mOverrideWarning && !mForceOverride.getSelection()) { + setErrorMessage("Enable overriding of existing files before clicking Finish"); + mBuilder.setCanGenerate(false); + } else { + setErrorMessage(null); + mBuilder.setCanGenerate(true); + } + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + mProjectRootWarning.setText(""); + + String commonRoot = mBuilder.getCommonRoot().toOSString(); + computeOverride(commonRoot); + mProjectRootLabel.setText(commonRoot); + mTableViewer.setInput(mBuilder); + mTableViewer.getTable().setFocus(); + mBuilder.setCanFinish(false); + mBuilder.setCanGenerate(true); + updateEnablement(); + } + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportMessages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportMessages.properties index 38d7771..1a6dbb1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportMessages.properties +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportMessages.properties @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -PageTitle=Generate Gradle Buildfiles -PageDescription=Generates Gradle buildfiles based on the configuration of the Java projects +PageTitle=Generate Gradle Build files +PageDescription=Generates Gradle build files based on the configuration of the Java projects SelectProjects=Select the projects to use to &generate the Gradle buildfiles: ConfirmOverwrite=Are you sure you want to overwrite the buildfiles for these projects? ConfirmOverwriteTitle=Overwrite Buildfiles? diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportStatus.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportStatus.java new file mode 100644 index 0000000..3459235 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportStatus.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; + +import java.io.File; + +public class ExportStatus { + + public static enum FileStatus { OK, VCS_FAILURE, IO_FAILURE; } + + private String mMainError = null; + private final Multimap<FileStatus, File> mFileStatus = ArrayListMultimap.create(); + + void addFileStatus(@NonNull FileStatus status, @NonNull File file) { + mFileStatus.put(status, file); + } + + boolean hasError() { + return mMainError != null || !mFileStatus.isEmpty(); + } + + public void setErrorMessage(String error) { + mMainError = error; + } + + @Nullable + public String getErrorMessage() { + return mMainError; + } + + @NonNull + public Multimap<FileStatus, File> getFileStatus() { + return mFileStatus; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/FinalPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/FinalPage.java new file mode 100644 index 0000000..9e976aa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/FinalPage.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.android.ide.eclipse.adt.internal.wizards.exportgradle.ExportStatus.FileStatus; +import com.google.common.collect.Multimap; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; + +import java.io.File; +import java.util.Collection; + +/** + * Final page to review the result of the export. + */ +public class FinalPage extends WizardPage { + + private final ProjectSetupBuilder mBuilder; + private ExportStatus mStatus; + + private Text mText; + + public FinalPage(ProjectSetupBuilder builder) { + super("FinalPage"); //$NON-NLS-1$ + mBuilder = builder; + setPageComplete(true); + setTitle(ExportMessages.PageTitle); + setDescription(ExportMessages.PageDescription); + } + + @Override + public void createControl(Composite parent) { + initializeDialogUnits(parent); + + mText = new Text(parent, SWT.MULTI | SWT.READ_ONLY); + mText.setLayoutData(new GridData(GridData.FILL_BOTH + | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); + + setControl(mText); + Dialog.applyDialogFont(parent); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + mStatus = mBuilder.getStatus(); + mBuilder.setCanFinish(!mStatus.hasError()); + mBuilder.setCanGenerate(false); + + StringBuilder sb = new StringBuilder(); + if (mStatus.hasError()) { + sb.append("There was an error!").append("\n\n"); + + String errorMsg = mStatus.getErrorMessage(); + if (errorMsg != null) { + sb.append(errorMsg); + } + + Multimap<FileStatus, File> fileStatusMap = mStatus.getFileStatus(); + Collection<File> files = fileStatusMap.values(); + if (files != null) { + sb.append("\n\n").append("Error on files:").append('\n'); + for (File file : files) { + sb.append("\n").append(file.getAbsolutePath()); + } + } + } else { + sb.append("Export successful.\n\n"); + + int count = mBuilder.getModuleCount(); + if (count > 1) { + sb.append(String.format("Exported %s modules", count)).append('\n'); + sb.append(String.format( + "Root folder: %s", mBuilder.getCommonRoot().toOSString())); + } else { + sb.append("Exported project: ").append(mBuilder.getCommonRoot().toOSString()); + } + + sb.append("\n\n").append("Choose 'import project' in Android Studio").append('\n'); + sb.append("and select the following file:").append("\n\t"); + + File bGradle = new File( + mBuilder.getCommonRoot().toFile(), BuildFileCreator.BUILD_FILE); + sb.append(bGradle.getAbsolutePath()); + + sb.append("\n\n").append("Do NOT import the Eclipse project itself!"); + } + + mText.setText(sb.toString()); + } + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportPage.java deleted file mode 100644 index 551b709..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportPage.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.eclipse.org/org/documents/epl-v10.php - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.ide.eclipse.adt.internal.wizards.exportgradle; - -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; -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.Path; -import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaModel; -import org.eclipse.jdt.core.IJavaModelMarker; -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 org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.viewers.CheckStateChangedEvent; -import org.eclipse.jface.viewers.CheckboxTableViewer; -import org.eclipse.jface.viewers.ICheckStateListener; -import org.eclipse.jface.viewers.TableLayout; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -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.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.ui.model.WorkbenchContentProvider; -import org.eclipse.ui.model.WorkbenchLabelProvider; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.google.common.base.Joiner; -import com.ibm.icu.text.MessageFormat; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -/** - * Displays a wizard page that lets the user choose the projects for which to create Gradle build - * files. - * <p> - * Based on {@link org.eclipse.ant.internal.ui.datatransfer.AntBuildfileExportPage} - */ -public class GradleExportPage extends WizardPage { - private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$ - - private static final Comparator<IJavaProject> PROJECT_COMPARATOR = - new Comparator<IJavaProject>() { - @Override - public int compare(IJavaProject o1, IJavaProject o2) { - return o1.getProject().getName().compareTo(o2.getProject().getName()); - } - }; - - private CheckboxTableViewer mTableViewer; - private List<IJavaProject> mSelectedJavaProjects = new ArrayList<IJavaProject>(); - - public GradleExportPage() { - super("GradleExportPage"); //$NON-NLS-1$ - setPageComplete(false); - setTitle(ExportMessages.PageTitle); - setDescription(ExportMessages.PageDescription); - } - - @Override - public void createControl(Composite parent) { - initializeDialogUnits(parent); - - Composite workArea = new Composite(parent, SWT.NONE); - setControl(workArea); - - workArea.setLayout(new GridLayout()); - workArea.setLayoutData(new GridData(GridData.FILL_BOTH - | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); - - Label title = new Label(workArea, SWT.NONE); - title.setText(ExportMessages.SelectProjects); - - Composite listComposite = new Composite(workArea, SWT.NONE); - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - layout.marginWidth = 0; - layout.makeColumnsEqualWidth = false; - listComposite.setLayout(layout); - - listComposite.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL - | GridData.GRAB_VERTICAL | GridData.FILL_BOTH)); - - Table table = new Table(listComposite, - SWT.CHECK | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - mTableViewer = new CheckboxTableViewer(table); - table.setLayout(new TableLayout()); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); - data.heightHint = 300; - table.setLayoutData(data); - mTableViewer.setContentProvider(new WorkbenchContentProvider() { - @Override - public Object[] getElements(Object element) { - if (element instanceof IJavaProject[]) { - return (IJavaProject[]) element; - } - return null; - } - }); - mTableViewer.setLabelProvider(new WorkbenchLabelProvider()); - mTableViewer.addCheckStateListener(new ICheckStateListener() { - @Override - public void checkStateChanged(CheckStateChangedEvent event) { - if (event.getChecked()) { - mSelectedJavaProjects.add((IJavaProject) event.getElement()); - } else { - mSelectedJavaProjects.remove(event.getElement()); - } - updateEnablement(); - } - }); - - initializeProjects(); - createSelectionButtons(listComposite); - setControl(workArea); - updateEnablement(); - Dialog.applyDialogFont(parent); - } - - /** - * Creates select all/deselect all buttons. - */ - private void createSelectionButtons(Composite composite) { - Composite buttonsComposite = new Composite(composite, SWT.NONE); - GridLayout layout = new GridLayout(); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttonsComposite.setLayout(layout); - - buttonsComposite.setLayoutData(new GridData( - GridData.VERTICAL_ALIGN_BEGINNING)); - - Button selectAll = new Button(buttonsComposite, SWT.PUSH); - selectAll.setText(ExportMessages.SelectAll); - selectAll.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - for (int i = 0; i < mTableViewer.getTable().getItemCount(); i++) { - mSelectedJavaProjects.add((IJavaProject) mTableViewer.getElementAt(i)); - } - mTableViewer.setAllChecked(true); - updateEnablement(); - } - }); - setButtonLayoutData(selectAll); - - Button deselectAll = new Button(buttonsComposite, SWT.PUSH); - deselectAll.setText(ExportMessages.DeselectAll); - deselectAll.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - mSelectedJavaProjects.clear(); - mTableViewer.setAllChecked(false); - updateEnablement(); - } - }); - setButtonLayoutData(deselectAll); - } - - /** - * Populates the list with all the eligible projects in the workspace. - */ - private void initializeProjects() { - IWorkspaceRoot rootWorkspace = ResourcesPlugin.getWorkspace().getRoot(); - IJavaModel javaModel = JavaCore.create(rootWorkspace); - IJavaProject[] javaProjects; - try { - javaProjects = javaModel.getJavaProjects(); - } catch (JavaModelException e) { - javaProjects = new IJavaProject[0]; - } - mTableViewer.setInput(javaProjects); - // Check any necessary projects - if (mSelectedJavaProjects != null) { - mTableViewer.setCheckedElements(mSelectedJavaProjects.toArray( - new IJavaProject[mSelectedJavaProjects.size()])); - } - } - - /** - * Enables/disables the finish button on the wizard and displays error messages as needed. - */ - private void updateEnablement() { - boolean complete = true; - if (mSelectedJavaProjects.size() == 0) { - setErrorMessage(ExportMessages.NoProjectsError); - complete = false; - } - List<String> cyclicProjects; - try { - cyclicProjects = getCyclicProjects(getProjects(false)); - if (cyclicProjects.size() > 0) { - setErrorMessage(MessageFormat.format(ExportMessages.CyclicProjectsError, - new Object[] { Joiner.on(", ").join(cyclicProjects) })); //$NON-NLS-1$ - complete = false; - } - } catch (CoreException e) {} - if (complete) { - setErrorMessage(null); - } - setPageComplete(complete); - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) { - mTableViewer.getTable().setFocus(); - } - } - - /** - * Converts Eclipse Java projects to Gradle build files. Displays error dialogs. - */ - public boolean generateBuildfiles() { - setErrorMessage(null); - final List<String> projectNames = new ArrayList<String>(); - final Set<IJavaProject> projects; - try { - projects = getProjects(true); - if (projects.size() == 0) { - return false; - } - } catch (JavaModelException e) { - AdtPlugin.log(e, null); - setErrorMessage(MessageFormat.format( - ExportMessages.ExportFailedError, new Object[] { e.toString() })); - return false; - } - IRunnableWithProgress runnable = new IRunnableWithProgress() { - @Override - public void run(IProgressMonitor pm) throws InterruptedException { - SubMonitor localmonitor = SubMonitor.convert(pm, ExportMessages.StatusMessage, - projects.size()); - Exception problem = null; - try { - projectNames.addAll(BuildFileCreator.createBuildFiles(projects, getShell(), - localmonitor.newChild(projects.size()))); - } catch (JavaModelException e) { - problem = e; - } catch (IOException e) { - problem = e; - } catch (CoreException e) { - problem = e; - } - - if (problem != null) { - AdtPlugin.log(problem, null); - setErrorMessage(MessageFormat.format(ExportMessages.ExportFailedError, - new Object[] { problem.toString() })); - } - } - }; - - try { - getContainer().run(false, false, runnable); - } catch (InvocationTargetException e) { - AdtPlugin.log(e, null); - return false; - } catch (InterruptedException e) { - AdtPlugin.log(e, null); - return false; - } - if (getErrorMessage() != null) { - return false; - } - return true; - } - - /** - * Get projects to write buildfiles for. Opens confirmation dialog. - * - * @param displayConfirmation if set to true a dialog prompts for - * confirmation before overwriting files - * @return set of project names - */ - private Set<IJavaProject> getProjects(boolean displayConfirmation) throws JavaModelException { - // collect all projects to create buildfiles for - Set<IJavaProject> projects = new TreeSet<IJavaProject>(PROJECT_COMPARATOR); - for (IJavaProject javaProject : mSelectedJavaProjects) { - projects.addAll(getClasspathProjectsRecursive(javaProject)); - projects.add(javaProject); - } - - // confirm overwrite - List<String> confirmOverwrite = getConfirmOverwriteSet(projects); - if (displayConfirmation && confirmOverwrite.size() > 0) { - String message = ExportMessages.ConfirmOverwrite + NEWLINE + - Joiner.on(NEWLINE).join(confirmOverwrite); - if (!MessageDialog.openQuestion(getShell(), - ExportMessages.ConfirmOverwriteTitle, message)) { - return new TreeSet<IJavaProject>(PROJECT_COMPARATOR); - } - } - return projects; - } - - /** - * Returns given projects that have cyclic dependencies. - * - * @param javaProjects list of IJavaProject objects - * @return set of project names - */ - private List<String> getCyclicProjects(Set<IJavaProject> projects) throws CoreException { - - List<String> cyclicProjects = new ArrayList<String>(); - for (IJavaProject javaProject : projects) { - if (hasCyclicDependency(javaProject)) { - cyclicProjects.add(javaProject.getProject().getName()); - } - } - return cyclicProjects; - } - - /** - * Check if given project has a cyclic dependency. - * <p> - * See {@link org.eclipse.jdt.core.tests.model.ClasspathTests.numberOfCycleMarkers} - */ - public static boolean hasCyclicDependency(IJavaProject javaProject) - throws CoreException { - IMarker[] markers = javaProject.getProject().findMarkers( - IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, - IResource.DEPTH_ONE); - for (int i = 0; i < markers.length; i++) { - IMarker marker = markers[i]; - String cycleAttr = (String) marker - .getAttribute(IJavaModelMarker.CYCLE_DETECTED); - if (cycleAttr != null && cycleAttr.equals("true")) { //$NON-NLS-1$ - return true; - } - } - return false; - } - - /** - * Get list of projects which have already a buildfile. - * - * @param javaProjects list of IJavaProject objects - * @return set of project names - */ - private List<String> getConfirmOverwriteSet(Set<IJavaProject> javaProjects) - { - List<String> result = new ArrayList<String>(javaProjects.size()); - for (IJavaProject project : javaProjects) { - String projectRoot = project.getResource().getLocation().toString(); - if (new File(projectRoot, "build.gradle").exists()) { - result.add(project.getProject().getName()); - } - } - return result; - } - - /** - * Get Java project from resource. - */ - private static IJavaProject getJavaProjectByName(String name) { - try { - IProject project = ResourcesPlugin.getWorkspace().getRoot() - .getProject(name); - if (project.exists()) { - return JavaCore.create(project); - } - } catch (IllegalArgumentException iae) { - } - return null; - } - - /** - * Get Java project for given root. - */ - private static IJavaProject getJavaProject(String root) { - IPath path = new Path(root); - if (path.segmentCount() == 1) { - return getJavaProjectByName(root); - } - IResource resource = ResourcesPlugin.getWorkspace().getRoot() - .findMember(path); - if (resource != null && resource.getType() == IResource.PROJECT) { - if (resource.exists()) { - return (IJavaProject) JavaCore.create(resource); - } - } - return null; - } - - /** - * Get for given project all directly and indirectly dependent projects. - * - * @return set of IJavaProject objects - */ - private static List<IJavaProject> getClasspathProjectsRecursive(IJavaProject project) - throws JavaModelException { - LinkedList<IJavaProject> result = new LinkedList<IJavaProject>(); - getClasspathProjectsRecursive(project, result); - return result; - } - - private static void getClasspathProjectsRecursive(IJavaProject project, - LinkedList<IJavaProject> result) throws JavaModelException { - List<IJavaProject> projects = new ArrayList<IJavaProject>(); - IClasspathEntry entries[] = project.getRawClasspath(); - for (IClasspathEntry classpathEntry : entries) { - if (classpathEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE - && classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { - // found required project on build path - String subProjectRoot = classpathEntry.getPath().toString(); - IJavaProject subProject = getJavaProject(subProjectRoot); - // is project available in workspace - if (subProject != null) { - projects.add(subProject); - } - } - } - for (IJavaProject javaProject : projects) { - if (!result.contains(javaProject)) { - result.addFirst(javaProject); - getClasspathProjectsRecursive(javaProject, result); // recursion - } - } - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportWizard.java index 5ff4c53..29f8802 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleExportWizard.java @@ -16,26 +16,50 @@ package com.android.ide.eclipse.adt.internal.wizards.exportgradle; +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; import org.eclipse.ui.IExportWizard; import org.eclipse.ui.IWorkbench; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; + public class GradleExportWizard extends Wizard implements IExportWizard { - private GradleExportPage mMainPage; + + private ProjectSetupBuilder mBuilder = new ProjectSetupBuilder(); + + private ProjectSelectionPage mFirstPage; + private ConfirmationPage mSecondPage; + private FinalPage mFinalPage; /** * Creates buildfile. */ @Override public boolean performFinish() { - return mMainPage.generateBuildfiles(); + if (mBuilder.canGenerate()) { + generateBuildfiles(mSecondPage); + getContainer().showPage(mFinalPage); + return false; + } + + return true; } @Override public void addPages() { - mMainPage = new GradleExportPage(); - addPage(mMainPage); + mFirstPage = new ProjectSelectionPage(mBuilder); + addPage(mFirstPage); + mSecondPage = new ConfirmationPage(mBuilder); + addPage(mSecondPage); + mFinalPage = new FinalPage(mBuilder); + addPage(mFinalPage); } @Override @@ -43,4 +67,44 @@ public class GradleExportWizard extends Wizard implements IExportWizard { setWindowTitle(ExportMessages.WindowTitle); setNeedsProgressMonitor(true); } + + @Override + public boolean canFinish() { + return mBuilder.canFinish() || mBuilder.canGenerate(); + } + + /** + * Converts Eclipse Java projects to Gradle build files. Displays error dialogs. + */ + public boolean generateBuildfiles(final WizardPage page) { + IRunnableWithProgress runnable = new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor pm) throws InterruptedException { + Collection<GradleModule> modules = mBuilder.getModules(); + final int count = modules.size(); + + SubMonitor localmonitor = SubMonitor.convert(pm, ExportMessages.StatusMessage, + count); + BuildFileCreator.createBuildFiles( + mBuilder, + page.getShell(), + localmonitor.newChild(count)); + } + }; + + try { + getContainer().run(false, false, runnable); + } catch (InvocationTargetException e) { + AdtPlugin.log(e, null); + return false; + } catch (InterruptedException e) { + AdtPlugin.log(e, null); + return false; + } + if (page.getErrorMessage() != null) { + return false; + } + return true; + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleModule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleModule.java new file mode 100644 index 0000000..684f03b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/GradleModule.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.android.annotations.NonNull; +import com.google.common.collect.Lists; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jdt.core.IJavaProject; + +import java.util.List; + +/** + * A configured Gradle module for export. This includes gradle path, dependency, type, etc... + */ +public class GradleModule { + + @NonNull + private final IJavaProject mJavaProject; + + private String mPath; + private Type mType; + + private final List<GradleModule> mDependencies = Lists.newArrayList(); + + public static enum Type { ANDROID, JAVA }; + + GradleModule(@NonNull IJavaProject javaProject) { + mJavaProject = javaProject; + } + + @NonNull + public IJavaProject getJavaProject() { + return mJavaProject; + } + + @NonNull + public IProject getProject() { + return mJavaProject.getProject(); + } + + boolean isConfigured() { + return mType != null; + } + + public void setType(Type type) { + mType = type; + } + + public Type getType() { + return mType; + } + + public void addDependency(GradleModule module) { + mDependencies.add(module); + } + + public List<GradleModule> getDependencies() { + return mDependencies; + } + + public void setPath(String path) { + mPath = path; + } + + public String getPath() { + return mPath; + } + + @Override + public String toString() { + return "GradleModule [mJavaProject=" + mJavaProject + ", mPath=" + mPath + ", mType=" + + mType + ", mDependencies=" + mDependencies + "]"; + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSelectionPage.java new file mode 100644 index 0000000..81c7a73 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSelectionPage.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.ibm.icu.text.MessageFormat; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaModelMarker; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +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.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * Displays a wizard page that lets the user choose the projects for which to create Gradle build + * files. + * <p> + * Based on {@link org.eclipse.ant.internal.ui.datatransfer.AntBuildfileExportPage} + */ +public class ProjectSelectionPage extends WizardPage { + + private final ProjectSetupBuilder mBuilder; + private CheckboxTableViewer mTableViewer; + private List<IJavaProject> mSelectedJavaProjects = Lists.newArrayList(); + + public ProjectSelectionPage(ProjectSetupBuilder builder) { + super("GradleExportPage"); //$NON-NLS-1$ + mBuilder = builder; + setPageComplete(false); + setTitle(ExportMessages.PageTitle); + setDescription(ExportMessages.PageDescription); + } + + @Override + public void createControl(Composite parent) { + initializeDialogUnits(parent); + + Composite workArea = new Composite(parent, SWT.NONE); + setControl(workArea); + + workArea.setLayout(new GridLayout()); + workArea.setLayoutData(new GridData(GridData.FILL_BOTH + | GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); + + Label title = new Label(workArea, SWT.NONE); + title.setText(ExportMessages.SelectProjects); + + Composite listComposite = new Composite(workArea, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 0; + layout.makeColumnsEqualWidth = false; + listComposite.setLayout(layout); + + listComposite.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL + | GridData.GRAB_VERTICAL | GridData.FILL_BOTH)); + + Table table = new Table(listComposite, + SWT.CHECK | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + mTableViewer = new CheckboxTableViewer(table); + table.setLayout(new TableLayout()); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.heightHint = 300; + table.setLayoutData(data); + mTableViewer.setContentProvider(new WorkbenchContentProvider() { + @Override + public Object[] getElements(Object element) { + if (element instanceof IJavaProject[]) { + return (IJavaProject[]) element; + } + return null; + } + }); + mTableViewer.setLabelProvider(new WorkbenchLabelProvider()); + mTableViewer.addCheckStateListener(new ICheckStateListener() { + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + if (event.getChecked()) { + mSelectedJavaProjects.add((IJavaProject) event.getElement()); + } else { + mSelectedJavaProjects.remove(event.getElement()); + } + updateEnablement(); + } + }); + + initializeProjects(); + createSelectionButtons(listComposite); + setControl(workArea); + updateEnablement(); + Dialog.applyDialogFont(parent); + } + + /** + * Creates select all/deselect all buttons. + */ + private void createSelectionButtons(Composite composite) { + Composite buttonsComposite = new Composite(composite, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttonsComposite.setLayout(layout); + + buttonsComposite.setLayoutData(new GridData( + GridData.VERTICAL_ALIGN_BEGINNING)); + + Button selectAll = new Button(buttonsComposite, SWT.PUSH); + selectAll.setText(ExportMessages.SelectAll); + selectAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + for (int i = 0; i < mTableViewer.getTable().getItemCount(); i++) { + mSelectedJavaProjects.add((IJavaProject) mTableViewer.getElementAt(i)); + } + mTableViewer.setAllChecked(true); + updateEnablement(); + } + }); + setButtonLayoutData(selectAll); + + Button deselectAll = new Button(buttonsComposite, SWT.PUSH); + deselectAll.setText(ExportMessages.DeselectAll); + deselectAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mSelectedJavaProjects.clear(); + mTableViewer.setAllChecked(false); + updateEnablement(); + } + }); + setButtonLayoutData(deselectAll); + } + + /** + * Populates the list with all the eligible projects in the workspace. + */ + private void initializeProjects() { + IWorkspaceRoot rootWorkspace = ResourcesPlugin.getWorkspace().getRoot(); + IJavaModel javaModel = JavaCore.create(rootWorkspace); + IJavaProject[] javaProjects; + try { + javaProjects = javaModel.getJavaProjects(); + } catch (JavaModelException e) { + javaProjects = new IJavaProject[0]; + } + mTableViewer.setInput(javaProjects); + // Check any necessary projects + if (mSelectedJavaProjects != null) { + mTableViewer.setCheckedElements(mSelectedJavaProjects.toArray( + new IJavaProject[mSelectedJavaProjects.size()])); + } + } + + /** + * Enables/disables the finish button on the wizard and displays error messages as needed. + */ + private void updateEnablement() { + String error = null; + try { + if (mSelectedJavaProjects.size() == 0) { + error = ExportMessages.NoProjectsError; + return; + } + + List<String> cyclicProjects; + try { + cyclicProjects = getCyclicProjects(mSelectedJavaProjects); + if (cyclicProjects.size() > 0) { + error = MessageFormat.format(ExportMessages.CyclicProjectsError, + new Object[] { Joiner.on(", ").join(cyclicProjects) }); //$NON-NLS-1$ + return; + } + + error = mBuilder.setProject(mSelectedJavaProjects); + if (error != null) { + return; + } + + } catch (CoreException ignored) { + // TODO: do something? + } + } finally { + setErrorMessage(error); + setPageComplete(error == null); + getContainer().updateButtons(); + } + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + mTableViewer.getTable().setFocus(); + mBuilder.setCanFinish(false); + mBuilder.setCanGenerate(false); + } + } + + /** + * Returns given projects that have cyclic dependencies. + * + * @param javaProjects list of IJavaProject objects + * @return set of project names + */ + private List<String> getCyclicProjects(List<IJavaProject> projects) throws CoreException { + + List<String> cyclicProjects = new ArrayList<String>(); + for (IJavaProject javaProject : projects) { + if (hasCyclicDependency(javaProject)) { + cyclicProjects.add(javaProject.getProject().getName()); + } + } + return cyclicProjects; + } + + /** + * Check if given project has a cyclic dependency. + * <p> + * See {@link org.eclipse.jdt.core.tests.model.ClasspathTests.numberOfCycleMarkers} + */ + private static boolean hasCyclicDependency(IJavaProject javaProject) + throws CoreException { + IMarker[] markers = javaProject.getProject().findMarkers( + IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, false, + IResource.DEPTH_ONE); + for (IMarker marker : markers) { + String cycleAttr = (String) marker + .getAttribute(IJavaModelMarker.CYCLE_DETECTED); + if (cycleAttr != null && cycleAttr.equals("true")) { //$NON-NLS-1$ + return true; + } + } + return false; + } +}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSetupBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSetupBuilder.java new file mode 100644 index 0000000..0df28ea --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ProjectSetupBuilder.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.wizards.exportgradle; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtConstants; +import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState; +import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +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 java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Class to setup the project and its modules. + */ +public class ProjectSetupBuilder { + + private final static class InternalException extends Exception { + private static final long serialVersionUID = 1L; + + InternalException(String message) { + super(message); + } + } + + private boolean mCanFinish = false; + private boolean mCanGenerate = false; + private final List<GradleModule> mOriginalModules = Lists.newArrayList(); + private final Map<IJavaProject, GradleModule> mModules = Maps.newHashMap(); + private IPath mCommonRoot; + private ExportStatus mStatus; + + public ProjectSetupBuilder() { + + } + + public void setCanGenerate(boolean generate) { + mCanGenerate = generate; + } + + public void setCanFinish(boolean canFinish) { + mCanFinish = canFinish; + } + + public boolean canFinish() { + return mCanFinish; + } + + public boolean canGenerate() { + return mCanGenerate; + } + + public void setStatus(ExportStatus status) { + mStatus = status; + } + + public ExportStatus getStatus() { + return mStatus; + } + + @NonNull + public String setProject(@NonNull List<IJavaProject> selectedProjects) + throws CoreException { + mModules.clear(); + + // build a list of all projects that must be included. This is in case + // some dependencies have not been included in the selected projects. We also include + // parent projects so that the full multi-project setup is correct. + // Note that if two projects are selected that are not related, both will be added + // in the same multi-project anyway. + try { + for (IJavaProject javaProject : selectedProjects) { + GradleModule module; + + if (javaProject.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { + module = processAndroidProject(javaProject); + } else { + module = processJavaProject(javaProject); + } + + mOriginalModules.add(module); + } + + Collection<GradleModule> modules = mModules.values(); + computeRootAndPaths(modules); + + return null; + } catch (InternalException e) { + return e.getMessage(); + } + } + + @NonNull + public Collection<GradleModule> getModules() { + return mModules.values(); + } + + public int getModuleCount() { + return mModules.size(); + } + + @Nullable + public IPath getCommonRoot() { + return mCommonRoot; + } + + @Nullable + public GradleModule getModule(IJavaProject javaProject) { + return mModules.get(javaProject); + } + + public boolean isOriginalProject(@NonNull IJavaProject javaProject) { + GradleModule module = mModules.get(javaProject); + return mOriginalModules.contains(module); + } + + @NonNull + public List<GradleModule> getOriginalModules() { + return mOriginalModules; + } + + @Nullable + public List<GradleModule> getShortestDependencyTo(GradleModule module) { + return findModule(module, mOriginalModules); + } + + @Nullable + public List<GradleModule> findModule(GradleModule toFind, GradleModule rootModule) { + if (toFind == rootModule) { + List<GradleModule> list = Lists.newArrayList(); + list.add(toFind); + return list; + } + + List<GradleModule> shortestChain = findModule(toFind, rootModule.getDependencies()); + + if (shortestChain != null) { + shortestChain.add(0, rootModule); + } + + return shortestChain; + } + + @Nullable + public List<GradleModule> findModule(GradleModule toFind, List<GradleModule> modules) { + List<GradleModule> currentChain = null; + + for (GradleModule child : modules) { + List<GradleModule> newChain = findModule(toFind, child); + if (currentChain == null) { + currentChain = newChain; + } else if (newChain != null) { + if (currentChain.size() > newChain.size()) { + currentChain = newChain; + } + } + } + + return currentChain; + } + + @NonNull + private GradleModule processAndroidProject(@NonNull IJavaProject javaProject) + throws InternalException, CoreException { + + // get/create the module + GradleModule module = createModuleOnDemand(javaProject); + if (module.isConfigured()) { + return module; + } + + module.setType(GradleModule.Type.ANDROID); + + ProjectState projectState = Sdk.getProjectState(javaProject.getProject()); + assert projectState != null; + + // add library project dependencies + List<LibraryState> libraryProjects = projectState.getLibraries(); + for (LibraryState libraryState : libraryProjects) { + ProjectState libProjectState = libraryState.getProjectState(); + if (libProjectState != null) { + IJavaProject javaLib = getJavaProject(libProjectState); + if (javaLib != null) { + GradleModule libModule = processAndroidProject(javaLib); + module.addDependency(libModule); + } else { + throw new InternalException(String.format( + "Project %1$s is missing. Needed by %2$s.\n" + + "Make sure all dependencies are opened.", + libraryState.getRelativePath(), + javaProject.getProject().getName())); + } + } else { + throw new InternalException(String.format( + "Project %1$s is missing. Needed by %2$s.\n" + + "Make sure all dependencies are opened.", + libraryState.getRelativePath(), + javaProject.getProject().getName())); + } + } + + // add java project dependencies + List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject); + for (IJavaProject javaDep : javaDepProjects) { + GradleModule libModule = processJavaProject(javaDep); + module.addDependency(libModule); + } + + return module; + } + + @NonNull + private GradleModule processJavaProject(@NonNull IJavaProject javaProject) + throws InternalException, CoreException { + // get/create the module + GradleModule module = createModuleOnDemand(javaProject); + + if (module.isConfigured()) { + return module; + } + + module.setType(GradleModule.Type.JAVA); + + // add java project dependencies + List<IJavaProject> javaDepProjects = getReferencedProjects(javaProject); + for (IJavaProject javaDep : javaDepProjects) { + // Java project should not reference Android project! + if (javaDep.getProject().hasNature(AdtConstants.NATURE_DEFAULT)) { + throw new InternalException(String.format( + "Java project %1$s depends on Android project %2$s!\n" + + "This is not a valid dependency", + javaProject.getProject().getName(), javaDep.getProject().getName())); + } + GradleModule libModule = processJavaProject(javaDep); + module.addDependency(libModule); + } + + return module; + } + + private void computeRootAndPaths(Collection<GradleModule> modules) throws InternalException { + // compute the common root. + mCommonRoot = determineCommonRoot(modules); + + // compute all the relative paths. + for (GradleModule module : modules) { + String path = getGradlePath(module.getJavaProject().getProject().getLocation(), + mCommonRoot); + + module.setPath(path); + } + } + + /** + * Finds the common parent directory shared by this project and all its dependencies. + * If there's only one project, returns the single project's folder. + * @throws InternalException + */ + @NonNull + private static IPath determineCommonRoot(Collection<GradleModule> modules) + throws InternalException { + IPath commonRoot = null; + for (GradleModule module : modules) { + if (commonRoot == null) { + commonRoot = module.getJavaProject().getProject().getLocation(); + } else { + commonRoot = findCommonRoot(commonRoot, + module.getJavaProject().getProject().getLocation()); + } + } + + return commonRoot; + } + + /** + * Converts the given path to be relative to the given root path, and converts it to + * Gradle project notation, such as is used in the settings.gradle file. + */ + @NonNull + private static String getGradlePath(IPath path, IPath root) { + IPath relativePath = path.makeRelativeTo(root); + String relativeString = relativePath.toOSString(); + return ":" + relativeString.replaceAll(Pattern.quote(File.separator), ":"); //$NON-NLS-1$ + } + + /** + * Given two IPaths, finds the parent directory of both of them. + * @throws InternalException + */ + @NonNull + private static IPath findCommonRoot(@NonNull IPath path1, @NonNull IPath path2) + throws InternalException { + if (path1.getDevice() != null && path1.getDevice().equals(path2.getDevice())) { + throw new InternalException( + "Different modules have been detected on different drives.\n" + + "This prevents finding a common root to all modules."); + } + + IPath result = path1.uptoSegment(0); + + final int count = Math.min(path1.segmentCount(), path2.segmentCount()); + for (int i = 0; i < count; i++) { + if (path1.segment(i).equals(path2.segment(i))) { + result = result.append(Path.SEPARATOR + path2.segment(i)); + } + } + return result; + } + + @Nullable + private IJavaProject getJavaProject(ProjectState projectState) { + try { + return BaseProjectHelper.getJavaProject(projectState.getProject()); + } catch (CoreException e) { + return null; + } + } + + @NonNull + private GradleModule createModuleOnDemand(@NonNull IJavaProject javaProject) { + GradleModule module = mModules.get(javaProject); + if (module == null) { + module = new GradleModule(javaProject); + mModules.put(javaProject, module); + } + + return module; + } + + @NonNull + private static List<IJavaProject> getReferencedProjects(IJavaProject javaProject) + throws JavaModelException, InternalException { + + List<IJavaProject> projects = Lists.newArrayList(); + + IClasspathEntry entries[] = javaProject.getRawClasspath(); + for (IClasspathEntry classpathEntry : entries) { + if (classpathEntry.getContentKind() == IPackageFragmentRoot.K_SOURCE + && classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + // found required project on build path + String subProjectRoot = classpathEntry.getPath().toString(); + IJavaProject subProject = getJavaProject(subProjectRoot); + // is project available in workspace? + if (subProject != null) { + projects.add(subProject); + } else { + throw new InternalException(String.format( + "Project '%s' is missing project dependency '%s' in Eclipse workspace.\n" + + "Make sure all dependencies are opened.", + javaProject.getProject().getName(), + classpathEntry.getPath().toString())); + } + } + } + + return projects; + } + + /** + * Get Java project for given root. + */ + @Nullable + private static IJavaProject getJavaProject(String root) { + IPath path = new Path(root); + if (path.segmentCount() == 1) { + return getJavaProjectByName(root); + } + IResource resource = ResourcesPlugin.getWorkspace().getRoot() + .findMember(path); + if (resource != null && resource.getType() == IResource.PROJECT) { + if (resource.exists()) { + return (IJavaProject) JavaCore.create(resource); + } + } + return null; + } + + /** + * Get Java project from resource. + */ + private static IJavaProject getJavaProjectByName(String name) { + try { + IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name); + if (project.exists()) { + return JavaCore.create(project); + } + } catch (IllegalArgumentException iae) { + } + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportGradleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportGradleTest.java index 2cbde5a..f5cc58b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportGradleTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/wizards/exportgradle/ExportGradleTest.java @@ -17,17 +17,6 @@ package com.android.ide.eclipse.adt.internal.wizards.exportgradle; import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_LIBRARY; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jface.operation.IRunnableContext; -import org.eclipse.jface.operation.IRunnableWithProgress; - import com.android.SdkConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.AdtUtils; @@ -40,11 +29,22 @@ import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardS import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode; import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy; import com.google.common.base.Charsets; -import com.google.common.collect.Sets; import com.google.common.io.Files; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; + import java.io.File; import java.lang.reflect.InvocationTargetException; +import java.util.Collections; public class ExportGradleTest extends AdtProjectTest { private QualifiedName ERROR_KEY = new QualifiedName(AdtPlugin.PLUGIN_ID, "JobErrorKey"); @@ -64,12 +64,17 @@ public class ExportGradleTest extends AdtProjectTest { public void testSimpleAndroidApp() throws Throwable { IProject project = getProject("simple-app"); final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); + + final ProjectSetupBuilder builder = new ProjectSetupBuilder(); + builder.setProject(Collections.singletonList(javaProject)); + Job job = new Job("Validate project") { @Override protected IStatus run(IProgressMonitor monitor) { try { - BuildFileCreator.createBuildFiles(Sets.newHashSet(javaProject), null, null); - File buildfile = new File(javaProject.getResource().getLocation().toString(), "build.gradle"); + BuildFileCreator.createBuildFiles(builder, null, monitor); + File buildfile = new File(javaProject.getResource().getLocation().toString(), + BuildFileCreator.BUILD_FILE); assertTrue(buildfile.exists()); String contents = Files.toString(buildfile, Charsets.UTF_8); String expectedContents = @@ -134,14 +139,19 @@ public class ExportGradleTest extends AdtProjectTest { if (projectProp != null) { projectProp.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); } + Job job = new Job("Validate project") { @Override protected IStatus run(IProgressMonitor monitor) { try { IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - BuildFileCreator.createBuildFiles(Sets.newHashSet(javaProject), null, null); - File buildfile = new File(javaProject.getResource().getLocation().toString(), "build.gradle"); + final ProjectSetupBuilder builder = new ProjectSetupBuilder(); + builder.setProject(Collections.singletonList(javaProject)); + + BuildFileCreator.createBuildFiles(builder, null, monitor); + File buildfile = new File(javaProject.getResource().getLocation().toString(), + BuildFileCreator.BUILD_FILE); assertTrue(buildfile.exists()); String contents = Files.toString(buildfile, Charsets.UTF_8); String expectedContents = @@ -199,7 +209,11 @@ public class ExportGradleTest extends AdtProjectTest { public void testPlainJavaProject() throws Throwable { IProject project = getJavaProject("simple-java"); final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project); - BuildFileCreator.createBuildFiles(Sets.newHashSet(javaProject), null, null); + + final ProjectSetupBuilder builder = new ProjectSetupBuilder(); + builder.setProject(Collections.singletonList(javaProject)); + + BuildFileCreator.createBuildFiles(builder, null, null); Job job = new Job("Validate project") { @Override protected IStatus run(IProgressMonitor monitor) { @@ -273,8 +287,6 @@ public class ExportGradleTest extends AdtProjectTest { return validateProjectExists(name); } - - /** * Compares two strings, disregarding whitespace. This makes the test less brittle with respect * to insignificant changes. |