diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index a16ee9b54454..f47bb5e9c89a 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -293,4 +293,5 @@ android.logcat.edit.logcat.filter.button=Edit selected logcat filter android.logcat.remove.logcat.filter.button=Remove selected logcat filter android.logcat.add.logcat.filter.button=Add a new logcat filter android.logcat.new.logcat.dialog.label=Filter logcat messages by different parameters.
Empty fields will match all messages. -android.logcat.new.filter.dialog.name.busy.error=Filter {0} already exists \ No newline at end of file +android.logcat.new.filter.dialog.name.busy.error=Filter {0} already exists +android.compile.messages.processing.external.apklib.dependencies=Processing external apklib dependencies \ No newline at end of file diff --git a/plugins/android/src/META-INF/maven-support.xml b/plugins/android/src/META-INF/maven-support.xml index f46d7735745c..fbf44ffad17a 100644 --- a/plugins/android/src/META-INF/maven-support.xml +++ b/plugins/android/src/META-INF/maven-support.xml @@ -5,6 +5,7 @@ + diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index 2e4fb9f08796..cc004a844ce9 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -163,6 +163,8 @@ serviceImplementation="org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowSettings"/> + diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java index ecc453d26461..41b4dcb3bf9a 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidCompileUtil.java @@ -369,11 +369,11 @@ public class AndroidCompileUtil { } @Nullable - private static ContentEntry findContentEntryForRoot(@NotNull ModifiableRootModel model, @NotNull VirtualFile root) { + public static ContentEntry findContentEntryForRoot(@NotNull ModifiableRootModel model, @NotNull VirtualFile root) { ContentEntry contentEntry = null; for (ContentEntry candidate : model.getContentEntries()) { VirtualFile contentRoot = candidate.getFile(); - if (contentRoot != null && VfsUtil.isAncestor(contentRoot, root, false)) { + if (contentRoot != null && VfsUtilCore.isAncestor(contentRoot, root, false)) { contentEntry = candidate; } } diff --git a/plugins/android/src/org/jetbrains/android/compiler/AndroidExternalApklibExtractingCompiler.java b/plugins/android/src/org/jetbrains/android/compiler/AndroidExternalApklibExtractingCompiler.java new file mode 100644 index 000000000000..f6559841db51 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/compiler/AndroidExternalApklibExtractingCompiler.java @@ -0,0 +1,246 @@ +package org.jetbrains.android.compiler; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.containers.HashSet; +import com.intellij.util.io.ZipUtil; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.maven.AndroidExternalApklibDependenciesManager; +import org.jetbrains.android.maven.AndroidMavenUtil; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.DataInput; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidExternalApklibExtractingCompiler implements SourceGeneratingCompiler { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidExternalApklibExtractingCompiler"); + + @Nullable + @Override + public VirtualFile getPresentableFile(CompileContext context, Module module, VirtualFile outputRoot, VirtualFile generatedFile) { + return null; + } + + @Override + public GenerationItem[] getGenerationItems(final CompileContext context) { + return ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public GenerationItem[] compute() { + final List result = new ArrayList(); + + for (Module module : ModuleManager.getInstance(context.getProject()).getModules()) { + final AndroidFacet facet = AndroidFacet.getInstance(module); + if (facet == null || !facet.getConfiguration().LIBRARY_PROJECT) { + continue; + } + + final String mavenIdStr = AndroidMavenUtil.getMavenIdStringByExtApklibModule(facet.getModule()); + if (mavenIdStr == null) { + continue; + } + + final AndroidExternalApklibDependenciesManager manager = + AndroidExternalApklibDependenciesManager.getInstance(context.getProject()); + final String artifactFilePath = manager.getArtifactFilePath(mavenIdStr); + + if (artifactFilePath == null) { + context.addMessage(CompilerMessageCategory.ERROR, + "Cannot find artifact file for generated module " + module.getName() + ". Try to force reimport Maven model", + null, -1, -1); + continue; + } + + final VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots(); + if (roots.length == 0) { + context.addMessage(CompilerMessageCategory.ERROR, "Cannot find any content root in generated module " + + module.getName() + + ". Try to force reimport Maven model", null, -1, -1); + continue; + } + + final VirtualFile root = roots[0]; + + result.add(new MyGenerationItem(module, FileUtil.toSystemDependentName(root.getPath()), + FileUtil.toSystemDependentName(artifactFilePath))); + } + + return result.toArray(new GenerationItem[result.size()]); + } + }); + } + + @Override + public GenerationItem[] generate(CompileContext context, + GenerationItem[] items, + VirtualFile outputRootDirectory) { + if (items == null || items.length == 0) { + return new GenerationItem[0]; + } + + context.getProgressIndicator().setText(AndroidBundle.message("android.compile.messages.processing.external.apklib.dependencies")); + + final GenerationItem[] generatedItems = doGenerate(context, items); + final Set generatedVFiles = new HashSet(); + final HashSet visited = new HashSet(); + + for (GenerationItem item : generatedItems) { + final MyGenerationItem genItem = (MyGenerationItem)item; + + final File srcRoot = new File(genItem.getGenContentRootPath() + '/' + + AndroidMavenUtil.APK_LIB_ARTIFACT_SOURCE_ROOT); + + final VirtualFile vSrcRoot = LocalFileSystem.getInstance().findFileByIoFile(srcRoot); + if (vSrcRoot != null) { + vSrcRoot.getParent().refresh(false, true); + AndroidUtils.collectFiles(vSrcRoot, visited, generatedVFiles); + } + } + + if (context instanceof CompileContextEx) { + ((CompileContextEx)context).markGenerated(generatedVFiles); + } + + return generatedItems; + } + + private static GenerationItem[] doGenerate(CompileContext context, GenerationItem[] items) { + final List result = new ArrayList(); + + for (GenerationItem item : items) { + final Module module = item.getModule(); + + if (!AndroidCompileUtil.isModuleAffected(context, module)) { + continue; + } + + final MyGenerationItem genItem = (MyGenerationItem)item; + + final String artifactPath = genItem.getArtifactPath(); + final File artifactFile = new File(artifactPath); + + if (!artifactFile.exists()) { + context.addMessage(CompilerMessageCategory.ERROR, "Cannot find file " + artifactPath, null, -1, -1); + continue; + } + + if (!artifactFile.isFile()) { + context.addMessage(CompilerMessageCategory.ERROR, artifactPath + " is not file", null, -1, -1); + continue; + } + + final String genContentRootPath = ((MyGenerationItem)item).getGenContentRootPath(); + final File genContentRootFile = new File(genContentRootPath); + + if (genContentRootFile.exists()) { + if (!FileUtil.delete(genContentRootFile)) { + context.addMessage(CompilerMessageCategory.ERROR, "Cannot delete old directory: " + genContentRootPath, null, -1, -1); + continue; + } + } + + if (!genContentRootFile.mkdir()) { + context.addMessage(CompilerMessageCategory.ERROR, "Cannot create directory " + genContentRootPath, null, -1, -1); + continue; + } + + try { + ZipUtil.extract(artifactFile, genContentRootFile, null); + } + catch (IOException e) { + final String message = e.getMessage(); + if (message == null) { + context.addMessage(CompilerMessageCategory.ERROR, "Unknown I/O error", null, -1, -1); + LOG.error(e); + } + else { + context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + LOG.info(e); + } + continue; + } + + result.add(genItem); + } + + return result.toArray(new GenerationItem[result.size()]); + } + + @NotNull + @Override + public String getDescription() { + return "Android External Apklib Extracting Compiler"; + } + + @Override + public boolean validateConfiguration(CompileScope scope) { + return true; + } + + @Override + public ValidityState createValidityState(DataInput in) throws IOException { + return TimestampValidityState.load(in); + } + + private static class MyGenerationItem implements GenerationItem { + private final Module myModule; + private final String myGenContentRootPath; + private final String myArtifactPath; + + private MyGenerationItem(@NotNull Module module, @NotNull String genContentRootPath, @NotNull String artifactPath) { + myModule = module; + myGenContentRootPath = genContentRootPath; + myArtifactPath = artifactPath; + } + + @Nullable + @Override + public String getPath() { + return myGenContentRootPath; + } + + @Override + public ValidityState getValidityState() { + return new TimestampValidityState(new File(myArtifactPath).lastModified()); + } + + @Override + @NotNull + public Module getModule() { + return myModule; + } + + @Override + public boolean isTestSource() { + return false; + } + + @NotNull + public String getGenContentRootPath() { + return myGenContentRootPath; + } + + @NotNull + public String getArtifactPath() { + return myArtifactPath; + } + } +} diff --git a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApt.java b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApt.java index fa560796acb3..8f6d9a6522e3 100644 --- a/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApt.java +++ b/plugins/android/src/org/jetbrains/android/compiler/tools/AndroidApt.java @@ -56,6 +56,13 @@ public final class AndroidApt { @NotNull String[] resourceDirsOsPaths, @NotNull String[] libPackages, boolean isLibrary) throws IOException { + final File outOsDir = new File(outDirOsPath); + if (!outOsDir.exists()) { + if (!outOsDir.mkdirs()) { + LOG.error("Unable to create directory " + outDirOsPath); + } + } + final String packageFolderOsPath = FileUtil.toSystemDependentName(outDirOsPath + '/' + aPackage.replace('.', '/')); /* We actually need to delete the manifest.java as it may become empty and diff --git a/plugins/android/src/org/jetbrains/android/maven/AndroidExternalApklibDependenciesManager.java b/plugins/android/src/org/jetbrains/android/maven/AndroidExternalApklibDependenciesManager.java new file mode 100644 index 000000000000..0767bfa8116c --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/maven/AndroidExternalApklibDependenciesManager.java @@ -0,0 +1,194 @@ +package org.jetbrains.android.maven; + +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; +import com.intellij.util.containers.HashMap; +import com.intellij.util.xmlb.annotations.AbstractCollection; +import com.intellij.util.xmlb.annotations.MapAnnotation; +import com.intellij.util.xmlb.annotations.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.maven.model.MavenId; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * @author Eugene.Kudelevsky + */ +@State( + name = "AndroidExternalApklibDependenciesManager", + storages = { + @Storage(file = "$WORKSPACE_FILE$") + } +) +public class AndroidExternalApklibDependenciesManager implements PersistentStateComponent { + private State myState = new State(); + + public static AndroidExternalApklibDependenciesManager getInstance(@NotNull Project project) { + return ServiceManager.getService(project, AndroidExternalApklibDependenciesManager.class); + } + + @Override + public AndroidExternalApklibDependenciesManager.State getState() { + return myState; + } + + @Override + public void loadState(AndroidExternalApklibDependenciesManager.State state) { + myState = state; + } + + @Nullable + public MyResolvedInfo getResolvedInfoForArtifact(@NotNull MavenId mavenId) { + final String key = AndroidMavenUtil.getMavenIdStringForFileName(mavenId); + return myState.getResolvedInfoMap().get(key); + } + + @Nullable + public String getArtifactFilePath(@NotNull String mavenIdStr) { + return myState.getArtifactFilesMap().get(mavenIdStr); + } + + public void setSdkInfoForArtifact(@NotNull MavenId mavenId, @NotNull MyResolvedInfo info) { + final String key = AndroidMavenUtil.getMavenIdStringForFileName(mavenId); + myState.getResolvedInfoMap().put(key, info); + } + + public void setArtifactFilePath(@NotNull MavenId mavenId, @NotNull String path) { + final String key = AndroidMavenUtil.getMavenIdStringForFileName(mavenId); + myState.getArtifactFilesMap().put(key, path); + } + + public static class State { + private Map myArtifactFilesMap = new HashMap(); + private Map myResolvedInfoMap = new HashMap(); + + @Tag("sdk-infos") + @MapAnnotation(surroundWithTag = false) + public Map getResolvedInfoMap() { + return myResolvedInfoMap; + } + + @Tag("artifacts") + @MapAnnotation(surroundWithTag = false) + public Map getArtifactFilesMap() { + return myArtifactFilesMap; + } + + public void setArtifactFilesMap(Map artifactFilesMap) { + myArtifactFilesMap = artifactFilesMap; + } + + public void setResolvedInfoMap(Map artifactId2SdkData) { + myResolvedInfoMap = artifactId2SdkData; + } + } + + @Tag("resolved-info") + public static class MyResolvedInfo { + private String mySdkPath; + private String myApiLevel; + private List myApklibDependencies = new ArrayList(); + + public MyResolvedInfo(String sdkPath, String apiLevel, Collection dependencyInfos) { + mySdkPath = sdkPath; + myApiLevel = apiLevel; + + myApklibDependencies = new ArrayList(dependencyInfos); + } + + public MyResolvedInfo() { + } + + public String getSdkPath() { + return mySdkPath; + } + + public String getApiLevel() { + return myApiLevel; + } + + @Tag("dependencies") + @AbstractCollection(surroundWithTag = false) + public List getApklibDependencies() { + return myApklibDependencies; + } + + public void setApklibDependencies(List apklibDependencies) { + myApklibDependencies = apklibDependencies; + } + + public void setSdkPath(String sdkPath) { + mySdkPath = sdkPath; + } + + public void setApiLevel(String apiLevel) { + myApiLevel = apiLevel; + } + } + + public static class MavenDependencyInfo { + private String myGroupId; + private String myArtifactId; + private String myVersion; + private String myType; + private String myLibraryName; + + public MavenDependencyInfo() { + } + + public MavenDependencyInfo(@NotNull MavenId mavenId, @NotNull String type, @NotNull String libraryName) { + myGroupId = mavenId.getGroupId(); + myArtifactId = mavenId.getArtifactId(); + myVersion = mavenId.getVersion(); + myType = type; + myLibraryName = libraryName; + } + + public String getGroupId() { + return myGroupId; + } + + public String getArtifactId() { + return myArtifactId; + } + + public String getVersion() { + return myVersion; + } + + public String getType() { + return myType; + } + + public String getLibraryName() { + return myLibraryName; + } + + public void setGroupId(String groupId) { + myGroupId = groupId; + } + + public void setArtifactId(String artifactId) { + myArtifactId = artifactId; + } + + public void setVersion(String version) { + myVersion = version; + } + + public void setType(String type) { + myType = type; + } + + public void setLibraryName(String libraryName) { + myLibraryName = libraryName; + } + } +} diff --git a/plugins/android/src/org/jetbrains/android/maven/AndroidFacetImporterBase.java b/plugins/android/src/org/jetbrains/android/maven/AndroidFacetImporterBase.java index 6c23177ed8f8..260e565e4e2c 100644 --- a/plugins/android/src/org/jetbrains/android/maven/AndroidFacetImporterBase.java +++ b/plugins/android/src/org/jetbrains/android/maven/AndroidFacetImporterBase.java @@ -17,14 +17,29 @@ package org.jetbrains.android.maven; import com.android.sdklib.IAndroidTarget; 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.roots.*; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; +import com.intellij.util.Processor; +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.facet.AndroidFacet; import org.jetbrains.android.facet.AndroidFacetConfiguration; import org.jetbrains.android.facet.AndroidFacetType; @@ -36,9 +51,16 @@ 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.MavenRootModelAdapter; +import org.jetbrains.idea.maven.model.MavenArtifact; +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 java.io.File; +import java.io.IOException; import java.util.*; @@ -47,6 +69,9 @@ import java.util.*; */ public abstract class AndroidFacetImporterBase extends FacetImporter { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.maven.AndroidFacetImporterBase"); + + private static final Key MODULE_IMPORTED = Key.create("ANDROID_NEWLY_CREATED_KEY"); + private static final Key NEED_TO_DELETE_OBSOLETE_MODULES_KEY = Key.create("NEED_TO_DELETE_OBSOLETE_MODULES_KEY"); public AndroidFacetImporterBase(@NotNull String pluginId) { super("com.jayway.maven.plugins.android.generation2", pluginId, FacetType.findInstance(AndroidFacetType.class), "Android"); @@ -66,7 +91,7 @@ public abstract class AndroidFacetImporterBase extends FacetImporter postTasks) { configurePaths(facet, mavenProject); configureAndroidPlatform(facet, mavenProject, modelsProvider); + importExternalApklibDependencies(module.getProject(), rootModel, modelsProvider, mavenTree, mavenProject, postTasks); + + if (hasApklibDependencies(mavenProject) && + MavenProjectsManager.getInstance(module.getProject()).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"); + } + + postTasks.add(new MyDeleteObsoleteApklibModulesTask(module.getProject(), mavenProject, mavenTree)); + } + + private static boolean hasApklibDependencies(@NotNull MavenProject mavenProject) { + for (MavenArtifact depArtifact : mavenProject.getDependencies()) { + if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(depArtifact.getType())) { + return true; + } + } + return false; + } + + private static void importExternalApklibDependencies(Project project, + MavenRootModelAdapter rootModelAdapter, + MavenModifiableModelsProvider modelsProvider, + MavenProjectsTree mavenTree, + MavenProject mavenProject, + List tasks) { + final ModifiableRootModel rootModel = rootModelAdapter.getRootModel(); + removeExtApklibDependencies(rootModel); + + for (MavenArtifact depArtifact : mavenProject.getDependencies()) { + if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(depArtifact.getType()) && + mavenTree.findProject(depArtifact) == null) { + + final ModifiableModuleModel moduleModel = modelsProvider.getModuleModel(); + final String apklibModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(depArtifact.getMavenId()); + Module apklibModule = moduleModel.findModuleByName(apklibModuleName); + + if (apklibModule == null || apklibModule.getUserData(MODULE_IMPORTED) == null) { + apklibModule = + importExternalApklibArtifact(project, rootModelAdapter, apklibModule, modelsProvider, mavenProject, mavenTree, depArtifact, + moduleModel); + if (apklibModule != null) { + apklibModule.putUserData(MODULE_IMPORTED, Boolean.TRUE); + } + } + + if (apklibModule != null) { + 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); + } + }); + } + + if (ArrayUtil.find(rootModel.getDependencyModuleNames(), apklibModuleName) < 0) { + addCompileModuleDependency(modelsProvider, rootModel, apklibModuleName); + } + } + } + } + + private static void removeExtApklibDependencies(ModifiableRootModel modifiableRootModel) { + for (OrderEntry entry : modifiableRootModel.getOrderEntries()) { + if (entry instanceof ModuleOrderEntry) { + final Module depModule = ((ModuleOrderEntry)entry).getModule(); + if (depModule != null && AndroidMavenUtil.isExtApklibModule(depModule)) { + modifiableRootModel.removeOrderEntry(entry); + } + } + } + } + + private static void addCompileModuleDependency(@NotNull MavenModifiableModelsProvider modelsProvider, + @NotNull ModifiableRootModel rootModel, + @NotNull final String moduleName) { + 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(DependencyScope.COMPILE); + } + + private static ModuleOrderEntry findModuleDependency(ModifiableRootModel rootModel, final String moduleName) { + final Ref result = Ref.create(null); + + rootModel.orderEntries().forEach(new Processor() { + @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, + MavenRootModelAdapter rootModelAdapter, + Module apklibModule, + MavenModifiableModelsProvider modelsProvider, + MavenProject mavenProject, + MavenProjectsTree mavenTree, + MavenArtifact artifact, + ModifiableModuleModel moduleModel) { + final MavenId artifactMavenId = artifact.getMavenId(); + + final String genModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(artifactMavenId); + + String targetDirPath = + AndroidMavenUtil.computePathToUnpackExtApklibArtifact(artifactMavenId, mavenProject, mavenTree.getProjects()); + + if (targetDirPath == null) { + return null; + } + + final File targetDir = new File(targetDirPath); + if (targetDir.exists()) { + if (!FileUtil.delete(targetDir)) { + LOG.error("Cannot delete old " + targetDirPath); + return null; + } + } + + if (!targetDir.mkdirs()) { + LOG.error("Cannot create directory " + targetDirPath); + return null; + } + + final File artifactFile = artifact.getFile(); + + final AndroidExternalApklibDependenciesManager adm = AndroidExternalApklibDependenciesManager.getInstance(project); + + adm.setArtifactFilePath(artifactMavenId, FileUtil.toSystemIndependentName(artifactFile.getPath())); + + if (artifactFile.exists()) { + try { + ZipUtil.extract(artifactFile, targetDir, null); + } + catch (IOException e) { + LOG.error(e); + return null; + } + } + else { + LOG.info("Cannot find file " + artifactFile.getPath()); + } + + 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 = vApklibDir.getPath() + '/' + genModuleName + ModuleFileType.DOT_DEFAULT_EXTENSION; + apklibModule = moduleModel.newModule(genModuleFilePath, StdModuleTypes.JAVA); + } + + 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); + } + else { + LOG.info("Cannot find " + AndroidMavenUtil.APK_LIB_ARTIFACT_SOURCE_ROOT + " directory in " + vApklibDir.getPath()); + } + + final AndroidFacet facet = AndroidUtils.addAndroidFacet(apklibModuleModel, vApklibDir, true); + + final AndroidFacetConfiguration configuration = facet.getConfiguration(); + configuration.RES_FOLDER_RELATIVE_PATH = '/' + AndroidMavenUtil.APK_LIB_ARTIFACT_RES_DIR; + configuration.LIBS_FOLDER_RELATIVE_PATH = '/' + AndroidMavenUtil.APK_LIB_ARTIFACT_NATIVE_LIBS_DIR; + configuration.MANIFEST_FILE_RELATIVE_PATH = '/' + AndroidMavenUtil.APK_LIB_ARTIFACT_MANIFEST_FILE; + + importSdkAndDependenciesForApklibArtifact(project, rootModelAdapter, apklibModuleModel, modelsProvider, mavenTree, artifact); + + return apklibModule; + } + + private static void importSdkAndDependenciesForApklibArtifact(Project project, + MavenRootModelAdapter rootModelAdapter, + ModifiableRootModel apklibModuleModel, + MavenModifiableModelsProvider modelsProvider, + MavenProjectsTree mavenTree, + MavenArtifact artifact) { + final AndroidExternalApklibDependenciesManager adm = AndroidExternalApklibDependenciesManager.getInstance(project); + final AndroidExternalApklibDependenciesManager.MyResolvedInfo resolvedInfo = + adm.getResolvedInfoForArtifact(artifact.getMavenId()); + + for (OrderEntry entry : apklibModuleModel.getOrderEntries()) { + if (entry instanceof ModuleOrderEntry || entry instanceof LibraryOrderEntry) { + apklibModuleModel.removeOrderEntry(entry); + } + } + + if (resolvedInfo != null) { + final Sdk sdk = findOrCreateAndroidPlatform(resolvedInfo.getSdkPath(), resolvedInfo.getApiLevel()); + + if (sdk != null) { + apklibModuleModel.setSdk(sdk); + } + + for (AndroidExternalApklibDependenciesManager.MavenDependencyInfo depArtifactInfo : resolvedInfo.getApklibDependencies()) { + final MavenId depMavenId = new MavenId(depArtifactInfo.getGroupId(), depArtifactInfo.getArtifactId(), + depArtifactInfo.getVersion()); + + final String type = depArtifactInfo.getType(); + + if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(type)) { + final MavenProject depProject = mavenTree.findProject(depMavenId); + + if (depProject != null) { + final Module depModule = MavenProjectsManager.getInstance(project).findModule(depProject); + if (depModule != null && findModuleDependency(apklibModuleModel, depModule.getName()) == null) { + apklibModuleModel.addModuleOrderEntry(depModule); + } + } + else { + final String depApklibGenModuleName = AndroidMavenUtil.getModuleNameForExtApklibArtifact(depMavenId); + addCompileModuleDependency(modelsProvider, apklibModuleModel, depApklibGenModuleName); + } + } + else { + final String libraryName = depArtifactInfo.getLibraryName(); + final ModifiableRootModel rootModel = rootModelAdapter.getRootModel(); + final LibraryOrderEntry libEntry = findLibraryByName(rootModel, libraryName); + + if (libEntry != null) { + final Library library = libEntry.getLibrary(); + + if (library != null && apklibModuleModel.findLibraryOrderEntry(library) == null) { + final LibraryOrderEntry newLibEntry = apklibModuleModel.addLibraryEntry(library); + newLibEntry.setExported(libEntry.isExported()); + newLibEntry.setScope(libEntry.getScope()); + } + } + } + } + } + else { + LOG.info("Cannot find sdk info for artifact " + artifact.getMavenId().getKey()); + } + } + + @Nullable + private static LibraryOrderEntry findLibraryByName(ModifiableRootModel model, final String libraryName) { + final Ref result = Ref.create(null); + model.orderEntries().forEach(new Processor() { + @Override + public boolean process(OrderEntry entry) { + if (entry instanceof LibraryOrderEntry) { + final LibraryOrderEntry libOrderEntry = (LibraryOrderEntry)entry; + final Library library = libOrderEntry.getLibrary(); + + if (library != null && libraryName.equals(library.getName())) { + result.set(libOrderEntry); + } + } + return true; + } + }); + return result.get(); + } + + @Override + public void resolve(final Project project, + MavenProject mavenProject, + NativeMavenProjectHolder nativeMavenProject, + MavenEmbedderWrapper embedder) + throws MavenProcessCanceledException { + + 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) { + + doResolveApklibArtifact(project, depArtifact, embedder, mavenProjectsManager); + } + } + } + + private void doResolveApklibArtifact(Project project, + MavenArtifact artifact, + MavenEmbedderWrapper embedder, + MavenProjectsManager mavenProjectsManager) throws MavenProcessCanceledException { + final File depArtifacetFile = new File(FileUtil.getNameWithoutExtension(artifact.getPath()) + ".pom"); + if (!depArtifacetFile.exists()) { + LOG.info("Cannot find file " + depArtifacetFile.getPath()); + return; + } + + final VirtualFile vDepArtifactFile = LocalFileSystem.getInstance().findFileByIoFile(depArtifacetFile); + if (vDepArtifactFile == null) { + LOG.error("Cannot find file " + depArtifacetFile.getPath() + " in VFS"); + 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; + } + }; + + projectForExternalApklib.read(generalSettings, mavenProjectsManager.getAvailableProfiles(), mavenProjectReader, locator); + projectForExternalApklib.resolve(project, generalSettings, embedder, mavenProjectReader, locator); + + final String apiLevel = getPlatformFromConfig(projectForExternalApklib); + final String sdkPath = getSdkPathFromConfig(projectForExternalApklib); + + final List dependencies = + new ArrayList(); + + for (MavenArtifact depArtifact : projectForExternalApklib.getDependencies()) { + final String depArtifactLibraryName = MavenRootModelAdapter.makeLibraryName(depArtifact); + + dependencies.add(new AndroidExternalApklibDependenciesManager.MavenDependencyInfo( + depArtifact.getMavenId(), depArtifact.getType(), depArtifactLibraryName)); + } + + final AndroidExternalApklibDependenciesManager apklibDependenciesManager = + AndroidExternalApklibDependenciesManager.getInstance(project); + + final AndroidExternalApklibDependenciesManager.MyResolvedInfo info = new AndroidExternalApklibDependenciesManager.MyResolvedInfo( + apiLevel != null ? apiLevel : "", + sdkPath != null ? sdkPath : "", + dependencies + ); + + apklibDependenciesManager.setSdkInfoForArtifact(artifact.getMavenId(), info); } private void configureAndroidPlatform(AndroidFacet facet, MavenProject project, MavenModifiableModelsProvider modelsProvider) { - final Sdk currentSdk = modelsProvider.getRootModel(facet.getModule()).getSdk(); + 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)) { return; } Sdk platformLib = findOrCreateAndroidPlatform(project); if (platformLib != null) { - modelsProvider.getRootModel(facet.getModule()).setSdk(platformLib); + model.setSdk(platformLib); } } @@ -139,11 +526,25 @@ public abstract class AndroidFacetImporterBase extends FacetImporter referredModules = new HashSet(); + + for (Module module : moduleModel.getModules()) { + if (!AndroidMavenUtil.isExtApklibModule(module) && + !AndroidCompileUtil.isGenModule(module)) { + collectDependenciesRecursively(module, referredModules); + } + } + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + 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); + } + } + + final Module genModule = AndroidCompileUtil.getGenModule(module); + if (genModule != null) { + moduleModel.disposeModule(genModule); + } + + moduleModel.disposeModule(module); + } + } + + moduleModel.commit(); + } + }); + } + }); + } + + private static void collectDependenciesRecursively(@NotNull Module root, @NotNull Set 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; + } + } } diff --git a/plugins/android/src/org/jetbrains/android/maven/AndroidMavenUtil.java b/plugins/android/src/org/jetbrains/android/maven/AndroidMavenUtil.java index 8d74784502c6..5b7191db51a2 100644 --- a/plugins/android/src/org/jetbrains/android/maven/AndroidMavenUtil.java +++ b/plugins/android/src/org/jetbrains/android/maven/AndroidMavenUtil.java @@ -15,22 +15,92 @@ */ package org.jetbrains.android.maven; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.maven.model.MavenId; +import org.jetbrains.idea.maven.project.MavenProject; + +import java.util.Collection; /** * @author Eugene.Kudelevsky */ public class AndroidMavenUtil { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.maven.AndroidMavenUtil"); + @NonNls public static final String APKSOURCES_DEPENDENCY_TYPE = "apksources"; @NonNls public static final String APKLIB_DEPENDENCY_AND_PACKAGING_TYPE = "apklib"; + @NonNls public static final String APK_PACKAGING_TYPE = "apk"; + + @NonNls public static final String APK_LIB_ARTIFACT_SOURCE_ROOT = "src"; + @NonNls public static final String APK_LIB_ARTIFACT_RES_DIR = "res"; + public static final String APK_LIB_ARTIFACT_NATIVE_LIBS_DIR = "libs"; + public static final String APK_LIB_ARTIFACT_MANIFEST_FILE = "AndroidManifest.xml"; + @NonNls private static final String APKLIB_MODULE_PREFIX = "~apklib-"; private AndroidMavenUtil() { } + @Nullable + public static String computePathToUnpackExtApklibArtifact(@NotNull MavenId mavenId, + @NotNull MavenProject project, + @NotNull Collection allProjects) { + String path = null; + boolean resultUnderApp = false; + + for (MavenProject p : allProjects) { + if (p.findDependencies(mavenId).size() > 0) { + final VirtualFile projectDir = p.getDirectoryFile(); + final boolean app = APK_PACKAGING_TYPE.equals(p.getPackaging()); + if (path == null || !resultUnderApp && app) { + path = projectDir.getPath() + '/' + getDirNameToUnpackExtApklibArtifact(mavenId); + resultUnderApp = app; + } + } + } + + if (path == null) { + path = project.getDirectoryFile().getPath() + '/' + getDirNameToUnpackExtApklibArtifact(mavenId); + } + return path; + } + + @NotNull + private static String getDirNameToUnpackExtApklibArtifact(@NotNull MavenId mavenId) { + return "gen-external-apklibs/" + getMavenIdStringForFileName(mavenId); + } + + @NotNull + public static String getModuleNameForExtApklibArtifact(MavenId mavenId) { + return APKLIB_MODULE_PREFIX + getMavenIdStringForFileName(mavenId); + } + + @Nullable + public static String getMavenIdStringByExtApklibModule(@NotNull Module module) { + final String moduleName = module.getName(); + + if (!moduleName.startsWith(APKLIB_MODULE_PREFIX)) { + return null; + } + + return moduleName.substring(APKLIB_MODULE_PREFIX.length()); + } + + public static boolean isExtApklibModule(@NotNull Module module) { + return module.getName().startsWith(APKLIB_MODULE_PREFIX); + } + + @NotNull + public static String getMavenIdStringForFileName(@NotNull MavenId mavenId) { + final String artifactId = mavenId.getKey().replace(':', '_'); + return artifactId != null ? artifactId : "null"; + } + public static boolean isMavenizedModule(@NotNull Module module) { AndroidMavenProvider mavenProxy = getMavenProvider(); return mavenProxy != null && mavenProxy.isMavenizedModule(module);