blob: 399074e4de24b2b6b4c55d7edbff379910516612 [file] [log] [blame]
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 || getResDirAndJarsIfAar(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, List<File>> pair = getResDirAndJarsIfAar(lib);
final File resDir = pair != null ? pair.getFirst() : null;
if (resDir != null) {
result.add(resDir.getPath());
}
}
}
@Nullable
public static Pair<File, List<File>> getResDirAndJarsIfAar(@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;
List<File> allJars = new ArrayList<File>();
for (File file : files) {
if (file.isDirectory()) {
if (SdkConstants.FD_RES.equals(file.getName())) {
resDir = file;
}
else {
return null;
}
}
else if (file.isFile()) {
if (SdkConstants.FN_CLASSES_JAR.equals(file.getName())) {
classesJar = file;
}
if (FileUtilRt.extensionEquals(file.getName(), "jar")) {
allJars.add(file);
}
else {
return null;
}
}
}
if (resDir != null && classesJar != null && FileUtil.pathsEqual(resDir.getParent(), classesJar.getParent())) {
return Pair.create(resDir, allJars);
}
}
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();
}
}
}