package com.intellij.jps.flex.build; import com.intellij.flex.FlexCommonBundle; import com.intellij.flex.FlexCommonUtils; import com.intellij.flex.build.AirDescriptorOptions; import com.intellij.flex.model.bc.*; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.PathUtilRt; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.Namespace; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.builders.BuildOutputConsumer; import org.jetbrains.jps.incremental.CompileContext; import org.jetbrains.jps.incremental.ProjectBuildException; import org.jetbrains.jps.incremental.messages.BuildMessage; import org.jetbrains.jps.incremental.messages.CompilerMessage; import org.jetbrains.jps.model.JpsEncodingConfigurationService; import org.jetbrains.jps.model.JpsEncodingProjectConfiguration; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.JpsOrderRootType; import org.jetbrains.jps.model.library.sdk.JpsSdk; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; public class FlexBuilderUtils { public static String getCompilerName(final JpsFlexBuildConfiguration bc) { String postfix = bc.isTempBCForCompilation() ? " - " + FlexCommonUtils.getBCSpecifier(bc) : ""; if (!bc.getName().equals(bc.getModule().getName())) postfix += " (module " + bc.getModule().getName() + ")"; return "[" + bc.getName() + postfix + "]"; } public static void performPostCompileActions(final CompileContext context, final @NotNull JpsFlexBuildConfiguration bc, final Collection<String> dirtyFilePaths, final BuildOutputConsumer outputConsumer) throws ProjectBuildException { final JpsSdk<?> sdk = bc.getSdk(); assert sdk != null; LinkageType linkage = bc.getDependencies().getFrameworkLinkage(); if (linkage == LinkageType.Default) { linkage = FlexCommonUtils.getDefaultFrameworkLinkage(sdk.getVersionString(), bc.getNature()); } if (linkage == LinkageType.RSL) { //handleFrameworkRsls(bc, compileInfoMessages); } if (bc.getOutputType() != OutputType.Application || FlexCommonUtils.isRLMTemporaryBC(bc) || FlexCommonUtils.isRuntimeStyleSheetBC(bc)) { return; } switch (bc.getTargetPlatform()) { case Web: if (bc.isUseHtmlWrapper()) { handleHtmlWrapper(context, bc, outputConsumer); } break; case Desktop: handleAirDescriptor(context, outputConsumer, dirtyFilePaths, bc, bc.getAirDesktopPackagingOptions()); break; case Mobile: if (bc.getAndroidPackagingOptions().isEnabled()) { handleAirDescriptor(context, outputConsumer, dirtyFilePaths, bc, bc.getAndroidPackagingOptions()); } if (bc.getIosPackagingOptions().isEnabled()) { handleAirDescriptor(context, outputConsumer, dirtyFilePaths, bc, bc.getIosPackagingOptions()); } break; } } private static void handleHtmlWrapper(final CompileContext context, final JpsFlexBuildConfiguration bc, final BuildOutputConsumer outputConsumer) { final File templateDir = new File(bc.getWrapperTemplatePath()); if (!templateDir.isDirectory()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("html.wrapper.dir.not.found", bc.getWrapperTemplatePath()))); return; } final File templateFile = new File(templateDir, FlexCommonUtils.HTML_WRAPPER_TEMPLATE_FILE_NAME); if (!templateFile.isFile()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("no.index.template.html.file", bc.getWrapperTemplatePath()))); return; } final InfoFromConfigFile info = InfoFromConfigFile.getInfoFromConfigFile(bc.getCompilerOptions().getAdditionalConfigFilePath()); final String outputFolderPath = StringUtil.notNullize(info.getOutputFolderPath(), bc.getOutputFolder()); final String outputFileName = bc.isTempBCForCompilation() ? bc.getOutputFileName() : StringUtil.notNullize(info.getOutputFileName(), bc.getOutputFileName()); final String targetPlayer = StringUtil.notNullize(info.getTargetPlayer(), bc.getDependencies().getTargetPlayer()); final File outputDir = new File(outputFolderPath); if (!outputDir.isDirectory()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("output.folder.does.not.exist", outputFolderPath))); return; } for (File file : templateDir.listFiles()) { if (FlexCommonUtils.HTML_WRAPPER_TEMPLATE_FILE_NAME.equals(file.getName())) { final JpsEncodingProjectConfiguration encodingConfiguration = JpsEncodingConfigurationService.getInstance().getEncodingConfiguration(bc.getModule().getProject()); final String encoding = encodingConfiguration == null ? null : encodingConfiguration.getEncoding(file); String wrapperText; try { try { wrapperText = FileUtil.loadFile(file, encoding); } catch (UnsupportedEncodingException e) { wrapperText = FileUtil.loadFile(file); } } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle .message("failed.to.load.template.file", file.getPath(), e.getMessage()))); return; } if (!wrapperText.contains(FlexCommonUtils.SWF_MACRO)) { context.processMessage( new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("no.swf.macro", file.getPath()))); return; } final String mainClass = StringUtil.notNullize(info.getMainClass(bc.getModule()), bc.getMainClass()); final String fixedText = replaceMacros(wrapperText, FileUtil.getNameWithoutExtension(outputFileName), targetPlayer, FlexCommonUtils.getPathToMainClassFile(mainClass, bc.getModule())); final String wrapperFileName = FlexCommonUtils.getWrapperFileName(bc); try { byte[] bytes; try { bytes = encoding == null ? fixedText.getBytes() : fixedText.getBytes(encoding); } catch (UnsupportedEncodingException e) { bytes = fixedText.getBytes(); } final File outputFile = new File(outputDir, wrapperFileName); FileUtil.writeToFile(outputFile, bytes); outputConsumer.registerOutputFile(outputFile, Collections.singletonList(file.getPath())); } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle .message("failed.to.create.file.in", wrapperFileName, outputDir.getPath(), e.getMessage()))); } } else { try { final File outputFile = new File(outputDir, file.getName()); if (file.isDirectory()) { FileUtil.createDirectory(outputFile); FileUtil.copyDir(file, outputFile); } else { FileUtil.copy(file, outputFile); } outputConsumer.registerOutputFile(outputFile, Collections.singletonList(file.getPath())); } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle .message("failed.to.copy.file", file.getName(), templateDir.getPath(), outputDir.getPath(), e.getMessage()))); } } } } private static String replaceMacros(final String wrapperText, final String outputFileName, final String targetPlayer, final String mainClassPath) { final Map<String, String> replacementMap = new THashMap<>(); replacementMap.put(FlexCommonUtils.SWF_MACRO, outputFileName); replacementMap.put(FlexCommonUtils.TITLE_MACRO, outputFileName); replacementMap.put(FlexCommonUtils.APPLICATION_MACRO, outputFileName); replacementMap.put(FlexCommonUtils.BG_COLOR_MACRO, "#ffffff"); replacementMap.put(FlexCommonUtils.WIDTH_MACRO, "100%"); replacementMap.put(FlexCommonUtils.HEIGHT_MACRO, "100%"); final List<String> versionParts = StringUtil.split(targetPlayer, "."); replacementMap.put(FlexCommonUtils.VERSION_MAJOR_MACRO, versionParts.size() >= 1 ? versionParts.get(0) : "0"); replacementMap.put(FlexCommonUtils.VERSION_MINOR_MACRO, versionParts.size() >= 2 ? versionParts.get(1) : "0"); replacementMap.put(FlexCommonUtils.VERSION_REVISION_MACRO, versionParts.size() >= 3 ? versionParts.get(2) : "0"); String swfMetadata = null; final File mainClassFile = new File(mainClassPath); if (mainClassFile.isFile()) { try { if (FileUtilRt.extensionEquals(mainClassPath, "mxml")) { final Element rootElement = JDOMUtil.load(mainClassFile); Element metadataElement = rootElement.getChild("Metadata", Namespace.getNamespace("http://www.adobe.com/2006/mxml")); if (metadataElement == null) { metadataElement = rootElement.getChild("Metadata", Namespace.getNamespace("http://ns.adobe.com/mxml/2009")); } if (metadataElement != null) { swfMetadata = getSwfMetadata(metadataElement.getTextNormalize()); } } else if (FileUtilRt.extensionEquals(mainClassPath, "as")) { swfMetadata = getSwfMetadata(FileUtil.loadFile(mainClassFile)); } } catch (JDOMException | IOException ignore) {/*unlucky*/} } final Map<String, String> attributesMap = getAttributesMap(swfMetadata); ContainerUtil.putIfNotNull(FlexCommonUtils.TITLE_MACRO, attributesMap.get(FlexCommonUtils.TITLE_ATTR), replacementMap); ContainerUtil.putIfNotNull(FlexCommonUtils.BG_COLOR_MACRO, attributesMap.get(FlexCommonUtils.BG_COLOR_ATTR), replacementMap); ContainerUtil.putIfNotNull(FlexCommonUtils.WIDTH_MACRO, attributesMap.get(FlexCommonUtils.WIDTH_ATTR), replacementMap); ContainerUtil.putIfNotNull(FlexCommonUtils.HEIGHT_MACRO, attributesMap.get(FlexCommonUtils.HEIGHT_ATTR), replacementMap); return FlexCommonUtils.replace(wrapperText, replacementMap); } @Nullable private static String getSwfMetadata(final String text) { // todo use lexer int swfIndex = -1; while ((swfIndex = text.indexOf("[SWF", swfIndex + 1)) > -1) { final String textBefore = text.substring(0, swfIndex); final int lfIndex = Math.max(textBefore.lastIndexOf('\n'), textBefore.lastIndexOf('\r')); final int lineCommentIndex = textBefore.lastIndexOf("//"); if (lineCommentIndex <= lfIndex) { final int endIndex = text.indexOf(']', swfIndex); return endIndex > swfIndex ? text.substring(swfIndex, endIndex + 1) : null; } } return null; } private static Map<String, String> getAttributesMap(final String metadata) { if (metadata == null) return Collections.emptyMap(); final THashMap<String, String> result = new THashMap<>(); final int beginIndex = metadata.indexOf('('); final int endIndex = metadata.lastIndexOf(')'); if (endIndex > beginIndex) { for (String attribute : StringUtil.split(metadata.substring(beginIndex + 1, endIndex), ",")) { final int eqIndex = attribute.indexOf('='); if (eqIndex > 0) { final String name = attribute.substring(0, eqIndex).trim(); final String value = StringUtil.stripQuotesAroundValue(attribute.substring(eqIndex + 1).trim()); result.put(name, value); } } } return result; } private static void handleAirDescriptor(final CompileContext context, final BuildOutputConsumer outputConsumer, final Collection<String> dirtyFilePaths, final JpsFlexBuildConfiguration bc, final JpsAirPackagingOptions packagingOptions) { if (packagingOptions.isUseGeneratedDescriptor()) { final boolean android = packagingOptions instanceof JpsAndroidPackagingOptions; final boolean ios = packagingOptions instanceof JpsIosPackagingOptions; final String descriptorFileName = FlexCommonUtils.getGeneratedAirDescriptorName(bc, packagingOptions); generateAirDescriptor(context, outputConsumer, dirtyFilePaths, bc, descriptorFileName, android, ios); } else { copyAndFixCustomAirDescriptor(context, outputConsumer, bc, packagingOptions); } } private static void generateAirDescriptor(final CompileContext context, final BuildOutputConsumer outputConsumer, final Collection<String> dirtyFilePaths, final JpsFlexBuildConfiguration bc, final String descriptorFileName, final boolean android, final boolean ios) { final JpsSdk<?> sdk = bc.getSdk(); assert sdk != null; final String outputFilePath = bc.getActualOutputFilePath(); final String outputFolderPath = PathUtilRt.getParentPath(outputFilePath); final File outputFolder = new File(outputFolderPath); if (!outputFolder.isDirectory()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle .message("output.folder.does.not.exist", outputFolder.getPath()))); return; } final String airVersion = FlexCommonUtils.getAirVersion(sdk.getHomePath(), sdk.getVersionString()); if (airVersion == null) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("failed.to.get.air.sdk.version.use.custom.descriptor"))); return; } final String appId = FlexCommonUtils.fixApplicationId(bc.getMainClass()); final String appName = StringUtil.getShortName(bc.getMainClass()); final String swfName = PathUtilRt.getFileName(outputFilePath); final String[] extensions = getAirExtensionIDs(bc); try { final AirDescriptorOptions descriptorOptions = new AirDescriptorOptions(airVersion, appId, appName, swfName, extensions, android, ios); final String descriptorText = descriptorOptions.getAirDescriptorText(); final File outputFile = new File(outputFolder, descriptorFileName); FileUtil.writeToFile(outputFile, descriptorText); outputConsumer.registerOutputFile(outputFile, dirtyFilePaths); } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("failed.to.generate.air.descriptor", e.getMessage()))); } } private static Collection<File> getANEFiles(final JpsFlexBuildConfiguration bc) { final Collection<File> result = new ArrayList<>(); for (JpsFlexDependencyEntry entry : bc.getDependencies().getEntries()) { if (entry instanceof JpsLibraryDependencyEntry) { final JpsLibrary library = ((JpsLibraryDependencyEntry)entry).getLibrary(); if (library != null) { for (File libFile : library.getFiles(JpsOrderRootType.COMPILED)) { if (libFile.isFile() && FileUtilRt.extensionEquals(libFile.getName(), "ane")) { result.add(libFile); } } } } } return result; } private static String[] getAirExtensionIDs(final JpsFlexBuildConfiguration bc) { final Collection<String> result = new ArrayList<>(); for (File aneFile : getANEFiles(bc)) { final String extensionId = getExtensionId(aneFile); ContainerUtil.addIfNotNull(result, extensionId); } return result.toArray(new String[result.size()]); } @Nullable private static String getExtensionId(final File aneFile) { try { final ZipFile zipFile = new ZipFile((aneFile)); try { final ZipEntry entry = zipFile.getEntry("META-INF/ANE/extension.xml"); if (entry != null) { final InputStream is = zipFile.getInputStream(entry); return FlexCommonUtils.findXMLElement(is, "<extension><id>"); } } finally { zipFile.close(); } } catch (IOException e) {/**/} return null; } private static void copyAndFixCustomAirDescriptor(final CompileContext context, final BuildOutputConsumer outputConsumer, final JpsFlexBuildConfiguration bc, final JpsAirPackagingOptions packagingOptions) { final String customDescriptorPath = packagingOptions.getCustomDescriptorPath(); final File descriptorTemplateFile = new File(customDescriptorPath); if (!descriptorTemplateFile.isFile()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("air.descriptor.not.found", customDescriptorPath))); return; } final String outputFilePath = bc.getActualOutputFilePath(); final String outputFolderPath = PathUtilRt.getParentPath(outputFilePath); final File outputFolder = new File(outputFolderPath); if (!outputFolder.isDirectory()) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle .message("output.folder.does.not.exist", outputFolder.getPath()))); return; } final String content; try { content = fixInitialContent(descriptorTemplateFile, PathUtilRt.getFileName(outputFilePath)); } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message( "failed.to.open.air.descriptor", descriptorTemplateFile.getPath(), e.getMessage()))); return; } catch (JDOMException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message( "incorrect.air.descriptor.content", descriptorTemplateFile.getPath(), e.getMessage()))); return; } try { final String descriptorFileName = bc.isTempBCForCompilation() ? FlexCommonUtils.getGeneratedAirDescriptorName(bc, packagingOptions) : descriptorTemplateFile.getName(); final File outputFile = new File(outputFolder, descriptorFileName); FileUtil.writeToFile(outputFile, content.getBytes("UTF-8")); outputConsumer.registerOutputFile(outputFile, Collections.singletonList(descriptorTemplateFile.getPath())); } catch (IOException e) { context.processMessage(new CompilerMessage(getCompilerName(bc), BuildMessage.Kind.ERROR, FlexCommonBundle.message("failed.to.copy.air.descriptor", e.getMessage()))); } } private static String fixInitialContent(final File descriptorFile, final String swfName) throws IOException, JDOMException { // hardcoded UTF-8 makes it work the same way as it worked in FlexCompilationUtils.fixInitialContent() for ages (UTF-8 is hardcoded in JDOMUtil) final String descriptorContent = FileUtil.loadFile(descriptorFile, "UTF-8"); final Element rootElement = JDOMUtil.load(StringUtil.trimStart(descriptorContent, "\uFEFF")); if (!"application".equals(rootElement.getName())) { throw new JDOMException("incorrect root tag"); } Element initialWindowElement = rootElement.getChild("initialWindow", rootElement.getNamespace()); if (initialWindowElement == null) { initialWindowElement = new Element("initialWindow", rootElement.getNamespace()); rootElement.addContent(initialWindowElement); } Element contentElement = initialWindowElement.getChild("content", rootElement.getNamespace()); if (contentElement == null) { contentElement = new Element("content", rootElement.getNamespace()); initialWindowElement.addContent(contentElement); } contentElement.setText(swfName); return JDOMUtil.write(rootElement, SystemProperties.getLineSeparator()); } }