/** * Creates the merging report file. * * @param mergingReport the merging activities report to serialize. */ private void writeReport(MergingReport mergingReport) { FileWriter fileWriter = null; try { if (!mReportFile.get().getParentFile().exists() && !mReportFile.get().getParentFile().mkdirs()) { mLogger.warning( String.format( "Cannot create %1$s manifest merger report file," + "build will continue but merging activities " + "will not be documented", mReportFile.get().getAbsolutePath())); } else { fileWriter = new FileWriter(mReportFile.get()); mergingReport.getActions().log(fileWriter); } } catch (IOException e) { mLogger.warning( String.format( "Error '%1$s' while writing the merger report file, " + "build can continue but merging activities " + "will not be documented ", e.getMessage())); } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { mLogger.warning( String.format( "Error '%1$s' while closing the merger report file, " + "build can continue but merging activities " + "will not be documented ", e.getMessage())); } } } }
/** * Perform high level ordering of files merging and delegates actual merging to {@link * XmlDocument#merge(XmlDocument, com.android.manifmerger.MergingReport.Builder)} * * @return the merging activity report. * @throws MergeFailureException if the merging cannot be completed (for instance, if xml files * cannot be loaded). */ private MergingReport merge() throws MergeFailureException { // initiate a new merging report MergingReport.Builder mergingReportBuilder = new MergingReport.Builder(mLogger); SelectorResolver selectors = new SelectorResolver(); // load all the libraries xml files up front to have a list of all possible node:selector // values. List<LoadedManifestInfo> loadedLibraryDocuments = loadLibraries(selectors, mergingReportBuilder); // load the main manifest file to do some checking along the way. LoadedManifestInfo loadedMainManifestInfo = load( new ManifestInfo( mManifestFile.getName(), mManifestFile, XmlDocument.Type.MAIN, Optional.<String>absent() /* mainManifestPackageName */), selectors, mergingReportBuilder); // first do we have a package declaration in the main manifest ? Optional<XmlAttribute> mainPackageAttribute = loadedMainManifestInfo.getXmlDocument().getPackage(); if (!mainPackageAttribute.isPresent()) { mergingReportBuilder.addMessage( loadedMainManifestInfo.getXmlDocument().getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR, String.format( "Main AndroidManifest.xml at %1$s manifest:package attribute " + "is not declared", loadedMainManifestInfo.getXmlDocument().getSourceLocation().print(true))); return mergingReportBuilder.build(); } // check for placeholders presence. Map<String, Object> finalPlaceHolderValues = mPlaceHolderValues; if (!mPlaceHolderValues.containsKey(APPLICATION_ID)) { finalPlaceHolderValues = ImmutableMap.<String, Object>builder() .putAll(mPlaceHolderValues) .put(PACKAGE_NAME, mainPackageAttribute.get().getValue()) .put(APPLICATION_ID, mainPackageAttribute.get().getValue()) .build(); } // perform system property injection performSystemPropertiesInjection(mergingReportBuilder, loadedMainManifestInfo.getXmlDocument()); // force the re-parsing of the xml as elements may have been added through system // property injection. loadedMainManifestInfo = new LoadedManifestInfo( loadedMainManifestInfo, loadedMainManifestInfo.getOriginalPackageName(), loadedMainManifestInfo.getXmlDocument().reparse()); // invariant : xmlDocumentOptional holds the higher priority document and we try to // merge in lower priority documents. Optional<XmlDocument> xmlDocumentOptional = Optional.absent(); for (File inputFile : mFlavorsAndBuildTypeFiles) { mLogger.info("Merging flavors and build manifest %s \n", inputFile.getPath()); LoadedManifestInfo overlayDocument = load( new ManifestInfo( null, inputFile, XmlDocument.Type.OVERLAY, Optional.of(mainPackageAttribute.get().getValue())), selectors, mergingReportBuilder); // check package declaration. Optional<XmlAttribute> packageAttribute = overlayDocument.getXmlDocument().getPackage(); // if both files declare a package name, it should be the same. if (loadedMainManifestInfo.getOriginalPackageName().isPresent() && packageAttribute.isPresent() && !loadedMainManifestInfo .getOriginalPackageName() .get() .equals(packageAttribute.get().getValue())) { // no suggestion for library since this is actually forbidden to change the // the package name per flavor. String message = mMergeType == MergeType.APPLICATION ? String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s\n" + "\tSuggestion: remove the overlay declaration at %5$s " + "\tand place it in the build.gradle:\n" + "\t\tflavorName {\n" + "\t\t\tapplicationId = \"%2$s\"\n" + "\t\t}", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition(), packageAttribute.get().getSourceLocation().print(true)) : String.format( "Overlay manifest:package attribute declared at %1$s value=(%2$s)\n" + "\thas a different value=(%3$s) " + "declared in main manifest at %4$s", packageAttribute.get().printPosition(), packageAttribute.get().getValue(), mainPackageAttribute.get().getValue(), mainPackageAttribute.get().printPosition()); mergingReportBuilder.addMessage( overlayDocument.getXmlDocument().getSourceLocation(), 0, 0, MergingReport.Record.Severity.ERROR, message); return mergingReportBuilder.build(); } overlayDocument .getXmlDocument() .getRootNode() .getXml() .setAttribute("package", mainPackageAttribute.get().getValue()); xmlDocumentOptional = merge(xmlDocumentOptional, overlayDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } } mLogger.info("Merging main manifest %s\n", mManifestFile.getPath()); xmlDocumentOptional = merge(xmlDocumentOptional, loadedMainManifestInfo, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } // force main manifest package into resulting merged file when creating a library manifest. if (mMergeType == MergeType.LIBRARY) { // extract the package name... String mainManifestPackageName = loadedMainManifestInfo.getXmlDocument().getRootNode().getXml().getAttribute("package"); // save it in the selector instance. if (!Strings.isNullOrEmpty(mainManifestPackageName)) { xmlDocumentOptional .get() .getRootNode() .getXml() .setAttribute("package", mainManifestPackageName); } } for (LoadedManifestInfo libraryDocument : loadedLibraryDocuments) { mLogger.info("Merging library manifest " + libraryDocument.getLocation()); xmlDocumentOptional = merge(xmlDocumentOptional, libraryDocument, mergingReportBuilder); if (!xmlDocumentOptional.isPresent()) { return mergingReportBuilder.build(); } } // done with proper merging phase, now we need to trim unwanted elements, placeholder // substitution and system properties injection. ElementsTrimmer.trim(xmlDocumentOptional.get(), mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { return mergingReportBuilder.build(); } // do one last placeholder substitution, this is useful as we don't stop the build // when a library failed a placeholder substitution, but the element might have // been overridden so the problem was transient. However, with the final document // ready, all placeholders values must have been provided. KeyBasedValueResolver<String> placeHolderValueResolver = new MapBasedKeyBasedValueResolver<String>(finalPlaceHolderValues); PlaceholderHandler placeholderHandler = new PlaceholderHandler(); placeholderHandler.visit( mMergeType, xmlDocumentOptional.get(), placeHolderValueResolver, mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { return mergingReportBuilder.build(); } // perform system property injection. performSystemPropertiesInjection(mergingReportBuilder, xmlDocumentOptional.get()); XmlDocument finalMergedDocument = xmlDocumentOptional.get(); PostValidator.validate(finalMergedDocument, mergingReportBuilder); if (mergingReportBuilder.hasErrors()) { finalMergedDocument .getRootNode() .addMessage( mergingReportBuilder, MergingReport.Record.Severity.WARNING, "Post merge validation failed"); } // only remove tools annotations if we are packaging an application. if (mOptionalFeatures.contains(Invoker.Feature.REMOVE_TOOLS_DECLARATIONS)) { finalMergedDocument = ToolsInstructionsCleaner.cleanToolsReferences(finalMergedDocument, mLogger); } if (mOptionalFeatures.contains(Invoker.Feature.EXTRACT_FQCNS)) { extractFcqns(finalMergedDocument); } if (finalMergedDocument != null) { mergingReportBuilder.setMergedDocument(finalMergedDocument); } MergingReport mergingReport = mergingReportBuilder.build(); StdLogger stdLogger = new StdLogger(StdLogger.Level.INFO); mergingReport.log(stdLogger); stdLogger.verbose(mergingReport.getMergedDocument().get().prettyPrint()); if (mReportFile.isPresent()) { writeReport(mergingReport); } return mergingReport; }