package com.intellij.lang.javascript.flex.build; import com.intellij.ProjectTopics; import com.intellij.flex.model.bc.BuildConfigurationNature; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration; import com.intellij.lang.javascript.flex.projectStructure.model.impl.Factory; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.ModuleListener; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.*; public class FlexCompilerDependenciesCache { private final Project myProject; private final Map<Module, Collection<BCInfo>> myCache = new THashMap<>(); private static final String[] TAGS_FOR_FILE_PATHS_IN_CONFIG_FILE = {"<flex-config><compiler><external-library-path><path-element>", "<flex-config><compiler><local-font-paths><path-element>", "<flex-config><compiler><library-path><path-element>", "<flex-config><compiler><namespaces><namespace><manifest>", /*"<flex-config><compiler><source-path><path-element>", "<flex-config><include-sources><path-element>",*/ "<flex-config><compiler><theme><filename>", "<flex-config><include-file><path>", "<flex-config><include-stylesheet><path>", "<flex-config><file-specs><path-element>", "<flex-config><compiler><include-libraries><library>", "<flex-config><compiler><local-fonts-snapshot>", "<flex-config><compiler><defaults-css-url>", "<flex-config><compiler><defaults-css-files><filename>", "<flex-config><load-config>", "<flex-config><load-externs>", "<flex-config><link-report>", "<flex-config><services>", "<flex-config><metadata><raw-metadata>", // "<flex-config><output>" intentionally excluded, because already handled }; public FlexCompilerDependenciesCache(final Project project) { myProject = project; project.getMessageBus().connect(project).subscribe(ProjectTopics.MODULES, new ModuleListener() { public void moduleRemoved(@NotNull final Project project, @NotNull final Module module) { myCache.remove(module); } }); } public void clear() { myCache.clear(); } public void markModuleDirty(final Module module) { myCache.remove(module); } public void markBCDirty(final Module module, final FlexBuildConfiguration bc) { final Collection<BCInfo> infosForModule = myCache.get(module); final BCInfo existingInfo = infosForModule == null ? null : findCacheForBC(infosForModule, bc); if (existingInfo != null) { infosForModule.remove(existingInfo); if (infosForModule.isEmpty()) { myCache.remove(module); } } } public void markModuleDirtyIfInSourceRoot(final VirtualFile file) { if (myCache.isEmpty()) return; final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); final Module module = fileIndex.getModuleForFile(file); if (module != null && fileIndex.getSourceRootForFile(file) != null && !fileIndex.isInTestSourceContent(file)) { markModuleDirty(module); } } public boolean isNothingChangedSincePreviousCompilation(final Module module, final FlexBuildConfiguration bc) { final Collection<BCInfo> infosForModule = myCache.get(module); final BCInfo existingInfo = infosForModule == null ? null : findCacheForBC(infosForModule, bc); if (existingInfo == null) { return false; } final String[] currentSourceRoots = ModuleRootManager.getInstance(module).getSourceRootUrls(); if (!Arrays.equals(existingInfo.mySourceRootUrls, currentSourceRoots) || existingInfo.timestampsChanged()) { infosForModule.remove(existingInfo); if (infosForModule.isEmpty()) { myCache.remove(module); } return false; } return true; } public void cacheBC(final Module module, final FlexBuildConfiguration bc, final List<VirtualFile> configFiles) { Collection<BCInfo> infosForModule = myCache.get(module); if (infosForModule == null) { infosForModule = new ArrayList<>(); myCache.put(module, infosForModule); } else { final BCInfo existingInfo = findCacheForBC(infosForModule, bc); if (existingInfo != null) { infosForModule.remove(existingInfo); } } final VirtualFile outputFile = FlexCompilationManager.refreshAndFindFileInWriteAction(bc.getActualOutputFilePath()); if (outputFile == null) return; final BCInfo bcInfo = new BCInfo(Factory.getCopy(bc), ModuleRootManager.getInstance(module).getSourceRootUrls()); infosForModule.add(bcInfo); bcInfo.addFileDependency(outputFile.getPath()); final String workDirPath = FlexUtils.getFlexCompilerWorkDirPath(module.getProject(), null); for (VirtualFile configFile : configFiles) { addFileDependencies(bcInfo, configFile, workDirPath); } if (bc.isTempBCForCompilation() && !bc.getCompilerOptions().getAdditionalConfigFilePath().isEmpty()) { bcInfo.addFileDependency(bc.getCompilerOptions().getAdditionalConfigFilePath()); } final BuildConfigurationNature nature = bc.getNature(); if (nature.isApp() && !nature.isWebPlatform()) { if (nature.isDesktopPlatform()) { if (!bc.getAirDesktopPackagingOptions().isUseGeneratedDescriptor()) { bcInfo.addFileDependency(bc.getAirDesktopPackagingOptions().getCustomDescriptorPath()); } } else { if (bc.getAndroidPackagingOptions().isEnabled() && !bc.getAndroidPackagingOptions().isUseGeneratedDescriptor()) { bcInfo.addFileDependency(bc.getAndroidPackagingOptions().getCustomDescriptorPath()); } if (bc.getIosPackagingOptions().isEnabled() && !bc.getIosPackagingOptions().isUseGeneratedDescriptor()) { bcInfo.addFileDependency(bc.getIosPackagingOptions().getCustomDescriptorPath()); } } } } @Nullable private static BCInfo findCacheForBC(final @NotNull Collection<BCInfo> bcInfos, @NotNull final FlexBuildConfiguration bc) { return ContainerUtil.find(bcInfos, info -> info.myBC.isEqual(bc)); } private static void addFileDependencies(final BCInfo bcInfo, final VirtualFile configFile, final String workDirPath) { bcInfo.addFileDependency(configFile.getPath()); try { final Map<String, List<String>> elementsMap = FlexUtils.findXMLElements(configFile.getInputStream(), Arrays.asList(TAGS_FOR_FILE_PATHS_IN_CONFIG_FILE)); for (List<String> filePathList : elementsMap.values()) { for (String filePath : filePathList) { bcInfo.addFileDependency(filePath, configFile.getParent().getPath(), workDirPath); } } } catch (IOException e) {/*ignore*/} } private static class BCInfo { private final FlexBuildConfiguration myBC; private final String[] mySourceRootUrls; private final Collection<Pair<File, Long>> myFileToTimestamp = new ArrayList<>(); private BCInfo(final FlexBuildConfiguration bc, final String[] sourceRootUrls) { myBC = bc; mySourceRootUrls = sourceRootUrls; } private void addFileDependency(final String filePath, final String... potentialBaseDirs) { final File file = new File(FileUtil.toSystemDependentName(filePath)); if (file.exists()) { myFileToTimestamp.add(Pair.create(file, file.lastModified())); } else if (potentialBaseDirs != null) { for (String baseDir : potentialBaseDirs) { final File file1 = new File(FileUtil.toSystemDependentName(baseDir + '/' + filePath)); if (file1.exists()) { myFileToTimestamp.add(Pair.create(file1, file1.lastModified())); break; } } } } public boolean timestampsChanged() { for (Pair<File, Long> fileAndTimestamp : myFileToTimestamp) { if (fileAndTimestamp.first.lastModified() != fileAndTimestamp.second) { return true; } } return false; } } }