package com.intellij.lang.javascript.flex.build; import com.intellij.compiler.CompilerConfiguration; import com.intellij.flex.FlexCommonBundle; import com.intellij.flex.FlexCommonUtils; import com.intellij.flex.build.FlexCompilerConfigFileUtilBase; import com.intellij.flex.model.bc.*; import com.intellij.flex.model.sdk.RslUtil; import com.intellij.javascript.flex.FlexApplicationComponent; import com.intellij.lang.javascript.JavaScriptSupportLoader; import com.intellij.lang.javascript.flex.FlexUtils; import com.intellij.lang.javascript.flex.flexunit.FlexUnitPrecompileTask; import com.intellij.lang.javascript.flex.projectStructure.FlexProjectLevelCompilerOptionsHolder; import com.intellij.lang.javascript.flex.projectStructure.model.*; import com.intellij.lang.javascript.flex.projectStructure.model.FlexBuildConfiguration; import com.intellij.lang.javascript.flex.projectStructure.options.BCUtils; import com.intellij.lang.javascript.flex.projectStructure.options.FlexProjectRootsUtil; import com.intellij.lang.javascript.flex.sdk.FlexSdkUtils; import com.intellij.lang.javascript.flex.sdk.FlexmojosSdkType; import com.intellij.lang.javascript.psi.ecmal4.JSPackageStatement; import com.intellij.lang.javascript.psi.ecmal4.JSQualifiedNamedElement; import com.intellij.lang.javascript.psi.stubs.JSQualifiedElementIndex; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.NullableComputable; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.PathUtil; import com.intellij.util.PlatformUtils; import com.intellij.util.SystemProperties; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.util.*; public class CompilerConfigGenerator { private static final String[] LIB_ORDER = {"framework", "textLayout", "osmf", "spark", "sparkskins", "rpc", "charts", "spark_dmv", "mx", "advancedgrids"}; private final Module myModule; private final FlexBuildConfiguration myBC; private final boolean myFlexUnit; private final boolean myCSS; private final Sdk mySdk; private final boolean myFlexmojos; private final CompilerOptions myModuleLevelCompilerOptions; private final CompilerOptions myProjectLevelCompilerOptions; private CompilerConfigGenerator(final @NotNull Module module, final @NotNull FlexBuildConfiguration bc, final @NotNull CompilerOptions moduleLevelCompilerOptions, final @NotNull CompilerOptions projectLevelCompilerOptions) throws IOException { myModule = module; myBC = bc; myFlexUnit = BCUtils.isFlexUnitBC(myBC); myCSS = BCUtils.isRuntimeStyleSheetBC(bc); mySdk = bc.getSdk(); if (mySdk == null) { throw new IOException(FlexCommonBundle.message("sdk.not.set.for.bc.0.of.module.1", bc.getName(), module.getName())); } myFlexmojos = mySdk.getSdkType() == FlexmojosSdkType.getInstance(); myModuleLevelCompilerOptions = moduleLevelCompilerOptions; myProjectLevelCompilerOptions = projectLevelCompilerOptions; } public static VirtualFile getOrCreateConfigFile(final Module module, final FlexBuildConfiguration bc) throws IOException { final CompilerConfigGenerator generator = new CompilerConfigGenerator(module, bc, FlexBuildConfigurationManager.getInstance(module).getModuleLevelCompilerOptions(), FlexProjectLevelCompilerOptionsHolder.getInstance(module.getProject()).getProjectLevelCompilerOptions()); String text = generator.generateConfigFileText(); if (bc.isTempBCForCompilation()) { final FlexBuildConfiguration originalBC = FlexBuildConfigurationManager.getInstance(module).findConfigurationByName(bc.getName()); final boolean makeExternalLibsMerged = BCUtils.isFlexUnitBC(bc) || (originalBC != null && originalBC.getOutputType() == OutputType.Library); final boolean makeIncludedLibsMerged = BCUtils.isRuntimeStyleSheetBC(bc); text = FlexCompilerConfigFileUtilBase.mergeWithCustomConfigFile(text, bc.getCompilerOptions().getAdditionalConfigFilePath(), makeExternalLibsMerged, makeIncludedLibsMerged); } final String name = getConfigFileName(module, bc.getName(), PlatformUtils.getPlatformPrefix().toLowerCase(), BCUtils.getBCSpecifier(bc)); return getOrCreateConfigFile(name, text); } private String generateConfigFileText() throws IOException { final Element rootElement = new Element(FlexCompilerConfigFileUtilBase.FLEX_CONFIG, FlexApplicationComponent.HTTP_WWW_ADOBE_COM_2006_FLEX_CONFIG); addMandatoryOptions(rootElement); addSourcePaths(rootElement); if (!myFlexmojos) { handleOptionsWithSpecialValues(rootElement); addNamespaces(rootElement); addRootsFromSdk(rootElement); } addLibs(rootElement); addOtherOptions(rootElement); addInputOutputPaths(rootElement); return JDOMUtil.writeElement(rootElement); } private void addMandatoryOptions(final Element rootElement) { if (!BCUtils.isRLMTemporaryBC(myBC) && !BCUtils.isRuntimeStyleSheetBC(myBC) && BCUtils.canHaveRLMsAndRuntimeStylesheets(myBC) && myBC.getRLMs().size() > 0) { addOption(rootElement, CompilerOptionInfo.LINK_REPORT_INFO, getLinkReportFilePath(myModule, myBC.getName())); } if (BCUtils.isRLMTemporaryBC(myBC) && !myBC.getOptimizeFor().isEmpty()) { final String customLinkReportPath = getCustomLinkReportPath(myModule, myBC); final String linkReportPath = StringUtil.notNullize(customLinkReportPath, getLinkReportFilePath(myModule, myBC.getName())); addOption(rootElement, CompilerOptionInfo.LOAD_EXTERNS_INFO, linkReportPath); } addOption(rootElement, CompilerOptionInfo.WARN_NO_CONSTRUCTOR_INFO, "false"); if (myFlexmojos) return; final BuildConfigurationNature nature = myBC.getNature(); final String targetPlayer = nature.isWebPlatform() ? myBC.getDependencies().getTargetPlayer() : FlexCommonUtils.getMaximumTargetPlayer(mySdk.getHomePath()); addOption(rootElement, CompilerOptionInfo.TARGET_PLAYER_INFO, targetPlayer); if (FlexSdkUtils.isAirSdkWithoutFlex(mySdk) || StringUtil.compareVersionNumbers(mySdk.getVersionString(), "4.5") >= 0) { final String swfVersion; if (nature.isWebPlatform()) { swfVersion = FlexCommonUtils.getSwfVersionForTargetPlayer(targetPlayer); } else { String airVersion = getAirVersionIfCustomDescriptor(myBC); if (airVersion == null) { airVersion = FlexCommonUtils.getAirVersion(mySdk.getHomePath(), mySdk.getVersionString()); } swfVersion = airVersion != null ? FlexCommonUtils.getSwfVersionForAirVersion(airVersion) : FlexCommonUtils.getSwfVersionForSdk_THE_WORST_WAY(mySdk.getVersionString()); } addOption(rootElement, CompilerOptionInfo.SWF_VERSION_INFO, swfVersion); } if (nature.isMobilePlatform()) { addOption(rootElement, CompilerOptionInfo.MOBILE_INFO, "true"); addOption(rootElement, CompilerOptionInfo.PRELOADER_INFO, "spark.preloaders.SplashScreen"); } if (!FlexSdkUtils.isAirSdkWithoutFlex(mySdk)) { final String accessible = nature.isMobilePlatform() ? "false" : StringUtil.compareVersionNumbers(mySdk.getVersionString(), "4") >= 0 ? "true" : "false"; addOption(rootElement, CompilerOptionInfo.ACCESSIBLE_INFO, accessible); final String fontManagers = StringUtil.compareVersionNumbers(mySdk.getVersionString(), "4") >= 0 ? "flash.fonts.JREFontManager" + CompilerOptionInfo.LIST_ENTRIES_SEPARATOR + "flash.fonts.BatikFontManager" + CompilerOptionInfo.LIST_ENTRIES_SEPARATOR + "flash.fonts.AFEFontManager" + CompilerOptionInfo.LIST_ENTRIES_SEPARATOR + "flash.fonts.CFFFontManager" : "flash.fonts.JREFontManager" + CompilerOptionInfo.LIST_ENTRIES_SEPARATOR + "flash.fonts.AFEFontManager" + CompilerOptionInfo.LIST_ENTRIES_SEPARATOR + "flash.fonts.BatikFontManager"; addOption(rootElement, CompilerOptionInfo.FONT_MANAGERS_INFO, fontManagers); addOption(rootElement, CompilerOptionInfo.STATIC_RSLS_INFO, "false"); } } @Nullable private static String getAirVersionIfCustomDescriptor(final FlexBuildConfiguration bc) { if (bc.getTargetPlatform() == TargetPlatform.Desktop) { final AirDesktopPackagingOptions packagingOptions = bc.getAirDesktopPackagingOptions(); if (!packagingOptions.isUseGeneratedDescriptor()) { return FlexCommonUtils.parseAirVersionFromDescriptorFile(packagingOptions.getCustomDescriptorPath()); } } else if (bc.getTargetPlatform() == TargetPlatform.Mobile) { final AndroidPackagingOptions androidOptions = bc.getAndroidPackagingOptions(); final IosPackagingOptions iosPackagingOptions = bc.getIosPackagingOptions(); // if at least one of descriptors is generated - return null if (androidOptions.isEnabled() && androidOptions.isUseGeneratedDescriptor() || iosPackagingOptions.isEnabled() && iosPackagingOptions.isUseGeneratedDescriptor()) { return null; } String androidAirVersion = null; String iosAirVersion = null; if (androidOptions.isEnabled() && !androidOptions.isUseGeneratedDescriptor()) { androidAirVersion = FlexCommonUtils.parseAirVersionFromDescriptorFile(androidOptions.getCustomDescriptorPath()); } if (iosPackagingOptions.isEnabled() && !iosPackagingOptions.isUseGeneratedDescriptor()) { iosAirVersion = FlexCommonUtils.parseAirVersionFromDescriptorFile(iosPackagingOptions.getCustomDescriptorPath()); } if (androidAirVersion == null) return iosAirVersion; if (iosAirVersion == null) return androidAirVersion; // return minimal return StringUtil.compareVersionNumbers(androidAirVersion, iosAirVersion) > 0 ? iosAirVersion : androidAirVersion; } return null; } @Nullable private static String getCustomLinkReportPath(final Module module, final FlexBuildConfiguration rlmBC) { final FlexBuildConfiguration appBC = FlexBuildConfigurationManager.getInstance(module).findConfigurationByName(rlmBC.getName()); if (appBC != null) { final List<String> linkReports = FlexCommonUtils.getOptionValues(appBC.getCompilerOptions().getAdditionalOptions(), "link-report"); if (!linkReports.isEmpty()) { final String path = linkReports.get(0); if (new File(path).isFile()) return path; final String absPath = FlexUtils.getFlexCompilerWorkDirPath(module.getProject(), null) + "/" + path; if (new File(absPath).isFile()) return absPath; } else { final String configFilePath = appBC.getCompilerOptions().getAdditionalConfigFilePath(); if (!configFilePath.isEmpty()) { final VirtualFile configFile = LocalFileSystem.getInstance().findFileByPath(configFilePath); if (configFile != null) { try { String path = FlexUtils.findXMLElement(configFile.getInputStream(), "<flex-config><link-report>"); if (path != null) { path = path.trim(); if (new File(path).isFile()) return path; // I have no idea why Flex compiler treats path relative to source root for "link-report" option for (VirtualFile srcRoot : ModuleRootManager.getInstance(module).getSourceRoots()) { final String absPath = srcRoot.getPath() + "/" + path; if (new File(absPath).isFile()) return absPath; } } } catch (IOException ignore) {/*ignore*/} } } } } return null; } /** * Adds options that get incorrect default values inside compiler code if not set explicitly. */ private void handleOptionsWithSpecialValues(final Element rootElement) { for (final CompilerOptionInfo info : CompilerOptionInfo.getOptionsWithSpecialValues()) { final Pair<String, ValueSource> valueAndSource = getValueAndSource(info); final boolean themeForPureAS = myBC.isPureAs() && "compiler.theme".equals(info.ID); if (valueAndSource.second == ValueSource.GlobalDefault && (!valueAndSource.first.isEmpty() || themeForPureAS)) { // do not add empty preloader to Web/Desktop, let compiler take default itself (mx.preloaders.SparkDownloadProgressBar when -compatibility-version >= 4.0 and mx.preloaders.DownloadProgressBar when -compatibility-version < 4.0) addOption(rootElement, info, valueAndSource.first); } } } private void addNamespaces(final Element rootElement) { final StringBuilder namespaceBuilder = new StringBuilder(); FlexSdkUtils.processStandardNamespaces(myBC, (namespace, relativePath) -> { if (namespaceBuilder.length() > 0) { namespaceBuilder.append(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); } namespaceBuilder.append(namespace).append(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR) .append(CompilerOptionInfo.FLEX_SDK_MACRO + "/").append(relativePath); }); if (namespaceBuilder.length() == 0) return; final CompilerOptionInfo info = CompilerOptionInfo.getOptionInfo("compiler.namespaces.namespace"); addOption(rootElement, info, namespaceBuilder.toString()); } private void addRootsFromSdk(final Element rootElement) { final CompilerOptionInfo localeInfo = CompilerOptionInfo.getOptionInfo("compiler.locale"); if (!getValueAndSource(localeInfo).first.isEmpty()) { addOption(rootElement, CompilerOptionInfo.LIBRARY_PATH_INFO, mySdk.getHomePath() + "/frameworks/locale/{locale}"); } final Map<String, String> libNameToRslInfo = new THashMap<>(); for (final String swcUrl : mySdk.getRootProvider().getUrls(OrderRootType.CLASSES)) { final String swcPath = VirtualFileManager.extractPath(StringUtil.trimEnd(swcUrl, JarFileSystem.JAR_SEPARATOR)); if (!swcPath.toLowerCase().endsWith(".swc")) { Logger.getInstance(CompilerConfigGenerator.class.getName()).warn("Unexpected URL in Flex SDK classes: " + swcUrl); continue; } LinkageType linkageType = BCUtils.getSdkEntryLinkageType(swcPath, myBC); // check applicability if (linkageType == null) continue; // resolve default if (linkageType == LinkageType.Default) linkageType = myBC.getDependencies().getFrameworkLinkage(); if (linkageType == LinkageType.Default) { linkageType = FlexCommonUtils.getDefaultFrameworkLinkage(mySdk.getVersionString(), myBC.getNature()); } if (myCSS && linkageType == LinkageType.Include) linkageType = LinkageType.Merged; final CompilerOptionInfo info = linkageType == LinkageType.Merged ? CompilerOptionInfo.LIBRARY_PATH_INFO : linkageType == LinkageType.RSL ? CompilerOptionInfo.LIBRARY_PATH_INFO : linkageType == LinkageType.External ? CompilerOptionInfo.EXTERNAL_LIBRARY_INFO : linkageType == LinkageType.Include ? CompilerOptionInfo.INCLUDE_LIBRARY_INFO : null; assert info != null : swcPath + ": " + linkageType.getShortText(); addOption(rootElement, info, swcPath); if (linkageType == LinkageType.RSL) { final List<String> rslUrls = RslUtil.getRslUrls(mySdk.getHomePath(), swcPath); if (rslUrls.isEmpty()) continue; final StringBuilder rslBuilder = new StringBuilder(); final String firstUrl = rslUrls.get(0); rslBuilder .append(swcPath) .append(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR) .append(firstUrl) .append(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR); if (firstUrl.startsWith("http://")) { rslBuilder.append("http://fpdownload.adobe.com/pub/swz/crossdomain.xml"); } if (rslUrls.size() > 1) { final String secondUrl = rslUrls.get(1); rslBuilder .append(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR) .append(secondUrl) .append(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR); if (secondUrl.startsWith("http://")) { rslBuilder.append("http://fpdownload.adobe.com/pub/swz/crossdomain.xml"); } } final String swcName = PathUtil.getFileName(swcPath); final String libName = swcName.substring(0, swcName.length() - ".swc".length()); libNameToRslInfo.put(libName, rslBuilder.toString()); } } if (myBC.getNature().isLib()) { final String theme = getValueAndSource(CompilerOptionInfo.getOptionInfo("compiler.theme")).first; if (theme != null && theme.toLowerCase().endsWith(".swc")) { addOption(rootElement, CompilerOptionInfo.LIBRARY_PATH_INFO, theme); } } addRslInfo(rootElement, libNameToRslInfo); } private void addRslInfo(final Element rootElement, final Map<String, String> libNameToRslInfo) { if (libNameToRslInfo.isEmpty()) return; // RSL order is important! for (final String libName : LIB_ORDER) { final String rslInfo = libNameToRslInfo.remove(libName); if (rslInfo != null) { final CompilerOptionInfo option = StringUtil.split(rslInfo, CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR, true, false).size() == 3 ? CompilerOptionInfo.RSL_ONE_URL_PATH_INFO : CompilerOptionInfo.RSL_TWO_URLS_PATH_INFO; addOption(rootElement, option, rslInfo); } } // now add other in random order, though up to Flex SDK 4.5.1 the map should be empty at this stage for (final String rslInfo : libNameToRslInfo.values()) { final CompilerOptionInfo option = StringUtil.split(rslInfo, CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR, true, false).size() == 3 ? CompilerOptionInfo.RSL_ONE_URL_PATH_INFO : CompilerOptionInfo.RSL_TWO_URLS_PATH_INFO; addOption(rootElement, option, rslInfo); } } private void addLibs(final Element rootElement) { for (final DependencyEntry entry : myBC.getDependencies().getEntries()) { LinkageType linkageType = entry.getDependencyType().getLinkageType(); if (linkageType == LinkageType.Test) { if (myFlexUnit) { linkageType = LinkageType.Merged; } else { continue; } } if (myCSS && linkageType == LinkageType.Include) linkageType = LinkageType.Merged; if (entry instanceof BuildConfigurationEntry) { if (linkageType == LinkageType.LoadInRuntime) continue; final FlexBuildConfiguration dependencyBC = ((BuildConfigurationEntry)entry).findBuildConfiguration(); if (dependencyBC != null && FlexCommonUtils.checkDependencyType(myBC.getOutputType(), dependencyBC.getOutputType(), linkageType)) { addLib(rootElement, dependencyBC.getActualOutputFilePath(), linkageType); } } else if (entry instanceof ModuleLibraryEntry) { final LibraryOrderEntry orderEntry = FlexProjectRootsUtil.findOrderEntry((ModuleLibraryEntry)entry, ModuleRootManager.getInstance(myModule)); if (orderEntry != null) { addLibraryRoots(rootElement, orderEntry.getRootFiles(OrderRootType.CLASSES), linkageType); } } else if (entry instanceof SharedLibraryEntry) { final Library library = FlexProjectRootsUtil.findOrderEntry(myModule.getProject(), (SharedLibraryEntry)entry); if (library != null) { addLibraryRoots(rootElement, library.getFiles((OrderRootType.CLASSES)), linkageType); } } } if (myFlexUnit) { final Collection<String> flexUnitLibNames = FlexCommonUtils .getFlexUnitSupportLibNames(myBC.getNature(), myBC.getDependencies().getComponentSet(), getPathToFlexUnitMainClass(myModule.getProject(), myBC.getNature(), myBC.getMainClass())); for (String libName : flexUnitLibNames) { final String libPath = FlexCommonUtils.getPathToBundledJar(libName); final VirtualFile file = LocalFileSystem.getInstance().findFileByPath(libPath); assert file != null; addLibraryRoots(rootElement, new VirtualFile[]{file}, LinkageType.Merged); } } } private void addLibraryRoots(final Element rootElement, final VirtualFile[] libClassRoots, final LinkageType linkageType) { for (VirtualFile libFile : libClassRoots) { libFile = FlexCompilerHandler.getRealFile(libFile); if (libFile == null) continue; if (libFile.isDirectory()) { addOption(rootElement, CompilerOptionInfo.SOURCE_PATH_INFO, libFile.getPath()); } else { if ("ane".equalsIgnoreCase(libFile.getExtension())) { addLib(rootElement, libFile.getPath(), LinkageType.External); } else if ("swc".equalsIgnoreCase(libFile.getExtension())) { // "airglobal.swc" and "playerglobal.swc" file names are hardcoded in Flex compiler // including libraries like "playerglobal-3.5.0.12683-9.swc" may lead to error at runtime like "VerifyError Error #1079: Native methods are not allowed in loaded code." // so here we just skip including such libraries in config file. // Compilation should be ok because base flexmojos config file contains correct reference to its copy in target/classes/libraries/playerglobal.swc final String libFileName = libFile.getName().toLowerCase(); if (libFileName.startsWith("airglobal") && !libFileName.equals("airglobal.swc") || libFileName.startsWith("playerglobal") && !libFileName.equals("playerglobal.swc")) { continue; } addLib(rootElement, libFile.getPath(), linkageType); } } } } private void addLib(final Element rootElement, final String swcPath, final LinkageType linkageType) { final CompilerOptionInfo info = linkageType == LinkageType.Merged || linkageType == LinkageType.RSL ? CompilerOptionInfo.LIBRARY_PATH_INFO : linkageType == LinkageType.External ? CompilerOptionInfo.EXTERNAL_LIBRARY_INFO : linkageType == LinkageType.Include ? CompilerOptionInfo.INCLUDE_LIBRARY_INFO : null; assert info != null : swcPath + ": " + linkageType; addOption(rootElement, info, swcPath); if (linkageType == LinkageType.RSL) { // todo add RSL URLs } } private void addSourcePaths(final Element rootElement) { final String localeValue = getValueAndSource(CompilerOptionInfo.getOptionInfo("compiler.locale")).first; final List<String> locales = StringUtil.split(localeValue, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); // when adding source paths we respect locales set both in UI and in Additional compiler options locales.addAll(FlexCommonUtils.getOptionValues(myProjectLevelCompilerOptions.getAdditionalOptions(), "locale", "compiler.locale")); locales.addAll(FlexCommonUtils.getOptionValues(myModuleLevelCompilerOptions.getAdditionalOptions(), "locale", "compiler.locale")); locales.addAll(FlexCommonUtils.getOptionValues(myBC.getCompilerOptions().getAdditionalOptions(), "locale", "compiler.locale")); final Set<String> sourcePathsWithLocaleToken = new THashSet<>(); // Set - to avoid duplication of paths like "locale/{locale}" final List<String> sourcePathsWithoutLocaleToken = new LinkedList<>(); for (final VirtualFile sourceRoot : ModuleRootManager.getInstance(myModule).getSourceRoots(includeTestRoots())) { if (locales.contains(sourceRoot.getName())) { sourcePathsWithLocaleToken.add(sourceRoot.getParent().getPath() + "/" + FlexCommonUtils.LOCALE_TOKEN); } else { sourcePathsWithoutLocaleToken.add(sourceRoot.getPath()); } } final StringBuilder sourcePathBuilder = new StringBuilder(); if (myCSS) { final String cssFolderPath = PathUtil.getParentPath(myBC.getMainClass()); if (!sourcePathsWithoutLocaleToken.contains(cssFolderPath)) { sourcePathBuilder.append(cssFolderPath); } } for (final String sourcePath : sourcePathsWithLocaleToken) { if (sourcePathBuilder.length() > 0) { sourcePathBuilder.append(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); } sourcePathBuilder.append(sourcePath); } for (final String sourcePath : sourcePathsWithoutLocaleToken) { if (sourcePathBuilder.length() > 0) { sourcePathBuilder.append(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); } sourcePathBuilder.append(sourcePath); } addOption(rootElement, CompilerOptionInfo.SOURCE_PATH_INFO, sourcePathBuilder.toString()); } private boolean includeTestRoots() { if (myFlexUnit) return true; if (myCSS) return false; if (myBC.getOutputType() != OutputType.Application) return false; final String path = FlexUtils.getPathToMainClassFile(myBC.getMainClass(), myModule); final VirtualFile file = path.isEmpty() ? null : LocalFileSystem.getInstance().findFileByPath(path); return file != null && ModuleRootManager.getInstance(myModule).getFileIndex().isInTestSourceContent(file); } private void addOtherOptions(final Element rootElement) { final Map<String, String> options = new THashMap<>(myProjectLevelCompilerOptions.getAllOptions()); options.putAll(myModuleLevelCompilerOptions.getAllOptions()); options.putAll(myBC.getCompilerOptions().getAllOptions()); final String addOptions = myProjectLevelCompilerOptions.getAdditionalOptions() + " " + myModuleLevelCompilerOptions.getAdditionalOptions() + " " + myBC.getCompilerOptions().getAdditionalOptions(); final List<String> contextRootInAddOptions = FlexCommonUtils.getOptionValues(addOptions, "context-root", "compiler.context-root"); if (options.get("compiler.context-root") == null && contextRootInAddOptions.isEmpty()) { final List<String> servicesInAddOptions = FlexCommonUtils.getOptionValues(addOptions, "services", "compiler.services"); if (options.get("compiler.services") != null || !servicesInAddOptions.isEmpty()) { options.put("compiler.context-root", ""); } } for (final Map.Entry<String, String> entry : options.entrySet()) { addOption(rootElement, CompilerOptionInfo.getOptionInfo(entry.getKey()), entry.getValue()); } final String namespacesRaw = options.get("compiler.namespaces.namespace"); if (namespacesRaw != null && myBC.getOutputType() == OutputType.Library) { final String namespaces = FlexUtils.replacePathMacros(namespacesRaw, myModule, myFlexmojos ? "" : mySdk.getHomePath()); final StringBuilder buf = new StringBuilder(); for (final String listEntry : StringUtil.split(namespaces, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR)) { final int tabIndex = listEntry.indexOf(CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR); assert tabIndex != -1 : namespaces; final String namespace = listEntry.substring(0, tabIndex); if (buf.length() > 0) buf.append(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR); buf.append(namespace); } if (buf.length() > 0) { addOption(rootElement, CompilerOptionInfo.INCLUDE_NAMESPACES_INFO, buf.toString()); } } } private void addInputOutputPaths(final Element rootElement) throws IOException { if (myBC.getOutputType() == OutputType.Library) { addFilesIncludedInSwc(rootElement); if (!myFlexmojos) { addLibClasses(rootElement); } } else { final InfoFromConfigFile info = FlexCompilerConfigFileUtil.getInfoFromConfigFile(myBC.getCompilerOptions().getAdditionalConfigFilePath()); final String pathToMainClassFile = myCSS ? myBC.getMainClass() : myFlexUnit ? getPathToFlexUnitMainClass(myModule.getProject(), myBC.getNature(), myBC.getMainClass()) : FlexUtils.getPathToMainClassFile(myBC.getMainClass(), myModule); if (pathToMainClassFile.isEmpty() && info.getMainClass(myModule) == null && !ApplicationManager.getApplication().isUnitTestMode()) { throw new IOException(FlexCommonBundle.message("bc.incorrect.main.class", myBC.getMainClass(), myBC.getName(), myModule.getName())); } if (!pathToMainClassFile.isEmpty()) { addOption(rootElement, CompilerOptionInfo.MAIN_CLASS_INFO, FileUtil.toSystemIndependentName(pathToMainClassFile)); } } addOption(rootElement, CompilerOptionInfo.OUTPUT_PATH_INFO, myBC.getActualOutputFilePath()); } private void addFilesIncludedInSwc(final Element rootElement) { final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myModule.getProject()).getFileIndex(); final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myModule.getProject()); final Map<String, String> filePathToPathInSwc = new THashMap<>(); for (String path : myBC.getCompilerOptions().getFilesToIncludeInSWC()) { final VirtualFile fileOrDir = LocalFileSystem.getInstance().findFileByPath(path); if (fileOrDir == null || compilerConfiguration.isExcludedFromCompilation(fileOrDir) || FileTypeManager.getInstance().isFileIgnored(fileOrDir)) { continue; } if (fileOrDir.isDirectory()) { final VirtualFile srcRoot = fileIndex.getModuleForFile(fileOrDir) == myModule ? fileIndex.getSourceRootForFile(fileOrDir) : null; final String baseRelativePath = srcRoot == null ? fileOrDir.getName() : VfsUtilCore.getRelativePath(fileOrDir, srcRoot, '/'); assert baseRelativePath != null; VfsUtilCore.visitChildrenRecursively(fileOrDir, new VirtualFileVisitor() { @Override public boolean visitFile(@NotNull final VirtualFile file) { if (FileTypeManager.getInstance().isFileIgnored(file)) return false; if (!file.isDirectory() && !FlexCommonUtils.isSourceFile(file.getName()) && !compilerConfiguration.isExcludedFromCompilation(file)) { final String relativePath = VfsUtilCore.getRelativePath(file, fileOrDir, '/'); final String pathInSwc = baseRelativePath.isEmpty() ? relativePath : baseRelativePath + "/" + relativePath; filePathToPathInSwc.put(file.getPath(), pathInSwc); } return true; } }); } else { final VirtualFile srcRoot = fileIndex.getSourceRootForFile(fileOrDir); final String relativePath = srcRoot == null ? null : VfsUtilCore.getRelativePath(fileOrDir, srcRoot, '/'); final String pathInSwc = StringUtil.notNullize(relativePath, fileOrDir.getName()); filePathToPathInSwc.put(fileOrDir.getPath(), pathInSwc); } } for (Map.Entry<String, String> entry : filePathToPathInSwc.entrySet()) { final String value = entry.getValue() + CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR + entry.getKey(); addOption(rootElement, CompilerOptionInfo.INCLUDE_FILE_INFO, value); } } private void addLibClasses(final Element rootElement) throws IOException { final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myModule.getProject()).getFileIndex(); final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myModule.getProject()); final Ref<Boolean> noClasses = new Ref<>(true); for (final VirtualFile sourceRoot : ModuleRootManager.getInstance(myModule).getSourceRoots(false)) { fileIndex.iterateContentUnderDirectory(sourceRoot, file -> { if (file.isDirectory()) return true; if (!FlexCommonUtils.isSourceFile(file.getName())) return true; if (compilerConfiguration.isExcludedFromCompilation(file)) return true; final String packageText = VfsUtilCore.getRelativePath(file.getParent(), sourceRoot, '.'); assert packageText != null : sourceRoot.getPath() + ": " + file.getPath(); final String qName = (packageText.length() > 0 ? packageText + "." : "") + file.getNameWithoutExtension(); if (isSourceFileWithPublicDeclaration(myModule, file, qName)) { addOption(rootElement, CompilerOptionInfo.INCLUDE_CLASSES_INFO, qName); noClasses.set(false); } return true; }); } if (noClasses.get() && myBC.getCompilerOptions().getFilesToIncludeInSWC().isEmpty() && !ApplicationManager.getApplication().isUnitTestMode()) { throw new IOException(FlexCommonBundle.message("nothing.to.compile.in.library", myModule.getName(), myBC.getName())); } } private void addOption(final Element rootElement, final CompilerOptionInfo info, final String rawValue) { if (!info.isApplicable(mySdk.getVersionString(), myBC.getNature())) { return; } final String value = FlexUtils.replacePathMacros(rawValue, myModule, myFlexmojos ? "" : mySdk.getHomePath()); final String pathInFlexConfig = info.ID.startsWith("compiler.debug") ? "compiler.debug" : info.ID; final List<String> elementNames = StringUtil.split(pathInFlexConfig, "."); Element parentElement = rootElement; for (int i1 = 0; i1 < elementNames.size() - 1; i1++) { parentElement = getOrCreateElement(parentElement, elementNames.get(i1)); } final String elementName = elementNames.get(elementNames.size() - 1); switch (info.TYPE) { case Boolean: case String: case Int: case File: final Element simpleElement = new Element(elementName, parentElement.getNamespace()); simpleElement.setText(value); parentElement.addContent(simpleElement); break; case List: if (info.LIST_ELEMENTS.length == 1) { final Element listHolderElement = getOrCreateElement(parentElement, elementName); for (final String listElementValue : StringUtil.split(value, CompilerOptionInfo.LIST_ENTRIES_SEPARATOR)) { final Element child = new Element(info.LIST_ELEMENTS[0].NAME, listHolderElement.getNamespace()); child.setText(listElementValue); listHolderElement.addContent(child); } } else { for (final String listEntry : StringUtil.split(value, String.valueOf(CompilerOptionInfo.LIST_ENTRIES_SEPARATOR))) { final Element repeatableListHolderElement = new Element(elementName, parentElement.getNamespace()); final List<String> values = StringUtil.split(listEntry, CompilerOptionInfo.LIST_ENTRY_PARTS_SEPARATOR, true, false); assert info.LIST_ELEMENTS.length == values.size() : info.ID + "=" + value; for (int i = 0; i < info.LIST_ELEMENTS.length; i++) { final Element child = new Element(info.LIST_ELEMENTS[i].NAME, repeatableListHolderElement.getNamespace()); child.setText(values.get(i)); repeatableListHolderElement.addContent(child); } parentElement.addContent(repeatableListHolderElement); } } break; default: assert false : info.DISPLAY_NAME; } } private static Element getOrCreateElement(final Element parentElement, final String elementName) { Element child = parentElement.getChild(elementName, parentElement.getNamespace()); if (child == null) { child = new Element(elementName, parentElement.getNamespace()); parentElement.addContent(child); } return child; } private Pair<String, ValueSource> getValueAndSource(final CompilerOptionInfo info) { assert !info.isGroup() : info.DISPLAY_NAME; final String bcLevelValue = myBC.getCompilerOptions().getOption(info.ID); if (bcLevelValue != null) return Pair.create(bcLevelValue, ValueSource.BC); final String moduleLevelValue = myModuleLevelCompilerOptions.getOption(info.ID); if (moduleLevelValue != null) return Pair.create(moduleLevelValue, ValueSource.ModuleDefault); final String projectLevelValue = myProjectLevelCompilerOptions.getOption(info.ID); if (projectLevelValue != null) return Pair.create(projectLevelValue, ValueSource.ProjectDefault); return Pair.create(info.getDefaultValue(mySdk.getVersionString(), myBC.getNature(), myBC.getDependencies().getComponentSet()), ValueSource.GlobalDefault); } private static VirtualFile getOrCreateConfigFile(final String fileName, final String text) throws IOException { final VirtualFile existingConfigFile = FlexCompilationManager.refreshAndFindFileInWriteAction( FlexCommonUtils.getTempFlexConfigsDirPath() + "/" + fileName); if (existingConfigFile != null && existingConfigFile.isValid() && Arrays.equals(text.getBytes(), existingConfigFile.contentsToByteArray())) { return existingConfigFile; } final Ref<VirtualFile> fileRef = new Ref<>(); final Ref<IOException> error = new Ref<>(); final Runnable runnable = new Runnable() { @Override public void run() { fileRef.set(ApplicationManager.getApplication().runWriteAction(new NullableComputable<VirtualFile>() { @Override public VirtualFile compute() { try { final String baseDirPath = FlexCommonUtils.getTempFlexConfigsDirPath(); final VirtualFile baseDir = VfsUtil.createDirectories(baseDirPath); VirtualFile configFile = baseDir.findChild(fileName); if (configFile == null) { configFile = baseDir.createChildData(this, fileName); } VfsUtil.saveText(configFile, text); return configFile; } catch (IOException ex) { error.set(ex); } return null; } })); } }; if (ApplicationManager.getApplication().isUnitTestMode()) { runnable.run(); } else { ApplicationManager.getApplication().invokeAndWait(runnable); } if (!error.isNull()) { throw error.get(); } return fileRef.get(); } private static String getConfigFileName(final Module module, final @Nullable String bcName, final String prefix, final @Nullable String postfix) { final String hash1 = Integer.toHexString((SystemProperties.getUserName() + module.getProject().getName()).hashCode()).toUpperCase(); final String hash2 = Integer.toHexString((module.getName() + StringUtil.notNullize(bcName)).hashCode()).toUpperCase(); return prefix + "-" + hash1 + "-" + hash2 + (postfix == null ? ".xml" : ("-" + postfix.replace(' ', '-') + ".xml")); } private static String getLinkReportFilePath(final Module module, final String bcName) { final String fileName = getConfigFileName(module, bcName, PlatformUtils.getPlatformPrefix().toLowerCase(), "link-report"); return FlexCommonUtils.getTempFlexConfigsDirPath() + "/" + fileName; } private static boolean isSourceFileWithPublicDeclaration(final Module module, final VirtualFile file, final String qName) { return JavaScriptSupportLoader.isMxmlOrFxgFile(file) || ReadAction.compute(() -> { // we include file in compilation if it has (or intended to have) some public declaration (class, namespace, function) which is equivalent to having JSPackageStatement declaration. // But first we try to find it in JSQualifiedElementIndex because it is faster. final Collection<JSQualifiedNamedElement> elements = StubIndex.getInstance() .getElements(JSQualifiedElementIndex.KEY, qName.hashCode(), module.getProject(), GlobalSearchScope.moduleScope(module), JSQualifiedNamedElement.class); if (elements.isEmpty()) { // If SomeClass.as contains IncorrectClass definition - we want to include this class into compilation so that compilation fails. final PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(file); return psiFile != null && PsiTreeUtil.getChildOfType(psiFile, JSPackageStatement.class) != null; } else { return true; } }); } private static String getPathToFlexUnitMainClass(final Project project, final BuildConfigurationNature nature, final String mainClass) { return FlexUnitPrecompileTask.getPathToFlexUnitTempDirectory(project) + "/" + mainClass + FlexCommonUtils.getFlexUnitLauncherExtension(nature); } }