| /* |
| * Copyright 2000-2012 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.android.maven; |
| |
| import com.android.SdkConstants; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.tools.idea.gradle.service.notification.hyperlink.CustomNotificationListener; |
| import com.android.tools.idea.gradle.service.notification.hyperlink.OpenAndroidSdkManagerHyperlink; |
| import com.intellij.facet.FacetType; |
| import com.intellij.ide.highlighter.ModuleFileType; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.ModifiableModuleModel; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleManager; |
| import com.intellij.openapi.module.StdModuleTypes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.projectRoots.SdkModificator; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.ui.OrderRoot; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.util.io.FileUtilRt; |
| import com.intellij.openapi.vfs.*; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.PathUtil; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.io.ZipUtil; |
| import org.jdom.Element; |
| import org.jetbrains.android.compiler.AndroidCompileUtil; |
| import org.jetbrains.android.compiler.AndroidDexCompilerConfiguration; |
| import org.jetbrains.android.facet.AndroidFacet; |
| import org.jetbrains.android.facet.AndroidFacetConfiguration; |
| import org.jetbrains.android.facet.AndroidFacetType; |
| import org.jetbrains.android.facet.AndroidRootUtil; |
| import org.jetbrains.android.sdk.*; |
| import org.jetbrains.android.util.AndroidNativeLibData; |
| import org.jetbrains.android.util.AndroidUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.idea.maven.importing.FacetImporter; |
| import org.jetbrains.idea.maven.importing.MavenModifiableModelsProvider; |
| import org.jetbrains.idea.maven.importing.MavenModuleImporter; |
| import org.jetbrains.idea.maven.importing.MavenRootModelAdapter; |
| import org.jetbrains.idea.maven.model.MavenArtifact; |
| import org.jetbrains.idea.maven.model.MavenConstants; |
| import org.jetbrains.idea.maven.model.MavenId; |
| import org.jetbrains.idea.maven.project.*; |
| import org.jetbrains.idea.maven.server.MavenEmbedderWrapper; |
| import org.jetbrains.idea.maven.server.NativeMavenProjectHolder; |
| import org.jetbrains.idea.maven.utils.MavenProcessCanceledException; |
| import org.jetbrains.idea.maven.utils.MavenProgressIndicator; |
| import org.jetbrains.jps.android.model.impl.AndroidImportableProperty; |
| import org.jetbrains.jps.util.JpsPathUtil; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.*; |
| |
| import static org.jetbrains.android.sdk.AndroidSdkUtils.getAndroidSdkAdditionalData; |
| import static org.jetbrains.android.sdk.AndroidSdkUtils.isAndroidSdk; |
| |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFacet, AndroidFacetConfiguration, AndroidFacetType> { |
| private static final String DEX_CORE_LIBRARY_PROPERTY = "dexCoreLibrary"; |
| |
| public static volatile String ANDROID_SDK_PATH_TEST = null; |
| |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.maven.AndroidFacetImporterBase"); |
| |
| private static final Key<Boolean> MODULE_IMPORTED = Key.create("ANDROID_NEWLY_CREATED_KEY"); |
| @NonNls private static final String DEFAULT_NATIVE_ARCHITECTURE = "armeabi"; |
| |
| private static final Key<Boolean> DELETE_OBSOLETE_MODULE_TASK_KEY = Key.create("DELETE_OBSOLETE_MODULE_TASK"); |
| private static final Key<Set<MavenId>> RESOLVED_APKLIB_ARTIFACTS_KEY = Key.create("RESOLVED_APKLIB_ARTIFACTS"); |
| private static final Key<Map<MavenId, String>> IMPORTED_AAR_ARTIFACTS = Key.create("IMPORTED_AAR_ARTIFACTS"); |
| |
| public AndroidFacetImporterBase(@NotNull String groupId, @NotNull String pluginId) { |
| super(groupId, pluginId, FacetType.findInstance(AndroidFacetType.class)); |
| } |
| |
| @Override |
| public boolean isApplicable(MavenProject mavenProject) { |
| return ArrayUtil.find(getSupportedPackagingTypes(), mavenProject.getPackaging()) >= 0 && |
| super.isApplicable(mavenProject); |
| } |
| |
| |
| @Override |
| public void getSupportedPackagings(Collection<String> result) { |
| result.addAll(Arrays.asList(getSupportedPackagingTypes())); |
| } |
| |
| @NotNull |
| private static String[] getSupportedPackagingTypes() { |
| return new String[]{AndroidMavenUtil.APK_PACKAGING_TYPE, AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE, |
| AndroidMavenUtil.AAR_DEPENDENCY_AND_PACKAGING_TYPE}; |
| } |
| |
| @Override |
| public void getSupportedDependencyTypes(Collection<String> result, SupportedRequestType type) { |
| result.add(AndroidMavenUtil.APKSOURCES_DEPENDENCY_TYPE); |
| result.add(AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE); |
| result.add(AndroidMavenUtil.AAR_DEPENDENCY_AND_PACKAGING_TYPE); |
| } |
| |
| @Override |
| protected void setupFacet(AndroidFacet facet, MavenProject mavenProject) { |
| String mavenProjectDirPath = FileUtil.toSystemIndependentName(mavenProject.getDirectory()); |
| facet.getConfiguration().init(facet.getModule(), mavenProjectDirPath); |
| AndroidMavenProviderImpl.setPathsToDefault(mavenProject, facet.getModule(), facet.getConfiguration()); |
| |
| final boolean hasApkSources = AndroidMavenProviderImpl.hasApkSourcesDependency(mavenProject); |
| AndroidMavenProviderImpl.configureAaptCompilation(mavenProject, facet.getModule(), facet.getConfiguration(), hasApkSources); |
| final String packaging = mavenProject.getPackaging(); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(packaging) || |
| AndroidMavenUtil.AAR_DEPENDENCY_AND_PACKAGING_TYPE.equals(packaging)) { |
| facet.setLibraryProject(true); |
| } |
| facet.getConfiguration().setIncludeAssetsFromLibraries(true); |
| |
| if (hasApkSources) { |
| AndroidUtils.reportImportErrorToEventLog("'apksources' dependency is deprecated and can be poorly supported by IDE. " + |
| "It is strongly recommended to use 'apklib' dependency instead.", |
| facet.getModule().getName(), facet.getModule().getProject()); |
| } |
| |
| if (Boolean.parseBoolean(findConfigValue(mavenProject, DEX_CORE_LIBRARY_PROPERTY))) { |
| AndroidDexCompilerConfiguration.getInstance(facet.getModule().getProject()).CORE_LIBRARY = true; |
| } |
| } |
| |
| @Override |
| protected void reimportFacet(MavenModifiableModelsProvider modelsProvider, |
| Module module, |
| MavenRootModelAdapter rootModel, |
| AndroidFacet facet, |
| MavenProjectsTree mavenTree, |
| MavenProject mavenProject, |
| MavenProjectChanges changes, |
| Map<MavenProject, String> mavenProjectToModuleName, |
| List<MavenProjectsProcessorTask> postTasks) { |
| configurePaths(facet, mavenProject); |
| facet.getProperties().ENABLE_MANIFEST_MERGING = Boolean.parseBoolean(findConfigValue(mavenProject, "mergeManifests")); |
| facet.getProperties().COMPILE_CUSTOM_GENERATED_SOURCES = false; |
| |
| configureAndroidPlatform(facet, mavenProject, modelsProvider); |
| final Project project = module.getProject(); |
| importExternalAndroidLibDependencies(project, rootModel, modelsProvider, mavenTree, mavenProject, mavenProjectToModuleName, |
| postTasks); |
| |
| if (hasAndroidLibDependencies(mavenProject) && |
| MavenProjectsManager.getInstance(project).getImportingSettings().isUseMavenOutput()) { |
| // IDEA's apklibs building model is different from Maven's one, so we cannot use the same |
| rootModel.useModuleOutput(mavenProject.getBuildDirectory() + "/idea-classes", |
| mavenProject.getBuildDirectory() + "/idea-test-classes"); |
| } |
| project.putUserData(DELETE_OBSOLETE_MODULE_TASK_KEY, Boolean.TRUE); |
| postTasks.add(new MyDeleteObsoleteApklibModulesTask(project)); |
| |
| // exclude folders where Maven generates sources if gen source roots were changed by user manually |
| final AndroidFacetConfiguration defaultConfig = new AndroidFacetConfiguration(); |
| AndroidMavenProviderImpl.setPathsToDefault(mavenProject, module, defaultConfig); |
| |
| if (!defaultConfig.getState().GEN_FOLDER_RELATIVE_PATH_APT.equals( |
| facet.getProperties().GEN_FOLDER_RELATIVE_PATH_APT)) { |
| final String rPath = mavenProject.getGeneratedSourcesDirectory(false) + "/r"; |
| rootModel.unregisterAll(rPath, false, true); |
| rootModel.addExcludedFolder(rPath); |
| } |
| |
| if (!defaultConfig.getState().GEN_FOLDER_RELATIVE_PATH_AIDL.equals( |
| facet.getProperties().GEN_FOLDER_RELATIVE_PATH_AIDL)) { |
| final String aidlPath = mavenProject.getGeneratedSourcesDirectory(false) + "/aidl"; |
| rootModel.unregisterAll(aidlPath, false, true); |
| rootModel.addExcludedFolder(aidlPath); |
| } |
| |
| if (facet.getProperties().LIBRARY_PROJECT) { |
| removeAttachedJarDependency(modelsProvider, mavenTree, mavenProject); |
| } |
| } |
| |
| private static void removeAttachedJarDependency(MavenModifiableModelsProvider modelsProvider, |
| MavenProjectsTree mavenTree, |
| MavenProject mavenProject) { |
| for (MavenArtifact depArtifact : mavenProject.getDependencies()) { |
| final MavenProject depProject = mavenTree.findProject(depArtifact); |
| |
| if (depProject == null) { |
| continue; |
| } |
| final String attachedJarsLibName = MavenModuleImporter.getAttachedJarsLibName(depArtifact); |
| final Library attachedJarsLib = modelsProvider.getLibraryByName(attachedJarsLibName); |
| |
| if (attachedJarsLib != null) { |
| final Library.ModifiableModel attachedJarsLibModel = modelsProvider.getLibraryModel(attachedJarsLib); |
| |
| if (attachedJarsLibModel != null) { |
| final String targetJarPath = depProject.getBuildDirectory() + "/" + depProject.getFinalName() + ".jar"; |
| |
| for (String url : attachedJarsLibModel.getUrls(OrderRootType.CLASSES)) { |
| if (FileUtil.pathsEqual(targetJarPath, JpsPathUtil.urlToPath(url))) { |
| attachedJarsLibModel.removeRoot(url, OrderRootType.CLASSES); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void importNativeDependencies(@NotNull AndroidFacet facet, @NotNull MavenProject mavenProject, @NotNull String moduleDirPath) { |
| final List<AndroidNativeLibData> additionalNativeLibs = new ArrayList<AndroidNativeLibData>(); |
| final String localRepository = MavenProjectsManager.getInstance(facet.getModule().getProject()).getLocalRepository().getPath(); |
| |
| String defaultArchitecture = getPathFromConfig(facet.getModule(), mavenProject, moduleDirPath, |
| "nativeLibrariesDependenciesHardwareArchitectureDefault", false, true); |
| if (defaultArchitecture == null) { |
| defaultArchitecture = DEFAULT_NATIVE_ARCHITECTURE; |
| } |
| final String forcedArchitecture = getPathFromConfig(facet.getModule(), mavenProject, moduleDirPath, |
| "nativeLibrariesDependenciesHardwareArchitectureOverride", false, true); |
| |
| for (MavenArtifact depArtifact : mavenProject.getDependencies()) { |
| if (AndroidMavenUtil.SO_PACKAGING_AND_DEPENDENCY_TYPE.equals(depArtifact.getType())) { |
| final String architecture; |
| if (forcedArchitecture != null) { |
| architecture = forcedArchitecture; |
| } |
| else { |
| final String classifier = depArtifact.getClassifier(); |
| architecture = classifier != null ? classifier : defaultArchitecture; |
| } |
| final String path = FileUtil.toSystemIndependentName(localRepository + '/' + depArtifact.getRelativePath()); |
| final String artifactId = depArtifact.getArtifactId(); |
| final String targetFileName = artifactId.startsWith("lib") ? artifactId + ".so" : "lib" + artifactId + ".so"; |
| additionalNativeLibs.add(new AndroidNativeLibData(architecture, path, targetFileName)); |
| } |
| } |
| facet.getConfiguration().setAdditionalNativeLibraries(additionalNativeLibs); |
| } |
| |
| private static boolean hasAndroidLibDependencies(@NotNull MavenProject mavenProject) { |
| for (MavenArtifact depArtifact : mavenProject.getDependencies()) { |
| final String type = depArtifact.getType(); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(type) || |
| AndroidMavenUtil.AAR_DEPENDENCY_AND_PACKAGING_TYPE.equals(type)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void importExternalAndroidLibDependencies(Project project, |
| MavenRootModelAdapter rootModelAdapter, |
| MavenModifiableModelsProvider modelsProvider, |
| MavenProjectsTree mavenTree, |
| MavenProject mavenProject, |
| Map<MavenProject, String> mavenProject2ModuleName, |
| List<MavenProjectsProcessorTask> tasks) { |
| final ModifiableRootModel rootModel = rootModelAdapter.getRootModel(); |
| removeUselessDependencies(rootModel, modelsProvider, mavenProject); |
| |
| for (MavenArtifact depArtifact : mavenProject.getDependencies()) { |
| if (mavenTree.findProject(depArtifact) != null) { |
| continue; |
| } |
| final String type = depArtifact.getType(); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(type)) { |
| final AndroidExternalApklibDependenciesManager.MavenDependencyInfo depInfo = |
| AndroidExternalApklibDependenciesManager.MavenDependencyInfo.create(depArtifact); |
| |
| final String apklibModuleName = doImportExternalApklibDependency( |
| project, modelsProvider, mavenTree, mavenProject, |
| mavenProject2ModuleName, tasks, depInfo); |
| |
| if (ArrayUtil.find(rootModel.getDependencyModuleNames(), apklibModuleName) < 0) { |
| final DependencyScope scope = getApklibModuleDependencyScope(depArtifact); |
| |
| if (scope != null) { |
| addModuleDependency(modelsProvider, rootModel, apklibModuleName, scope); |
| } |
| } |
| } |
| else if (AndroidMavenUtil.AAR_DEPENDENCY_AND_PACKAGING_TYPE.equals(type) && |
| MavenConstants.SCOPE_COMPILE.equals(depArtifact.getScope())) { |
| importExternalAarDependency(depArtifact, mavenProject, mavenTree, rootModelAdapter, modelsProvider, project, tasks); |
| } |
| } |
| } |
| |
| @Nullable |
| private static String findExtractedAarDirectory(@NotNull List<MavenProject> allProjects, @NotNull String dirName) { |
| final LocalFileSystem lfs = LocalFileSystem.getInstance(); |
| |
| for (MavenProject project : allProjects) { |
| final VirtualFile file = lfs.refreshAndFindFileByPath(AndroidMavenUtil.getGenExternalApklibDirInProject(project) + "/" + dirName); |
| |
| if (file != null) { |
| return file.getPath(); |
| } |
| } |
| return null; |
| } |
| |
| private static void importExternalAarDependency(@NotNull MavenArtifact artifact, |
| @NotNull MavenProject mavenProject, |
| @NotNull MavenProjectsTree mavenTree, |
| @NotNull MavenRootModelAdapter rootModelAdapter, |
| @NotNull MavenModifiableModelsProvider modelsProvider, |
| @NotNull Project project, |
| @NotNull List<MavenProjectsProcessorTask> postTasks) { |
| final Library aarLibrary = rootModelAdapter.findLibrary(artifact); |
| |
| if (aarLibrary == null) { |
| return; |
| } |
| final MavenId mavenId = artifact.getMavenId(); |
| Map<MavenId, String> importedAarArtifacts = project.getUserData(IMPORTED_AAR_ARTIFACTS); |
| |
| if (importedAarArtifacts == null) { |
| importedAarArtifacts = new HashMap<MavenId, String>(); |
| project.putUserData(IMPORTED_AAR_ARTIFACTS, importedAarArtifacts); |
| |
| postTasks.add(new MavenProjectsProcessorTask() { |
| @Override |
| public void perform(Project project, MavenEmbeddersManager embeddersManager, MavenConsole console, MavenProgressIndicator indicator) |
| throws MavenProcessCanceledException { |
| project.putUserData(IMPORTED_AAR_ARTIFACTS, null); |
| } |
| }); |
| } |
| final List<MavenProject> allProjects = mavenTree.getProjects(); |
| String aarDirPath = importedAarArtifacts.get(mavenId); |
| |
| if (aarDirPath == null) { |
| final String aarDirName = AndroidMavenUtil.getMavenIdStringForFileName(mavenId); |
| aarDirPath = findExtractedAarDirectory(allProjects, aarDirName); |
| |
| if (aarDirPath == null) { |
| final String genDirPath = AndroidMavenUtil.computePathForGenExternalApklibsDir(mavenId, mavenProject, allProjects); |
| |
| if (genDirPath == null) { |
| return; |
| } |
| aarDirPath = genDirPath + "/" + aarDirName; |
| } |
| importedAarArtifacts.put(mavenId, aarDirPath); |
| extractArtifact(artifact.getPath(), aarDirPath, project, mavenProject.getName()); |
| } |
| final Library.ModifiableModel aarLibModel = modelsProvider.getLibraryModel(aarLibrary); |
| final String classesJarPath = aarDirPath + "/" + SdkConstants.FN_CLASSES_JAR; |
| final String classesJarUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, classesJarPath) + |
| JarFileSystem.JAR_SEPARATOR; |
| final String resDirUrl = VfsUtilCore.pathToUrl(aarDirPath + "/" + SdkConstants.FD_RES); |
| final Set<String> urlsToAdd = new HashSet<String>(Arrays.asList(classesJarUrl, resDirUrl)); |
| collectJarsInAarLibsFolder(aarDirPath, urlsToAdd); |
| |
| for (String url : aarLibModel.getUrls(OrderRootType.CLASSES)) { |
| if (!urlsToAdd.remove(url)) { |
| aarLibModel.removeRoot(url, OrderRootType.CLASSES); |
| } |
| } |
| for (String url : urlsToAdd) { |
| aarLibModel.addRoot(url, OrderRootType.CLASSES); |
| } |
| } |
| |
| private static void collectJarsInAarLibsFolder(@NotNull String aarDirPath, @NotNull Set<String> urlsToAdd) { |
| final File libsFolder = new File(aarDirPath, SdkConstants.LIBS_FOLDER); |
| |
| if (!libsFolder.isDirectory()) { |
| return; |
| } |
| final File[] children = libsFolder.listFiles(); |
| |
| if (children != null) { |
| for (File child : children) { |
| if (FileUtilRt.extensionEquals(child.getName(), "jar")) { |
| final String url = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, FileUtil. |
| toSystemIndependentName(child.getPath())) + JarFileSystem.JAR_SEPARATOR; |
| urlsToAdd.add(url); |
| } |
| } |
| } |
| } |
| |
| private static String doImportExternalApklibDependency(Project project, |
| MavenModifiableModelsProvider modelsProvider, |
| MavenProjectsTree mavenTree, |
| MavenProject mavenProject, |
| Map<MavenProject, String> mavenProject2ModuleName, |
| List<MavenProjectsProcessorTask> tasks, |
| AndroidExternalApklibDependenciesManager.MavenDependencyInfo depInfo) { |
| final MavenId depArtifactMavenId = new MavenId(depInfo.getGroupId(), depInfo.getArtifactId(), depInfo.getVersion()); |
| final ModifiableModuleModel moduleModel = modelsProvider.getModuleModel(); |
| final String apklibModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(depArtifactMavenId); |
| Module apklibModule = moduleModel.findModuleByName(apklibModuleName); |
| |
| if ((apklibModule == null || apklibModule.getUserData(MODULE_IMPORTED) == null) && |
| MavenConstants.SCOPE_COMPILE.equals(depInfo.getScope())) { |
| apklibModule = |
| importExternalApklibArtifact(project, apklibModule, modelsProvider, mavenProject, mavenTree, depArtifactMavenId, |
| depInfo.getPath(), moduleModel, mavenProject2ModuleName); |
| if (apklibModule != null) { |
| apklibModule.putUserData(MODULE_IMPORTED, Boolean.TRUE); |
| final Module finalGenModule = apklibModule; |
| |
| tasks.add(new MavenProjectsProcessorTask() { |
| @Override |
| public void perform(Project project, |
| MavenEmbeddersManager embeddersManager, |
| MavenConsole console, |
| MavenProgressIndicator indicator) |
| throws MavenProcessCanceledException { |
| finalGenModule.putUserData(MODULE_IMPORTED, null); |
| } |
| }); |
| final MavenArtifactResolvedInfo resolvedDepArtifact = |
| AndroidExternalApklibDependenciesManager.getInstance(project).getResolvedInfoForArtifact(depArtifactMavenId); |
| |
| if (resolvedDepArtifact != null) { |
| for (AndroidExternalApklibDependenciesManager.MavenDependencyInfo depDepInfo : resolvedDepArtifact.getDependencies()) { |
| final MavenId depDepMavenId = new MavenId(depDepInfo.getGroupId(), depDepInfo.getArtifactId(), depDepInfo.getVersion()); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(depDepInfo.getType()) && |
| mavenTree.findProject(depDepMavenId) == null) { |
| doImportExternalApklibDependency(project, modelsProvider, mavenTree, mavenProject, |
| mavenProject2ModuleName, tasks, depDepInfo); |
| } |
| } |
| } |
| else { |
| AndroidUtils.reportImportErrorToEventLog("Cannot find resolved info for artifact " + depArtifactMavenId.getKey(), |
| apklibModuleName, project); |
| } |
| } |
| } |
| return apklibModuleName; |
| } |
| |
| @Nullable |
| private static DependencyScope getApklibModuleDependencyScope(@NotNull MavenArtifact apklibArtifact) { |
| final String scope = apklibArtifact.getScope(); |
| |
| if (MavenConstants.SCOPE_COMPILE.equals(scope)) { |
| return DependencyScope.COMPILE; |
| } |
| else if (MavenConstants.SCOPE_PROVIDED.equals(scope)) { |
| return DependencyScope.PROVIDED; |
| } |
| else if (MavenConstants.SCOPE_TEST.equals(scope)) { |
| return DependencyScope.TEST; |
| } |
| return null; |
| } |
| |
| private static void removeUselessDependencies(ModifiableRootModel modifiableRootModel, |
| MavenModifiableModelsProvider modelsProvider, MavenProject mavenProject) { |
| for (OrderEntry entry : modifiableRootModel.getOrderEntries()) { |
| if (entry instanceof ModuleOrderEntry) { |
| final Module depModule = ((ModuleOrderEntry)entry).getModule(); |
| if (depModule != null && AndroidMavenUtil.isExtApklibModule(depModule)) { |
| modifiableRootModel.removeOrderEntry(entry); |
| } |
| } |
| else if (entry instanceof LibraryOrderEntry) { |
| final LibraryOrderEntry libOrderEntry = (LibraryOrderEntry)entry; |
| |
| if (containsDependencyOnApklibFile(libOrderEntry, modelsProvider) || |
| pointsIntoUnpackedLibsDir(libOrderEntry, modelsProvider, mavenProject)) { |
| modifiableRootModel.removeOrderEntry(entry); |
| } |
| } |
| } |
| } |
| |
| private static boolean pointsIntoUnpackedLibsDir(@NotNull LibraryOrderEntry entry, |
| @NotNull MavenModifiableModelsProvider provider, |
| @NotNull MavenProject mavenProject) { |
| final Library library = entry.getLibrary(); |
| |
| if (library == null) { |
| return false; |
| } |
| final Library.ModifiableModel libraryModel = provider.getLibraryModel(library); |
| final String[] urls = libraryModel.getUrls(OrderRootType.CLASSES); |
| final String unpackedLibsDir = FileUtil.toCanonicalPath(mavenProject.getBuildDirectory()) + "/unpacked-libs"; |
| |
| for (String url : urls) { |
| if (VfsUtilCore.urlToPath(url).startsWith(unpackedLibsDir)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean containsDependencyOnApklibFile(@NotNull LibraryOrderEntry libraryOrderEntry, |
| @NotNull MavenModifiableModelsProvider modelsProvider) { |
| final Library library = libraryOrderEntry.getLibrary(); |
| |
| if (library == null) { |
| return false; |
| } |
| final Library.ModifiableModel libraryModel = modelsProvider.getLibraryModel(library); |
| final String[] urls = libraryModel.getUrls(OrderRootType.CLASSES); |
| |
| for (String url : urls) { |
| final String fileName = PathUtil.getFileName(PathUtil.toPresentableUrl(url)); |
| |
| if (FileUtilRt.extensionEquals(fileName, "apklib")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void addModuleDependency(@NotNull MavenModifiableModelsProvider modelsProvider, |
| @NotNull ModifiableRootModel rootModel, |
| @NotNull final String moduleName, |
| @NotNull DependencyScope compile) { |
| if (findModuleDependency(rootModel, moduleName) != null) { |
| return; |
| } |
| |
| final Module module = modelsProvider.getModuleModel().findModuleByName(moduleName); |
| |
| final ModuleOrderEntry entry = module != null |
| ? rootModel.addModuleOrderEntry(module) |
| : rootModel.addInvalidModuleEntry(moduleName); |
| entry.setScope(compile); |
| } |
| |
| private static ModuleOrderEntry findModuleDependency(ModifiableRootModel rootModel, final String moduleName) { |
| final Ref<ModuleOrderEntry> result = Ref.create(null); |
| |
| rootModel.orderEntries().forEach(new Processor<OrderEntry>() { |
| @Override |
| public boolean process(OrderEntry entry) { |
| if (entry instanceof ModuleOrderEntry) { |
| final ModuleOrderEntry moduleEntry = (ModuleOrderEntry)entry; |
| final String name = moduleEntry.getModuleName(); |
| if (moduleName.equals(name)) { |
| result.set(moduleEntry); |
| } |
| } |
| return true; |
| } |
| }); |
| |
| return result.get(); |
| } |
| |
| @Nullable |
| private static Module importExternalApklibArtifact(Project project, |
| Module apklibModule, |
| MavenModifiableModelsProvider modelsProvider, |
| MavenProject mavenProject, |
| MavenProjectsTree mavenTree, |
| MavenId artifactMavenId, |
| String artifactFilePath, |
| ModifiableModuleModel moduleModel, |
| Map<MavenProject, String> mavenProject2ModuleName) { |
| final String genModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(artifactMavenId); |
| String genExternalApklibsDirPath = null; |
| String targetDirPath = null; |
| |
| if (apklibModule == null) { |
| genExternalApklibsDirPath = |
| AndroidMavenUtil.computePathForGenExternalApklibsDir(artifactMavenId, mavenProject, mavenTree.getProjects()); |
| |
| targetDirPath = genExternalApklibsDirPath != null |
| ? genExternalApklibsDirPath + '/' + AndroidMavenUtil.getMavenIdStringForFileName(artifactMavenId) |
| : null; |
| } |
| else { |
| final VirtualFile[] contentRoots = ModuleRootManager.getInstance(apklibModule).getContentRoots(); |
| if (contentRoots.length == 1) { |
| targetDirPath = contentRoots[0].getPath(); |
| } |
| else { |
| final String moduleDir = new File(apklibModule.getModuleFilePath()).getParent(); |
| if (moduleDir != null) { |
| targetDirPath = moduleDir + '/' + AndroidMavenUtil.getMavenIdStringForFileName(artifactMavenId); |
| } |
| } |
| } |
| |
| if (targetDirPath == null) { |
| return null; |
| } |
| |
| if (!extractArtifact(artifactFilePath, targetDirPath, project, genModuleName)){ |
| return null; |
| } |
| final AndroidExternalApklibDependenciesManager adm = AndroidExternalApklibDependenciesManager.getInstance(project); |
| adm.setArtifactFilePath(artifactMavenId, FileUtil.toSystemIndependentName(artifactFilePath)); |
| |
| final VirtualFile vApklibDir = LocalFileSystem.getInstance().refreshAndFindFileByPath(targetDirPath); |
| if (vApklibDir == null) { |
| LOG.error("Cannot find file " + targetDirPath + " in VFS"); |
| return null; |
| } |
| |
| if (apklibModule == null) { |
| final String genModuleFilePath = genExternalApklibsDirPath + '/' + genModuleName + ModuleFileType.DOT_DEFAULT_EXTENSION; |
| apklibModule = moduleModel.newModule(genModuleFilePath, StdModuleTypes.JAVA.getId()); |
| } |
| |
| final ModifiableRootModel apklibModuleModel = modelsProvider.getRootModel(apklibModule); |
| final ContentEntry contentEntry = apklibModuleModel.addContentEntry(vApklibDir); |
| |
| final VirtualFile sourceRoot = vApklibDir.findChild(AndroidMavenUtil.APK_LIB_ARTIFACT_SOURCE_ROOT); |
| if (sourceRoot != null) { |
| contentEntry.addSourceFolder(sourceRoot, false); |
| } |
| final AndroidFacet facet = AndroidUtils.addAndroidFacet(apklibModuleModel.getModule(), vApklibDir, true); |
| |
| final AndroidFacetConfiguration configuration = facet.getConfiguration(); |
| String s = AndroidRootUtil.getPathRelativeToModuleDir(apklibModule, vApklibDir.getPath()); |
| if (s != null) { |
| s = s.length() > 0 ? '/' + s + '/' : "/"; |
| configuration.getState().RES_FOLDER_RELATIVE_PATH = s + AndroidMavenUtil.APK_LIB_ARTIFACT_RES_DIR; |
| configuration.getState().LIBS_FOLDER_RELATIVE_PATH = s + AndroidMavenUtil.APK_LIB_ARTIFACT_NATIVE_LIBS_DIR; |
| configuration.getState().MANIFEST_FILE_RELATIVE_PATH = s + AndroidMavenUtil.APK_LIB_ARTIFACT_MANIFEST_FILE; |
| } |
| |
| importSdkAndDependenciesForApklibArtifact(project, apklibModuleModel, modelsProvider, mavenTree, |
| artifactMavenId, mavenProject2ModuleName); |
| return apklibModule; |
| } |
| |
| private static boolean extractArtifact(String zipFilePath, String targetDirPath, Project project, String moduleName) { |
| final File targetDir = new File(targetDirPath); |
| if (targetDir.exists()) { |
| if (!FileUtil.delete(targetDir)) { |
| AndroidUtils.reportImportErrorToEventLog("Cannot delete old " + targetDirPath, moduleName, project); |
| return false; |
| } |
| } |
| |
| if (!targetDir.mkdirs()) { |
| AndroidUtils.reportImportErrorToEventLog("Cannot create directory " + targetDirPath, moduleName, project); |
| return false; |
| } |
| final File artifactFile = new File(zipFilePath); |
| |
| if (artifactFile.exists()) { |
| try { |
| ZipUtil.extract(artifactFile, targetDir, null); |
| } |
| catch (IOException e) { |
| reportIoErrorToEventLog(e, moduleName, project); |
| return false; |
| } |
| } |
| else { |
| AndroidUtils.reportImportErrorToEventLog("Cannot find file " + artifactFile.getPath(), moduleName, project); |
| } |
| return true; |
| } |
| |
| private static void reportIoErrorToEventLog(IOException e, String moduleName, Project project) { |
| final String message = e.getMessage(); |
| |
| if (message == null) { |
| LOG.error(e); |
| } |
| else { |
| AndroidUtils.reportImportErrorToEventLog("I/O error: " + message, moduleName, project); |
| } |
| } |
| |
| private static void importSdkAndDependenciesForApklibArtifact(Project project, |
| ModifiableRootModel apklibModuleModel, |
| MavenModifiableModelsProvider modelsProvider, |
| MavenProjectsTree mavenTree, |
| MavenId artifactMavenId, |
| Map<MavenProject, String> mavenProject2ModuleName) { |
| final String apklibModuleName = apklibModuleModel.getModule().getName(); |
| final AndroidExternalApklibDependenciesManager adm = AndroidExternalApklibDependenciesManager.getInstance(project); |
| final MavenArtifactResolvedInfo resolvedInfo = |
| adm.getResolvedInfoForArtifact(artifactMavenId); |
| |
| for (OrderEntry entry : apklibModuleModel.getOrderEntries()) { |
| if (entry instanceof ModuleOrderEntry || entry instanceof LibraryOrderEntry) { |
| apklibModuleModel.removeOrderEntry(entry); |
| } |
| } |
| |
| if (resolvedInfo != null) { |
| final String apiLevel = resolvedInfo.getApiLevel(); |
| final Sdk sdk = findOrCreateAndroidPlatform(apiLevel, null); |
| |
| if (sdk != null) { |
| apklibModuleModel.setSdk(sdk); |
| } |
| else { |
| reportCannotFindAndroidPlatformError(apklibModuleName, apiLevel, project); |
| } |
| |
| for (AndroidExternalApklibDependenciesManager.MavenDependencyInfo depArtifactInfo : resolvedInfo.getDependencies()) { |
| final MavenId depMavenId = new MavenId(depArtifactInfo.getGroupId(), depArtifactInfo.getArtifactId(), |
| depArtifactInfo.getVersion()); |
| |
| final String type = depArtifactInfo.getType(); |
| final String scope = depArtifactInfo.getScope(); |
| final String path = depArtifactInfo.getPath(); |
| final String libName = depArtifactInfo.getLibName(); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(type) && |
| MavenConstants.SCOPE_COMPILE.equals(scope)) { |
| final MavenProject depProject = mavenTree.findProject(depMavenId); |
| |
| if (depProject != null) { |
| final String depModuleName = mavenProject2ModuleName.get(depProject); |
| |
| if (depModuleName != null) { |
| addModuleDependency(modelsProvider, apklibModuleModel, depModuleName, DependencyScope.COMPILE); |
| } |
| } |
| else { |
| final String depApklibGenModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(depMavenId); |
| addModuleDependency(modelsProvider, apklibModuleModel, depApklibGenModuleName, DependencyScope.COMPILE); |
| } |
| } |
| else { |
| final DependencyScope depScope = MavenModuleImporter.selectScope(scope); |
| |
| if (scope != null) { |
| addLibraryDependency(libName, depScope, modelsProvider, apklibModuleModel, path); |
| } |
| else { |
| LOG.info("Unknown Maven scope " + depScope); |
| } |
| } |
| } |
| } |
| else { |
| AndroidUtils.reportImportErrorToEventLog("Cannot find sdk info for artifact " + artifactMavenId.getKey(), apklibModuleName, |
| project); |
| } |
| } |
| |
| private static void addLibraryDependency(@NotNull String libraryName, |
| @NotNull DependencyScope scope, |
| @NotNull MavenModifiableModelsProvider provider, |
| @NotNull ModifiableRootModel model, |
| @NotNull String path) { |
| Library library = provider.getLibraryByName(libraryName); |
| |
| if (library == null) { |
| library = provider.createLibrary(libraryName); |
| } |
| Library.ModifiableModel libraryModel = provider.getLibraryModel(library); |
| updateUrl(libraryModel, path); |
| final LibraryOrderEntry entry = model.addLibraryEntry(library); |
| entry.setScope(scope); |
| } |
| |
| private static void updateUrl(@NotNull Library.ModifiableModel library, @NotNull String path) { |
| final OrderRootType type = OrderRootType.CLASSES; |
| final String newUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, path) + JarFileSystem.JAR_SEPARATOR; |
| boolean urlExists = false; |
| |
| for (String url : library.getUrls(type)) { |
| if (newUrl.equals(url)) { |
| urlExists = true; |
| } |
| else { |
| library.removeRoot(url, type); |
| } |
| } |
| |
| if (!urlExists) { |
| library.addRoot(newUrl, type); |
| } |
| } |
| |
| private static void reportCannotFindAndroidPlatformError(String moduleName, @Nullable String apiLevel, Project project) { |
| final OpenAndroidSdkManagerHyperlink hyperlink = new OpenAndroidSdkManagerHyperlink(); |
| AndroidUtils.reportImportErrorToEventLog( |
| "Cannot find appropriate Android platform" + (apiLevel != null ? " for API level " + apiLevel : "") + |
| ". " + hyperlink.toHtml(), |
| moduleName, project, new CustomNotificationListener(project, hyperlink)); |
| } |
| |
| @Override |
| public void resolve(final Project project, |
| MavenProject mavenProject, |
| NativeMavenProjectHolder nativeMavenProject, |
| MavenEmbedderWrapper embedder, |
| ResolveContext context) |
| throws MavenProcessCanceledException { |
| final AndroidExternalApklibDependenciesManager adm = |
| AndroidExternalApklibDependenciesManager.getInstance(project); |
| |
| for (MavenArtifact depArtifact : mavenProject.getDependencies()) { |
| final MavenProjectsManager mavenProjectsManager = MavenProjectsManager.getInstance(project); |
| |
| if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(depArtifact.getType()) && |
| mavenProjectsManager.findProject(depArtifact) == null && |
| MavenConstants.SCOPE_COMPILE.equals(depArtifact.getScope())) { |
| |
| Set<MavenId> resolvedArtifacts = context.getUserData(RESOLVED_APKLIB_ARTIFACTS_KEY); |
| |
| if (resolvedArtifacts == null) { |
| resolvedArtifacts = new HashSet<MavenId>(); |
| context.putUserData(RESOLVED_APKLIB_ARTIFACTS_KEY, resolvedArtifacts); |
| } |
| if (resolvedArtifacts.add(depArtifact.getMavenId())) { |
| doResolveApklibArtifact(project, depArtifact, embedder, mavenProjectsManager, mavenProject.getName(), adm, context); |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private static File buildFakeArtifactPomFile(@NotNull MavenArtifact artifact, @Nullable String moduleName, @NotNull Project project) { |
| File tmpFile = null; |
| try { |
| tmpFile = FileUtil.createTempFile("intellij_fake_artifat_pom", "tmp"); |
| FileUtil.writeToFile( |
| tmpFile, |
| "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + |
| " xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n" + |
| " <modelVersion>4.0.0</modelVersion>\n" + |
| " <groupId>intellij-fake-artifact-group</groupId>\n" + |
| " <artifactId>intellij-fake-artifact</artifactId>\n" + |
| " <version>1.0-SNAPSHOT</version>\n" + |
| " <packaging>jar</packaging>\n" + |
| " <name>Fake</name>" + |
| " <dependencies>" + |
| " <dependency>" + |
| " <groupId>" + artifact.getGroupId() + "</groupId>" + |
| " <artifactId>" + artifact.getArtifactId() + "</artifactId>" + |
| " <version>" + artifact.getVersion() + "</version>" + |
| " </dependency>" + |
| " </dependencies>" + |
| "</project>"); |
| return tmpFile; |
| } |
| catch (IOException e) { |
| reportIoErrorToEventLog(e, moduleName, project); |
| |
| if (tmpFile != null) { |
| FileUtil.delete(tmpFile); |
| } |
| return null; |
| } |
| } |
| |
| private void doResolveApklibArtifact(Project project, |
| MavenArtifact artifact, |
| MavenEmbedderWrapper embedder, |
| MavenProjectsManager mavenProjectsManager, |
| String moduleName, |
| AndroidExternalApklibDependenciesManager adm, |
| ResolveContext context) throws MavenProcessCanceledException { |
| final File depArtifacetFile = new File(FileUtil.getNameWithoutExtension(artifact.getPath()) + ".pom"); |
| if (!depArtifacetFile.exists()) { |
| AndroidUtils.reportImportErrorToEventLog("Cannot find file " + depArtifacetFile.getPath(), moduleName, project); |
| return; |
| } |
| |
| final VirtualFile vDepArtifactFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(depArtifacetFile); |
| if (vDepArtifactFile == null) { |
| AndroidUtils.reportImportErrorToEventLog("Cannot find file " + depArtifacetFile.getPath() + " in VFS", moduleName, project); |
| return; |
| } |
| |
| final MavenProject projectForExternalApklib = new MavenProject(vDepArtifactFile); |
| final MavenGeneralSettings generalSettings = mavenProjectsManager.getGeneralSettings(); |
| final MavenProjectReader mavenProjectReader = new MavenProjectReader(); |
| |
| final MavenProjectReaderProjectLocator locator = new MavenProjectReaderProjectLocator() { |
| @Nullable |
| @Override |
| public VirtualFile findProjectFile(MavenId coordinates) { |
| return null; |
| } |
| }; |
| final MavenArtifactResolvedInfo info = new MavenArtifactResolvedInfo(); |
| final MavenId mavenId = artifact.getMavenId(); |
| adm.setResolvedInfoForArtifact(mavenId, info); |
| |
| projectForExternalApklib.read(generalSettings, mavenProjectsManager.getExplicitProfiles(), mavenProjectReader, locator); |
| projectForExternalApklib.resolve(project, generalSettings, embedder, mavenProjectReader, locator, context); |
| |
| final String apiLevel = getPlatformFromConfig(projectForExternalApklib); |
| |
| final List<AndroidExternalApklibDependenciesManager.MavenDependencyInfo> dependencies = |
| new ArrayList<AndroidExternalApklibDependenciesManager.MavenDependencyInfo>(); |
| |
| List<MavenArtifact> deps = projectForExternalApklib.getDependencies(); |
| |
| if (deps.isEmpty()) { |
| // Hack for solving IDEA-119450. Maven reports "unknown packaging 'apklib'" when resolving if android plugin is not specified |
| // in the "build" section of the pom, so we create fake jar artifact dependent on the apklib artifact and resolve it |
| final File fakePomFile = buildFakeArtifactPomFile(artifact, moduleName, project); |
| |
| if (fakePomFile != null) { |
| try { |
| final VirtualFile vFakePomFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(fakePomFile); |
| |
| if (vFakePomFile != null) { |
| final MavenProject fakeProject = new MavenProject(vFakePomFile); |
| fakeProject.read(generalSettings, mavenProjectsManager.getExplicitProfiles(), mavenProjectReader, locator); |
| fakeProject.resolve(project, generalSettings, embedder, mavenProjectReader, locator, context); |
| deps = fakeProject.getDependencies(); |
| |
| for (Iterator<MavenArtifact> it = deps.iterator(); it.hasNext(); ) { |
| final MavenArtifact dep = it.next(); |
| |
| if (dep.getMavenId().equals(mavenId)) { |
| it.remove(); |
| } |
| } |
| } |
| else { |
| LOG.error("Cannot find file " + fakePomFile.getPath() + " in the VFS"); |
| } |
| } |
| finally { |
| FileUtil.delete(fakePomFile); |
| } |
| } |
| } |
| for (MavenArtifact depArtifact : deps) { |
| dependencies.add(AndroidExternalApklibDependenciesManager.MavenDependencyInfo.create(depArtifact)); |
| } |
| info.setApiLevel(apiLevel != null ? apiLevel : ""); |
| info.setDependencies(dependencies); |
| } |
| |
| private void configureAndroidPlatform(AndroidFacet facet, MavenProject project, MavenModifiableModelsProvider modelsProvider) { |
| final ModifiableRootModel model = modelsProvider.getRootModel(facet.getModule()); |
| configureAndroidPlatform(project, model); |
| } |
| |
| private void configureAndroidPlatform(MavenProject project, ModifiableRootModel model) { |
| final Sdk currentSdk = model.getSdk(); |
| |
| if (currentSdk == null || !isAppropriateSdk(currentSdk, project)) { |
| final String apiLevel = getPlatformFromConfig(project); |
| final String predefinedSdkPath = getSdkPathFromConfig(project); |
| final Sdk platformLib = findOrCreateAndroidPlatform(apiLevel, predefinedSdkPath); |
| |
| if (platformLib != null) { |
| model.setSdk(platformLib); |
| } |
| else { |
| reportCannotFindAndroidPlatformError(model.getModule().getName(), apiLevel, model.getProject()); |
| } |
| } |
| } |
| |
| private boolean isAppropriateSdk(@NotNull Sdk sdk, MavenProject mavenProject) { |
| if (!isAndroidSdk(sdk)) { |
| return false; |
| } |
| final String platformId = getPlatformFromConfig(mavenProject); |
| final AndroidPlatform androidPlatform = AndroidPlatform.getInstance(sdk); |
| if (androidPlatform == null) { |
| return false; |
| } |
| |
| final IAndroidTarget target = androidPlatform.getTarget(); |
| return (platformId == null || AndroidSdkUtils.targetHasId(target, platformId)) && |
| AndroidSdkUtils.checkSdkRoots(sdk, target, true); |
| } |
| |
| @Nullable |
| private static Sdk findOrCreateAndroidPlatform(String apiLevel, String predefinedSdkPath) { |
| if (predefinedSdkPath != null) { |
| final Sdk sdk = doFindOrCreateAndroidPlatform(predefinedSdkPath, apiLevel); |
| if (sdk != null) { |
| return sdk; |
| } |
| } |
| String sdkPath; |
| |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| sdkPath = ANDROID_SDK_PATH_TEST; |
| } |
| else { |
| sdkPath = System.getenv(SdkConstants.ANDROID_HOME_ENV); |
| } |
| LOG.info("android home: " + sdkPath); |
| |
| if (sdkPath != null) { |
| final Sdk sdk = doFindOrCreateAndroidPlatform(sdkPath, apiLevel); |
| if (sdk != null) { |
| return sdk; |
| } |
| } |
| |
| final Collection<String> candidates = AndroidSdkUtils.getAndroidSdkPathsFromExistingPlatforms(); |
| LOG.info("suggested sdks: " + candidates); |
| |
| for (String candidate : candidates) { |
| final Sdk sdk = doFindOrCreateAndroidPlatform(candidate, apiLevel); |
| if (sdk != null) { |
| return sdk; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static Sdk doFindOrCreateAndroidPlatform(@Nullable String sdkPath, @Nullable String apiLevel) { |
| if (sdkPath != null) { |
| AndroidSdkData sdkData = AndroidSdkData.getSdkData(sdkPath); |
| if (sdkData != null) { |
| IAndroidTarget target = apiLevel != null && apiLevel.length() > 0 |
| ? sdkData.findTargetByApiLevel(apiLevel) |
| : findNewestPlatformTarget(sdkData); |
| if (target != null) { |
| Sdk library = AndroidSdkUtils.findAppropriateAndroidPlatform(target, sdkData, true); |
| if (library == null) { |
| library = createNewAndroidSdkForMaven(sdkPath, target); |
| } |
| return library; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static IAndroidTarget findNewestPlatformTarget(AndroidSdkData data) { |
| IAndroidTarget result = null; |
| |
| for (IAndroidTarget target : data.getTargets()) { |
| if (target.isPlatform() && (result == null || result.getVersion().compareTo(target.getVersion()) < 0)) { |
| result = target; |
| } |
| } |
| return result; |
| } |
| |
| @Nullable |
| private static Sdk createNewAndroidSdkForMaven(String sdkPath, IAndroidTarget target) { |
| String sdkName = "Maven " + AndroidSdkUtils.chooseNameForNewLibrary(target); |
| Sdk sdk = AndroidSdkUtils.createNewAndroidPlatform(target, sdkPath, sdkName, false); |
| |
| if (sdk == null) { |
| return null; |
| } |
| SdkModificator modificator = sdk.getSdkModificator(); |
| |
| for (OrderRoot root : AndroidSdkUtils.getLibraryRootsForTarget(target, sdkPath, false)) { |
| modificator.addRoot(root.getFile(), root.getType()); |
| } |
| AndroidSdkAdditionalData data = getAndroidSdkAdditionalData(sdk); |
| |
| if (data != null) { |
| final Sdk javaSdk = data.getJavaSdk(); |
| |
| if (javaSdk != null) { |
| for (VirtualFile file : javaSdk.getRootProvider().getFiles(OrderRootType.CLASSES)) { |
| modificator.addRoot(file, OrderRootType.CLASSES); |
| } |
| } |
| else { |
| LOG.error("AndroidSdkUtils.createNewAndroidPlatform should return Android SDK with a valid JDK reference, or return null"); |
| } |
| } |
| modificator.commitChanges(); |
| return sdk; |
| } |
| |
| @Nullable |
| private String getPlatformFromConfig(MavenProject project) { |
| Element sdkRoot = getConfig(project, "sdk"); |
| if (sdkRoot != null) { |
| Element platform = sdkRoot.getChild("platform"); |
| if (platform != null) { |
| return platform.getValue(); |
| } |
| } |
| final String platformFromProperty = |
| project.getProperties().getProperty("android.sdk.platform"); |
| |
| if (platformFromProperty != null) { |
| return platformFromProperty; |
| } |
| return null; |
| } |
| |
| @Nullable |
| private String getSdkPathFromConfig(MavenProject project) { |
| Element sdkRoot = getConfig(project, "sdk"); |
| if (sdkRoot != null) { |
| Element path = sdkRoot.getChild("path"); |
| if (path != null) { |
| return path.getValue(); |
| } |
| } |
| final String pathFromProperty = |
| project.getProperties().getProperty("android.sdk.path"); |
| |
| if (pathFromProperty != null) { |
| return pathFromProperty; |
| } |
| return null; |
| } |
| |
| private void configurePaths(AndroidFacet facet, MavenProject project) { |
| Module module = facet.getModule(); |
| String moduleDirPath = AndroidRootUtil.getModuleDirPath(module); |
| if (moduleDirPath == null) { |
| return; |
| } |
| AndroidFacetConfiguration configuration = facet.getConfiguration(); |
| |
| if (configuration.isImportedProperty(AndroidImportableProperty.RESOURCES_DIR_PATH)) { |
| String resFolderRelPath = getPathFromConfig(module, project, moduleDirPath, "resourceDirectory", true, true); |
| |
| if (resFolderRelPath != null && isFullyResolved(resFolderRelPath)) { |
| configuration.getState().RES_FOLDER_RELATIVE_PATH = '/' + resFolderRelPath; |
| } |
| String resFolderForCompilerRelPath = getPathFromConfig(module, project, moduleDirPath, "resourceDirectory", false, true); |
| |
| if (resFolderForCompilerRelPath != null && |
| !resFolderForCompilerRelPath.equals(resFolderRelPath)) { |
| configuration.getState().USE_CUSTOM_APK_RESOURCE_FOLDER = true; |
| configuration.getState().CUSTOM_APK_RESOURCE_FOLDER = '/' + resFolderForCompilerRelPath; |
| configuration.getState().RUN_PROCESS_RESOURCES_MAVEN_TASK = true; |
| } |
| } |
| configuration.getState().RES_OVERLAY_FOLDERS = Arrays.asList("/res-overlay"); |
| |
| Element resourceOverlayDirectories = getConfig(project, "resourceOverlayDirectories"); |
| if (resourceOverlayDirectories != null) { |
| List<String> dirs = new ArrayList<String>(); |
| for (Object child : resourceOverlayDirectories.getChildren()) { |
| String dir = ((Element)child).getTextTrim(); |
| if (dir != null && dir.length() > 0) { |
| String relativePath = getRelativePath(moduleDirPath, makePath(project, dir)); |
| if (relativePath != null && relativePath.length() > 0) { |
| dirs.add('/' + relativePath); |
| } |
| } |
| } |
| if (dirs.size() > 0) { |
| configuration.getState().RES_OVERLAY_FOLDERS = dirs; |
| } |
| } |
| else { |
| String resOverlayFolderRelPath = getPathFromConfig(module, project, moduleDirPath, "resourceOverlayDirectory", true, true); |
| if (resOverlayFolderRelPath != null && isFullyResolved(resOverlayFolderRelPath)) { |
| configuration.getState().RES_OVERLAY_FOLDERS = Arrays.asList('/' + resOverlayFolderRelPath); |
| } |
| } |
| |
| if (configuration.isImportedProperty(AndroidImportableProperty.ASSETS_DIR_PATH)) { |
| String assetsFolderRelPath = getPathFromConfig(module, project, moduleDirPath, "assetsDirectory", false, true); |
| if (assetsFolderRelPath != null && isFullyResolved(assetsFolderRelPath)) { |
| configuration.getState().ASSETS_FOLDER_RELATIVE_PATH = '/' + assetsFolderRelPath; |
| } |
| } |
| |
| if (configuration.isImportedProperty(AndroidImportableProperty.MANIFEST_FILE_PATH)) { |
| String manifestFileRelPath = getPathFromConfig(module, project, moduleDirPath, "androidManifestFile", true, false); |
| if (manifestFileRelPath != null && isFullyResolved(manifestFileRelPath)) { |
| configuration.getState().MANIFEST_FILE_RELATIVE_PATH = '/' + manifestFileRelPath; |
| } |
| |
| String manifestFileForCompilerRelPath = getPathFromConfig(module, project, moduleDirPath, "androidManifestFile", false, false); |
| if (manifestFileForCompilerRelPath != null && |
| !manifestFileForCompilerRelPath.equals(manifestFileRelPath) && |
| isFullyResolved(manifestFileForCompilerRelPath)) { |
| configuration.getState().USE_CUSTOM_COMPILER_MANIFEST = true; |
| configuration.getState().CUSTOM_COMPILER_MANIFEST = '/' + manifestFileForCompilerRelPath; |
| configuration.getState().RUN_PROCESS_RESOURCES_MAVEN_TASK = true; |
| } |
| } |
| |
| if (MavenProjectsManager.getInstance(module.getProject()).getImportingSettings().isUseMavenOutput()) { |
| final String buildDirectory = FileUtil.toSystemIndependentName(project.getBuildDirectory()); |
| final String buildDirRelPath = FileUtil.getRelativePath(moduleDirPath, buildDirectory, '/'); |
| configuration.getState().APK_PATH = '/' + buildDirRelPath + '/' + AndroidCompileUtil.getApkName(module); |
| } |
| else { |
| configuration.getState().APK_PATH = ""; |
| } |
| if (configuration.isImportedProperty(AndroidImportableProperty.NATIVE_LIBS_DIR_PATH)) { |
| String nativeLibsFolderRelPath = getPathFromConfig(module, project, moduleDirPath, "nativeLibrariesDirectory", false, true); |
| if (nativeLibsFolderRelPath != null && isFullyResolved(nativeLibsFolderRelPath)) { |
| configuration.getState().LIBS_FOLDER_RELATIVE_PATH = '/' + nativeLibsFolderRelPath; |
| } |
| } |
| importNativeDependencies(facet, project, moduleDirPath); |
| } |
| |
| private static boolean isFullyResolved(@NotNull String s) { |
| return !s.contains("${"); |
| } |
| |
| @Nullable |
| private String getPathFromConfig(Module module, |
| MavenProject project, |
| String moduleDirPath, |
| String configTagName, |
| boolean inResourceDir, |
| boolean directory) { |
| String resourceDir = findConfigValue(project, configTagName); |
| if (resourceDir != null) { |
| String path = makePath(project, resourceDir); |
| if (inResourceDir) { |
| MyResourceProcessor processor = new MyResourceProcessor(path, directory); |
| AndroidMavenProviderImpl.processResources(module, project, processor); |
| if (processor.myResult != null) { |
| path = processor.myResult.getPath(); |
| } |
| } |
| String resFolderRelPath = getRelativePath(moduleDirPath, path); |
| if (resFolderRelPath != null) { |
| return resFolderRelPath; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static String getRelativePath(String basePath, String absPath) { |
| absPath = FileUtil.toSystemIndependentName(absPath); |
| return FileUtil.getRelativePath(basePath, absPath, '/'); |
| } |
| |
| @Override |
| public void collectExcludedFolders(MavenProject mavenProject, List<String> result) { |
| result.add(mavenProject.getGeneratedSourcesDirectory(false) + "/combined-resources"); |
| result.add(mavenProject.getGeneratedSourcesDirectory(false) + "/combined-assets"); |
| result.add(mavenProject.getGeneratedSourcesDirectory(false) + "/extracted-dependencies"); |
| } |
| |
| private static class MyResourceProcessor implements AndroidMavenProviderImpl.ResourceProcessor { |
| private final String myResourceOutputPath; |
| private final boolean myDirectory; |
| |
| private VirtualFile myResult; |
| |
| private MyResourceProcessor(String resourceOutputPath, boolean directory) { |
| myResourceOutputPath = resourceOutputPath; |
| myDirectory = directory; |
| } |
| |
| @Override |
| public boolean process(@NotNull VirtualFile resource, @NotNull String outputPath) { |
| if (!myDirectory && resource.isDirectory()) { |
| return false; |
| } |
| if (outputPath.endsWith("/")) { |
| outputPath = outputPath.substring(0, outputPath.length() - 1); |
| } |
| if (FileUtil.pathsEqual(outputPath, myResourceOutputPath)) { |
| myResult = resource; |
| return true; |
| } |
| |
| if (myDirectory) { |
| // we're looking for for resource directory |
| |
| if (outputPath.toLowerCase().startsWith(myResourceOutputPath.toLowerCase())) { |
| final String parentPath = outputPath.substring(0, myResourceOutputPath.length()); |
| if (FileUtil.pathsEqual(parentPath, myResourceOutputPath)) { |
| |
| if (resource.isDirectory()) { |
| // copying of directory that is located in resource dir, so resource dir is parent |
| final VirtualFile parent = resource.getParent(); |
| if (parent != null) { |
| myResult = parent; |
| return true; |
| } |
| } |
| else { |
| // copying of resource file, we have to skip resource-type specific directory |
| final VirtualFile parent = resource.getParent(); |
| final VirtualFile gp = parent != null ? parent.getParent() : null; |
| if (gp != null) { |
| myResult = gp; |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| return false; |
| } |
| } |
| |
| private static class MyDeleteObsoleteApklibModulesTask implements MavenProjectsProcessorTask { |
| private final Project myProject; |
| |
| public MyDeleteObsoleteApklibModulesTask(@NotNull Project project) { |
| myProject = project; |
| } |
| |
| @Override |
| public void perform(final Project project, |
| MavenEmbeddersManager embeddersManager, |
| MavenConsole console, |
| MavenProgressIndicator indicator) |
| throws MavenProcessCanceledException { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (project.isDisposed() || project.getUserData(DELETE_OBSOLETE_MODULE_TASK_KEY) != Boolean.TRUE) { |
| return; |
| } |
| project.putUserData(DELETE_OBSOLETE_MODULE_TASK_KEY, null); |
| final ModifiableModuleModel moduleModel = ModuleManager.getInstance(project).getModifiableModel(); |
| final Set<Module> referredModules = new HashSet<Module>(); |
| |
| for (Module module : moduleModel.getModules()) { |
| if (!AndroidMavenUtil.isExtApklibModule(module)) { |
| collectDependenciesRecursively(module, referredModules); |
| } |
| } |
| |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| boolean modelChanged = false; |
| |
| for (final Module module : moduleModel.getModules()) { |
| if (AndroidMavenUtil.isExtApklibModule(module) && !referredModules.contains(module)) { |
| final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots(); |
| |
| if (contentRoots.length > 0) { |
| final VirtualFile contentRoot = contentRoots[0]; |
| try { |
| contentRoot.delete(myProject); |
| } |
| catch (IOException e) { |
| LOG.error(e); |
| } |
| } |
| moduleModel.disposeModule(module); |
| modelChanged = true; |
| } |
| } |
| if (modelChanged) { |
| moduleModel.commit(); |
| } |
| else { |
| moduleModel.dispose(); |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| private static void collectDependenciesRecursively(@NotNull Module root, @NotNull Set<Module> result) { |
| if (!result.add(root)) { |
| return; |
| } |
| |
| for (Module depModule : ModuleRootManager.getInstance(root).getDependencies()) { |
| collectDependenciesRecursively(depModule, result); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| if (!super.equals(o)) return false; |
| |
| MyDeleteObsoleteApklibModulesTask task = (MyDeleteObsoleteApklibModulesTask)o; |
| |
| if (!myProject.equals(task.myProject)) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| result = 31 * result + myProject.hashCode(); |
| return result; |
| } |
| } |
| } |