| package org.jetbrains.jps.android; |
| |
| import com.android.SdkConstants; |
| import com.android.resources.ResourceFolderType; |
| import com.android.resources.ResourceType; |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.internal.build.BuildConfigGenerator; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Key; |
| 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.HashMap; |
| import com.intellij.util.containers.HashSet; |
| import gnu.trove.THashSet; |
| import gnu.trove.TObjectLongHashMap; |
| import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode; |
| import org.jetbrains.android.compiler.tools.AndroidApt; |
| import org.jetbrains.android.compiler.tools.AndroidIdl; |
| import org.jetbrains.android.compiler.tools.AndroidRenderscript; |
| import org.jetbrains.android.util.*; |
| 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.model.AndroidApplicationArtifactType; |
| import org.jetbrains.jps.android.model.JpsAndroidApplicationArtifactProperties; |
| import org.jetbrains.jps.android.model.JpsAndroidModuleExtension; |
| import org.jetbrains.jps.builders.DirtyFilesHolder; |
| import org.jetbrains.jps.builders.FileProcessor; |
| import org.jetbrains.jps.builders.java.ExcludedJavaSourceRootProvider; |
| import org.jetbrains.jps.builders.java.JavaBuilderUtil; |
| import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType; |
| import org.jetbrains.jps.builders.java.JavaSourceRootDescriptor; |
| import org.jetbrains.jps.builders.storage.SourceToOutputMapping; |
| import org.jetbrains.jps.incremental.*; |
| import org.jetbrains.jps.incremental.fs.CompilationRound; |
| 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.messages.ProgressMessage; |
| import org.jetbrains.jps.incremental.storage.BuildDataManager; |
| import org.jetbrains.jps.model.JpsElement; |
| import org.jetbrains.jps.model.artifact.JpsArtifact; |
| import org.jetbrains.jps.model.java.JpsJavaClasspathKind; |
| import org.jetbrains.jps.model.java.JpsJavaExtensionService; |
| import org.jetbrains.jps.model.module.JpsDependencyElement; |
| import org.jetbrains.jps.model.module.JpsModule; |
| import org.jetbrains.jps.model.module.JpsModuleDependency; |
| import org.jetbrains.jps.model.module.JpsModuleSourceRoot; |
| import org.jetbrains.jps.service.JpsServiceManager; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| /** |
| * @author Eugene.Kudelevsky |
| */ |
| public class AndroidSourceGeneratingBuilder extends ModuleLevelBuilder { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.android.AndroidSourceGeneratingBuilder"); |
| |
| @NonNls private static final String ANDROID_VALIDATOR = "android-validator"; |
| @NonNls private static final String ANDROID_IDL_COMPILER = "android-idl-compiler"; |
| @NonNls private static final String ANDROID_RENDERSCRIPT_COMPILER = "android-renderscript-compiler"; |
| @NonNls private static final String ANDROID_BUILD_CONFIG_GENERATOR = "android-buildconfig-generator"; |
| @NonNls private static final String ANDROID_APT_COMPILER = "android-apt-compiler"; |
| @NonNls private static final String ANDROID_GENERATED_SOURCES_PROCESSOR = "android-generated-sources-processor"; |
| @NonNls private static final String BUILDER_NAME = "Android Source Generator"; |
| |
| @NonNls private static final String AIDL_EXTENSION = "aidl"; |
| @NonNls private static final String RENDERSCRIPT_EXTENSION = "rs"; |
| @NonNls private static final String PERMISSION_TAG = "permission"; |
| @NonNls private static final String PERMISSION_GROUP_TAG = "permission-group"; |
| @NonNls private static final String NAME_ATTRIBUTE = "name"; |
| |
| private static final int MIN_PLATFORM_TOOLS_REVISION = 11; |
| private static final int MIN_SDK_TOOLS_REVISION = 19; |
| |
| public static final Key<Boolean> IS_ENABLED = Key.create("_android_source_generator_enabled_"); |
| |
| @NonNls private static final String R_TXT_OUTPUT_DIR_NAME = "r_txt"; |
| |
| public AndroidSourceGeneratingBuilder() { |
| super(BuilderCategory.SOURCE_GENERATOR); |
| } |
| |
| @Override |
| public void buildStarted(CompileContext context) { |
| IS_ENABLED.set(context, true); |
| } |
| |
| @Override |
| public void buildFinished(CompileContext context) { |
| AndroidBuildDataCache.clean(); |
| } |
| |
| @Override |
| public ModuleLevelBuilder.ExitCode build(CompileContext context, |
| ModuleChunk chunk, |
| DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder, |
| OutputConsumer outputConsumer) throws ProjectBuildException { |
| if (!IS_ENABLED.get(context, Boolean.TRUE) || chunk.containsTests() || !AndroidJpsUtil.isAndroidProjectWithoutGradleFacet(chunk)) { |
| return ExitCode.NOTHING_DONE; |
| } |
| |
| try { |
| return doBuild(context, chunk, dirtyFilesHolder); |
| } |
| catch (Exception e) { |
| return AndroidJpsUtil.handleException(context, e, BUILDER_NAME, LOG); |
| } |
| } |
| |
| @Override |
| public List<String> getCompilableFileExtensions() { |
| return Arrays.asList(AIDL_EXTENSION, RENDERSCRIPT_EXTENSION); |
| } |
| |
| private static ModuleLevelBuilder.ExitCode doBuild(CompileContext context, |
| ModuleChunk chunk, |
| DirtyFilesHolder<JavaSourceRootDescriptor, ModuleBuildTarget> dirtyFilesHolder) |
| throws IOException { |
| final Map<JpsModule, MyModuleData> moduleDataMap = computeModuleDatas(chunk.getModules(), context); |
| if (moduleDataMap == null || moduleDataMap.size() == 0) { |
| return ExitCode.ABORT; |
| } |
| |
| if (!checkVersions(moduleDataMap, context)) { |
| return ExitCode.ABORT; |
| } |
| checkAndroidDependencies(moduleDataMap, context); |
| |
| if (!checkArtifacts(context)) { |
| return ExitCode.ABORT; |
| } |
| |
| if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) { |
| if (!clearAndroidStorages(context, chunk.getModules())) { |
| return ExitCode.ABORT; |
| } |
| } |
| |
| final Map<File, ModuleBuildTarget> idlFilesToCompile = new HashMap<File, ModuleBuildTarget>(); |
| final Map<File, ModuleBuildTarget> rsFilesToCompile = new HashMap<File, ModuleBuildTarget>(); |
| |
| dirtyFilesHolder.processDirtyFiles(new FileProcessor<JavaSourceRootDescriptor, ModuleBuildTarget>() { |
| @Override |
| public boolean apply(ModuleBuildTarget target, File file, JavaSourceRootDescriptor sourceRoot) throws IOException { |
| final JpsAndroidModuleExtension extension = AndroidJpsUtil.getExtension(target.getModule()); |
| |
| if (extension == null) { |
| return true; |
| } |
| String fileName = file.getName(); |
| |
| if (FileUtilRt.extensionEquals(fileName, AIDL_EXTENSION)) { |
| idlFilesToCompile.put(file, target); |
| } |
| else if (FileUtilRt.extensionEquals(fileName, RENDERSCRIPT_EXTENSION)) { |
| rsFilesToCompile.put(file, target); |
| } |
| |
| return true; |
| } |
| }); |
| boolean success = true; |
| |
| final BuildDataManager dataManager = context.getProjectDescriptor().dataManager; |
| if (JavaBuilderUtil.isForcedRecompilationAllJavaModules(context)) { |
| for (JpsModule module : moduleDataMap.keySet()) { |
| final File generatedSourcesStorage = AndroidJpsUtil.getGeneratedSourcesStorage(module, dataManager); |
| if (generatedSourcesStorage.exists() && |
| !deleteAndMarkRecursively(generatedSourcesStorage, context, BUILDER_NAME)) { |
| success = false; |
| } |
| |
| final File generatedResourcesStorage = AndroidJpsUtil.getGeneratedResourcesStorage(module, dataManager); |
| if (generatedResourcesStorage.exists() && |
| !deleteAndMarkRecursively(generatedResourcesStorage, context, BUILDER_NAME)) { |
| success = false; |
| } |
| } |
| } |
| |
| if (!success) { |
| return ExitCode.ABORT; |
| } |
| boolean didSomething = false; |
| |
| if (idlFilesToCompile.size() > 0) { |
| if (!runAidlCompiler(context, idlFilesToCompile, moduleDataMap)) { |
| success = false; |
| } |
| didSomething = true; |
| } |
| |
| if (rsFilesToCompile.size() > 0) { |
| if (!runRenderscriptCompiler(context, rsFilesToCompile, moduleDataMap)) { |
| success = false; |
| } |
| didSomething = true; |
| } |
| MyExitStatus status = runAaptCompiler(context, moduleDataMap); |
| |
| if (status == MyExitStatus.FAIL) { |
| success = false; |
| } |
| else if (status == MyExitStatus.OK) { |
| didSomething = true; |
| } |
| status = runBuildConfigGeneration(context, moduleDataMap); |
| |
| if (status == MyExitStatus.FAIL) { |
| success = false; |
| } |
| else if (status == MyExitStatus.OK) { |
| didSomething = true; |
| } |
| |
| if (!success) { |
| return ExitCode.ABORT; |
| } |
| status = copyGeneratedSources(moduleDataMap, dataManager, context); |
| if (status == MyExitStatus.FAIL) { |
| return ExitCode.ABORT; |
| } |
| else if (status == MyExitStatus.OK) { |
| didSomething = true; |
| } |
| |
| if (didSomething) { |
| return ExitCode.OK; |
| } |
| return ExitCode.NOTHING_DONE; |
| } |
| |
| @NotNull |
| private static List<String> filterExcludedByOtherProviders(@NotNull JpsModule module, @NotNull Collection<String> genRoots) { |
| final Set<String> genRootPaths = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY); |
| |
| for (String genRoot : genRoots) { |
| genRootPaths.add(FileUtil.toSystemIndependentName(genRoot)); |
| } |
| final List<String> result = new ArrayList<String>(); |
| final List<JpsModuleSourceRoot> genSourceRoots = new ArrayList<JpsModuleSourceRoot>(); |
| |
| for (JpsModuleSourceRoot root : module.getSourceRoots()) { |
| if (genRootPaths.contains(FileUtil.toSystemIndependentName(root.getFile().getPath()))) { |
| genSourceRoots.add(root); |
| } |
| } |
| final Iterable<ExcludedJavaSourceRootProvider> excludedRootProviders = JpsServiceManager. |
| getInstance().getExtensions(ExcludedJavaSourceRootProvider.class); |
| |
| for (JpsModuleSourceRoot genSourceRoot : genSourceRoots) { |
| boolean excluded = false; |
| |
| for (ExcludedJavaSourceRootProvider provider : excludedRootProviders) { |
| if (!(provider instanceof AndroidExcludedJavaSourceRootProvider) && |
| provider.isExcludedFromCompilation(module, genSourceRoot)) { |
| excluded = true; |
| break; |
| } |
| } |
| final String genRootFilePath = genSourceRoot.getFile().getPath(); |
| |
| if (!excluded) { |
| result.add(genRootFilePath); |
| } |
| } |
| return result; |
| } |
| |
| @NotNull |
| private static MyExitStatus copyGeneratedSources(@NotNull Map<JpsModule, MyModuleData> moduleDataMap, |
| @NotNull BuildDataManager dataManager, |
| @NotNull final CompileContext context) |
| throws IOException { |
| final Ref<Boolean> didSomething = Ref.create(false); |
| final Ref<Boolean> success = Ref.create(true); |
| |
| for (Map.Entry<JpsModule, MyModuleData> entry : moduleDataMap.entrySet()) { |
| final JpsModule module = entry.getKey(); |
| final MyModuleData data = entry.getValue(); |
| |
| if (!data.getAndroidExtension().isCopyCustomGeneratedSources()) { |
| continue; |
| } |
| final ModuleBuildTarget moduleTarget = new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION); |
| final AndroidGenSourcesCopyingStorage storage = context.getProjectDescriptor().dataManager.getStorage( |
| moduleTarget, AndroidGenSourcesCopyingStorage.PROVIDER); |
| |
| final Set<String> genDirs = AndroidJpsUtil.getGenDirs(data.getAndroidExtension()); |
| final List<String> filteredGenDirs = filterExcludedByOtherProviders(module, genDirs); |
| |
| final Set<String> forciblyExcludedDirs = new HashSet<String>(genDirs); |
| forciblyExcludedDirs.removeAll(filteredGenDirs); |
| warnUserAboutForciblyExcludedRoots(forciblyExcludedDirs, context); |
| |
| final AndroidFileSetState savedState = storage.read(); |
| |
| final AndroidFileSetState currentState = new AndroidFileSetState(filteredGenDirs, new Condition<File>() { |
| @Override |
| public boolean value(File file) { |
| try { |
| return shouldBeCopied(file); |
| } |
| catch (IOException e) { |
| return false; |
| } |
| } |
| }, true); |
| |
| if (currentState.equalsTo(savedState)) { |
| continue; |
| } |
| final File outDir = AndroidJpsUtil.getCopiedSourcesStorage(module, dataManager.getDataPaths()); |
| clearDirectoryIfNotEmpty(outDir, context, ANDROID_GENERATED_SOURCES_PROCESSOR); |
| final List<Pair<String, String>> copiedFiles = new ArrayList<Pair<String, String>>(); |
| |
| for (String path : filteredGenDirs) { |
| final File dir = new File(path); |
| |
| if (dir.isDirectory()) { |
| FileUtil.processFilesRecursively(dir, new Processor<File>() { |
| @Override |
| public boolean process(File file) { |
| try { |
| if (!shouldBeCopied(file)) { |
| return true; |
| } |
| final String relPath = FileUtil.getRelativePath(dir, file); |
| final File dstFile = new File(outDir.getPath() + "/" + relPath); |
| final File dstDir = dstFile.getParentFile(); |
| |
| if (!dstDir.exists() && !dstDir.mkdirs()) { |
| context.processMessage(new CompilerMessage( |
| ANDROID_GENERATED_SOURCES_PROCESSOR, BuildMessage.Kind.ERROR, AndroidJpsBundle.message( |
| "android.jps.cannot.create.directory", dstDir.getPath()))); |
| return true; |
| } |
| FileUtil.copy(file, dstFile); |
| copiedFiles.add(Pair.create(file.getPath(), dstFile.getPath())); |
| didSomething.set(true); |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, null, e, ANDROID_GENERATED_SOURCES_PROCESSOR); |
| success.set(false); |
| return true; |
| } |
| return true; |
| } |
| }); |
| } |
| } |
| final File generatedSourcesDir = AndroidJpsUtil.getGeneratedSourcesStorage( |
| module, dataManager.getDataPaths()); |
| final List<String> deletedFiles = new ArrayList<String>(); |
| |
| if (!removeCopiedFilesDuplicatingGeneratedFiles(context, outDir, generatedSourcesDir, deletedFiles)) { |
| success.set(false); |
| continue; |
| } |
| final AndroidBuildTestingManager testingManager = AndroidBuildTestingManager.getTestingManager(); |
| |
| if (testingManager != null) { |
| logGeneratedSourcesProcessing(testingManager, copiedFiles, deletedFiles); |
| } |
| markDirtyRecursively(outDir, context, ANDROID_GENERATED_SOURCES_PROCESSOR, false); |
| storage.saveState(currentState); |
| } |
| if (didSomething.get()) { |
| return success.get() ? MyExitStatus.OK : MyExitStatus.FAIL; |
| } |
| else { |
| return MyExitStatus.NOTHING_CHANGED; |
| } |
| } |
| |
| private static void warnUserAboutForciblyExcludedRoots(@NotNull Set<String> paths, @NotNull CompileContext context) { |
| for (String dir : paths) { |
| final boolean hasFileToCopy = !FileUtil.processFilesRecursively(new File(dir), new Processor<File>() { |
| @Override |
| public boolean process(File file) { |
| try { |
| return !shouldBeCopied(file); |
| } |
| catch (IOException e) { |
| return false; |
| } |
| } |
| }); |
| |
| if (hasFileToCopy) { |
| context.processMessage(new CompilerMessage(ANDROID_GENERATED_SOURCES_PROCESSOR, BuildMessage.Kind.WARNING, |
| "Source root " + FileUtil.toSystemDependentName(dir) + |
| " was forcibly excluded by the IDE, so custom generated files won't be compiled")); |
| } |
| } |
| } |
| |
| private static boolean shouldBeCopied(@NotNull File file) throws IOException { |
| return file.isFile() && |
| (!FileUtilRt.extensionEquals(file.getName(), "java") || |
| !isGeneratedByIdea(file)); |
| } |
| |
| private static boolean removeCopiedFilesDuplicatingGeneratedFiles(final CompileContext context, |
| final File copiedFilesDir, |
| final File generatedSourcesDir, |
| final List<String> deletedFiles) { |
| if (!generatedSourcesDir.isDirectory()) { |
| return true; |
| } |
| final File[] genRoots = generatedSourcesDir.listFiles(); |
| |
| if (genRoots == null || genRoots.length == 0) { |
| return true; |
| } |
| final Ref<Boolean> success = Ref.create(true); |
| |
| FileUtil.processFilesRecursively(copiedFilesDir, new Processor<File>() { |
| @Override |
| public boolean process(File file) { |
| if (!file.isFile()) { |
| return true; |
| } |
| final String relPath = FileUtil.getRelativePath(copiedFilesDir, file); |
| |
| if (relPath != null) { |
| boolean toDelete = false; |
| |
| for (File genRoot : genRoots) { |
| final File genFile = new File(genRoot.getPath() + "/" + relPath); |
| |
| if (genFile.exists()) { |
| LOG.debug("File " + file.getPath() + " duplicates generated file " + genFile.getPath() + ", so it'll be deleted"); |
| toDelete = true; |
| break; |
| } |
| } |
| |
| if (toDelete) { |
| if (!FileUtil.delete(file)) { |
| context.processMessage(new CompilerMessage(ANDROID_GENERATED_SOURCES_PROCESSOR, BuildMessage.Kind.ERROR, |
| "Cannot remove file " + file.getPath())); |
| success.set(false); |
| } |
| else { |
| deletedFiles.add(file.getPath()); |
| } |
| } |
| } |
| return true; |
| } |
| }); |
| return success.get(); |
| } |
| |
| private static void logGeneratedSourcesProcessing(AndroidBuildTestingManager manager, |
| List<Pair<String, String>> copiedFiles, |
| List<String> deletedFiles) { |
| if (copiedFiles.size() == 0 && deletedFiles.size() == 0) { |
| return; |
| } |
| final StringBuilder message = new StringBuilder(ANDROID_GENERATED_SOURCES_PROCESSOR); |
| message.append("\n"); |
| |
| if (copiedFiles.size() > 0) { |
| Collections.sort(copiedFiles, new Comparator<Pair<String, String>>() { |
| @Override |
| public int compare(Pair<String, String> o1, Pair<String, String> o2) { |
| return (o1.getFirst() + o1.getSecond()).compareTo(o2.getFirst() + o2.getSecond()); |
| } |
| }); |
| message.append("Copied files\n"); |
| |
| for (Pair<String, String> pair : copiedFiles) { |
| message.append(pair.getFirst()).append('\n').append(pair.getSecond()).append('\n'); |
| } |
| } |
| |
| if (deletedFiles.size() > 0) { |
| Collections.sort(deletedFiles); |
| message.append("Deleted files\n"); |
| |
| for (String path : deletedFiles) { |
| message.append(path).append('\n'); |
| } |
| } |
| manager.getCommandExecutor().log(message.toString()); |
| } |
| |
| private static boolean isGeneratedByIdea(File file) throws IOException { |
| final String text = FileUtil.loadFile(file); |
| return text.startsWith(AndroidCommonUtils.AUTOGENERATED_JAVA_FILE_HEADER); |
| } |
| |
| private static boolean clearAndroidStorages(@NotNull CompileContext context, @NotNull Collection<JpsModule> modules) { |
| for (JpsModule module : modules) { |
| final File dir = AndroidJpsUtil.getDirectoryForIntermediateArtifacts(context, module); |
| if (dir.exists() && !FileUtil.delete(dir)) { |
| context.processMessage( |
| new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.cannot.delete", dir.getPath()))); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean checkVersions(@NotNull Map<JpsModule, MyModuleData> dataMap, @NotNull CompileContext context) { |
| for (Map.Entry<JpsModule, MyModuleData> entry : dataMap.entrySet()) { |
| final JpsModule module = entry.getKey(); |
| final AndroidPlatform platform = entry.getValue().getPlatform(); |
| |
| boolean success = true; |
| |
| final int platformToolsRevision = platform.getPlatformToolsRevision(); |
| if (platformToolsRevision >= 0 && platformToolsRevision < MIN_PLATFORM_TOOLS_REVISION) { |
| final String message = '[' + |
| module.getName() + |
| "] Incompatible version of Android SDK Platform-tools package. Min version is " + |
| MIN_PLATFORM_TOOLS_REVISION + |
| ". Please, update it though SDK manager"; |
| context.processMessage(new CompilerMessage(ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, message)); |
| success = false; |
| } |
| |
| final int sdkToolsRevision = platform.getSdkToolsRevision(); |
| if (sdkToolsRevision >= 0 && sdkToolsRevision < MIN_SDK_TOOLS_REVISION) { |
| final String message = '[' + module.getName() + "] Incompatible version " + |
| sdkToolsRevision + " of Android SDK Tools package. Min version is " + |
| MIN_SDK_TOOLS_REVISION + ". Please, update it though SDK manager"; |
| context.processMessage(new CompilerMessage(ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, message)); |
| success = false; |
| } |
| |
| // show error message only for first module, because all modules usualy have the same sdk specified |
| if (!success) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static void checkAndroidDependencies(@NotNull Map<JpsModule, MyModuleData> moduleDataMap, @NotNull CompileContext context) { |
| for (Map.Entry<JpsModule, MyModuleData> entry : moduleDataMap.entrySet()) { |
| final JpsModule module = entry.getKey(); |
| final MyModuleData moduleData = entry.getValue(); |
| final JpsAndroidModuleExtension extension = moduleData.getAndroidExtension(); |
| |
| if (extension.isLibrary()) { |
| continue; |
| } |
| |
| for (JpsDependencyElement item : JpsJavaExtensionService.getInstance() |
| .getDependencies(module, JpsJavaClasspathKind.PRODUCTION_RUNTIME, false)) { |
| if (item instanceof JpsModuleDependency) { |
| final JpsModule depModule = ((JpsModuleDependency)item).getModule(); |
| if (depModule != null) { |
| final JpsAndroidModuleExtension depExtension = AndroidJpsUtil.getExtension(depModule); |
| |
| if (depExtension != null && !depExtension.isLibrary()) { |
| String message = "Suspicious module dependency " + module.getName() + " -> " + depModule.getName() + |
| ": Android application module depends on other application module. Possibly, you should " + |
| "change type of module '" + depModule.getName() + |
| "' to 'Library' or change the dependency scope to 'Provided'."; |
| context.processMessage(new CompilerMessage(ANDROID_VALIDATOR, BuildMessage.Kind.WARNING, message)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static MyExitStatus runBuildConfigGeneration(@NotNull CompileContext context, |
| @NotNull Map<JpsModule, MyModuleData> moduleDataMap) throws IOException { |
| boolean success = true; |
| boolean didSomething = false; |
| |
| for (Map.Entry<JpsModule, MyModuleData> entry : moduleDataMap.entrySet()) { |
| final JpsModule module = entry.getKey(); |
| final ModuleBuildTarget moduleTarget = new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION); |
| final AndroidBuildConfigStateStorage storage = |
| context.getProjectDescriptor().dataManager.getStorage( |
| moduleTarget, AndroidBuildConfigStateStorage.PROVIDER); |
| |
| final MyModuleData moduleData = entry.getValue(); |
| final JpsAndroidModuleExtension extension = AndroidJpsUtil.getExtension(module); |
| |
| final File generatedSourcesDir = AndroidJpsUtil.getGeneratedSourcesStorage(module, context.getProjectDescriptor().dataManager); |
| final File outputDirectory = new File(generatedSourcesDir, AndroidJpsUtil.BUILD_CONFIG_GENERATED_SOURCE_ROOT_NAME); |
| |
| try { |
| if (extension == null || isLibraryWithBadCircularDependency(extension)) { |
| if (!clearDirectoryIfNotEmpty(outputDirectory, context, ANDROID_BUILD_CONFIG_GENERATOR)) { |
| success = false; |
| } |
| continue; |
| } |
| final String packageName = moduleData.getPackage(); |
| final boolean debug = !AndroidJpsUtil.isReleaseBuild(context); |
| final Set<String> libPackages = new HashSet<String>(getDepLibPackages(module).values()); |
| libPackages.remove(packageName); |
| |
| final AndroidBuildConfigState newState = new AndroidBuildConfigState(packageName, libPackages, debug); |
| |
| final AndroidBuildConfigState oldState = storage.getState(module.getName()); |
| if (newState.equalsTo(oldState)) { |
| continue; |
| } |
| didSomething = true; |
| context.processMessage(new ProgressMessage(AndroidJpsBundle.message("android.jps.progress.build.config", module.getName()))); |
| |
| // clear directory, because it may contain obsolete files (ex. if package name was changed) |
| if (!clearDirectory(outputDirectory, context, ANDROID_BUILD_CONFIG_GENERATOR)) { |
| success = false; |
| continue; |
| } |
| |
| if (doBuildConfigGeneration(packageName, libPackages, debug, outputDirectory, context)) { |
| storage.update(module.getName(), newState); |
| markDirtyRecursively(outputDirectory, context, ANDROID_BUILD_CONFIG_GENERATOR, true); |
| } |
| else { |
| storage.update(module.getName(), null); |
| success = false; |
| } |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, null, e, ANDROID_BUILD_CONFIG_GENERATOR); |
| success = false; |
| } |
| } |
| |
| if (!success) { |
| return MyExitStatus.FAIL; |
| } |
| else if (didSomething) { |
| return MyExitStatus.OK; |
| } |
| return MyExitStatus.NOTHING_CHANGED; |
| } |
| |
| private static boolean doBuildConfigGeneration(@NotNull String packageName, |
| @NotNull Collection<String> libPackages, |
| boolean debug, |
| @NotNull File outputDirectory, |
| @NotNull CompileContext context) { |
| if (!doBuildConfigGeneration(packageName, debug, outputDirectory.getPath(), context)) { |
| return false; |
| } |
| |
| for (String libPackage : libPackages) { |
| if (!doBuildConfigGeneration(libPackage, debug, outputDirectory.getPath(), context)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean doBuildConfigGeneration(@NotNull String packageName, |
| boolean debug, |
| @NotNull String outputDirOsPath, |
| @NotNull CompileContext context) { |
| final BuildConfigGenerator generator = new BuildConfigGenerator(outputDirOsPath, packageName, debug); |
| try { |
| generator.generate(); |
| return true; |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, null, e, ANDROID_BUILD_CONFIG_GENERATOR); |
| return false; |
| } |
| } |
| |
| private static boolean runAidlCompiler(@NotNull final CompileContext context, |
| @NotNull Map<File, ModuleBuildTarget> files, |
| @NotNull Map<JpsModule, MyModuleData> moduleDataMap) { |
| if (files.size() > 0) { |
| context.processMessage(new ProgressMessage(AndroidJpsBundle.message("android.jps.progress.aidl"))); |
| } |
| |
| boolean success = true; |
| |
| for (Map.Entry<File, ModuleBuildTarget> entry : files.entrySet()) { |
| final File file = entry.getKey(); |
| final ModuleBuildTarget buildTarget = entry.getValue(); |
| final String filePath = file.getPath(); |
| |
| final MyModuleData moduleData = moduleDataMap.get(buildTarget.getModule()); |
| |
| if (!LOG.assertTrue(moduleData != null)) { |
| context.processMessage( |
| new CompilerMessage(ANDROID_IDL_COMPILER, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.internal.error"))); |
| success = false; |
| continue; |
| } |
| final File generatedSourcesDir = |
| AndroidJpsUtil.getGeneratedSourcesStorage(buildTarget.getModule(), context.getProjectDescriptor().dataManager); |
| final File aidlOutputDirectory = new File(generatedSourcesDir, AndroidJpsUtil.AIDL_GENERATED_SOURCE_ROOT_NAME); |
| |
| if (!aidlOutputDirectory.exists() && !aidlOutputDirectory.mkdirs()) { |
| context.processMessage( |
| new CompilerMessage(ANDROID_IDL_COMPILER, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.cannot.create.directory", aidlOutputDirectory.getPath()))); |
| success = false; |
| continue; |
| } |
| |
| final IAndroidTarget target = moduleData.getPlatform().getTarget(); |
| |
| try { |
| final File[] sourceRoots = AndroidJpsUtil.getSourceRootsForModuleAndDependencies(buildTarget.getModule()); |
| final String[] sourceRootPaths = AndroidJpsUtil.toPaths(sourceRoots); |
| final String packageName = computePackageForFile(context, file); |
| |
| if (packageName == null) { |
| context.processMessage(new CompilerMessage(ANDROID_IDL_COMPILER, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.errors.cannot.compute.package", filePath))); |
| success = false; |
| continue; |
| } |
| |
| final File outputFile = new File(aidlOutputDirectory, packageName.replace('.', File.separatorChar) + |
| File.separator + FileUtil.getNameWithoutExtension(file) + ".java"); |
| final String outputFilePath = outputFile.getPath(); |
| final Map<AndroidCompilerMessageKind, List<String>> messages = |
| AndroidIdl.execute(target, filePath, outputFilePath, sourceRootPaths); |
| |
| addMessages(context, messages, filePath, ANDROID_IDL_COMPILER); |
| |
| if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { |
| success = false; |
| } |
| else if (outputFile.exists()) { |
| final SourceToOutputMapping sourceToOutputMap = context.getProjectDescriptor().dataManager.getSourceToOutputMap(buildTarget); |
| sourceToOutputMap.setOutput(filePath, outputFilePath); |
| FSOperations.markDirty(context, CompilationRound.CURRENT, outputFile); |
| } |
| } |
| catch (final IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, filePath, e, ANDROID_IDL_COMPILER); |
| success = false; |
| } |
| } |
| return success; |
| } |
| |
| private static boolean runRenderscriptCompiler(@NotNull final CompileContext context, |
| @NotNull Map<File, ModuleBuildTarget> files, |
| @NotNull Map<JpsModule, MyModuleData> moduleDataMap) { |
| if (files.size() > 0) { |
| context.processMessage(new ProgressMessage(AndroidJpsBundle.message("android.jps.progress.renderscript"))); |
| } |
| |
| boolean success = true; |
| |
| for (Map.Entry<File, ModuleBuildTarget> entry : files.entrySet()) { |
| final File file = entry.getKey(); |
| final ModuleBuildTarget buildTarget = entry.getValue(); |
| |
| final MyModuleData moduleData = moduleDataMap.get(buildTarget.getModule()); |
| if (!LOG.assertTrue(moduleData != null)) { |
| context.processMessage(new CompilerMessage(ANDROID_RENDERSCRIPT_COMPILER, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.internal.error"))); |
| success = false; |
| continue; |
| } |
| |
| final BuildDataManager dataManager = context.getProjectDescriptor().dataManager; |
| final File generatedSourcesDir = AndroidJpsUtil.getGeneratedSourcesStorage(buildTarget.getModule(), dataManager); |
| final File rsOutputDirectory = new File(generatedSourcesDir, AndroidJpsUtil.RENDERSCRIPT_GENERATED_SOURCE_ROOT_NAME); |
| if (!rsOutputDirectory.exists() && !rsOutputDirectory.mkdirs()) { |
| context.processMessage(new CompilerMessage(ANDROID_RENDERSCRIPT_COMPILER, BuildMessage.Kind.ERROR, AndroidJpsBundle |
| .message("android.jps.cannot.create.directory", rsOutputDirectory.getPath()))); |
| success = false; |
| continue; |
| } |
| |
| final File generatedResourcesDir = AndroidJpsUtil.getGeneratedResourcesStorage(buildTarget.getModule(), dataManager); |
| final File rawDir = new File(generatedResourcesDir, "raw"); |
| |
| if (!rawDir.exists() && !rawDir.mkdirs()) { |
| context.processMessage(new CompilerMessage(ANDROID_RENDERSCRIPT_COMPILER, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.cannot.create.directory", rawDir.getPath()))); |
| success = false; |
| continue; |
| } |
| |
| final AndroidPlatform platform = moduleData.getPlatform(); |
| final IAndroidTarget target = platform.getTarget(); |
| final String sdkLocation = platform.getSdk().getHomePath(); |
| final String filePath = file.getPath(); |
| |
| File tmpOutputDirectory = null; |
| |
| try { |
| tmpOutputDirectory = FileUtil.createTempDirectory("generated-rs-temp", null); |
| final String depFolderPath = getDependencyFolder(context, file, tmpOutputDirectory); |
| |
| final Map<AndroidCompilerMessageKind, List<String>> messages = |
| AndroidRenderscript.execute(sdkLocation, target, filePath, tmpOutputDirectory.getPath(), depFolderPath, rawDir.getPath()); |
| |
| addMessages(context, messages, filePath, ANDROID_RENDERSCRIPT_COMPILER); |
| |
| if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { |
| success = false; |
| } |
| else { |
| final List<File> newFiles = new ArrayList<File>(); |
| AndroidCommonUtils.moveAllFiles(tmpOutputDirectory, rsOutputDirectory, newFiles); |
| |
| final File bcFile = new File(rawDir, FileUtil.getNameWithoutExtension(file) + ".bc"); |
| if (bcFile.exists()) { |
| newFiles.add(bcFile); |
| } |
| final List<String> newFilePaths = Arrays.asList(AndroidJpsUtil.toPaths(newFiles.toArray(new File[newFiles.size()]))); |
| |
| final SourceToOutputMapping sourceToOutputMap = dataManager.getSourceToOutputMap(buildTarget); |
| sourceToOutputMap.setOutputs(filePath, newFilePaths); |
| |
| for (File newFile : newFiles) { |
| FSOperations.markDirty(context, CompilationRound.CURRENT, newFile); |
| } |
| } |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, filePath, e, ANDROID_RENDERSCRIPT_COMPILER); |
| success = false; |
| } |
| finally { |
| if (tmpOutputDirectory != null) { |
| FileUtil.delete(tmpOutputDirectory); |
| } |
| } |
| } |
| return success; |
| } |
| |
| private static MyExitStatus runAaptCompiler(@NotNull final CompileContext context, |
| @NotNull Map<JpsModule, MyModuleData> moduleDataMap) |
| throws IOException { |
| boolean success = true; |
| boolean didSomething = false; |
| |
| for (Map.Entry<JpsModule, MyModuleData> entry : moduleDataMap.entrySet()) { |
| final JpsModule module = entry.getKey(); |
| final ModuleBuildTarget moduleTarget = new ModuleBuildTarget(module, JavaModuleBuildTargetType.PRODUCTION); |
| final AndroidAptStateStorage storage = |
| context.getProjectDescriptor().dataManager.getStorage( |
| moduleTarget, AndroidAptStateStorage.PROVIDER); |
| |
| final MyModuleData moduleData = entry.getValue(); |
| final JpsAndroidModuleExtension extension = moduleData.getAndroidExtension(); |
| |
| final File generatedSourcesDir = AndroidJpsUtil.getGeneratedSourcesStorage(module, context.getProjectDescriptor().dataManager); |
| final File aptOutputDirectory = new File(generatedSourcesDir, AndroidJpsUtil.AAPT_GENERATED_SOURCE_ROOT_NAME); |
| final IAndroidTarget target = moduleData.getPlatform().getTarget(); |
| |
| try { |
| final String[] resPaths = AndroidJpsUtil.collectResourceDirsForCompilation(extension, false, context, true); |
| if (resPaths.length == 0) { |
| // there is no resources in the module |
| if (!clearDirectoryIfNotEmpty(aptOutputDirectory, context, ANDROID_APT_COMPILER)) { |
| success = false; |
| } |
| continue; |
| } |
| final String packageName = moduleData.getPackage(); |
| final File manifestFile; |
| |
| if (extension.isLibrary() || !extension.isManifestMergingEnabled()) { |
| manifestFile = moduleData.getManifestFileForCompiler(); |
| } |
| else { |
| manifestFile = new File(AndroidJpsUtil.getPreprocessedManifestDirectory(module, context. |
| getProjectDescriptor().dataManager.getDataPaths()), SdkConstants.FN_ANDROID_MANIFEST_XML); |
| } |
| |
| if (isLibraryWithBadCircularDependency(extension)) { |
| if (!clearDirectoryIfNotEmpty(aptOutputDirectory, context, ANDROID_APT_COMPILER)) { |
| success = false; |
| } |
| continue; |
| } |
| final Map<JpsModule, String> packageMap = getDepLibPackages(module); |
| packageMap.put(module, packageName); |
| |
| final JpsModule circularDepLibWithSamePackage = findCircularDependencyOnLibraryWithSamePackage(extension, packageMap); |
| if (circularDepLibWithSamePackage != null && !extension.isLibrary()) { |
| final String message = "Generated fields in " + |
| packageName + |
| ".R class in module '" + |
| module.getName() + |
| "' won't be final, because of circular dependency on module '" + |
| circularDepLibWithSamePackage.getName() + |
| "'"; |
| context.processMessage(new CompilerMessage(ANDROID_APT_COMPILER, BuildMessage.Kind.WARNING, message)); |
| } |
| final boolean generateNonFinalFields = extension.isLibrary() || circularDepLibWithSamePackage != null; |
| |
| AndroidAptValidityState oldState; |
| |
| try { |
| oldState = storage.getState(module.getName()); |
| } |
| catch (IOException e) { |
| LOG.info(e); |
| oldState = null; |
| } |
| final Map<String, ResourceFileData> resources = new HashMap<String, ResourceFileData>(); |
| final TObjectLongHashMap<String> valueResFilesTimestamps = new TObjectLongHashMap<String>(); |
| collectResources(resPaths, resources, valueResFilesTimestamps, oldState); |
| |
| final List<ResourceEntry> manifestElements = collectManifestElements(manifestFile); |
| final List<Pair<String, String>> libRTextFilesAndPackages = new ArrayList<Pair<String, String>>(packageMap.size()); |
| |
| for (Map.Entry<JpsModule, String> entry1 : packageMap.entrySet()) { |
| final String libPackage = entry1.getValue(); |
| |
| if (!packageName.equals(libPackage)) { |
| final String libRTxtFilePath = new File(new File(AndroidJpsUtil.getDirectoryForIntermediateArtifacts( |
| context, entry1.getKey()), R_TXT_OUTPUT_DIR_NAME), SdkConstants.FN_RESOURCE_TEXT).getPath(); |
| libRTextFilesAndPackages.add(Pair.create(libRTxtFilePath, libPackage)); |
| } |
| } |
| AndroidJpsUtil.collectRTextFilesFromAarDeps(module, libRTextFilesAndPackages); |
| |
| final File outputDirForArtifacts = AndroidJpsUtil.getDirectoryForIntermediateArtifacts(context, module); |
| final String proguardOutputCfgFilePath; |
| |
| if (AndroidJpsUtil.getProGuardConfigIfShouldRun(context, extension) != null) { |
| if (AndroidJpsUtil.createDirIfNotExist(outputDirForArtifacts, context, BUILDER_NAME) == null) { |
| success = false; |
| continue; |
| } |
| proguardOutputCfgFilePath = new File(outputDirForArtifacts, AndroidCommonUtils.PROGUARD_CFG_OUTPUT_FILE_NAME).getPath(); |
| } |
| else { |
| proguardOutputCfgFilePath = null; |
| } |
| String rTxtOutDirOsPath = null; |
| |
| if (extension.isLibrary() || libRTextFilesAndPackages.size() > 0) { |
| final File rTxtOutDir = new File(outputDirForArtifacts, R_TXT_OUTPUT_DIR_NAME); |
| |
| if (AndroidJpsUtil.createDirIfNotExist(rTxtOutDir, context, BUILDER_NAME) == null) { |
| success = false; |
| continue; |
| } |
| rTxtOutDirOsPath = rTxtOutDir.getPath(); |
| } |
| final AndroidAptValidityState newState = |
| new AndroidAptValidityState(resources, valueResFilesTimestamps, manifestElements, libRTextFilesAndPackages, |
| packageName, proguardOutputCfgFilePath, rTxtOutDirOsPath, extension.isLibrary()); |
| |
| if (newState.equalsTo(oldState)) { |
| // we need to update state, because it also contains myValueResFilesTimestamps not taking into account by equalsTo() |
| storage.update(module.getName(), newState); |
| continue; |
| } |
| didSomething = true; |
| context.processMessage(new ProgressMessage(AndroidJpsBundle.message("android.jps.progress.aapt", module.getName()))); |
| |
| File tmpOutputDir = null; |
| try { |
| tmpOutputDir = FileUtil.createTempDirectory("android_apt_output", "tmp"); |
| final Map<AndroidCompilerMessageKind, List<String>> messages = AndroidApt.compile( |
| target, -1, manifestFile.getPath(), packageName, tmpOutputDir.getPath(), resPaths, libRTextFilesAndPackages, |
| generateNonFinalFields, proguardOutputCfgFilePath, rTxtOutDirOsPath, !extension.isLibrary()); |
| |
| AndroidJpsUtil.addMessages(context, messages, ANDROID_APT_COMPILER, module.getName()); |
| |
| if (messages.get(AndroidCompilerMessageKind.ERROR).size() > 0) { |
| success = false; |
| storage.update(module.getName(), null); |
| } |
| else { |
| if (!AndroidCommonUtils.directoriesContainSameContent(tmpOutputDir, aptOutputDirectory, JavaFilesFilter.INSTANCE)) { |
| if (!deleteAndMarkRecursively(aptOutputDirectory, context, ANDROID_APT_COMPILER)) { |
| success = false; |
| continue; |
| } |
| final File parent = aptOutputDirectory.getParentFile(); |
| if (parent != null && !parent.exists() && !parent.mkdirs()) { |
| context.processMessage(new CompilerMessage(ANDROID_APT_COMPILER, BuildMessage.Kind.ERROR, AndroidJpsBundle.message( |
| "android.jps.cannot.create.directory", parent.getPath()))); |
| success = false; |
| continue; |
| } |
| // we use copyDir instead of moveDirWithContent here, because tmp directory may be located on other disk and |
| // moveDirWithContent doesn't work for such case |
| FileUtil.copyDir(tmpOutputDir, aptOutputDirectory); |
| markDirtyRecursively(aptOutputDirectory, context, ANDROID_APT_COMPILER, true); |
| } |
| storage.update(module.getName(), newState); |
| } |
| } |
| finally { |
| if (tmpOutputDir != null) { |
| FileUtil.delete(tmpOutputDir); |
| } |
| } |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, null, e, ANDROID_APT_COMPILER); |
| success = false; |
| } |
| } |
| if (!success) { |
| return MyExitStatus.FAIL; |
| } |
| else if (didSomething) { |
| return MyExitStatus.OK; |
| } |
| return MyExitStatus.NOTHING_CHANGED; |
| } |
| |
| private static boolean clearDirectory(File dir, CompileContext context, String compilerName) throws IOException { |
| if (!deleteAndMarkRecursively(dir, context, compilerName)) { |
| return false; |
| } |
| |
| if (!dir.mkdirs()) { |
| context.processMessage(new CompilerMessage(compilerName, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.cannot.create.directory", dir.getPath()))); |
| return false; |
| } |
| return true; |
| } |
| |
| private static boolean clearDirectoryIfNotEmpty(@NotNull File dir, @NotNull CompileContext context, String compilerName) |
| throws IOException { |
| if (dir.isDirectory()) { |
| final String[] list = dir.list(); |
| if (list != null && list.length > 0) { |
| return clearDirectory(dir, context, compilerName); |
| } |
| } |
| return true; |
| } |
| |
| private static boolean deleteAndMarkRecursively(@NotNull File dir, @NotNull CompileContext context, @NotNull String compilerName) |
| throws IOException { |
| if (dir.exists()) { |
| final List<File> filesToDelete = collectJavaFilesRecursively(dir); |
| if (!FileUtil.delete(dir)) { |
| context.processMessage( |
| new CompilerMessage(compilerName, BuildMessage.Kind.ERROR, AndroidJpsBundle.message("android.jps.cannot.delete", dir.getPath()))); |
| return false; |
| } |
| |
| for (File file : filesToDelete) { |
| FSOperations.markDeleted(context, file); |
| } |
| } |
| return true; |
| } |
| |
| private static boolean markDirtyRecursively(@NotNull File dir, |
| @NotNull final CompileContext context, |
| @NotNull final String compilerName, |
| final boolean javaFilesOnly) { |
| final Ref<Boolean> success = Ref.create(true); |
| |
| FileUtil.processFilesRecursively(dir, new Processor<File>() { |
| @Override |
| public boolean process(File file) { |
| if (file.isFile() && (!javaFilesOnly || FileUtilRt.extensionEquals(file.getName(), "java"))) { |
| try { |
| FSOperations.markDirty(context, CompilationRound.CURRENT, file); |
| } |
| catch (IOException e) { |
| AndroidJpsUtil.reportExceptionError(context, null, e, compilerName); |
| success.set(false); |
| return false; |
| } |
| } |
| return true; |
| } |
| }); |
| return success.get(); |
| } |
| |
| @NotNull |
| private static List<File> collectJavaFilesRecursively(@NotNull File dir) { |
| final List<File> result = new ArrayList<File>(); |
| |
| FileUtil.processFilesRecursively(dir, new Processor<File>() { |
| @Override |
| public boolean process(File file) { |
| if (file.isFile() && FileUtilRt.extensionEquals(file.getName(), "java")) { |
| result.add(file); |
| } |
| return true; |
| } |
| }); |
| return result; |
| } |
| |
| @NotNull |
| private static Map<JpsModule, String> getDepLibPackages(@NotNull JpsModule module) throws IOException { |
| final Map<JpsModule, String> result = new HashMap<JpsModule, String>(); |
| |
| for (JpsAndroidModuleExtension depExtension : AndroidJpsUtil.getAllAndroidDependencies(module, true)) { |
| final File depManifestFile = AndroidJpsUtil.getManifestFileForCompilationPath(depExtension); |
| |
| if (depManifestFile != null && depManifestFile.exists()) { |
| final String packageName = AndroidJpsUtil.parsePackageNameFromManifestFile(depManifestFile); |
| |
| if (packageName != null) { |
| result.put(depExtension.getModule(), packageName); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @NotNull |
| private static Map<String, ResourceFileData> collectResources(@NotNull String[] resPaths, |
| @NotNull Map<String, ResourceFileData> resDataMap, |
| @NotNull TObjectLongHashMap<String> valueResFilesTimestamps, |
| @Nullable AndroidAptValidityState oldState) |
| throws IOException { |
| |
| for (String resDirPath : resPaths) { |
| final File[] resSubdirs = new File(resDirPath).listFiles(); |
| |
| if (resSubdirs != null) { |
| for (File resSubdir : resSubdirs) { |
| final String resType = AndroidCommonUtils.getResourceTypeByDirName(resSubdir.getName()); |
| |
| if (resType != null) { |
| final File[] resFiles = resSubdir.listFiles(); |
| |
| if (resFiles != null) { |
| for (File resFile : resFiles) { |
| collectResources(resFile, resType, resDataMap, valueResFilesTimestamps, oldState); |
| } |
| } |
| } |
| } |
| } |
| } |
| return resDataMap; |
| } |
| |
| private static void collectResources(@NotNull File resFile, |
| @NotNull String resType, |
| @NotNull Map<String, ResourceFileData> resDataMap, |
| @NotNull TObjectLongHashMap<String> valueResFilesTimestamps, |
| @Nullable AndroidAptValidityState oldState) |
| throws IOException { |
| final String resFilePath = FileUtil.toSystemIndependentName(resFile.getPath()); |
| final long resFileTimestamp = resFile.lastModified(); |
| |
| if (ResourceFolderType.VALUES.getName().equals(resType) && FileUtilRt.extensionEquals(resFile.getName(), "xml")) { |
| ResourceFileData dataToReuse = null; |
| |
| if (oldState != null) { |
| final long oldTimestamp = oldState.getValueResourceFilesTimestamps().get(resFilePath); |
| |
| if (resFileTimestamp == oldTimestamp) { |
| dataToReuse = oldState.getResources().get(resFilePath); |
| } |
| } |
| |
| if (dataToReuse != null) { |
| resDataMap.put(resFilePath, dataToReuse); |
| } |
| else { |
| final List<ResourceEntry> entries = AndroidBuildDataCache.getInstance().getParsedValueResourceFile(resFile); |
| resDataMap.put(resFilePath, new ResourceFileData(entries, 0)); |
| } |
| valueResFilesTimestamps.put(resFilePath, resFileTimestamp); |
| } |
| else { |
| final ResourceType resTypeObj = ResourceType.getEnum(resType); |
| final boolean idProvidingType = |
| resTypeObj != null && ArrayUtil.find(AndroidCommonUtils.ID_PROVIDING_RESOURCE_TYPES, resTypeObj) >= 0; |
| final ResourceFileData data = |
| new ResourceFileData(Collections.<ResourceEntry>emptyList(), idProvidingType ? resFileTimestamp : 0); |
| resDataMap.put(resFilePath, data); |
| } |
| } |
| |
| @NotNull |
| private static List<ResourceEntry> collectManifestElements(@NotNull File manifestFile) throws IOException { |
| final InputStream inputStream = new BufferedInputStream(new FileInputStream(manifestFile)); |
| try { |
| final List<ResourceEntry> result = new ArrayList<ResourceEntry>(); |
| |
| FormsParsing.parse(inputStream, new FormsParsing.IXMLBuilderAdapter() { |
| String myLastName; |
| |
| @Override |
| public void startElement(String name, String nsPrefix, String nsURI, String systemID, int lineNr) |
| throws Exception { |
| myLastName = null; |
| } |
| |
| @Override |
| public void addAttribute(String key, String nsPrefix, String nsURI, String value, String type) |
| throws Exception { |
| if (value != null && NAME_ATTRIBUTE.equals(key)) { |
| myLastName = value; |
| } |
| } |
| |
| @Override |
| public void elementAttributesProcessed(String name, String nsPrefix, String nsURI) throws Exception { |
| if (myLastName != null && PERMISSION_TAG.equals(name) || PERMISSION_GROUP_TAG.equals(name)) { |
| assert myLastName != null; |
| result.add(new ResourceEntry(name, myLastName, "")); |
| } |
| } |
| }); |
| |
| return result; |
| } |
| finally { |
| inputStream.close(); |
| } |
| } |
| |
| @Nullable |
| private static String getDependencyFolder(@NotNull CompileContext context, @NotNull File sourceFile, @NotNull File genFolder) { |
| final JavaSourceRootDescriptor descriptor = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context, |
| sourceFile); |
| if (descriptor == null) { |
| return null; |
| } |
| final File sourceRoot = descriptor.root; |
| |
| final File parent = FileUtilRt.getParentFile(sourceFile); |
| if (parent == null) { |
| return null; |
| } |
| |
| if (FileUtil.filesEqual(parent, sourceRoot)) { |
| return genFolder.getPath(); |
| } |
| final String relativePath = FileUtil.getRelativePath(sourceRoot, parent); |
| assert relativePath != null; |
| return genFolder.getPath() + '/' + relativePath; |
| } |
| |
| @Nullable |
| private static Map<JpsModule, MyModuleData> computeModuleDatas(@NotNull Collection<JpsModule> modules, @NotNull CompileContext context) |
| throws IOException { |
| final Map<JpsModule, MyModuleData> moduleDataMap = new HashMap<JpsModule, MyModuleData>(); |
| |
| boolean success = true; |
| |
| for (JpsModule module : modules) { |
| final JpsAndroidModuleExtension extension = AndroidJpsUtil.getExtension(module); |
| if (extension == null) { |
| continue; |
| } |
| |
| final AndroidPlatform platform = AndroidJpsUtil.getAndroidPlatform(module, context, BUILDER_NAME); |
| if (platform == null) { |
| success = false; |
| continue; |
| } |
| |
| final File manifestFile = AndroidJpsUtil.getManifestFileForCompilationPath(extension); |
| if (manifestFile == null || !manifestFile.exists()) { |
| context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, |
| AndroidJpsBundle.message("android.jps.errors.manifest.not.found", module.getName()))); |
| success = false; |
| continue; |
| } |
| |
| final String packageName = AndroidJpsUtil.parsePackageNameFromManifestFile(manifestFile); |
| if (packageName == null || packageName.length() == 0) { |
| context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, AndroidJpsBundle |
| .message("android.jps.errors.package.not.specified", module.getName()))); |
| success = false; |
| continue; |
| } |
| |
| if (!AndroidCommonUtils.contains2Identifiers(packageName)) { |
| context |
| .processMessage(new CompilerMessage(BUILDER_NAME, extension.isLibrary() ? BuildMessage.Kind.WARNING : BuildMessage.Kind.ERROR, |
| AndroidJpsBundle |
| .message("android.jps.errors.incorrect.package.name", module.getName()))); |
| success = false; |
| continue; |
| } |
| |
| moduleDataMap.put(module, new MyModuleData(platform, extension, manifestFile, packageName)); |
| } |
| |
| return success ? moduleDataMap : null; |
| } |
| |
| @Nullable |
| private static String computePackageForFile(@NotNull CompileContext context, @NotNull File file) throws IOException { |
| final JavaSourceRootDescriptor descriptor = context.getProjectDescriptor().getBuildRootIndex().findJavaRootDescriptor(context, file); |
| if (descriptor == null) { |
| return null; |
| } |
| |
| final String relPath = FileUtil.getRelativePath(descriptor.root, FileUtilRt.getParentFile(file)); |
| if (relPath == null) { |
| return null; |
| } |
| |
| return FileUtil.toSystemIndependentName(relPath).replace('/', '.'); |
| } |
| |
| @NotNull |
| @Override |
| public String getPresentableName() { |
| return BUILDER_NAME; |
| } |
| |
| // support for lib<->lib and app<->lib circular dependencies |
| // see IDEA-79737 for details |
| private static boolean isLibraryWithBadCircularDependency(@NotNull JpsAndroidModuleExtension extension) |
| throws IOException { |
| if (!extension.isLibrary()) { |
| return false; |
| } |
| final List<JpsAndroidModuleExtension> dependencies = AndroidJpsUtil.getAllAndroidDependencies(extension.getModule(), false); |
| |
| for (JpsAndroidModuleExtension depExtension : dependencies) { |
| final List<JpsAndroidModuleExtension> depDependencies = AndroidJpsUtil.getAllAndroidDependencies(depExtension.getModule(), true); |
| |
| if (depDependencies.contains(extension) && |
| dependencies.contains(depExtension) && |
| (depExtension.getModule().getName().compareTo(extension.getModule().getName()) < 0 || !depExtension.isLibrary())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static void addMessages(@NotNull CompileContext context, |
| @NotNull Map<AndroidCompilerMessageKind, List<String>> messages, |
| @NotNull String sourcePath, |
| @NotNull String builderName) { |
| for (Map.Entry<AndroidCompilerMessageKind, List<String>> entry : messages.entrySet()) { |
| final AndroidCompilerMessageKind kind = entry.getKey(); |
| final BuildMessage.Kind buildMessageKind = AndroidJpsUtil.toBuildMessageKind(kind); |
| |
| if (buildMessageKind == null) { |
| continue; |
| } |
| |
| for (String message : entry.getValue()) { |
| context.processMessage(new CompilerMessage(builderName, buildMessageKind, message, sourcePath)); |
| } |
| } |
| } |
| |
| @Nullable |
| public static JpsModule findCircularDependencyOnLibraryWithSamePackage(@NotNull JpsAndroidModuleExtension extension, |
| @NotNull Map<JpsModule, String> packageMap) { |
| final String aPackage = packageMap.get(extension.getModule()); |
| if (aPackage == null || aPackage.length() == 0) { |
| return null; |
| } |
| |
| for (JpsAndroidModuleExtension depExtension : AndroidJpsUtil.getAllAndroidDependencies(extension.getModule(), true)) { |
| if (aPackage.equals(packageMap.get(depExtension.getModule()))) { |
| final List<JpsAndroidModuleExtension> depDependencies = AndroidJpsUtil.getAllAndroidDependencies(depExtension.getModule(), false); |
| |
| if (depDependencies.contains(extension)) { |
| // circular dependency on library with the same package |
| return depExtension.getModule(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static boolean checkUnambiguousAndRecursiveArtifacts(CompileContext context, List<JpsArtifact> artifacts) { |
| boolean success = true; |
| |
| for (JpsArtifact artifact : artifacts) { |
| if (artifact.getArtifactType() instanceof AndroidApplicationArtifactType) { |
| final List<JpsAndroidModuleExtension> facets = AndroidJpsUtil.getAllPackagedFacets(artifact); |
| |
| if (facets.size() > 1) { |
| context.processMessage(new CompilerMessage( |
| ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, "Cannot build artifact '" + artifact.getName() + |
| "' because it contains more than one Android package")); |
| success = false; |
| continue; |
| } |
| final String artifactOutputPath = artifact.getOutputFilePath(); |
| |
| if (artifactOutputPath != null && facets.size() > 0) { |
| final JpsAndroidModuleExtension facet = facets.get(0); |
| final String apkPath = AndroidFinalPackageElementBuilder.getApkPath(facet); |
| |
| if (FileUtil.pathsEqual(apkPath, artifactOutputPath)) { |
| context.processMessage(new CompilerMessage( |
| ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, |
| "Incorrect output path for artifact '" + artifact.getName() + "': " + FileUtil.toSystemDependentName(apkPath))); |
| success = false; |
| } |
| } |
| } |
| } |
| return success; |
| } |
| |
| private static boolean checkArtifacts(@NotNull CompileContext context) { |
| final List<JpsArtifact> artifacts = AndroidJpsUtil.getAndroidArtifactsToBuild(context); |
| |
| if (!checkUnambiguousAndRecursiveArtifacts(context, artifacts)) { |
| return false; |
| } |
| |
| final Set<JpsArtifact> debugArtifacts = new HashSet<JpsArtifact>(); |
| final Set<JpsArtifact> releaseArtifacts = new HashSet<JpsArtifact>(); |
| final Map<String, List<JpsArtifact>> moduleName2Artifact = new HashMap<String, List<JpsArtifact>>(); |
| |
| for (JpsArtifact artifact : artifacts) { |
| final JpsElement properties = artifact.getProperties(); |
| |
| if (!(properties instanceof JpsAndroidApplicationArtifactProperties)) { |
| continue; |
| } |
| |
| final AndroidArtifactSigningMode mode = ((JpsAndroidApplicationArtifactProperties)properties).getSigningMode(); |
| |
| if (mode == AndroidArtifactSigningMode.DEBUG || mode == AndroidArtifactSigningMode.DEBUG_WITH_CUSTOM_CERTIFICATE) { |
| debugArtifacts.add(artifact); |
| } |
| else { |
| releaseArtifacts.add(artifact); |
| } |
| final JpsAndroidModuleExtension facet = AndroidJpsUtil.getPackagedFacet(artifact); |
| |
| if (facet != null) { |
| final String moduleName = facet.getModule().getName(); |
| List<JpsArtifact> list = moduleName2Artifact.get(moduleName); |
| |
| if (list == null) { |
| list = new ArrayList<JpsArtifact>(); |
| moduleName2Artifact.put(moduleName, list); |
| } |
| list.add(artifact); |
| } |
| } |
| boolean success = true; |
| |
| if (debugArtifacts.size() > 0 && releaseArtifacts.size() > 0) { |
| final String message = "Cannot build debug and release Android artifacts in the same session\n" + |
| "Debug artifacts: " + artifactsToString(debugArtifacts) + "\n" + |
| "Release artifacts: " + artifactsToString(releaseArtifacts); |
| context.processMessage(new CompilerMessage(ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, message)); |
| success = false; |
| } |
| |
| if (releaseArtifacts.size() > 0 && |
| AndroidJpsUtil.getRunConfigurationTypeId(context) != null) { |
| final String message = "Cannot build release Android artifacts in the 'make before run' session\n" + |
| "Release artifacts: " + artifactsToString(releaseArtifacts); |
| context.processMessage(new CompilerMessage(ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, message)); |
| success = false; |
| } |
| |
| for (Map.Entry<String, List<JpsArtifact>> entry : moduleName2Artifact.entrySet()) { |
| final List<JpsArtifact> list = entry.getValue(); |
| final String moduleName = entry.getKey(); |
| |
| if (list.size() > 1) { |
| final JpsArtifact firstArtifact = list.get(0); |
| final Object[] firstArtifactProGuardOptions = getProGuardOptions(firstArtifact); |
| |
| for (int i = 1; i < list.size(); i++) { |
| final JpsArtifact artifact = list.get(i); |
| if (!Arrays.equals(getProGuardOptions(artifact), firstArtifactProGuardOptions)) { |
| context.processMessage(new CompilerMessage( |
| ANDROID_VALIDATOR, BuildMessage.Kind.ERROR, "Artifacts related to the same module '" + |
| moduleName + |
| "' have different ProGuard options: " + |
| firstArtifact.getName() + |
| ", " + |
| artifact.getName())); |
| success = false; |
| break; |
| } |
| } |
| } |
| } |
| |
| return success; |
| } |
| |
| @NotNull |
| private static Object[] getProGuardOptions(@NotNull JpsArtifact artifact) { |
| final JpsElement properties = artifact.getProperties(); |
| |
| if (properties instanceof JpsAndroidApplicationArtifactProperties) { |
| final JpsAndroidApplicationArtifactProperties p = (JpsAndroidApplicationArtifactProperties)properties; |
| final boolean runProGuard = p.isRunProGuard(); |
| |
| return runProGuard |
| ? new Object[]{runProGuard, p.getProGuardCfgFiles()} |
| : new Object[]{runProGuard}; |
| } |
| return ArrayUtil.EMPTY_OBJECT_ARRAY; |
| } |
| |
| @NotNull |
| private static String artifactsToString(Collection<JpsArtifact> artifacts) { |
| final StringBuilder result = new StringBuilder(); |
| |
| for (JpsArtifact artifact : artifacts) { |
| if (result.length() > 0) { |
| result.append(", "); |
| } |
| result.append(artifact.getName()); |
| } |
| return result.toString(); |
| } |
| |
| private static class MyModuleData { |
| private final AndroidPlatform myPlatform; |
| private final JpsAndroidModuleExtension myAndroidExtension; |
| private final File myManifestFileForCompiler; |
| private final String myPackage; |
| |
| private MyModuleData(@NotNull AndroidPlatform platform, |
| @NotNull JpsAndroidModuleExtension extension, |
| @NotNull File manifestFileForCompiler, |
| @NotNull String aPackage) { |
| myPlatform = platform; |
| myAndroidExtension = extension; |
| myManifestFileForCompiler = manifestFileForCompiler; |
| myPackage = aPackage; |
| } |
| |
| @NotNull |
| public AndroidPlatform getPlatform() { |
| return myPlatform; |
| } |
| |
| @NotNull |
| public JpsAndroidModuleExtension getAndroidExtension() { |
| return myAndroidExtension; |
| } |
| |
| @NotNull |
| public File getManifestFileForCompiler() { |
| return myManifestFileForCompiler; |
| } |
| |
| @NotNull |
| public String getPackage() { |
| return myPackage; |
| } |
| } |
| |
| private static enum MyExitStatus { |
| OK, FAIL, NOTHING_CHANGED |
| } |
| } |