Home | History | Annotate | Download | only in exportgradle
      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      */
    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      */
    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      */
    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      */
    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      */
    340     private void appendBuildScript() {
    341         appendBuildScript(mBuildFile);
    342     }
    343 
    344     /**
    345      * Outputs boilerplate header information common to all Gradle build files.
    346      */
    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      */
    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      */
    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      */
    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      */
    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      */
    468     private void finishAndroidTask() {
    469         mBuildFile.append("}\n"); //$NON-NLS-1$
    470     }
    471 
    472     /**
    473      * Outputs a boilerplate header for non-Android projects
    474      */
    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      */
    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      */
    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 
    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      */
    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
    589     private static File getGradleWrapperPropertiesFilePath(@NonNull File projectRootDir) {
    590         return new File(projectRootDir, GRADLEW_PROPERTIES_PATH);
    591     }
    592 
    593     @Nullable
    594     public static File findWrapperPropertiesFile(@NonNull File projectRootDir) {
    595       File wrapperPropertiesFile = getGradleWrapperPropertiesFilePath(projectRootDir);
    596       return wrapperPropertiesFile.isFile() ? wrapperPropertiesFile : null;
    597     }
    598 
    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
    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
    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