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