1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ide.eclipse.adt.internal.wizards.exportgradle;
18 
19 import static com.android.SdkConstants.GRADLE_LATEST_VERSION;
20 import static com.android.SdkConstants.GRADLE_PLUGIN_LATEST_VERSION;
21 import static com.android.SdkConstants.GRADLE_PLUGIN_NAME;
22 
23 import com.android.SdkConstants;
24 import com.android.annotations.NonNull;
25 import com.android.annotations.Nullable;
26 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
27 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
29 import com.android.ide.eclipse.adt.io.IFolderWrapper;
30 import com.android.io.IAbstractFile;
31 import com.android.sdklib.io.FileOp;
32 import com.android.xml.AndroidManifest;
33 import com.google.common.base.Charsets;
34 import com.google.common.base.Joiner;
35 import com.google.common.collect.Lists;
36 import com.google.common.io.Closeables;
37 import com.google.common.io.Files;
38 
39 import org.eclipse.core.resources.IFile;
40 import org.eclipse.core.resources.IProject;
41 import org.eclipse.core.resources.IWorkspaceRoot;
42 import org.eclipse.core.resources.ResourcesPlugin;
43 import org.eclipse.core.runtime.CoreException;
44 import org.eclipse.core.runtime.IPath;
45 import org.eclipse.core.runtime.IProgressMonitor;
46 import org.eclipse.core.runtime.IStatus;
47 import org.eclipse.core.runtime.Path;
48 import org.eclipse.core.runtime.SubMonitor;
49 import org.eclipse.jdt.core.IClasspathEntry;
50 import org.eclipse.jdt.core.IJavaProject;
51 import org.eclipse.jdt.core.JavaCore;
52 import org.eclipse.osgi.util.NLS;
53 import org.eclipse.swt.widgets.Shell;
54 
55 import java.io.ByteArrayInputStream;
56 import java.io.File;
57 import java.io.FileInputStream;
58 import java.io.FileOutputStream;
59 import java.io.IOException;
60 import java.io.InputStream;
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.Comparator;
64 import java.util.List;
65 import java.util.Properties;
66 import java.util.Set;
67 import java.util.TreeSet;
68 
69 /**
70  * Creates build.gradle and settings.gradle files for a set of projects.
71  * <p>
72  * Based on {@link org.eclipse.ant.internal.ui.datatransfer.BuildFileCreator}
73  */
74 public class BuildFileCreator {
75     static final String BUILD_FILE = "build.gradle"; //$NON-NLS-1$
76     static final String SETTINGS_FILE = "settings.gradle"; //$NON-NLS-1$
77     private static final String NEWLINE = System.getProperty("line.separator"); //$NON-NLS-1$
78     private static final String GRADLE_WRAPPER_LOCATION =
79             "tools/templates/gradle/wrapper"; //$NON-NLS-1$
80     static final String PLUGIN_CLASSPATH =
81             "classpath '" + GRADLE_PLUGIN_NAME + GRADLE_PLUGIN_LATEST_VERSION + "'"; //$NON-NLS-1$
82     static final String MAVEN_REPOSITORY = "mavenCentral()"; //$NON-NLS-1$
83 
84     private static final String[] GRADLE_WRAPPER_FILES = new String[] {
85         "gradlew", //$NON-NLS-1$
86         "gradlew.bat", //$NON-NLS-1$
87         "gradle/wrapper/gradle-wrapper.jar", //$NON-NLS-1$
88         "gradle/wrapper/gradle-wrapper.properties" //$NON-NLS-1$
89     };
90 
91     private static final Comparator<IFile> FILE_COMPARATOR = new Comparator<IFile>() {
92         @Override
93         public int compare(IFile o1, IFile o2) {
94             return o1.toString().compareTo(o2.toString());
95         }
96     };
97 
98     private final GradleModule mModule;
99     private final StringBuilder mBuildFile = new StringBuilder();
100 
101     /**
102      * Create buildfile for the projects.
103      *
104      * @param shell parent instance for dialogs
105      * @return project names for which buildfiles were created
106      * @throws InterruptedException thrown when user cancels task
107      */
createBuildFiles( @onNull ProjectSetupBuilder builder, @NonNull Shell shell, @NonNull IProgressMonitor pm)108     public static void createBuildFiles(
109             @NonNull ProjectSetupBuilder builder,
110             @NonNull Shell shell,
111             @NonNull IProgressMonitor pm) {
112 
113         File gradleLocation = new File(Sdk.getCurrent().getSdkOsLocation(), GRADLE_WRAPPER_LOCATION);
114         SubMonitor localmonitor = null;
115 
116         try {
117             // See if we have a Gradle wrapper in the SDK templates directory. If so, we can copy
118             // it over.
119             boolean hasGradleWrapper = true;
120             for (File wrapperFile : getGradleWrapperFiles(gradleLocation)) {
121                 if (!wrapperFile.exists()) {
122                     hasGradleWrapper = false;
123                 }
124             }
125 
126             Collection<GradleModule> modules = builder.getModules();
127             boolean multiModules = modules.size() > 1;
128 
129             // determine files to create/change
130             List<IFile> files = new ArrayList<IFile>();
131 
132             // add the build.gradle file for all modules.
133             for (GradleModule module : modules) {
134                 // build.gradle file
135                 IFile file = module.getProject().getFile(BuildFileCreator.BUILD_FILE);
136                 files.add(file);
137             }
138 
139             // get the commonRoot for all modules. If only one module, this returns the path
140             // of the project.
141             IPath commonRoot = builder.getCommonRoot();
142 
143             IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
144             IPath workspaceLocation = workspaceRoot.getLocation();
145 
146             IPath relativePath = commonRoot.makeRelativeTo(workspaceLocation);
147             // if makeRelativePath to returns the same path, then commonRoot is not in the
148             // workspace.
149             boolean rootInWorkspace = !relativePath.equals(commonRoot);
150             // we only care if the root is a workspace project. if it's the workspace folder itself,
151             // then the files won't be handled by the workspace.
152             rootInWorkspace = rootInWorkspace && relativePath.segmentCount() > 0;
153 
154             File settingsFile = new File(commonRoot.toFile(), SETTINGS_FILE);
155 
156             // more than one modules -> generate settings.gradle
157             if (multiModules && rootInWorkspace) {
158 
159                 // Locate the settings.gradle file and add it to the changed files list
160                 IPath settingsGradle = Path.fromOSString(settingsFile.getAbsolutePath());
161 
162                 // different path, means commonRoot is inside the workspace, which means we have
163                 // to add settings.gradle and wrapper files to the list of files to add.
164                 IFile iFile = workspaceRoot.getFile(settingsGradle);
165                 if (iFile != null) {
166                     files.add(iFile);
167                 }
168             }
169 
170             // Gradle wrapper files
171             if (hasGradleWrapper && rootInWorkspace) {
172                 // See if there already wrapper files there and only mark nonexistent ones for
173                 // creation.
174                 for (File wrapperFile : getGradleWrapperFiles(commonRoot.toFile())) {
175                     if (!wrapperFile.exists()) {
176                         IPath path = Path.fromOSString(wrapperFile.getAbsolutePath());
177                         IFile file = workspaceRoot.getFile(path);
178                         files.add(file);
179                     }
180                 }
181             }
182 
183             ExportStatus status = new ExportStatus();
184             builder.setStatus(status);
185 
186             // Trigger checkout of changed files
187             Set<IFile> confirmedFiles = validateEdit(files, status, shell);
188 
189             if (status.hasError()) {
190                 return;
191             }
192 
193             // Now iterate over all the modules and generate the build files.
194             localmonitor = SubMonitor.convert(pm, ExportMessages.PageTitle,
195                     confirmedFiles.size());
196             List<String> projectSettingsPath = Lists.newArrayList();
197             for (GradleModule currentModule : modules) {
198                 IProject moduleProject = currentModule.getProject();
199 
200                 IFile file = moduleProject.getFile(BuildFileCreator.BUILD_FILE);
201                 if (!confirmedFiles.contains(file)) {
202                     continue;
203                 }
204 
205                 localmonitor.setTaskName(NLS.bind(ExportMessages.FileStatusMessage,
206                         moduleProject.getName()));
207 
208                 ProjectState projectState = Sdk.getProjectState(moduleProject);
209                 BuildFileCreator instance = new BuildFileCreator(currentModule, shell);
210                 if (projectState != null) {
211                     // This is an Android project
212                     if (!multiModules) {
213                         instance.appendBuildScript();
214                     }
215                     instance.appendHeader(projectState.isLibrary());
216                     instance.appendDependencies();
217                     instance.startAndroidTask(projectState);
218                     //instance.appendDefaultConfig();
219                     instance.createAndroidSourceSets();
220                     instance.finishAndroidTask();
221                 } else {
222                     // This is a plain Java project
223                     instance.appendJavaHeader();
224                     instance.createJavaSourceSets();
225                 }
226 
227                try {
228                     // Write the build file
229                     String buildfile = instance.mBuildFile.toString();
230                     InputStream is =
231                             new ByteArrayInputStream(buildfile.getBytes("UTF-8")); //$NON-NLS-1$
232                     if (file.exists()) {
233                         file.setContents(is, true, true, null);
234                     } else {
235                         file.create(is, true, null);
236                     }
237                } catch (Exception e) {
238                      status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE,
239                              file.getLocation().toFile());
240                      status.setErrorMessage(e.getMessage());
241                      return;
242                }
243 
244                if (localmonitor.isCanceled()) {
245                    return;
246                }
247                localmonitor.worked(1);
248 
249                 // get the project path to add it to the settings.gradle.
250                 projectSettingsPath.add(currentModule.getPath());
251             }
252 
253             // write the settings file.
254             if (multiModules) {
255                 try {
256                     writeGradleSettingsFile(settingsFile, projectSettingsPath);
257                 } catch (IOException e) {
258                     status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, settingsFile);
259                     status.setErrorMessage(e.getMessage());
260                     return;
261                 }
262                 File mainBuildFile = new File(commonRoot.toFile(), BUILD_FILE);
263                 try {
264                     writeRootBuildGradle(mainBuildFile);
265                 } catch (IOException e) {
266                     status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, mainBuildFile);
267                     status.setErrorMessage(e.getMessage());
268                     return;
269                 }
270             }
271 
272             // finally write the wrapper
273             // TODO check we can based on where it is
274             if (hasGradleWrapper) {
275                 copyGradleWrapper(gradleLocation, commonRoot.toFile(), status);
276                 if (status.hasError()) {
277                     return;
278                 }
279             }
280 
281         } finally {
282             if (localmonitor != null && !localmonitor.isCanceled()) {
283                 localmonitor.done();
284             }
285             if (pm != null) {
286                 pm.done();
287             }
288         }
289     }
290 
291     /**
292      * @param GradleModule create buildfile for this project
293      * @param shell parent instance for dialogs
294      */
BuildFileCreator(GradleModule module, Shell shell)295     private BuildFileCreator(GradleModule module, Shell shell) {
296         mModule = module;
297     }
298 
299     /**
300      * Return the files that comprise the Gradle wrapper as a collection of {@link File} instances.
301      * @param root
302      * @return
303      */
getGradleWrapperFiles(File root)304     private static List<File> getGradleWrapperFiles(File root) {
305         List<File> files = new ArrayList<File>(GRADLE_WRAPPER_FILES.length);
306         for (String file : GRADLE_WRAPPER_FILES) {
307             files.add(new File(root, file));
308         }
309         return files;
310     }
311 
312     /**
313      * Copy the Gradle wrapper files from one directory to another.
314      */
copyGradleWrapper(File from, File to, ExportStatus status)315     private static void copyGradleWrapper(File from, File to, ExportStatus status) {
316         for (String file : GRADLE_WRAPPER_FILES) {
317             File dest = new File(to, file);
318             try {
319                 File src = new File(from, file);
320                 dest.getParentFile().mkdirs();
321                 new FileOp().copyFile(src, dest);
322 
323                 if (src.getName().equals(GRADLE_PROPERTIES)) {
324                     updateGradleDistributionUrl(GRADLE_LATEST_VERSION, dest);
325                 }
326                 dest.setExecutable(src.canExecute());
327                 status.addFileStatus(ExportStatus.FileStatus.OK, dest);
328             } catch (IOException e) {
329                 status.addFileStatus(ExportStatus.FileStatus.IO_FAILURE, dest);
330                 return;
331             }
332         }
333     }
334 
335     /**
336      * Outputs boilerplate buildscript information common to all Gradle build files.
337      */
appendBuildScript()338     private void appendBuildScript() {
339         appendBuildScript(mBuildFile);
340     }
341 
342     /**
343      * Outputs boilerplate header information common to all Gradle build files.
344      */
appendBuildScript(StringBuilder builder)345     private static void appendBuildScript(StringBuilder builder) {
346         builder.append("buildscript {\n"); //$NON-NLS-1$
347         builder.append("    repositories {\n"); //$NON-NLS-1$
348         builder.append("        " + MAVEN_REPOSITORY + "\n"); //$NON-NLS-1$
349         builder.append("    }\n"); //$NON-NLS-1$
350         builder.append("    dependencies {\n"); //$NON-NLS-1$
351         builder.append("        " + PLUGIN_CLASSPATH + "\n"); //$NON-NLS-1$
352         builder.append("    }\n"); //$NON-NLS-1$
353         builder.append("}\n"); //$NON-NLS-1$
354     }
355 
356     /**
357      * Outputs boilerplate header information common to all Gradle build files.
358      */
appendHeader(boolean isLibrary)359     private void appendHeader(boolean isLibrary) {
360         if (isLibrary) {
361             mBuildFile.append("apply plugin: 'android-library'\n"); //$NON-NLS-1$
362         } else {
363             mBuildFile.append("apply plugin: 'android'\n"); //$NON-NLS-1$
364         }
365         mBuildFile.append("\n"); //$NON-NLS-1$
366     }
367 
368     /**
369      * Outputs a block which sets up library and project dependencies.
370      */
appendDependencies()371     private void appendDependencies() {
372         mBuildFile.append("dependencies {\n"); //$NON-NLS-1$
373 
374         // first the local jars.
375         // TODO: Fix
376         mBuildFile.append("    compile fileTree(dir: 'libs', include: '*.jar')\n"); //$NON-NLS-1$
377 
378         for (GradleModule dep : mModule.getDependencies()) {
379             mBuildFile.append("    compile project('" + dep.getPath() + "')\n"); //$NON-NLS-1$ //$NON-NLS-2$
380         }
381 
382         mBuildFile.append("}\n"); //$NON-NLS-1$
383         mBuildFile.append("\n"); //$NON-NLS-1$
384     }
385 
386     /**
387      * Outputs the beginning of an Android task in the build file.
388      */
startAndroidTask(ProjectState projectState)389     private void startAndroidTask(ProjectState projectState) {
390         int buildApi = projectState.getTarget().getVersion().getApiLevel();
391         String toolsVersion = projectState.getTarget().getBuildToolInfo().getRevision().toString();
392         mBuildFile.append("android {\n"); //$NON-NLS-1$
393         mBuildFile.append("    compileSdkVersion " + buildApi + "\n"); //$NON-NLS-1$
394         mBuildFile.append("    buildToolsVersion \"" + toolsVersion + "\"\n"); //$NON-NLS-1$
395         mBuildFile.append("\n"); //$NON-NLS-1$
396 
397         try {
398             IJavaProject javaProject = BaseProjectHelper.getJavaProject(projectState.getProject());
399             // otherwise we check source compatibility
400             String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
401             if (JavaCore.VERSION_1_7.equals(source)) {
402                 mBuildFile.append(
403                         "    compileOptions {\n" + //$NON-NLS-1$
404                         "        sourceCompatibility JavaVersion.VERSION_1_7\n" + //$NON-NLS-1$
405                         "        targetCompatibility JavaVersion.VERSION_1_7\n" + //$NON-NLS-1$
406                         "    }\n" + //$NON-NLS-1$
407                         "\n"); //$NON-NLS-1$
408             }
409         } catch (CoreException e) {
410             // Ignore compliance level, go with default
411         }
412     }
413 
414     /**
415      * Outputs a sourceSets block to the Android task that locates all of the various source
416      * subdirectories in the project.
417      */
createAndroidSourceSets()418     private void createAndroidSourceSets() {
419         IFolderWrapper projectFolder = new IFolderWrapper(mModule.getProject());
420         IAbstractFile mManifestFile = AndroidManifest.getManifest(projectFolder);
421         if (mManifestFile == null) {
422             return;
423         }
424         List<String> srcDirs = new ArrayList<String>();
425         for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) {
426             if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE ||
427                     SdkConstants.FD_GEN_SOURCES.equals(entry.getPath().lastSegment())) {
428                 continue;
429             }
430             IPath path = entry.getPath().removeFirstSegments(1);
431             srcDirs.add("'" + path.toOSString() + "'"); //$NON-NLS-1$
432         }
433 
434         String srcPaths = Joiner.on(",").join(srcDirs);
435 
436         mBuildFile.append("    sourceSets {\n"); //$NON-NLS-1$
437         mBuildFile.append("        main {\n"); //$NON-NLS-1$
438         mBuildFile.append("            manifest.srcFile '" + SdkConstants.FN_ANDROID_MANIFEST_XML + "'\n"); //$NON-NLS-1$
439         mBuildFile.append("            java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
440         mBuildFile.append("            resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
441         mBuildFile.append("            aidl.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
442         mBuildFile.append("            renderscript.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
443         mBuildFile.append("            res.srcDirs = ['res']\n"); //$NON-NLS-1$
444         mBuildFile.append("            assets.srcDirs = ['assets']\n"); //$NON-NLS-1$
445         mBuildFile.append("        }\n"); //$NON-NLS-1$
446         mBuildFile.append("\n"); //$NON-NLS-1$
447         mBuildFile.append("        // Move the tests to tests/java, tests/res, etc...\n"); //$NON-NLS-1$
448         mBuildFile.append("        instrumentTest.setRoot('tests')\n"); //$NON-NLS-1$
449         if (srcDirs.contains("'src'")) {
450             mBuildFile.append("\n"); //$NON-NLS-1$
451             mBuildFile.append("        // Move the build types to build-types/<type>\n"); //$NON-NLS-1$
452             mBuildFile.append("        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...\n"); //$NON-NLS-1$
453             mBuildFile.append("        // This moves them out of them default location under src/<type>/... which would\n"); //$NON-NLS-1$
454             mBuildFile.append("        // conflict with src/ being used by the main source set.\n"); //$NON-NLS-1$
455             mBuildFile.append("        // Adding new build types or product flavors should be accompanied\n"); //$NON-NLS-1$
456             mBuildFile.append("        // by a similar customization.\n"); //$NON-NLS-1$
457             mBuildFile.append("        debug.setRoot('build-types/debug')\n"); //$NON-NLS-1$
458             mBuildFile.append("        release.setRoot('build-types/release')\n"); //$NON-NLS-1$
459         }
460         mBuildFile.append("    }\n"); //$NON-NLS-1$
461     }
462 
463     /**
464      * Outputs the completion of the Android task in the build file.
465      */
finishAndroidTask()466     private void finishAndroidTask() {
467         mBuildFile.append("}\n"); //$NON-NLS-1$
468     }
469 
470     /**
471      * Outputs a boilerplate header for non-Android projects
472      */
appendJavaHeader()473     private void appendJavaHeader() {
474         mBuildFile.append("apply plugin: 'java'\n"); //$NON-NLS-1$
475     }
476 
477     /**
478      * Outputs a sourceSets block for non-Android projects to locate the source directories.
479      */
createJavaSourceSets()480     private void createJavaSourceSets() {
481         List<String> dirs = new ArrayList<String>();
482         for (IClasspathEntry entry : mModule.getJavaProject().readRawClasspath()) {
483             if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
484                 continue;
485             }
486             IPath path = entry.getPath().removeFirstSegments(1);
487             dirs.add("'" + path.toOSString() + "'"); //$NON-NLS-1$
488         }
489 
490         String srcPaths = Joiner.on(",").join(dirs);
491 
492         mBuildFile.append("sourceSets {\n"); //$NON-NLS-1$
493         mBuildFile.append("    main.java.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
494         mBuildFile.append("    main.resources.srcDirs = [" + srcPaths + "]\n"); //$NON-NLS-1$
495         mBuildFile.append("    test.java.srcDirs = ['tests/java']\n"); //$NON-NLS-1$
496         mBuildFile.append("    test.resources.srcDirs = ['tests/resources']\n"); //$NON-NLS-1$
497         mBuildFile.append("}\n"); //$NON-NLS-1$
498     }
499 
500     /**
501      * Merges the new subproject dependencies into the settings.gradle file if it already exists,
502      * and creates one if it does not.
503      * @throws IOException
504      */
writeGradleSettingsFile(File settingsFile, List<String> projectPaths)505     private static void writeGradleSettingsFile(File settingsFile, List<String> projectPaths)
506             throws IOException {
507         StringBuilder contents = new StringBuilder();
508         for (String path : projectPaths) {
509             contents.append("include '").append(path).append("'\n"); //$NON-NLS-1$ //$NON-NLS-2$
510         }
511 
512         Files.write(contents.toString(), settingsFile, Charsets.UTF_8);
513     }
514 
writeRootBuildGradle(File buildFile)515     private static void writeRootBuildGradle(File buildFile) throws IOException {
516         StringBuilder sb = new StringBuilder(
517                 "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n");
518 
519         appendBuildScript(sb);
520 
521         Files.write(sb.toString(), buildFile, Charsets.UTF_8);
522     }
523 
524     /**
525      * Request write access to given files. Depending on the version control
526      * plug-in opens a confirm checkout dialog.
527      *
528      * @param shell
529      *            parent instance for dialogs
530      * @return <code>IFile</code> objects for which user confirmed checkout
531      * @throws CoreException
532      *             thrown if project is under version control, but not connected
533      */
validateEdit( @onNull List<IFile> files, @NonNull ExportStatus exportStatus, @NonNull Shell shell)534     static Set<IFile> validateEdit(
535             @NonNull List<IFile> files,
536             @NonNull ExportStatus exportStatus,
537             @NonNull Shell shell) {
538         Set<IFile> confirmedFiles = new TreeSet<IFile>(FILE_COMPARATOR);
539         if (files.size() == 0) {
540             return confirmedFiles;
541         }
542         IStatus status = (files.get(0)).getWorkspace().validateEdit(
543                 files.toArray(new IFile[files.size()]), shell);
544         if (status.isMultiStatus() && status.getChildren().length > 0) {
545             for (int i = 0; i < status.getChildren().length; i++) {
546                 IStatus statusChild = status.getChildren()[i];
547                 if (statusChild.isOK()) {
548                     confirmedFiles.add(files.get(i));
549                 } else {
550                     exportStatus.addFileStatus(
551                             ExportStatus.FileStatus.VCS_FAILURE,
552                             files.get(i).getLocation().toFile());
553                 }
554             }
555         } else if (status.isOK()) {
556             confirmedFiles.addAll(files);
557         }
558         if (status.getSeverity() == IStatus.ERROR) {
559             // not possible to checkout files: not connected to version
560             // control plugin or hijacked files and made read-only, so
561             // collect error messages provided by validator and re-throw
562             StringBuffer message = new StringBuffer(status.getPlugin() + ": " //$NON-NLS-1$
563                     + status.getMessage() + NEWLINE);
564             if (status.isMultiStatus()) {
565                 for (int i = 0; i < status.getChildren().length; i++) {
566                     IStatus statusChild = status.getChildren()[i];
567                     message.append(statusChild.getMessage() + NEWLINE);
568                 }
569             }
570             String s = message.toString();
571             exportStatus.setErrorMessage(s);
572         }
573 
574         return confirmedFiles;
575     }
576 
577     // -------------------------------------------------------------------------------
578     // Fix gradle wrapper version. This code is from GradleUtil in the Studio plugin:
579     // -------------------------------------------------------------------------------
580 
581     private static final String GRADLE_PROPERTIES = "gradle-wrapper.properties";
582     private static final String GRADLEW_PROPERTIES_PATH =
583             "gradle" + File.separator + "wrapper" + File.separator + GRADLE_PROPERTIES;
584     private static final String GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME = "distributionUrl";
585 
586     @NonNull
getGradleWrapperPropertiesFilePath(@onNull File projectRootDir)587     private static File getGradleWrapperPropertiesFilePath(@NonNull File projectRootDir) {
588         return new File(projectRootDir, GRADLEW_PROPERTIES_PATH);
589     }
590 
591     @Nullable
findWrapperPropertiesFile(@onNull File projectRootDir)592     public static File findWrapperPropertiesFile(@NonNull File projectRootDir) {
593       File wrapperPropertiesFile = getGradleWrapperPropertiesFilePath(projectRootDir);
594       return wrapperPropertiesFile.isFile() ? wrapperPropertiesFile : null;
595     }
596 
updateGradleDistributionUrl( @onNull String gradleVersion, @NonNull File propertiesFile)597     private static boolean updateGradleDistributionUrl(
598             @NonNull String gradleVersion,
599             @NonNull File propertiesFile) throws IOException {
600         Properties properties = loadGradleWrapperProperties(propertiesFile);
601         String gradleDistributionUrl = getGradleDistributionUrl(gradleVersion, false);
602         String property = properties.getProperty(GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME);
603         if (property != null
604                 && (property.equals(gradleDistributionUrl) || property
605                         .equals(getGradleDistributionUrl(gradleVersion, true)))) {
606             return false;
607         }
608         properties.setProperty(GRADLEW_DISTRIBUTION_URL_PROPERTY_NAME, gradleDistributionUrl);
609         FileOutputStream out = null;
610         try {
611             out = new FileOutputStream(propertiesFile);
612             properties.store(out, null);
613             return true;
614         } finally {
615             Closeables.close(out, true);
616         }
617     }
618 
619     @NonNull
loadGradleWrapperProperties(@onNull File propertiesFile)620     private static Properties loadGradleWrapperProperties(@NonNull File propertiesFile)
621             throws IOException {
622         Properties properties = new Properties();
623         FileInputStream fileInputStream = null;
624         try {
625             fileInputStream = new FileInputStream(propertiesFile);
626             properties.load(fileInputStream);
627             return properties;
628         } finally {
629             Closeables.close(fileInputStream, true);
630         }
631     }
632 
633     @NonNull
getGradleDistributionUrl(@onNull String gradleVersion, boolean binOnly)634     private static String getGradleDistributionUrl(@NonNull String gradleVersion,
635             boolean binOnly) {
636         String suffix = binOnly ? "bin" : "all";
637         return String.format("http://services.gradle.org/distributions/gradle-%1$s-" + suffix
638                 + ".zip", gradleVersion);
639     }
640 }
641