IDEA-75734 android+maven: support for external "apklib" dependencies

This commit is contained in:
Eugene Kudelevsky
2011-11-17 15:35:05 +04:00
parent 060fbc4dc3
commit efb6b28c86
9 changed files with 1038 additions and 13 deletions
@@ -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=<html>Filter logcat messages by different parameters.<br>Empty fields will match all messages.</html>
android.logcat.new.filter.dialog.name.busy.error=Filter {0} already exists
android.logcat.new.filter.dialog.name.busy.error=Filter {0} already exists
android.compile.messages.processing.external.apklib.dependencies=Processing external apklib dependencies
@@ -5,6 +5,7 @@
<importer implementation="org.jetbrains.android.maven.AndroidFacetImporter2"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<compiler implementation="org.jetbrains.android.compiler.AndroidExternalApklibExtractingCompiler" order="first"/>
<compiler implementation="org.jetbrains.android.compiler.AndroidMavenResourcesCompiler"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains">
+2
View File
@@ -163,6 +163,8 @@
serviceImplementation="org.jetbrains.android.uipreview.AndroidLayoutPreviewToolWindowSettings"/>
<projectService serviceInterface="org.jetbrains.android.logcat.AndroidConfiguredLogFilters"
serviceImplementation="org.jetbrains.android.logcat.AndroidConfiguredLogFilters"/>
<projectService serviceInterface="org.jetbrains.android.maven.AndroidExternalApklibDependenciesManager"
serviceImplementation="org.jetbrains.android.maven.AndroidExternalApklibDependenciesManager"/>
<xml.xmlExtension implementation="org.jetbrains.android.dom.AndroidXmlExtension" order="first"/>
<fileBasedIndex implementation="org.jetbrains.android.AndroidIdIndex"/>
<referencesSearch implementation="org.jetbrains.android.AndroidReferenceSearchExecutor"/>
@@ -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;
}
}
@@ -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<GenerationItem[]>() {
@Override
public GenerationItem[] compute() {
final List<GenerationItem> result = new ArrayList<GenerationItem>();
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<VirtualFile> generatedVFiles = new HashSet<VirtualFile>();
final HashSet<VirtualFile> visited = new HashSet<VirtualFile>();
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<GenerationItem> result = new ArrayList<GenerationItem>();
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;
}
}
}
@@ -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
@@ -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<AndroidExternalApklibDependenciesManager.State> {
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<String, String> myArtifactFilesMap = new HashMap<String, String>();
private Map<String, MyResolvedInfo> myResolvedInfoMap = new HashMap<String, MyResolvedInfo>();
@Tag("sdk-infos")
@MapAnnotation(surroundWithTag = false)
public Map<String, MyResolvedInfo> getResolvedInfoMap() {
return myResolvedInfoMap;
}
@Tag("artifacts")
@MapAnnotation(surroundWithTag = false)
public Map<String, String> getArtifactFilesMap() {
return myArtifactFilesMap;
}
public void setArtifactFilesMap(Map<String, String> artifactFilesMap) {
myArtifactFilesMap = artifactFilesMap;
}
public void setResolvedInfoMap(Map<String, MyResolvedInfo> artifactId2SdkData) {
myResolvedInfoMap = artifactId2SdkData;
}
}
@Tag("resolved-info")
public static class MyResolvedInfo {
private String mySdkPath;
private String myApiLevel;
private List<MavenDependencyInfo> myApklibDependencies = new ArrayList<MavenDependencyInfo>();
public MyResolvedInfo(String sdkPath, String apiLevel, Collection<MavenDependencyInfo> dependencyInfos) {
mySdkPath = sdkPath;
myApiLevel = apiLevel;
myApklibDependencies = new ArrayList<MavenDependencyInfo>(dependencyInfos);
}
public MyResolvedInfo() {
}
public String getSdkPath() {
return mySdkPath;
}
public String getApiLevel() {
return myApiLevel;
}
@Tag("dependencies")
@AbstractCollection(surroundWithTag = false)
public List<MavenDependencyInfo> getApklibDependencies() {
return myApklibDependencies;
}
public void setApklibDependencies(List<MavenDependencyInfo> 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;
}
}
}
@@ -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<AndroidFacet, AndroidFacetConfiguration, AndroidFacetType> {
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");
private static final Key<Boolean> 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<AndroidFace
@NotNull
private static String[] getSupportedPackagingTypes() {
return new String[] {"apk", AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE};
return new String[]{AndroidMavenUtil.APK_PACKAGING_TYPE, AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE};
}
@Override
@@ -81,7 +106,7 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
facet.getConfiguration().init(facet.getModule(), mavenProjectDirPath);
AndroidMavenProviderImpl.setPathsToDefault(mavenProject, facet.getModule(), facet.getConfiguration());
AndroidMavenProviderImpl.configureAaptCompilation(mavenProject, facet.getModule(), facet.getConfiguration(),
AndroidMavenProviderImpl.hasApkSourcesDependency(mavenProject));
AndroidMavenProviderImpl.hasApkSourcesDependency(mavenProject));
if (AndroidMavenUtil.APKLIB_DEPENDENCY_AND_PACKAGING_TYPE.equals(mavenProject.getPackaging())) {
facet.getConfiguration().LIBRARY_PROJECT = true;
@@ -100,17 +125,379 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
List<MavenProjectsProcessorTask> 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<MavenProjectsProcessorTask> 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<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,
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<LibraryOrderEntry> result = Ref.create(null);
model.orderEntries().forEach(new Processor<OrderEntry>() {
@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<AndroidExternalApklibDependenciesManager.MavenDependencyInfo> dependencies =
new ArrayList<AndroidExternalApklibDependenciesManager.MavenDependencyInfo>();
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<AndroidFace
@Nullable
private Sdk findOrCreateAndroidPlatform(MavenProject project) {
final String apiLevel = getPlatformFromConfig(project);
final String predefinedSdkPath = getSdkPathFromConfig(project);
return findOrCreateAndroidPlatform(apiLevel, predefinedSdkPath);
}
@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 = System.getenv(AndroidSdkUtils.ANDROID_HOME_ENV);
LOG.info("android home: " + sdkPath);
if (sdkPath != null) {
final Sdk sdk = findOrCreateAndroidPlatform(project, sdkPath);
final Sdk sdk = doFindOrCreateAndroidPlatform(sdkPath, apiLevel);
if (sdk != null) {
return sdk;
}
@@ -153,7 +554,7 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
LOG.info("suggested sdks: " + candidates);
for (String candidate : candidates) {
final Sdk sdk = findOrCreateAndroidPlatform(project, candidate);
final Sdk sdk = doFindOrCreateAndroidPlatform(candidate, apiLevel);
if (sdk != null) {
return sdk;
}
@@ -162,10 +563,8 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
}
@Nullable
private Sdk findOrCreateAndroidPlatform(MavenProject project, String sdkPath) {
private static Sdk doFindOrCreateAndroidPlatform(String sdkPath, String apiLevel) {
if (sdkPath != null) {
final String apiLevel = getPlatformFromConfig(project);
if (apiLevel == null) {
return null;
}
@@ -196,6 +595,18 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
}
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();
}
}
return null;
}
private void configurePaths(AndroidFacet facet, MavenProject project) {
Module module = facet.getModule();
@@ -355,8 +766,101 @@ public abstract class AndroidFacetImporterBase extends FacetImporter<AndroidFace
}
return false;
}
return false;
}
}
private static class MyDeleteObsoleteApklibModulesTask extends MavenProjectsProcessorBasicTask {
private final Project myProject;
public MyDeleteObsoleteApklibModulesTask(@NotNull Project project,
@NotNull MavenProject mavenProject,
@NotNull MavenProjectsTree mavenTree) {
super(mavenProject, mavenTree);
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() {
final ModifiableModuleModel moduleModel = ModuleManager.getInstance(project).getModifiableModel();
final Set<Module> referredModules = new HashSet<Module>();
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<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;
}
}
}
@@ -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<MavenProject> 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);