| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tools.gradle.eclipse; |
| |
| import static com.android.SdkConstants.ANDROID_MANIFEST_XML; |
| import static com.android.SdkConstants.ANDROID_URI; |
| import static com.android.SdkConstants.ATTR_NAME; |
| import static com.android.SdkConstants.ATTR_PACKAGE; |
| import static com.android.SdkConstants.FD_EXTRAS; |
| import static com.android.SdkConstants.FD_GRADLE; |
| import static com.android.SdkConstants.FD_RES; |
| import static com.android.SdkConstants.FD_SOURCES; |
| import static com.android.SdkConstants.FN_BUILD_GRADLE; |
| import static com.android.SdkConstants.FN_GRADLE_WRAPPER_UNIX; |
| import static com.android.SdkConstants.FN_GRADLE_WRAPPER_WIN; |
| import static com.android.SdkConstants.FN_LOCAL_PROPERTIES; |
| import static com.android.SdkConstants.FN_SETTINGS_GRADLE; |
| import static com.android.SdkConstants.GRADLE_PLUGIN_LATEST_VERSION; |
| import static com.android.SdkConstants.GRADLE_PLUGIN_NAME; |
| import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_NDK; |
| import static com.android.sdklib.internal.project.ProjectProperties.PROPERTY_SDK; |
| import static com.android.xml.AndroidManifest.NODE_INSTRUMENTATION; |
| import static com.google.common.base.Charsets.UTF_8; |
| import static java.io.File.separator; |
| import static java.io.File.separatorChar; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.repository.GradleCoordinate; |
| import com.android.sdklib.BuildToolInfo; |
| import com.android.sdklib.SdkManager; |
| import com.android.sdklib.repository.descriptors.IPkgDesc; |
| import com.android.sdklib.repository.descriptors.PkgType; |
| import com.android.sdklib.repository.local.LocalPkgInfo; |
| import com.android.sdklib.repository.local.LocalSdk; |
| import com.android.utils.ILogger; |
| import com.android.utils.SdkUtils; |
| import com.android.utils.StdLogger; |
| import com.android.utils.XmlUtils; |
| import com.google.common.base.Charsets; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Sets; |
| import com.google.common.io.Closeables; |
| import com.google.common.io.Files; |
| import com.google.common.primitives.Bytes; |
| |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| /** |
| * Importer which can generate Android Gradle projects. |
| * <p> |
| * It currently only supports importing ADT projects, so it will require |
| * some tweaks to handle importing from other types of projects. |
| * <p> |
| * The importer primarily imports complete ADT projects into complete (new) Gradle |
| * projects, but it can also be used to import one or more ADT projects into an |
| * existing Gradle project as new modules. See {@link #exportIntoProject} for |
| * details on how to do this. |
| * <p> |
| * TODO: |
| * <ul> |
| * <li>Migrate SDK folder from local.properties. If should make doubly sure that |
| * the repository you point to contains the app support library and other |
| * libraries that may be needed.</li> |
| * <li>Consider whether I can make this import mechanism work for Maven and plain |
| * sources as well?</li> |
| * <li>Make it optional whether we replace the directory structure with the Gradle one?</li> |
| * <li>Allow migrating a project in-place?</li> |
| * <li>If I have a workspace, check to see if there are problem markers and if |
| * so warn that the project may not be buildable</li> |
| * <li>Optional: at the end of the import, migrate Eclipse settings too -- |
| * such as code styles, compiler flags (especially those for the |
| * project), ask about enabling eclipse key bindings, etc?</li> |
| * <li>If replaceJars=false, insert *comments* in the source code for potential |
| * replacements such that users don't forget and consider switching in the future</li> |
| * <li>Figure out if we can reuse fragments from the default freemarker templates for |
| * the code generation part.</li> |
| * <li>Allow option to preserve module nesting hierarchy. It currently flattens.</li> |
| * <li>Make it possible to use this wizard to migrate an already exported Eclipse project?</li> |
| * <li>Consider making the export create an HTML file and open in browser?</li> |
| * </ul> |
| */ |
| public class GradleImport { |
| public static final String NL = SdkUtils.getLineSeparator(); |
| public static final int CURRENT_COMPILE_VERSION = 19; |
| public static final String CURRENT_BUILD_TOOLS_VERSION = SdkConstants.MIN_BUILD_TOOLS_VERSION; |
| public static final String ANDROID_GRADLE_PLUGIN = |
| GRADLE_PLUGIN_NAME + GRADLE_PLUGIN_LATEST_VERSION; |
| public static final String MAVEN_URL_PROPERTY = "android.mavenRepoUrl"; |
| private static final String WORKSPACE_PROPERTY = "android.eclipseWorkspace"; |
| |
| static final String MAVEN_REPOSITORY; |
| static { |
| String repository = System.getProperty(MAVEN_URL_PROPERTY); |
| if (repository == null) { |
| repository = "mavenCentral()"; |
| } else { |
| repository = "maven { url '" + repository + "' }"; |
| } |
| MAVEN_REPOSITORY = repository; |
| } |
| |
| public static final String ECLIPSE_DOT_CLASSPATH = ".classpath"; |
| public static final String ECLIPSE_DOT_PROJECT = ".project"; |
| public static final String IMPORT_SUMMARY_TXT = "import-summary.txt"; |
| |
| /** |
| * Whether we should place the repository definitions in the global build.gradle rather |
| * than in each module |
| */ |
| static final boolean DECLARE_GLOBAL_REPOSITORIES = true; |
| |
| private List<? extends ImportModule> mRootModules; |
| private Set<ImportModule> mModules; |
| private ImportSummary mSummary; |
| private File mWorkspaceLocation; |
| private File mGradleWrapperLocation; |
| private File mSdkLocation; |
| private File mNdkLocation; |
| private SdkManager mSdkManager; |
| private Set<String> mHandledJars = Sets.newHashSet(); |
| private Map<String,File> mWorkspaceProjects; |
| |
| /** Whether we should convert project names to lowercase module names */ |
| private boolean mGradleNameStyle = true; |
| /** Whether we should try to replace jars with dependencies */ |
| private boolean mReplaceJars = true; |
| /** Whether we should try to replace libs with dependencies */ |
| private boolean mReplaceLibs = true; |
| /** Whether the importer is in "import into existing project" mode. In that case, |
| * some different choices are made; for example, we don't rewrite the module name |
| * to "app" when importing a single project; we preserve the project name.*/ |
| private boolean mImportIntoExisting; |
| /** Whether we should emit per-module repository definitions */ |
| @SuppressWarnings("PointlessBooleanExpression") |
| private boolean mPerModuleRepositories = !DECLARE_GLOBAL_REPOSITORIES; |
| |
| private final List<String> mWarnings = Lists.newArrayList(); |
| private final List<String> mErrors = Lists.newArrayList(); |
| private Map<String, File> mPathMap = Maps.newTreeMap(); |
| /** |
| * Set of modules user chose to import. Can be <code>null</code> when all |
| * modules will be imported |
| */ |
| private Set<String> mSelectedModules; |
| |
| public GradleImport() { |
| String workspace = System.getProperty(WORKSPACE_PROPERTY); |
| if (workspace != null) { |
| mWorkspaceLocation = new File(workspace); |
| } |
| } |
| |
| /** Imports the given projects. Note that this just reads in the project state; |
| * it does not actually write out a Gradle project. For that, you should call |
| * {@link #exportProject(java.io.File, boolean)}. |
| * |
| * @param projectDirs the project directories to import |
| * @throws IOException if something is wrong |
| */ |
| public void importProjects(@NonNull List<File> projectDirs) throws IOException { |
| mSummary = new ImportSummary(this); |
| mProjectMap.clear(); |
| mHandledJars.clear(); |
| mWarnings.clear(); |
| mErrors.clear(); |
| mWorkspaceProjects = null; |
| mRootModules = Collections.emptyList(); |
| mModules = Sets.newHashSet(); |
| |
| for (File file : projectDirs) { |
| if (file.isFile()) { |
| assert !file.isDirectory(); |
| file = file.getParentFile(); |
| } |
| |
| guessWorkspace(file); |
| |
| if (isAdtProjectDir(file)) { |
| guessSdk(file); |
| guessNdk(file); |
| |
| try { |
| EclipseProject.getProject(this, file); |
| } catch (ImportException e) { |
| // Already recorded |
| return; |
| } catch (Exception e) { |
| reportError(null, file, e.toString(), false); |
| return; |
| } |
| } else { |
| reportError(null, file, "Not a recognized project: " + file, false); |
| return; |
| } |
| } |
| |
| // Find unique projects. (We can register projects under multiple paths |
| // if the dir and the canonical dir differ, so pick unique values here) |
| Set<EclipseProject> projects = Sets.newHashSet(mProjectMap.values()); |
| mRootModules = EclipseProject.performImport(this, projects); |
| for (ImportModule module : mRootModules) { |
| mModules.add(module); |
| mModules.addAll(module.getAllDependencies()); |
| } |
| } |
| |
| public static boolean isEclipseProjectDir(@Nullable File file) { |
| return file != null && file.isDirectory() |
| && new File(file, ECLIPSE_DOT_CLASSPATH).exists() |
| && new File(file, ECLIPSE_DOT_PROJECT).exists(); |
| } |
| |
| public static boolean isAdtProjectDir(@Nullable File file) { |
| return new File(file, ANDROID_MANIFEST_XML).exists() && |
| (isEclipseProjectDir(file) || |
| (new File(file, FD_RES).exists() && |
| new File(file, FD_SOURCES).exists())); |
| } |
| |
| /** Sets location of gradle wrapper to copy into exported project, if known */ |
| @NonNull |
| public GradleImport setGradleWrapperLocation(@NonNull File gradleWrapper) { |
| mGradleWrapperLocation = gradleWrapper; |
| return this; |
| } |
| |
| /** Sets location of the SDK to use with the import, if known */ |
| @NonNull |
| public GradleImport setSdkLocation(@Nullable File sdkLocation) { |
| mSdkLocation = sdkLocation; |
| return this; |
| } |
| |
| /** Returns the location of the SDK to use with the import, if known */ |
| @Nullable |
| public File getSdkLocation() { |
| return mSdkLocation; |
| } |
| |
| /** Sets SDK manager to use with the import, if known */ |
| @NonNull |
| public GradleImport setSdkManager(@NonNull SdkManager sdkManager) { |
| mSdkManager = sdkManager; |
| mSdkLocation = new File(sdkManager.getLocation()); |
| return this; |
| } |
| |
| @Nullable |
| public SdkManager getSdkManager() { |
| if (mSdkManager == null && mSdkLocation != null && mSdkLocation.exists()) { |
| ILogger logger = new StdLogger(StdLogger.Level.INFO); |
| mSdkManager = SdkManager.createManager(mSdkLocation.getPath(), logger); |
| } |
| |
| return mSdkManager; |
| } |
| |
| /** Sets location of the SDK to use with the import, if known */ |
| @NonNull |
| public GradleImport setNdkLocation(@Nullable File ndkLocation) { |
| mNdkLocation = ndkLocation; |
| return this; |
| } |
| |
| /** Gets location of the SDK to use with the import, if known */ |
| @Nullable |
| public File getNdkLocation() { |
| return mNdkLocation; |
| } |
| |
| /** Sets location of Eclipse workspace, if known */ |
| public GradleImport setEclipseWorkspace(@NonNull File workspace) { |
| mWorkspaceLocation = workspace; |
| assert mWorkspaceLocation.exists() : workspace.getPath(); |
| mWorkspaceProjects = null; |
| return this; |
| } |
| |
| /** Gets location of Eclipse workspace, if known */ |
| @Nullable |
| public File getEclipseWorkspace() { |
| return mWorkspaceLocation; |
| } |
| |
| /** Whether import should attempt to replace jars with dependencies */ |
| @NonNull |
| public GradleImport setReplaceJars(boolean replaceJars) { |
| mReplaceJars = replaceJars; |
| return this; |
| } |
| |
| /** Whether import should attempt to replace jars with dependencies */ |
| public boolean isReplaceJars() { |
| return mReplaceJars; |
| } |
| |
| /** Whether import should attempt to replace inlined library projects with dependencies */ |
| public boolean isReplaceLibs() { |
| return mReplaceLibs; |
| } |
| |
| /** Whether import should attempt to replace inlined library projects with dependencies */ |
| public GradleImport setReplaceLibs(boolean replaceLibs) { |
| mReplaceLibs = replaceLibs; |
| return this; |
| } |
| |
| /** Whether import should lower-case module names from ADT project names */ |
| @NonNull |
| public GradleImport setGradleNameStyle(boolean lowerCase) { |
| mGradleNameStyle = lowerCase; |
| return this; |
| } |
| |
| /** Whether we're in "import into existing project" mode, or import complete new project |
| * mode (the default) */ |
| public boolean isImportIntoExisting() { |
| return mImportIntoExisting; |
| } |
| |
| /** Sets whether we're in "import into existing project" mode, or import complete new project |
| * mode (the default). When importing into existing projects some different behaviors are |
| * applied; for example, we don't change the module name to "app" when there is just one |
| * project being imported. */ |
| public void setImportIntoExisting(boolean importIntoExisting) { |
| mImportIntoExisting = importIntoExisting; |
| } |
| |
| /** |
| * Returns whether the importer emits the repository definitions in each module's build.gradle |
| * rather than at the top level in the shared build.gradle |
| */ |
| public boolean isPerModuleRepositories() { |
| return mPerModuleRepositories; |
| } |
| |
| /** |
| * Sets whether the importer emits the repository definitions in each module's build.gradle |
| * rather than at the top level in the shared build.gradle |
| */ |
| public void setPerModuleRepositories(boolean perModuleRepositories) { |
| mPerModuleRepositories = perModuleRepositories; |
| } |
| |
| /** Whether import should lower-case module names from ADT project names */ |
| public boolean isGradleNameStyle() { |
| return mGradleNameStyle; |
| } |
| |
| private void guessWorkspace(@NonNull File projectDir) { |
| if (mWorkspaceLocation == null) { |
| File dir = projectDir.getParentFile(); |
| while (dir != null) { |
| if (isEclipseWorkspaceDir(dir)) { |
| setEclipseWorkspace(dir); |
| break; |
| } |
| dir = dir.getParentFile(); |
| } |
| } |
| } |
| |
| private void guessSdk(@NonNull File projectDir) { |
| if (mSdkLocation == null) { |
| mSdkLocation = getDirFromLocalProperties(projectDir, PROPERTY_SDK); |
| |
| if (mSdkLocation == null && mWorkspaceLocation != null) { |
| mSdkLocation = getDirFromWorkspaceSetting(getAdtSettingsFile(), |
| "com.android.ide.eclipse.adt.sdk"); |
| } |
| } |
| } |
| |
| private void guessNdk(@NonNull File projectDir) { |
| if (mNdkLocation == null) { |
| mNdkLocation = getDirFromLocalProperties(projectDir, PROPERTY_NDK); |
| |
| if (mNdkLocation == null && mWorkspaceLocation != null) { |
| mNdkLocation = getDirFromWorkspaceSetting(getNdkSettingsFile(), "ndkLocation"); |
| } |
| } |
| } |
| |
| @Nullable |
| private static File getDirFromLocalProperties(@NonNull File projectDir, |
| @NonNull String property) { |
| File localProperties = new File(projectDir, FN_LOCAL_PROPERTIES); |
| if (localProperties.exists()) { |
| try { |
| Properties properties = getProperties(localProperties); |
| if (properties != null) { |
| String sdk = properties.getProperty(property); |
| if (sdk != null) { |
| File dir = new File(sdk); |
| if (dir.exists()) { |
| return dir; |
| } else { |
| dir = new File(sdk.replace('/', separatorChar)); |
| if (dir.exists()) { |
| return dir; |
| } |
| } |
| } |
| } |
| } catch (IOException e) { |
| // ignore properties |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private File getDirFromWorkspaceSetting(@NonNull File settings, @NonNull String property) { |
| //noinspection VariableNotUsedInsideIf |
| if (mWorkspaceLocation != null) { |
| if (settings.exists()) { |
| try { |
| Properties properties = getProperties(settings); |
| if (properties != null) { |
| String path = properties.getProperty(property); |
| if (path == null) { |
| return null; |
| } |
| File dir = new File(path); |
| if (dir.exists()) { |
| return dir; |
| } else { |
| dir = new File(path.replace('/', separatorChar)); |
| if (dir.exists()) { |
| return dir; |
| } |
| } |
| } |
| } catch (IOException e) { |
| // Ignore workspace data |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| public static boolean isEclipseWorkspaceDir(@NonNull File file) { |
| return file.isDirectory() && |
| new File(file, ".metadata" + separator + "version.ini").exists(); |
| } |
| |
| @Nullable |
| public File resolveWorkspacePath(@Nullable EclipseProject fromProject, @NonNull String path, boolean record) { |
| if (path.isEmpty()) { |
| return null; |
| } |
| |
| // If file within project, must match on all prefixes |
| for (Map.Entry<String,File> entry : mPathMap.entrySet()) { |
| String workspacePath = entry.getKey(); |
| File file = entry.getValue(); |
| if (file != null && path.startsWith(workspacePath)) { |
| if (path.equals(workspacePath)) { |
| return file; |
| } else { |
| path = path.substring(workspacePath.length()); |
| if (path.charAt(0) == '/' || path.charAt(0) == separatorChar) { |
| path = path.substring(1); |
| } |
| File resolved = new File(file, path.replace('/', separatorChar)); |
| if (resolved.exists()) { |
| return resolved; |
| } |
| } |
| } |
| } |
| |
| if (fromProject != null && mWorkspaceLocation == null) { |
| guessWorkspace(fromProject.getDir()); |
| } |
| |
| if (mWorkspaceLocation != null) { |
| // Is the file present directly in the workspace? |
| char first = path.charAt(0); |
| if (first != '/') { |
| return null; |
| } |
| File f = new File(mWorkspaceLocation, path.substring(1).replace('/', separatorChar)); |
| if (f.exists()) { |
| mPathMap.put(path, f); |
| return f; |
| } |
| |
| // Other files may be in other file systems, mapped by a .location link in the |
| // workspace metadata |
| if (mWorkspaceProjects == null) { |
| mWorkspaceProjects = Maps.newHashMap(); |
| File projectDir = new File(mWorkspaceLocation, ".metadata" + separator + ".plugins" |
| + separator + "org.eclipse.core.resources" + separator + ".projects"); |
| File[] projects = projectDir.exists() ? projectDir.listFiles() : null; |
| byte[] target = "URI//file:".getBytes(Charsets.US_ASCII); |
| if (projects != null) { |
| for (File project : projects) { |
| File location = new File(project, ".location"); |
| if (location.exists()) { |
| try { |
| byte[] bytes = Files.toByteArray(location); |
| int start = Bytes.indexOf(bytes, target); |
| if (start != -1) { |
| int end = start + target.length; |
| for (; end < bytes.length; end++) { |
| if (bytes[end] == (byte)0) { |
| break; |
| } |
| } |
| try { |
| int length = end - start; |
| String s = new String(bytes, start, length, UTF_8); |
| s = s.substring(5); // skip URI// |
| File file = SdkUtils.urlToFile(s); |
| if (file.exists()) { |
| String name = project.getName(); |
| mWorkspaceProjects.put('/' + name, file); |
| //noinspection ConstantConditions |
| } |
| } catch (Throwable t) { |
| // Ignore binary data we can't read |
| } |
| } |
| } catch (IOException e) { |
| reportWarning((ImportModule) null, location, |
| "Can't read .location file"); |
| } |
| } |
| } |
| } |
| } |
| |
| // Is it just a project root? |
| File project = mWorkspaceProjects.get(path); |
| if (project != null) { |
| mPathMap.put(path, project); |
| return project; |
| } |
| |
| // If file within project, must match on all prefixes |
| for (Map.Entry<String,File> entry : mWorkspaceProjects.entrySet()) { |
| String workspacePath = entry.getKey(); |
| File file = entry.getValue(); |
| if (file != null && path.startsWith(workspacePath)) { |
| if (path.equals(workspacePath)) { |
| return file; |
| } else { |
| path = path.substring(workspacePath.length()); |
| if (path.charAt(0) == '/' || path.charAt(0) == separatorChar) { |
| path = path.substring(1); |
| } |
| File resolved = new File(file, path.replace('/', separatorChar)); |
| if (resolved.exists()) { |
| return resolved; |
| } |
| } |
| } |
| } |
| |
| // Record path as one we need to resolve |
| if (record) { |
| mPathMap.put(path, null); |
| } |
| } else if (record) { |
| // Record path as one we need to resolve |
| mPathMap.put(path, null); |
| } |
| |
| return null; |
| } |
| |
| public void exportProject(@NonNull File destDir, boolean allowNonEmpty) throws IOException { |
| mSummary.setDestDir(destDir); |
| if (!isImportIntoExisting()) { |
| createDestDir(destDir, allowNonEmpty); |
| createProjectBuildGradle(new File(destDir, FN_BUILD_GRADLE)); |
| |
| exportGradleWrapper(destDir); |
| exportLocalProperties(destDir); |
| } |
| exportSettingsGradle(new File(destDir, FN_SETTINGS_GRADLE), isImportIntoExisting()); |
| for (ImportModule module : getModulesToImport()) { |
| exportModule(new File(destDir, module.getModuleName()), module); |
| } |
| |
| mSummary.write(new File(destDir, IMPORT_SUMMARY_TXT)); |
| } |
| |
| private Iterable<? extends ImportModule> getModulesToImport() { |
| if (mSelectedModules == null) { |
| return mRootModules; |
| } else { |
| return Iterables.filter(mRootModules, new Predicate<ImportModule>() { |
| @Override |
| public boolean apply(ImportModule input) { |
| return mSelectedModules.contains(input.getModuleName()); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Like {@link #exportProject(java.io.File, boolean)}, but writes into an existing |
| * project instead of creating a new one. |
| * <p> |
| * <b>NOTE</b>: When performing an import into an existing project, note that |
| * you should call {@link #setImportIntoExisting(boolean)} before the call to |
| * read in projects ({@link #importProjects(java.util.List)}. Note also that |
| * you should call {@link #setPerModuleRepositories(boolean)} with a suitable |
| * value based on whether the existing project defines shared repositories. |
| * This is similar to how we pass the "perModuleRepositories" variable to |
| * our Freemarker templates (such as |
| * templates/gradle-projects/NewAndroidModule/root/build.gradle.ftl ) so it |
| * can decide whether to include this info in the new module. In Studio we |
| * set it based on whether $PROJECT/build.gradle contains "repositories" (this |
| * is done in NewModuleWizard). |
| * </p> |
| * |
| * @param projectDir the root directory containing the project to write into |
| * @param updateSettings whether the importer should attempt to update the settings.gradle |
| * file in the project or not. Clients such as Android Studio may |
| * wish to pass false here in order to handle this part |
| * @param writeSummary whether we should generate an import summary |
| * @param destDirMap optional map from ADT project dir to destination directory to |
| * write each module as. |
| * @return the list of imported module directories |
| */ |
| @NonNull |
| public List<File> exportIntoProject(@NonNull File projectDir, boolean updateSettings, |
| boolean writeSummary, @Nullable Map<File,File> destDirMap) throws IOException { |
| mSummary.setDestDir(projectDir); |
| |
| List<File> imported = Lists.newArrayListWithExpectedSize(mRootModules.size()); |
| for (ImportModule module : getModulesToImport()) { |
| File moduleDir = null; |
| if (destDirMap != null) { |
| moduleDir = destDirMap.get(module.getDir()); |
| } |
| if (moduleDir == null) { |
| moduleDir = new File(projectDir, module.getModuleName()); |
| if (moduleDir.exists()) { |
| module.pickUniqueName(projectDir); |
| moduleDir = new File(projectDir, module.getModuleName()); |
| assert !moduleDir.exists(); |
| } |
| } |
| exportModule(moduleDir, module); |
| imported.add(moduleDir); |
| } |
| |
| if (updateSettings) { |
| exportSettingsGradle(new File(projectDir, FN_SETTINGS_GRADLE), true); |
| } |
| |
| if (writeSummary) { |
| mSummary.write(new File(projectDir, IMPORT_SUMMARY_TXT)); |
| } |
| |
| return imported; |
| } |
| |
| private void exportGradleWrapper(@NonNull File destDir) throws IOException { |
| if (mGradleWrapperLocation != null && mGradleWrapperLocation.exists()) { |
| File gradlewDest = new File(destDir, FN_GRADLE_WRAPPER_UNIX); |
| copyDir(new File(mGradleWrapperLocation, FN_GRADLE_WRAPPER_UNIX), gradlewDest, null); |
| boolean madeExecutable = gradlewDest.setExecutable(true); |
| if (!madeExecutable) { |
| reportWarning((ImportModule) null, gradlewDest, |
| "Could not make gradle wrapper script executable"); |
| } |
| copyDir(new File(mGradleWrapperLocation, FN_GRADLE_WRAPPER_WIN), |
| new File(destDir, FN_GRADLE_WRAPPER_WIN), null); |
| copyDir(new File(mGradleWrapperLocation, FD_GRADLE), new File(destDir, FD_GRADLE), |
| null); |
| } |
| } |
| |
| // Write local.properties file |
| private void exportLocalProperties(@NonNull File destDir) throws IOException { |
| boolean needsNdk = needsNdk(); |
| if (mNdkLocation != null && needsNdk || mSdkLocation != null) { |
| Properties properties = new Properties(); |
| if (mSdkLocation != null) { |
| properties.setProperty(PROPERTY_SDK, mSdkLocation.getPath()); |
| } |
| if (mNdkLocation != null && needsNdk) { |
| properties.setProperty(PROPERTY_NDK, mNdkLocation.getPath()); |
| } |
| |
| FileOutputStream out = null; |
| try { |
| //noinspection IOResourceOpenedButNotSafelyClosed |
| out = new FileOutputStream(new File(destDir, FN_LOCAL_PROPERTIES)); |
| properties.store(out, |
| "# This file must *NOT* be checked into Version Control Systems,\n" + |
| "# as it contains information specific to your local configuration.\n" + |
| "\n" + |
| "# Location of the SDK. This is only used by Gradle.\n"); |
| } finally { |
| Closeables.close(out, true); |
| } |
| } |
| } |
| |
| /** Returns true if this project appears to need the NDK */ |
| public boolean needsNdk() { |
| for (ImportModule module : mModules) { |
| if (module.isNdkProject()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private void exportModule(File destDir, ImportModule module) throws IOException { |
| mkdirs(destDir); |
| createModuleBuildGradle(new File(destDir, FN_BUILD_GRADLE), module); |
| module.copyInto(destDir); |
| } |
| |
| @SuppressWarnings("MethodMayBeStatic") |
| /** Ensure that the given directory exists, and if it can't be created, report an I/O error */ |
| public void mkdirs(@NonNull File destDir) throws IOException { |
| if (!destDir.exists()) { |
| boolean ok = destDir.mkdirs(); |
| if (!ok) { |
| reportError(null, destDir, "Could not make directory " + destDir); |
| } |
| } |
| } |
| |
| private void createModuleBuildGradle(@NonNull File file, ImportModule module) |
| throws IOException { |
| StringBuilder sb = new StringBuilder(500); |
| |
| if (module.isApp() || module.isAndroidLibrary()) { |
| //noinspection PointlessBooleanExpression,ConstantConditions |
| if (mPerModuleRepositories) { |
| appendRepositories(sb, true); |
| } |
| |
| if (module.isApp()) { |
| sb.append("apply plugin: 'android'").append(NL); |
| } else { |
| assert module.isAndroidLibrary(); |
| sb.append("apply plugin: 'android-library'").append(NL); |
| } |
| sb.append(NL); |
| //noinspection PointlessBooleanExpression,ConstantConditions |
| if (mPerModuleRepositories) { |
| sb.append("repositories {").append(NL); |
| sb.append(" ").append(MAVEN_REPOSITORY).append(NL); |
| sb.append("}").append(NL); |
| sb.append(NL); |
| } |
| sb.append("android {").append(NL); |
| String compileSdkVersion = Integer.toString(module.getCompileSdkVersion()); |
| int minSdkVersion = module.getMinSdkVersion(); |
| int targetSdkVersion = module.getTargetSdkVersion(); |
| String minSdkVersionString = Integer.toString(minSdkVersion); |
| String targetSdkVersionString = Integer.toString(targetSdkVersion); |
| sb.append(" compileSdkVersion ").append(compileSdkVersion).append(NL); |
| sb.append(" buildToolsVersion \"").append(getBuildToolsVersion()).append("\"") |
| .append(NL); |
| sb.append(NL); |
| sb.append(" defaultConfig {").append(NL); |
| if (module.getPackage() != null) { |
| sb.append(" applicationId \"").append(module.getPackage()).append('"') |
| .append(NL); |
| } |
| if (minSdkVersion >= 1) { |
| sb.append(" minSdkVersion ").append(minSdkVersionString).append(NL); |
| } |
| if (targetSdkVersion > 1 && module.getCompileSdkVersion() > 3) { |
| sb.append(" targetSdkVersion ").append(targetSdkVersionString).append(NL); |
| } |
| |
| String languageLevel = module.getLanguageLevel(); |
| if (!languageLevel.equals(EclipseProject.DEFAULT_LANGUAGE_LEVEL)) { |
| sb.append(" compileOptions {").append(NL); |
| String level = languageLevel.replace('.','_'); // 1.6 => 1_6 |
| sb.append(" sourceCompatibility JavaVersion.VERSION_").append(level) |
| .append(NL); |
| sb.append(" targetCompatibility JavaVersion.VERSION_").append(level) |
| .append(NL); |
| sb.append(" }").append(NL); |
| } |
| |
| if (module.isNdkProject() && module.getNativeModuleName() != null) { |
| sb.append(NL); |
| sb.append(" ndk {").append(NL); |
| sb.append(" moduleName \"").append(module.getNativeModuleName()) |
| .append("\"").append(NL); |
| sb.append(" }").append(NL); |
| } |
| |
| if (module.getInstrumentationDir() != null) { |
| sb.append(NL); |
| File manifestFile = new File(module.getInstrumentationDir(), ANDROID_MANIFEST_XML); |
| assert manifestFile.exists() : manifestFile; |
| Document manifest = getXmlDocument(manifestFile, true); |
| if (manifest != null && manifest.getDocumentElement() != null) { |
| String pkg = manifest.getDocumentElement().getAttribute(ATTR_PACKAGE); |
| if (pkg != null && !pkg.isEmpty()) { |
| sb.append(" testApplicationId \"").append(pkg).append("\"") |
| .append(NL); |
| } |
| NodeList list = manifest.getElementsByTagName(NODE_INSTRUMENTATION); |
| if (list.getLength() > 0) { |
| Element tag = (Element) list.item(0); |
| String runner = tag.getAttributeNS(ANDROID_URI, ATTR_NAME); |
| if (runner != null && !runner.isEmpty()) { |
| sb.append(" testInstrumentationRunner \"").append(runner) |
| .append("\"").append(NL); |
| } |
| Attr attr = tag.getAttributeNodeNS(ANDROID_URI, "functionalTest"); |
| if (attr != null) { |
| sb.append(" testFunctionalTest ").append(attr.getValue()) |
| .append(NL); |
| } |
| attr = tag.getAttributeNodeNS(ANDROID_URI, "handleProfiling"); |
| if (attr != null) { |
| sb.append(" testHandlingProfiling ").append(attr.getValue()) |
| .append(NL); |
| } |
| } |
| } |
| } |
| |
| sb.append(" }").append(NL); |
| sb.append(NL); |
| |
| List<File> localRules = module.getLocalProguardFiles(); |
| List<File> sdkRules = module.getSdkProguardFiles(); |
| if (!localRules.isEmpty() || !sdkRules.isEmpty()) { |
| // User specified ProGuard rules; replicate exactly |
| sb.append(" buildTypes {").append(NL); |
| sb.append(" release {").append(NL); |
| sb.append(" runProguard true").append(NL); |
| sb.append(" proguardFiles "); |
| sb.append(generateProguardFileList(localRules, sdkRules)).append(NL); |
| sb.append(" }").append(NL); |
| sb.append(" }").append(NL); |
| } else { |
| // User didn't specify ProGuard rules; put in defaults (but off) |
| sb.append(" buildTypes {").append(NL); |
| sb.append(" release {").append(NL); |
| sb.append(" runProguard false").append(NL); |
| sb.append(" proguardFiles getDefaultProguardFile('proguard-" |
| + "android.txt'), 'proguard-rules.txt'").append(NL); |
| sb.append(" }").append(NL); |
| sb.append(" }").append(NL); |
| } |
| sb.append("}").append(NL); |
| appendDependencies(sb, module); |
| |
| } else if (module.isJavaLibrary()) { |
| //noinspection PointlessBooleanExpression,ConstantConditions |
| if (mPerModuleRepositories) { |
| appendRepositories(sb, false); |
| } |
| |
| sb.append("apply plugin: 'java'").append(NL); |
| |
| String languageLevel = module.getLanguageLevel(); |
| if (!languageLevel.equals(EclipseProject.DEFAULT_LANGUAGE_LEVEL)) { |
| sb.append(NL); |
| sb.append("sourceCompatibility = \""); |
| sb.append(languageLevel); |
| sb.append("\"").append(NL); |
| sb.append("targetCompatibility = \""); |
| sb.append(languageLevel); |
| sb.append("\"").append(NL); |
| } |
| |
| appendDependencies(sb, module); |
| } else { |
| assert false : module; |
| } |
| |
| Files.write(sb.toString(), file, UTF_8); |
| } |
| |
| String getBuildToolsVersion() { |
| SdkManager sdkManager = getSdkManager(); |
| if (sdkManager != null) { |
| final BuildToolInfo buildTool = sdkManager.getLatestBuildTool(); |
| if (buildTool != null) { |
| return buildTool.getRevision().toString(); |
| } |
| } |
| |
| return CURRENT_BUILD_TOOLS_VERSION; |
| } |
| |
| private static String generateProguardFileList(List<File> localRules, List<File> sdkRules) { |
| assert !localRules.isEmpty() || !sdkRules.isEmpty(); |
| StringBuilder sb = new StringBuilder(); |
| for (File rule : sdkRules) { |
| if (sb.length() > 0) { |
| sb.append(", "); |
| } |
| sb.append("getDefaultProguardFile('"); |
| sb.append(escapeGroovyStringLiteral(rule.getName())); |
| sb.append("')"); |
| } |
| |
| for (File rule : localRules) { |
| if (sb.length() > 0) { |
| sb.append(", "); |
| } |
| sb.append("'"); |
| // Note: project config files are flattened into the module structure (see |
| // ImportModule#copyInto handler) |
| sb.append(escapeGroovyStringLiteral(rule.getName())); |
| sb.append("'"); |
| } |
| |
| return sb.toString(); |
| } |
| |
| private static void appendDependencies(@NonNull StringBuilder sb, |
| @NonNull ImportModule module) |
| throws IOException { |
| if (!module.getDirectDependencies().isEmpty() |
| || !module.getDependencies().isEmpty() |
| || !module.getJarDependencies().isEmpty() |
| || !module.getTestDependencies().isEmpty() |
| || !module.getTestJarDependencies().isEmpty()) { |
| sb.append(NL); |
| sb.append("dependencies {").append(NL); |
| for (ImportModule lib : module.getDirectDependencies()) { |
| if (lib.isReplacedWithDependency()) { |
| continue; |
| } |
| sb.append(" compile project('").append(lib.getModuleReference()).append("')") |
| .append(NL); |
| } |
| for (GradleCoordinate dependency : module.getDependencies()) { |
| sb.append(" compile '").append(dependency.toString()).append("'").append(NL); |
| } |
| for (File jar : module.getJarDependencies()) { |
| String path = jar.getPath().replace(separatorChar, '/'); // Always / in gradle |
| sb.append(" compile files('").append(escapeGroovyStringLiteral(path)) |
| .append("')").append(NL); |
| } |
| for (GradleCoordinate dependency : module.getTestDependencies()) { |
| sb.append(" androidTestCompile '").append(dependency.toString()).append("'") |
| .append(NL); |
| } |
| for (File jar : module.getTestJarDependencies()) { |
| String path = jar.getPath().replace(separatorChar, '/'); |
| sb.append(" androidTestCompile files('") |
| .append(escapeGroovyStringLiteral(path)).append("')").append(NL); |
| } |
| sb.append("}").append(NL); |
| } |
| } |
| |
| private static String escapeGroovyStringLiteral(String s) { |
| StringBuilder sb = new StringBuilder(s.length() + 5); |
| for (int i = 0, n = s.length(); i < n; i++) { |
| char c = s.charAt(i); |
| if (c == '\\' || c == '\'') { |
| sb.append('\\'); |
| } |
| sb.append(c); |
| } |
| return sb.toString(); |
| } |
| |
| private void appendRepositories(@NonNull StringBuilder sb, boolean needAndroidPlugin) { |
| //noinspection PointlessBooleanExpression,ConstantConditions |
| if (mPerModuleRepositories) { |
| sb.append("buildscript {").append(NL); |
| sb.append(" repositories {").append(NL); |
| sb.append(" ").append(MAVEN_REPOSITORY).append(NL); |
| sb.append(" }").append(NL); |
| if (needAndroidPlugin) { |
| sb.append(" dependencies {").append(NL); |
| sb.append(" classpath '" + ANDROID_GRADLE_PLUGIN + "'").append(NL); |
| sb.append(" }").append(NL); |
| } |
| sb.append("}").append(NL); |
| } |
| } |
| |
| private void createProjectBuildGradle(@NonNull File file) throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| sb.append( |
| "// Top-level build file where you can add configuration options common to all sub-projects/modules."); |
| |
| //noinspection PointlessBooleanExpression,ConstantConditions |
| if (!mPerModuleRepositories) { |
| sb.append(NL); |
| sb.append("buildscript {").append(NL); |
| sb.append(" repositories {").append(NL); |
| sb.append(" ").append(MAVEN_REPOSITORY).append(NL); |
| sb.append(" }").append(NL); |
| sb.append(" dependencies {").append(NL); |
| sb.append(" classpath '" + ANDROID_GRADLE_PLUGIN + "'").append(NL); |
| sb.append(" }").append(NL); |
| sb.append("}").append(NL); |
| sb.append(NL); |
| sb.append("allprojects {").append(NL); |
| sb.append(" repositories {").append(NL); |
| sb.append(" ").append(MAVEN_REPOSITORY).append(NL); |
| sb.append(" }").append(NL); |
| sb.append("}"); |
| } |
| sb.append(NL); |
| Files.write(sb.toString(), file, UTF_8); |
| } |
| |
| private void exportSettingsGradle(@NonNull File file, boolean append) throws IOException { |
| StringBuilder sb = new StringBuilder(); |
| if (append) { |
| if (!file.exists()) { |
| append = false; |
| } else { |
| // Ensure that the new include statements are separate code statements, not |
| // for example inserted at the end of a // line comment |
| String existing = Files.toString(file, UTF_8); |
| if (!existing.endsWith(NL)) { |
| sb.append(NL); |
| } |
| } |
| } |
| |
| for (ImportModule module : getModulesToImport()) { |
| sb.append("include '"); |
| sb.append(module.getModuleReference()); |
| sb.append("'"); |
| sb.append(NL); |
| } |
| |
| String code = sb.toString(); |
| if (append) { |
| Files.append(code, file, UTF_8); |
| } else { |
| Files.write(code, file, UTF_8); |
| } |
| } |
| |
| private void createDestDir(@NonNull File destDir, boolean allowNonEmpty) throws IOException { |
| if (destDir.exists()) { |
| if (!allowNonEmpty) { |
| File[] files = destDir.listFiles(); |
| if (files != null && files.length > 0) { |
| throw new IOException("Destination directory " + destDir + " should be empty"); |
| } |
| } |
| } else { |
| mkdirs(destDir); |
| } |
| } |
| |
| @NonNull |
| public List<String> getWarnings() { |
| return mWarnings; |
| } |
| |
| @NonNull |
| public List<String> getErrors() { |
| return mErrors; |
| } |
| |
| /** |
| * Returns module names to module source locations mappings. |
| */ |
| @NonNull |
| public Map<String, File> getDetectedModuleLocations() { |
| TreeMap<String, File> modules = new TreeMap<String, File>(); |
| for (ImportModule module : mModules) { |
| modules.put(module.getModuleName(), module.getCanonicalModuleDir()); |
| } |
| return modules; |
| } |
| |
| public void setModulesToImport(Map<String, File> modules) { |
| mSelectedModules = ImmutableSet.copyOf(modules.keySet()); |
| } |
| |
| private static class ImportException extends RuntimeException { |
| private String mMessage; |
| |
| private ImportException(@NonNull String message) { |
| mMessage = message; |
| } |
| |
| @Override |
| public String getMessage() { |
| return mMessage; |
| } |
| |
| @Override |
| public String toString() { |
| return getMessage(); |
| } |
| } |
| |
| @SuppressWarnings("MethodMayBeStatic") |
| public void reportError( |
| @Nullable EclipseProject project, |
| @Nullable File file, |
| @NonNull String message) { |
| reportError(project, file, message, true); |
| } |
| |
| public void reportError( |
| @Nullable EclipseProject project, |
| @Nullable File file, |
| @NonNull String message, |
| boolean abort) { |
| String text = formatMessage(project != null ? project.getName() : null, file, message); |
| mErrors.add(text); |
| if (abort) { |
| throw new ImportException(text); |
| } |
| } |
| |
| public void reportWarning( |
| @Nullable ImportModule module, |
| @Nullable File file, |
| @NonNull String message) { |
| String moduleName = module != null ? module.getOriginalName() : null; |
| mWarnings.add(formatMessage(moduleName, file, message)); |
| } |
| |
| public void reportWarning( |
| @Nullable EclipseProject project, |
| @Nullable File file, |
| @NonNull String message) { |
| String moduleName = project != null ? project.getName() : null; |
| mWarnings.add(formatMessage(moduleName, file, message)); |
| } |
| |
| private static String formatMessage( |
| @Nullable String project, |
| @Nullable File file, |
| @NonNull String message) { |
| StringBuilder sb = new StringBuilder(); |
| if (project != null) { |
| sb.append("Project ").append(project).append(":"); |
| } |
| if (file != null) { |
| sb.append(file.getPath()); |
| sb.append(":\n"); |
| } |
| |
| sb.append(message); |
| |
| return sb.toString(); |
| } |
| |
| @Nullable |
| File resolvePathVariable(@Nullable EclipseProject fromProject, @NonNull String name, boolean record) throws IOException { |
| File file = mPathMap.get(name); |
| if (file != null) { |
| return file; |
| } |
| |
| if (fromProject != null && mWorkspaceLocation == null) { |
| guessWorkspace(fromProject.getDir()); |
| } |
| |
| String value = null; |
| Properties properties = getJdtSettingsProperties(false); |
| if (properties != null) { |
| value = properties.getProperty("org.eclipse.jdt.core.classpathVariable." + name); |
| } |
| if (value == null) { |
| properties = getPathSettingsProperties(false); |
| if (properties != null) { |
| value = properties.getProperty("pathvariable." + name); |
| } |
| } |
| |
| if (value == null) { |
| if (record) { |
| mPathMap.put(name, null); |
| } |
| return null; |
| } |
| |
| file = new File(value.replace('/', separatorChar)); |
| |
| return file; |
| } |
| |
| @Nullable |
| private Properties getJdtSettingsProperties(boolean mustExist) throws IOException { |
| File settings = getJdtSettingsFile(); |
| if (!settings.exists()) { |
| if (mustExist) { |
| reportError(null, settings, "Settings file does not exist"); |
| } |
| return null; |
| } |
| |
| return getProperties(settings); |
| } |
| |
| private File getRuntimeSettingsDir() { |
| return new File(getWorkspaceLocation(), |
| ".metadata" + separator + |
| ".plugins" + separator + |
| "org.eclipse.core.runtime" + separator + |
| ".settings"); |
| } |
| |
| private File getJdtSettingsFile() { |
| return new File(getRuntimeSettingsDir(), "org.eclipse.jdt.core.prefs"); |
| } |
| |
| private File getPathSettingsFile() { |
| return new File(getRuntimeSettingsDir(), "org.eclipse.core.resources.prefs"); |
| } |
| |
| private File getNdkSettingsFile() { |
| return new File(getRuntimeSettingsDir(), "com.android.ide.eclipse.ndk.prefs"); |
| } |
| |
| private File getAdtSettingsFile() { |
| return new File(getRuntimeSettingsDir(), "com.android.ide.eclipse.adt.prefs"); |
| } |
| |
| @Nullable |
| private Properties getPathSettingsProperties(boolean mustExist) throws IOException { |
| File settings = getPathSettingsFile(); |
| if (!settings.exists()) { |
| if (mustExist) { |
| reportError(null, settings, "Settings file does not exist"); |
| } |
| return null; |
| } |
| |
| return getProperties(settings); |
| } |
| |
| private File getWorkspaceLocation() { |
| return mWorkspaceLocation; |
| } |
| |
| Document getXmlDocument(File file, boolean namespaceAware) throws IOException { |
| String xml = Files.toString(file, UTF_8); |
| try { |
| return XmlUtils.parseDocument(xml, namespaceAware); |
| } catch (Exception e) { |
| reportError(null, file, "Invalid XML file: " + file.getPath() + ":\n" |
| + e.getMessage()); |
| return null; |
| } |
| } |
| |
| static Properties getProperties(File file) throws IOException { |
| Properties properties = new Properties(); |
| InputStreamReader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), Charsets.UTF_8); |
| properties.load(reader); |
| Closeables.close(reader, true); |
| return properties; |
| } |
| |
| private Map<File, EclipseProject> mProjectMap = Maps.newHashMap(); |
| |
| Map<File, EclipseProject> getProjectMap() { |
| return mProjectMap; |
| } |
| |
| public ImportSummary getSummary() { |
| return mSummary; |
| } |
| |
| void registerProject(@NonNull EclipseProject project) { |
| // Register not just this directory but the canonical versions too, since library |
| // references in project.properties can be relative and can be made canonical; |
| // we want to make sure that a project known by any of these versions of the paths |
| // are treated as the same |
| mProjectMap.put(project.getDir(), project); |
| mProjectMap.put(project.getDir().getAbsoluteFile(), project); |
| mProjectMap.put(project.getCanonicalDir(), project); |
| } |
| |
| int getModuleCount() { |
| int moduleCount = 0; |
| for (ImportModule module : mModules) { |
| if (!module.isReplacedWithDependency()) { |
| moduleCount++; |
| } |
| } |
| return moduleCount; |
| } |
| |
| /** Returns a path map for workspace paths */ |
| public Map<String, File> getPathMap() { |
| return mPathMap; |
| } |
| |
| /** Interface used by the {@link #copyDir(java.io.File, java.io.File, CopyHandler)} handler */ |
| public interface CopyHandler { |
| /** |
| * Optionally handle the given file; returns true if the file has been |
| * handled |
| */ |
| boolean handle(@NonNull File source, @NonNull File dest) throws IOException; |
| } |
| |
| /** |
| * Handles copying the given source into the given destination, whether the source |
| * is a file or directory. An optional handler can be used to perform special handling, |
| * such as skipping files or changing the destination. |
| */ |
| public void copyDir(@NonNull File source, @NonNull File dest, @Nullable CopyHandler handler) |
| throws IOException { |
| if (handler != null && handler.handle(source, dest)) { |
| return; |
| } |
| if (source.isDirectory()) { |
| if (isIgnoredFile(source)) { |
| // Skip version control files when generating the migrated project; |
| // it will only have fragments of the project, and in some cases moved |
| // around, so don't pick up partial VCS state |
| return; |
| } |
| |
| mkdirs(dest); |
| File[] files = source.listFiles(); |
| if (files != null) { |
| for (File child : files) { |
| copyDir(child, new File(dest, child.getName()), handler); |
| } |
| } |
| } else { |
| Files.copy(source, dest); |
| } |
| } |
| |
| /** |
| * Returns true if the given file should be ignored (note: this may not return |
| * true for files inside ignored folders, so to determine if a given file should |
| * really be ignored you should check all ancestors as well, or only call this as |
| * part of a recursive directory traversal) |
| */ |
| static boolean isIgnoredFile(File file) { |
| String name = file.getName(); |
| return name.equals(".svn") || name.equals(".git") || name.equals(".hg") |
| || name.equals(".DS_Store") || name.endsWith("~") && name.length() > 1; |
| } |
| |
| /** Computes the relative path for the given file inside another directory */ |
| @Nullable |
| public static File computeRelativePath(@NonNull File canonicalBase, @NonNull File file) |
| throws IOException { |
| File canonical = file.getCanonicalFile(); |
| String canonicalPath = canonical.getPath(); |
| if (canonicalPath.startsWith(canonicalBase.getPath())) { |
| int length = canonicalBase.getPath().length(); |
| if (canonicalPath.length() == length) { |
| return new File("."); |
| } else if (canonicalPath.charAt(length) == separatorChar) { |
| return new File(canonicalPath.substring(length + 1)); |
| } else { |
| return new File(canonicalPath.substring(length)); |
| } |
| } |
| |
| return null; |
| } |
| |
| void markJarHandled(@NonNull File file) { |
| mHandledJars.add(file.getName()); |
| } |
| |
| boolean isJarHandled(@NonNull File file) { |
| return mHandledJars.contains(file.getName()); |
| } |
| |
| private boolean haveLocalRepository(String vendor) { |
| SdkManager sdkManager = getSdkManager(); |
| if (sdkManager != null) { |
| LocalSdk localSdk = sdkManager.getLocalSdk(); |
| LocalPkgInfo[] infos = localSdk.getPkgsInfos(PkgType.PKG_EXTRA); |
| for (LocalPkgInfo info : infos) { |
| IPkgDesc d = info.getDesc(); |
| //noinspection ConstantConditions,ConstantConditions |
| if (d.hasVendor() && vendor.equals(d.getVendor().getId()) && |
| d.hasPath() && "m2repository".equals(d.getPath())) { |
| return true; |
| } |
| } |
| } |
| |
| if (mSdkLocation != null) { |
| File repository = new File(mSdkLocation, |
| FD_EXTRAS + separator + vendor + separator + "m2repository"); |
| return repository.exists(); |
| } |
| |
| return false; |
| } |
| |
| public boolean needSupportRepository() { |
| return haveArtifact("com.android.support"); |
| } |
| |
| public boolean needGoogleRepository() { |
| return haveArtifact("com.google.android.gms"); |
| } |
| |
| private boolean haveArtifact(String groupId) { |
| for (ImportModule module : getModulesToImport()) { |
| for (GradleCoordinate dependency : module.getDependencies()) { |
| if (groupId.equals(dependency.getGroupId())) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| public boolean isMissingSupportRepository() { |
| return !haveLocalRepository("android"); |
| } |
| |
| public boolean isMissingGoogleRepository() { |
| return !haveLocalRepository("google"); |
| } |
| } |