package org.jetbrains.jps.android; import com.android.SdkConstants; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.repository.local.LocalSdk; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; import com.intellij.util.containers.HashSet; import com.intellij.util.containers.OrderedSet; import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode; import org.jetbrains.android.util.AndroidCommonUtils; import org.jetbrains.android.util.AndroidCompilerMessageKind; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.ModuleChunk; import org.jetbrains.jps.android.builder.AndroidAarDepsBuildTarget; import org.jetbrains.jps.android.model.*; import org.jetbrains.jps.android.model.impl.JpsAndroidFinalPackageElement; import org.jetbrains.jps.android.model.impl.JpsAndroidModuleExtensionImpl; import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType; import org.jetbrains.jps.builders.storage.BuildDataPaths; import org.jetbrains.jps.incremental.*; import org.jetbrains.jps.incremental.artifacts.ArtifactBuildTarget; import org.jetbrains.jps.incremental.artifacts.impl.JpsArtifactUtil; import org.jetbrains.jps.incremental.java.FormsParsing; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import org.jetbrains.jps.incremental.storage.BuildDataManager; import org.jetbrains.jps.model.JpsElement; import org.jetbrains.jps.model.JpsProject; import org.jetbrains.jps.model.JpsSimpleElement; import org.jetbrains.jps.model.artifact.JpsArtifact; import org.jetbrains.jps.model.artifact.JpsArtifactService; import org.jetbrains.jps.model.artifact.elements.JpsPackagingElement; import org.jetbrains.jps.model.java.*; import org.jetbrains.jps.model.java.impl.JpsJavaDependenciesEnumerationHandler; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.JpsLibraryRoot; import org.jetbrains.jps.model.library.JpsOrderRootType; import org.jetbrains.jps.model.library.sdk.JpsSdk; import org.jetbrains.jps.model.module.*; import org.jetbrains.jps.model.serialization.JpsModelSerializationDataService; import org.jetbrains.jps.util.JpsPathUtil; import java.io.*; import java.util.*; import java.util.regex.Matcher; /** * @author Eugene.Kudelevsky */ public class AndroidJpsUtil { private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.android.AndroidJpsUtil"); @NonNls public static final String ANDROID_FACET_TYPE_ID = "android"; @NonNls public static final String ANDROID_FACET_NAME = "Android"; @NonNls public static final String ANDROID_STORAGE_DIR = "android"; @NonNls private static final String RESOURCE_CACHE_STORAGE = "res_cache"; @NonNls private static final String INTERMEDIATE_ARTIFACTS_STORAGE = "intermediate_artifacts"; @NonNls public static final String GENERATED_RESOURCES_DIR_NAME = "generated_resources"; @NonNls public static final String AAPT_GENERATED_SOURCE_ROOT_NAME = "aapt"; @NonNls public static final String AIDL_GENERATED_SOURCE_ROOT_NAME = "aidl"; @NonNls public static final String RENDERSCRIPT_GENERATED_SOURCE_ROOT_NAME = "rs"; @NonNls public static final String BUILD_CONFIG_GENERATED_SOURCE_ROOT_NAME = "build_config"; @NonNls private static final String GENERATED_SOURCES_FOLDER_NAME = "generated_sources"; @NonNls private static final String PREPROCESSED_MANIFEST_FOLDER_NAME = "preprocessed_manifest"; @NonNls private static final String COPIED_SOURCES_FOLDER_NAME = "copied_sources"; @NonNls private static final String MANIFEST_TAG = "manifest"; private AndroidJpsUtil() { } /** * In a module imported from Maven dependencies are transitive, so we don't need to traverse all dependency tree * and compute all jars referred by library modules. Moreover it would be incorrect, * because Maven has dependency resolving algorithm based on versioning */ public static boolean shouldProcessDependenciesRecursively(JpsModule module) { return JpsJavaDependenciesEnumerationHandler.shouldProcessDependenciesRecursively( JpsJavaDependenciesEnumerationHandler.createHandlers(Collections.singletonList(module))); } @Nullable public static File getMainContentRoot(@NotNull JpsAndroidModuleExtension extension) throws IOException { final JpsModule module = extension.getModule(); final List<String> contentRoots = module.getContentRootsList().getUrls(); if (contentRoots.size() == 0) { return null; } final File manifestFile = extension.getManifestFile(); if (manifestFile != null) { for (String rootUrl : contentRoots) { final File root = JpsPathUtil.urlToFile(rootUrl); if (FileUtil.isAncestor(root, manifestFile, true)) { return root; } } } return JpsPathUtil.urlToFile(contentRoots.get(0)); } public static void addMessages(@NotNull CompileContext context, @NotNull Map<AndroidCompilerMessageKind, List<String>> messages, @NotNull String builderName, @NotNull String entryName) { for (Map.Entry<AndroidCompilerMessageKind, List<String>> entry : messages.entrySet()) { for (String message : entry.getValue()) { String filePath = null; int line = -1; final Matcher matcher = AndroidCommonUtils.COMPILER_MESSAGE_PATTERN.matcher(message); if (matcher.matches()) { filePath = matcher.group(1); line = Integer.parseInt(matcher.group(2)); } final BuildMessage.Kind category = toBuildMessageKind(entry.getKey()); if (category != null) { context.processMessage( new CompilerMessage(builderName, category, '[' + entryName + "] " + message, filePath, -1L, -1L, -1L, line, -1L)); } } } } @Nullable public static JpsAndroidModuleExtension getExtension(@NotNull JpsModule module) { return module.getContainer().getChild(JpsAndroidModuleExtensionImpl.KIND); } @NotNull public static String[] toPaths(@NotNull File[] files) { final String[] result = new String[files.length]; for (int i = 0; i < result.length; i++) { result[i] = files[i].getPath(); } return result; } @NotNull public static List<String> toPaths(@NotNull Collection<File> files) { if (files.size() == 0) { return Collections.emptyList(); } final List<String> result = new ArrayList<String>(files.size()); for (File file : files) { result.add(file.getPath()); } return result; } @NotNull public static File getDirectoryForIntermediateArtifacts(@NotNull CompileContext context, @NotNull JpsModule module) { return getDirectoryForIntermediateArtifacts(context.getProjectDescriptor().dataManager.getDataPaths(), module); } @NotNull public static File getDirectoryForIntermediateArtifacts(@NotNull BuildDataPaths dataPaths, @NotNull JpsModule module) { return new File(getDirectoryForIntermediateArtifacts(dataPaths), module.getName()); } @NotNull public static File getDirectoryForIntermediateArtifacts(@NotNull BuildDataPaths dataPaths) { final File androidStorage = new File(dataPaths.getDataStorageRoot(), ANDROID_STORAGE_DIR); return new File(androidStorage, INTERMEDIATE_ARTIFACTS_STORAGE); } @Nullable public static File createDirIfNotExist(@NotNull File dir, @NotNull CompileContext context, @NotNull String compilerName) { if (!dir.exists()) { if (!dir.mkdirs()) { context.processMessage(new CompilerMessage(compilerName, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.cannot.create.directory", dir.getPath()))); return null; } } return dir; } public static void addSubdirectories(@NotNull File baseDir, @NotNull Collection<String> result) { // only include files inside packages final File[] children = baseDir.listFiles(); if (children != null) { for (File child : children) { if (child.isDirectory()) { result.add(child.getPath()); } } } } @NotNull public static Set<String> getExternalLibraries(@NotNull CompileContext context, @NotNull JpsModule module, @NotNull AndroidPlatform platform) { final BuildDataPaths paths = context.getProjectDescriptor().dataManager.getDataPaths(); return getExternalLibraries(paths, module, platform, true, true, false); } @NotNull public static Set<String> getExternalLibraries(@NotNull BuildDataPaths paths, @NotNull JpsModule module, @Nullable AndroidPlatform platform, boolean resolveJars, boolean withAarDeps, boolean withPackagedAarDepsJar) { final Set<String> result = new HashSet<String>(); final AndroidDependencyProcessor processor = new AndroidDependencyProcessor() { @Override public void processExternalLibrary(@NotNull File file) { result.add(file.getPath()); } @Override public boolean isToProcess(@NotNull AndroidDependencyType type) { return type == AndroidDependencyType.EXTERNAL_LIBRARY; } }; processClasspath(paths, module, processor, resolveJars, withAarDeps); if (platform != null) { addAnnotationsJarIfNecessary(platform, result); } if (withPackagedAarDepsJar) { result.add(AndroidAarDepsBuildTarget.getOutputFile(module, paths).getPath()); } return result; } @NotNull public static Set<String> getProvidedLibraries(@NotNull BuildDataPaths paths, @NotNull JpsModule module) { final Set<String> result = new HashSet<String>(); processClasspath(paths, module, new AndroidDependencyProcessor() { @Override public void processProvidedLibrary(@NotNull File file) { result.add(file.getPath()); } @Override public boolean isToProcess(@NotNull AndroidDependencyType type) { return type == AndroidDependencyType.PROVIDED_LIBRARY; } }, false, false); return result; } private static void addAnnotationsJarIfNecessary(@NotNull AndroidPlatform platform, @NotNull Set<String> libs) { if (platform.needToAddAnnotationsJarToClasspath()) { final String sdkHomePath = platform.getSdk().getHomePath(); final String annotationsJarPath = FileUtil.toSystemIndependentName(sdkHomePath) + AndroidCommonUtils.ANNOTATIONS_JAR_RELATIVE_PATH; libs.add(annotationsJarPath); } } public static void processClasspath(@NotNull BuildDataPaths paths, @NotNull JpsModule module, @NotNull AndroidDependencyProcessor processor, boolean resolveJars, boolean withAarDeps) { final boolean recursive = shouldProcessDependenciesRecursively(module); processClasspath(paths, module, processor, new HashSet<String>(), false, recursive, resolveJars, withAarDeps); } private static void processClasspath(@NotNull BuildDataPaths paths, @NotNull final JpsModule module, @NotNull final AndroidDependencyProcessor processor, @NotNull final Set<String> visitedModules, final boolean exportedLibrariesOnly, final boolean recursive, final boolean resolveJars, final boolean withAarDeps) { if (!visitedModules.add(module.getName())) { return; } if (processor.isToProcess(AndroidDependencyType.EXTERNAL_LIBRARY)) { for (JpsDependencyElement item : JpsJavaExtensionService.getInstance().getDependencies(module, JpsJavaClasspathKind.PRODUCTION_RUNTIME, exportedLibrariesOnly)) { if (item instanceof JpsLibraryDependency) { final JpsLibrary library = ((JpsLibraryDependency)item).getLibrary(); if (library != null && (withAarDeps || getResDirAndClassesJarIfAar(library) == null)) { for (JpsLibraryRoot root : library.getRoots(JpsOrderRootType.COMPILED)) { final File file = JpsPathUtil.urlToFile(root.getUrl()); if (resolveJars) { processClassFilesAndJarsRecursively(file, new Processor<File>() { @Override public boolean process(File file) { processor.processExternalLibrary(file); return true; } }); } else { processor.processExternalLibrary(file); } } } } } } if (processor.isToProcess(AndroidDependencyType.PROVIDED_LIBRARY)) { for (JpsDependencyElement item : module.getDependenciesList().getDependencies()) { if (item instanceof JpsLibraryDependency) { final JpsLibrary library = ((JpsLibraryDependency)item).getLibrary(); final JpsJavaDependencyExtension extension = JpsJavaExtensionService.getInstance().getDependencyExtension(item); if (library != null && extension != null && extension.getScope() == JpsJavaDependencyScope.PROVIDED) { for (JpsLibraryRoot root : library.getRoots(JpsOrderRootType.COMPILED)) { processor.processProvidedLibrary(JpsPathUtil.urlToFile(root.getUrl())); } } } } } for (JpsDependencyElement item : JpsJavaExtensionService.getInstance().getDependencies(module, JpsJavaClasspathKind.PRODUCTION_RUNTIME, false)) { if (item instanceof JpsModuleDependency) { final JpsModule depModule = ((JpsModuleDependency)item).getModule(); if (depModule == null) continue; final JpsAndroidModuleExtension depExtension = getExtension(depModule); final boolean depLibrary = depExtension != null && depExtension.isLibrary(); final File depClassDir = new ModuleBuildTarget(depModule, JavaModuleBuildTargetType.PRODUCTION).getOutputDir(); if (depLibrary) { if (processor.isToProcess(AndroidDependencyType.ANDROID_LIBRARY_PACKAGE)) { final File intArtifactsDir = getDirectoryForIntermediateArtifacts(paths, depModule); final File packagedClassesJar = new File(intArtifactsDir, AndroidCommonUtils.CLASSES_JAR_FILE_NAME); processor.processAndroidLibraryPackage(packagedClassesJar, depModule); } if (processor.isToProcess(AndroidDependencyType.ANDROID_LIBRARY_OUTPUT_DIRECTORY)) { if (depClassDir != null) { processor.processAndroidLibraryOutputDirectory(depClassDir); } } } else if (processor.isToProcess(AndroidDependencyType.JAVA_MODULE_OUTPUT_DIR) && depExtension == null && depClassDir != null) { // do not support android-app->android-app compile dependencies processor.processJavaModuleOutputDirectory(depClassDir); } if (recursive) { final boolean newRecursive = shouldProcessDependenciesRecursively(depModule); processClasspath(paths, depModule, processor, visitedModules, !depLibrary || exportedLibrariesOnly, newRecursive, resolveJars, withAarDeps); } } } } public static void processClassFilesAndJarsRecursively(@NotNull File root, @NotNull final Processor<File> processor) { FileUtil.processFilesRecursively(root, new Processor<File>() { @Override public boolean process(File file) { if (file.isFile()) { String fileName = file.getName(); // NOTE: we should ignore apklib dependencies (IDEA-82976) if (FileUtilRt.extensionEquals(fileName, "jar") || FileUtilRt.extensionEquals(fileName, "class")) { if (!processor.process(file)) { return false; } } } return true; } }); } @Nullable public static Pair<IAndroidTarget, LocalSdk> getAndroidTarget(@NotNull JpsSdk<JpsSimpleElement<JpsAndroidSdkProperties>> sdk, @Nullable CompileContext context, String builderName) { JpsAndroidSdkProperties sdkProperties = sdk.getSdkProperties().getData(); final String targetHashString = sdkProperties.getBuildTargetHashString(); if (targetHashString == null) { if (context != null) { context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, "Cannot parse SDK " + sdk.getParent().getName() + ": build target is not specified")); } return null; } final LocalSdk localSdk = AndroidBuildDataCache.getInstance().getSdk(new File(sdk.getHomePath())); final IAndroidTarget target = localSdk.getTargetFromHashString(targetHashString); if (target == null) { if (context != null) { context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, "Cannot parse SDK '" + sdk.getParent().getName() + "': unknown target " + targetHashString)); } return null; } return Pair.create(target, localSdk); } @Nullable public static BuildMessage.Kind toBuildMessageKind(@NotNull AndroidCompilerMessageKind kind) { switch (kind) { case ERROR: return BuildMessage.Kind.ERROR; case INFORMATION: return BuildMessage.Kind.INFO; case WARNING: return BuildMessage.Kind.WARNING; default: LOG.error("unknown AndroidCompilerMessageKind object " + kind); return null; } } public static void reportExceptionError(@NotNull CompileContext context, @Nullable String filePath, @NotNull Exception exception, @NotNull String builderName) { final String message = exception.getMessage(); if (message != null) { context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, message, filePath)); LOG.debug(exception); } else { context.processMessage(new CompilerMessage(builderName, exception)); } } public static ModuleLevelBuilder.ExitCode handleException(@NotNull CompileContext context, @NotNull Exception e, @NotNull String builderName, @Nullable Logger logger) throws ProjectBuildException { if (logger != null) { logger.info(e); } context.processMessage(new CompilerMessage(builderName, e)); throw new StopBuildException(); } @Nullable public static File getManifestFileForCompilationPath(@NotNull JpsAndroidModuleExtension extension) { return extension.useCustomManifestForCompilation() ? extension.getManifestFileForCompilation() : extension.getManifestFile(); } @Nullable public static AndroidPlatform getAndroidPlatform(@NotNull JpsModule module, @Nullable CompileContext context, String builderName) { final JpsSdk<JpsSimpleElement<JpsAndroidSdkProperties>> sdk = module.getSdk(JpsAndroidSdkType.INSTANCE); if (sdk == null) { if (context != null) { context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.errors.sdk.not.specified", module.getName()))); } return null; } final Pair<IAndroidTarget, LocalSdk> pair = getAndroidTarget(sdk, context, builderName); if (pair == null) { if (context != null) { context.processMessage(new CompilerMessage(builderName, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.errors.sdk.invalid", module.getName()))); } return null; } return new AndroidPlatform(sdk, pair.getFirst(), pair.getSecond()); } @NotNull public static String[] collectResourceDirsForCompilation(@NotNull JpsAndroidModuleExtension extension, boolean withCacheDirs, @NotNull CompileContext context, boolean checkExistence) { final BuildDataPaths dataPaths = context.getProjectDescriptor().dataManager.getDataPaths(); return collectResourceDirsForCompilation(extension, withCacheDirs, dataPaths, checkExistence); } @NotNull public static String[] collectResourceDirsForCompilation(@NotNull JpsAndroidModuleExtension extension, boolean withCacheDirs, @NotNull BuildDataPaths dataPaths, boolean checkExistence) { final Collection<String> result = new OrderedSet<String>(); addCompilableResourceDirsForModule(extension, withCacheDirs, dataPaths, result, checkExistence); for (JpsAndroidModuleExtension depExtension : getAllAndroidDependencies(extension.getModule(), true)) { addCompilableResourceDirsForModule(depExtension, withCacheDirs, dataPaths, result, checkExistence); } return ArrayUtil.toStringArray(result); } private static void addCompilableResourceDirsForModule(JpsAndroidModuleExtension extension, boolean withCacheDirs, BuildDataPaths dataPaths, final Collection<String> result, boolean checkExistence) { if (withCacheDirs) { final File resourcesCacheDir = getResourcesCacheDir(extension.getModule(), dataPaths); if (!checkExistence || resourcesCacheDir.exists()) { result.add(resourcesCacheDir.getPath()); } } for (File resOverlayDir : extension.getResourceOverlayDirs()) { if (resOverlayDir != null && (!checkExistence || resOverlayDir.exists())) { result.add(resOverlayDir.getPath()); } } final File resDir = getResourceDirForCompilationPath(extension); if (resDir != null && (!checkExistence || resDir.exists())) { result.add(resDir.getPath()); } final File generatedResourcesStorage = getGeneratedResourcesStorage(extension.getModule(), dataPaths); if (!checkExistence || generatedResourcesStorage.exists()) { result.add(generatedResourcesStorage.getPath()); } collectResDirectoriesFromAarDeps(extension.getModule(), result); } @Nullable public static File getResourceDirForCompilationPath(@NotNull JpsAndroidModuleExtension extension) { return extension.useCustomResFolderForCompilation() ? extension.getResourceDirForCompilation() : extension.getResourceDir(); } @NotNull public static List<JpsAndroidModuleExtension> getAllAndroidDependencies(@NotNull JpsModule module, boolean librariesOnly) { return AndroidBuildDataCache.getInstance().getAllAndroidDependencies(module, librariesOnly); } public static boolean isLightBuild(@NotNull CompileContext context) { final String typeId = getRunConfigurationTypeId(context); return typeId != null && AndroidCommonUtils.isTestConfiguration(typeId); } @Nullable public static String getRunConfigurationTypeId(@NotNull CompileContext context) { return context.getBuilderParameter("RUN_CONFIGURATION_TYPE_ID"); } public static boolean isReleaseBuild(@NotNull CompileContext context) { if (Boolean.parseBoolean(context.getBuilderParameter(AndroidCommonUtils.RELEASE_BUILD_OPTION))) { return true; } for (JpsArtifact artifact : getAndroidArtifactsToBuild(context)) { final JpsElement props = artifact.getProperties(); if (props instanceof JpsAndroidApplicationArtifactProperties) { final AndroidArtifactSigningMode signingMode = ((JpsAndroidApplicationArtifactProperties)props).getSigningMode(); if (signingMode != AndroidArtifactSigningMode.DEBUG && signingMode != AndroidArtifactSigningMode.DEBUG_WITH_CUSTOM_CERTIFICATE) { return true; } } } return false; } @NotNull public static List<JpsArtifact> getAndroidArtifactsToBuild(@NotNull CompileContext context) { final List<JpsArtifact> artifacts = JpsArtifactService.getInstance().getArtifacts(context.getProjectDescriptor().getProject()); final List<JpsArtifact> result = new ArrayList<JpsArtifact>(); for (JpsArtifact artifact : artifacts) { if (artifact.getArtifactType() instanceof AndroidApplicationArtifactType && context.getScope().isAffected(new ArtifactBuildTarget(artifact))) { result.add(artifact); } } return result; } @NotNull public static File getResourcesCacheDir(@NotNull JpsModule module, @NotNull BuildDataPaths dataPaths) { final File androidStorage = new File(dataPaths.getDataStorageRoot(), ANDROID_STORAGE_DIR); return new File(new File(androidStorage, RESOURCE_CACHE_STORAGE), module.getName()); } @NotNull public static File[] getSourceRootsForModuleAndDependencies(@NotNull JpsModule rootModule) { final Set<File> result = new HashSet<File>(); for (JpsModule module : getRuntimeModuleDeps(rootModule)) { final JpsAndroidModuleExtension extension = getExtension(module); File resDir = null; File resDirForCompilation = null; if (extension != null) { resDir = extension.getResourceDir(); resDirForCompilation = extension.getResourceDirForCompilation(); } for (JpsModuleSourceRoot root : module.getSourceRoots()) { final File rootDir = JpsPathUtil.urlToFile(root.getUrl()); if ((JavaSourceRootType.SOURCE.equals(root.getRootType()) || JavaSourceRootType.TEST_SOURCE.equals(root.getRootType()) && extension != null && extension.isPackTestCode()) && !FileUtil.filesEqual(rootDir, resDir) && !rootDir.equals(resDirForCompilation)) { result.add(rootDir); } } } return result.toArray(new File[result.size()]); } @NotNull public static File[] getJavaOutputRootsForModuleAndDependencies(@NotNull JpsModule rootModule) { final Set<File> result = new HashSet<File>(); for (JpsModule module : getRuntimeModuleDeps(rootModule)) { final JpsAndroidModuleExtension extension = getExtension(module); final File outputDir = JpsJavaExtensionService.getInstance().getOutputDirectory(module, false); if (outputDir != null) { result.add(outputDir); } if (extension != null && extension.isPackTestCode()) { final File testOutputDir = JpsJavaExtensionService.getInstance().getOutputDirectory(module, true); if (testOutputDir != null) { result.add(testOutputDir); } } } return result.toArray(new File[result.size()]); } private static Set<JpsModule> getRuntimeModuleDeps(JpsModule rootModule) { return JpsJavaExtensionService.getInstance().enumerateDependencies( Collections.singletonList(rootModule)).recursively().runtimeOnly().getModules(); } @Nullable public static String getApkPath(@NotNull JpsAndroidModuleExtension extension, @NotNull File outputDirForPackagedArtifacts) { final String apkRelativePath = extension.getApkRelativePath(); final JpsModule module = extension.getModule(); if (apkRelativePath == null || apkRelativePath.length() == 0) { return new File(outputDirForPackagedArtifacts, getApkName(module)).getPath(); } File moduleBaseDirectory = JpsModelSerializationDataService.getBaseDirectory(module); return moduleBaseDirectory != null ? FileUtil.toSystemDependentName(moduleBaseDirectory.getAbsolutePath() + apkRelativePath) : null; } @NotNull public static String getApkName(@NotNull JpsModule module) { return module.getName() + ".apk"; } @NotNull public static File getGeneratedSourcesStorage(@NotNull JpsModule module, BuildDataManager dataManager) { return getGeneratedSourcesStorage(module, dataManager.getDataPaths()); } @NotNull public static File getGeneratedSourcesStorage(@NotNull JpsModule module, final BuildDataPaths dataPaths) { final File targetDataRoot = dataPaths.getTargetDataRoot( new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION)); return getStorageDir(targetDataRoot, GENERATED_SOURCES_FOLDER_NAME); } @NotNull public static File getPreprocessedManifestDirectory(@NotNull JpsModule module, @NotNull BuildDataPaths dataPaths) { final File androidStorage = new File(dataPaths.getDataStorageRoot(), ANDROID_STORAGE_DIR); return new File(new File(androidStorage, PREPROCESSED_MANIFEST_FOLDER_NAME), module.getName()); } @Nullable public static File getPreprocessedManifestFile(@NotNull JpsAndroidModuleExtension extension, @NotNull BuildDataPaths dataPaths) { if (extension.isLibrary() || !extension.isManifestMergingEnabled()) { return getManifestFileForCompilationPath(extension); } final File dir = getPreprocessedManifestDirectory(extension.getModule(), dataPaths); return new File(dir, SdkConstants.FN_ANDROID_MANIFEST_XML); } @NotNull public static File getCopiedSourcesStorage(@NotNull JpsModule module, @NotNull BuildDataPaths dataPaths) { final File targetDataRoot = dataPaths.getTargetDataRoot( new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION)); return getStorageDir(targetDataRoot, COPIED_SOURCES_FOLDER_NAME); } @NotNull public static File getGeneratedResourcesStorage(@NotNull JpsModule module, BuildDataManager dataManager) { return getGeneratedResourcesStorage(module, dataManager.getDataPaths()); } @NotNull private static File getGeneratedResourcesStorage(@NotNull JpsModule module, @NotNull BuildDataPaths dataPaths) { final File targetDataRoot = dataPaths.getTargetDataRoot( new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION)); return getStorageDir(targetDataRoot, GENERATED_RESOURCES_DIR_NAME); } @NotNull public static File getStorageFile(@NotNull File dataStorageRoot, @NotNull String storageName) { return new File(getStorageDir(dataStorageRoot, storageName), storageName); } @NotNull public static File getStorageDir(@NotNull File dataStorageRoot, @NotNull String storageName) { return new File(new File(dataStorageRoot, ANDROID_STORAGE_DIR), storageName); } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") @Nullable private static Properties readPropertyFile(@NotNull File file) { final Properties properties = new Properties(); try { properties.load(new FileInputStream(file)); return properties; } catch (IOException e) { LOG.info(e); } return null; } @Nullable public static Pair<String, File> getProjectPropertyValue(@NotNull JpsAndroidModuleExtension extension, @NotNull String propertyKey) { final File root; try { root = getMainContentRoot(extension); } catch (IOException e) { return null; } if (root == null) { return null; } final File projectProperties = new File(root, SdkConstants.FN_PROJECT_PROPERTIES); if (projectProperties.exists()) { final Properties properties = readPropertyFile(projectProperties); if (properties != null) { final String value = properties.getProperty(propertyKey); if (value != null) { return Pair.create(value, projectProperties); } } } return null; } @NotNull public static Set<String> getGenDirs(@NotNull JpsAndroidModuleExtension extension) throws IOException { final Set<String> result = new HashSet<String>(); File dir = extension.getAaptGenDir(); if (dir != null) { result.add(dir.getPath()); } dir = extension.getAidlGenDir(); if (dir != null) { result.add(dir.getPath()); } return result; } @Nullable public static JpsAndroidModuleExtension getPackagedFacet(@NotNull JpsArtifact artifact) { final List<JpsAndroidModuleExtension> facets = getAllPackagedFacets(artifact); return facets.size() == 1 ? facets.get(0) : null; } @NotNull public static List<JpsAndroidModuleExtension> getAllPackagedFacets(JpsArtifact artifact) { final List<JpsAndroidModuleExtension> extensions = new ArrayList<JpsAndroidModuleExtension>(); JpsArtifactUtil.processPackagingElements(artifact.getRootElement(), new Processor<JpsPackagingElement>() { @Override public boolean process(JpsPackagingElement element) { if (element instanceof JpsAndroidFinalPackageElement) { final JpsModuleReference reference = ((JpsAndroidFinalPackageElement)element).getModuleReference(); final JpsModule module = reference != null ? reference.resolve() : null; final JpsAndroidModuleExtension extension = module != null ? getExtension(module) : null; if (extension != null) { extensions.add(extension); } } return true; } }); return extensions; } @NotNull private static File[] toFiles(@NotNull String[] paths) { final File[] files = new File[paths.length]; for (int i = 0; i < paths.length; i++) { files[i] = new File(paths[i]); } return files; } public static ProGuardOptions getProGuardConfigIfShouldRun(@NotNull CompileContext context, @NotNull JpsAndroidModuleExtension extension) throws IOException { if (extension.isRunProguard()) { return new ProGuardOptions(extension.getProguardConfigFiles(extension.getModule())); } final String cfgPathsStrFromContext = context.getBuilderParameter(AndroidCommonUtils.PROGUARD_CFG_PATHS_OPTION); if (cfgPathsStrFromContext != null && cfgPathsStrFromContext.length() > 0) { final String[] paths = cfgPathsStrFromContext.split(File.pathSeparator); if (paths.length > 0) { final File[] files = toFiles(paths); return new ProGuardOptions(Arrays.asList(files)); } } for (JpsArtifact artifact : getAndroidArtifactsToBuild(context)) { final JpsAndroidModuleExtension facetFromArtifact = getPackagedFacet(artifact); final JpsModule moduleFromArtifact = facetFromArtifact != null ? facetFromArtifact.getModule() : null; if (moduleFromArtifact != null && moduleFromArtifact.equals(extension.getModule())) { final JpsElement props = artifact.getProperties(); if (props instanceof JpsAndroidApplicationArtifactProperties) { final JpsAndroidApplicationArtifactProperties androidProps = (JpsAndroidApplicationArtifactProperties)props; if (androidProps.isRunProGuard()) { final List<String> cfgFileUrls = androidProps.getProGuardCfgFiles(moduleFromArtifact); final List<File> cfgPaths = cfgFileUrls != null ? urlsToFiles(cfgFileUrls) : null; return new ProGuardOptions(cfgPaths); } } } } return null; } @NotNull public static List<File> urlsToFiles(@NotNull List<String> urls) { if (urls.isEmpty()) { return Collections.emptyList(); } final List<File> result = new ArrayList<File>(); for (String path : urls) { result.add(JpsPathUtil.urlToFile(path)); } return result; } /** * Indicates whether the given project is a non-Gradle Android project. * * @param project the given project. * @return {@code true} if the the given project is a non-Gradle Android project, {@code false} otherwise. */ public static boolean isAndroidProjectWithoutGradleFacet(@NotNull JpsProject project) { return isAndroidProjectWithoutGradleFacet(project.getModules()); } /** * Indicates whether the given modules belong to a non-Gradle Android project. * * @param chunk the given modules. * @return {@code true} if the the given modules belong to a non-Gradle Android project, {@code false} otherwise. */ public static boolean isAndroidProjectWithoutGradleFacet(@NotNull ModuleChunk chunk) { return isAndroidProjectWithoutGradleFacet(chunk.getModules()); } private static boolean isAndroidProjectWithoutGradleFacet(@NotNull Collection<JpsModule> modules) { boolean hasAndroidFacet = false; for (JpsModule module : modules) { JpsAndroidModuleExtension androidFacet = getExtension(module); if (androidFacet != null) { hasAndroidFacet = true; if (androidFacet.isGradleProject()) { return false; } } } return hasAndroidFacet; } public static void collectRTextFilesFromAarDeps(@NotNull JpsModule module, @NotNull Collection<Pair<String, String>> result) { final ArrayList<String> resDirs = new ArrayList<String>(); collectResDirectoriesFromAarDeps(module, resDirs); for (String dir : resDirs) { final File parent = new File(dir).getParentFile(); final File manifestFile = new File(parent, SdkConstants.FN_ANDROID_MANIFEST_XML); final File rTxt = new File(parent, SdkConstants.FN_RESOURCE_TEXT); if (manifestFile.exists() && rTxt.exists()) { try { final String packageName = parsePackageNameFromManifestFile(manifestFile); if (packageName != null && packageName.length() > 0) { result.add(Pair.create(rTxt.getPath(), packageName)); } } catch (IOException e) { LOG.debug(e); } } } } public static void collectResDirectoriesFromAarDeps(@NotNull JpsModule module, @NotNull Collection<String> result) { final Set<JpsLibrary> libs = JpsJavaExtensionService.getInstance().enumerateDependencies(Collections.singletonList(module)). runtimeOnly().productionOnly().getLibraries(); for (JpsLibrary lib : libs) { final Pair<File, File> pair = getResDirAndClassesJarIfAar(lib); final File resDir = pair != null ? pair.getFirst() : null; if (resDir != null) { result.add(resDir.getPath()); } } } @Nullable public static Pair<File, File> getResDirAndClassesJarIfAar(@NotNull JpsLibrary lib) { final List<File> files = lib.getFiles(JpsOrderRootType.COMPILED); if (files.size() == 1) { final File file = files.get(0); if (file.isDirectory() && SdkConstants.FD_RES.equals(file.getName())) { return Pair.create(file, null); } } else if (files.size() == 2) { File resDir = null; File classesJar = null; for (File file : files) { if (file.isDirectory() && SdkConstants.FD_RES.equals(file.getName())) { resDir = file; } else if (file.isFile() && SdkConstants.FN_CLASSES_JAR.equals(file.getName())) { classesJar = file; } } if (resDir != null && classesJar != null && FileUtil.pathsEqual(resDir.getParent(), classesJar.getParent())) { return Pair.create(resDir, classesJar); } } return null; } @Nullable public static String parsePackageNameFromManifestFile(@NotNull File manifestFile) throws IOException { final InputStream inputStream = new BufferedInputStream(new FileInputStream(manifestFile)); try { final Ref<String> packageName = new Ref<String>(null); FormsParsing.parse(inputStream, new FormsParsing.IXMLBuilderAdapter() { boolean processingManifestTagAttrs = false; @Override public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr) throws Exception { if (MANIFEST_TAG.equals(name)) { processingManifestTagAttrs = true; } } @Override public void addAttribute(String key, String nsPrefix, String nsURI, String value, String type) throws Exception { if (value != null && AndroidCommonUtils.PACKAGE_MANIFEST_ATTRIBUTE.equals(key)) { packageName.set(value.trim()); } } @Override public void elementAttributesProcessed(String name, String nsPrefix, String nsURI) throws Exception { stop(); } }); return packageName.get(); } finally { inputStream.close(); } } }