private List<LoadedManifestInfo> loadLibraries(
      SelectorResolver selectors, MergingReport.Builder mergingReportBuilder)
      throws MergeFailureException {

    ImmutableList.Builder<LoadedManifestInfo> loadedLibraryDocuments = ImmutableList.builder();
    for (Pair<String, File> libraryFile : mLibraryFiles) {
      mLogger.info("Loading library manifest " + libraryFile.getSecond().getPath());
      ManifestInfo manifestInfo =
          new ManifestInfo(
              libraryFile.getFirst(),
              libraryFile.getSecond(),
              XmlDocument.Type.LIBRARY,
              Optional.<String>absent());
      XmlDocument libraryDocument;
      try {
        libraryDocument =
            XmlLoader.load(
                selectors,
                mSystemPropertyResolver,
                manifestInfo.mName,
                manifestInfo.mLocation,
                XmlDocument.Type.LIBRARY,
                Optional.<String>absent() /* mainManifestPackageName */);
      } catch (Exception e) {
        throw new MergeFailureException(e);
      }
      // extract the package name...
      String libraryPackage = libraryDocument.getRootNode().getXml().getAttribute("package");
      // save it in the selector instance.
      if (!Strings.isNullOrEmpty(libraryPackage)) {
        selectors.addSelector(libraryPackage, libraryFile.getFirst());
      }

      // perform placeholder substitution, this is useful when the library is using
      // a placeholder in a key element, we however do not need to record these
      // substitutions so feed it with a fake merging report.
      MergingReport.Builder builder = new MergingReport.Builder(mergingReportBuilder.getLogger());
      builder.getActionRecorder().recordDefaultNodeAction(libraryDocument.getRootNode());
      performPlaceHolderSubstitution(manifestInfo, libraryDocument, builder);
      if (builder.hasErrors()) {
        // we log the errors but continue, in case the error is of no consequence
        // to the application consuming the library.
        builder.build().log(mLogger);
      }

      loadedLibraryDocuments.add(
          new LoadedManifestInfo(
              manifestInfo,
              Optional.fromNullable(libraryDocument.getPackageName()),
              libraryDocument));
    }
    return loadedLibraryDocuments.build();
  }
  /**
   * Lists remote packages available for install using {@link UpdaterData#updateOrInstallAll_NoGUI}.
   *
   * @param includeAll True to list and install all packages, including obsolete ones.
   * @param extendedOutput True to display more details on each package.
   */
  public void listRemotePackages_NoGUI(boolean includeAll, boolean extendedOutput) {

    List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);

    mSdkLog.info("Packages available for installation or update: %1$d\n", archives.size());

    int index = 1;
    for (ArchiveInfo ai : archives) {
      Archive a = ai.getNewArchive();
      if (a != null) {
        Package p = a.getParentPackage();
        if (p != null) {
          if (extendedOutput) {
            mSdkLog.info("----------\n");
            mSdkLog.info("id: %1$d or \"%2$s\"\n", index, p.installId());
            mSdkLog.info(
                "     Type: %1$s\n",
                p.getClass()
                    .getSimpleName()
                    .replaceAll("Package", "")); // $NON-NLS-1$ //$NON-NLS-2$
            String desc = LineUtil.reformatLine("     Desc: %s\n", p.getLongDescription());
            mSdkLog.info("%s", desc); // $NON-NLS-1$
          } else {
            mSdkLog.info("%1$ 4d- %2$s\n", index, p.getShortDescription());
          }
          index++;
        }
      }
    }
  }
Exemple #3
0
  protected void notifyToolsNeedsToBeRestarted(int flags) {

    String msg = null;
    if ((flags & TOOLS_MSG_UPDATED_FROM_ADT) == TOOLS_MSG_UPDATED_FROM_ADT) {
      msg =
          "The Android SDK and AVD Manager that you are currently using has been updated. "
              + "Please also run Eclipse > Help > Check for Updates to see if the Android "
              + "plug-in needs to be updated.";

    } else if ((flags & TOOLS_MSG_UPDATED_FROM_SDKMAN) == TOOLS_MSG_UPDATED_FROM_SDKMAN) {
      msg =
          "The Android SDK and AVD Manager that you are currently using has been updated. "
              + "It is recommended that you now close the manager window and re-open it. "
              + "If you use Eclipse, please run Help > Check for Updates to see if the Android "
              + "plug-in needs to be updated.";
    } else if ((flags & NO_TOOLS_MSG) == NO_TOOLS_MSG) {
      return;
    }
    mSdkLog.info("%s", msg); // $NON-NLS-1$
  }
  /**
   * Tries to update all the *existing* local packages. This version is intended to run without a
   * GUI and only outputs to the current {@link ILogger}.
   *
   * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()} or
   *     package indexes to limit the packages we can update or install. A null or empty list means
   *     to update everything possible.
   * @param includeAll True to list and install all packages, including obsolete ones.
   * @param dryMode True to check what would be updated/installed but do not actually download or
   *     install anything.
   * @return A list of archives that have been installed. Can be null if nothing was done.
   */
  public List<Archive> updateOrInstallAll_NoGUI(
      Collection<String> pkgFilter, boolean includeAll, boolean dryMode) {

    List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeAll);

    // Filter the selected archives to only keep the ones matching the filter
    if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
      // Map filter types to an SdkRepository Package type,
      // e.g. create a map "platform" => PlatformPackage.class
      HashMap<String, Class<? extends Package>> pkgMap =
          new HashMap<String, Class<? extends Package>>();

      mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
      mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);

      // Prepare a map install-id => package instance
      HashMap<String, Package> installIdMap = new HashMap<String, Package>();
      for (ArchiveInfo ai : archives) {
        Archive a = ai.getNewArchive();
        if (a != null) {
          Package p = a.getParentPackage();
          if (p != null) {
            String id = p.installId();
            if (id != null && id.length() > 0 && !installIdMap.containsKey(id)) {
              installIdMap.put(id, p);
            }
          }
        }
      }

      // Now intersect this with the pkgFilter requested by the user, in order to
      // only keep the classes that the user wants to install.
      // We also create a set with the package indices requested by the user
      // and a set of install-ids requested by the user.

      HashSet<Class<? extends Package>> userFilteredClasses =
          new HashSet<Class<? extends Package>>();
      SparseIntArray userFilteredIndices = new SparseIntArray();
      Set<String> userFilteredInstallIds = new HashSet<String>();

      for (String type : pkgFilter) {
        if (installIdMap.containsKey(type)) {
          userFilteredInstallIds.add(type);

        } else if (type.replaceAll("[0-9]+", "").length() == 0) { // $NON-NLS-1$ //$NON-NLS-2$
          // An all-digit number is a package index requested by the user.
          int index = Integer.parseInt(type);
          userFilteredIndices.put(index, index);

        } else if (pkgMap.containsKey(type)) {
          userFilteredClasses.add(pkgMap.get(type));

        } else {
          // This should not happen unless there's a mismatch in the package map.
          mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
        }
      }

      // we don't need the maps anymore
      pkgMap = null;
      installIdMap = null;

      // Now filter the remote archives list to keep:
      // - any package which class matches userFilteredClasses
      // - any package index which matches userFilteredIndices
      // - any package install id which matches userFilteredInstallIds

      int index = 1;
      for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
        boolean keep = false;
        ArchiveInfo ai = it.next();
        Archive a = ai.getNewArchive();
        if (a != null) {
          Package p = a.getParentPackage();
          if (p != null) {
            if (userFilteredInstallIds.contains(p.installId())
                || userFilteredClasses.contains(p.getClass())
                || userFilteredIndices.get(index) > 0) {
              keep = true;
            }

            index++;
          }
        }

        if (!keep) {
          it.remove();
        }
      }

      if (archives.size() == 0) {
        mSdkLog.info(
            LineUtil.reflowLine(
                "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n"));
        return null;
      }
    }

    if (archives != null && archives.size() > 0) {
      if (dryMode) {
        mSdkLog.info("Packages selected for install:\n");
        for (ArchiveInfo ai : archives) {
          Archive a = ai.getNewArchive();
          if (a != null) {
            Package p = a.getParentPackage();
            if (p != null) {
              mSdkLog.info("- %1$s\n", p.getShortDescription());
            }
          }
        }
        mSdkLog.info("\nDry mode is on so nothing is actually being installed.\n");
      } else {
        return installArchives(archives, NO_TOOLS_MSG);
      }
    } else {
      mSdkLog.info("There is nothing to install or update.\n");
    }

    return null;
  }
  /**
   * 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;
  }
 @Override
 public void info(@NonNull String msgFormat, Object... args) {
   mLogger.info(msgFormat, args);
 }
Exemple #7
0
  /**
   * Validates that all archive licenses are accepted.
   *
   * <p>There are 2 cases: <br>
   * - When {@code acceptLicenses} is given, the licenses specified are automatically accepted and
   * all those not specified are automatically rejected. <br>
   * - When {@code acceptLicenses} is empty or null, licenses are collected and there's an input
   * prompt on StdOut to ask a yes/no question. To output, this uses the current {@link #mSdkLog}
   * which should be configured to send {@link ILogger#info(String, Object...)} directly to {@link
   * System#out}. <br>
   * Finally only accepted licenses are kept in the archive list.
   *
   * @param archives The archives to validate.
   * @param acceptLicenseIds A comma-separated list of licenses ids already approved.
   * @param numRetries The number of times the command-line will ask to accept a given license when
   *     the input doesn't match the expected y/n/yes/no answer. Use 0 for infinite. Useful for
   *     unit-tests. Once the number of retries is reached, the license is assumed as rejected.
   * @return True if there are any archives left to install.
   */
  @VisibleForTesting(visibility = Visibility.PRIVATE)
  boolean acceptLicense(List<ArchiveInfo> archives, String acceptLicenseIds, final int numRetries) {
    TreeSet<String> acceptedLids = new TreeSet<String>();
    if (acceptLicenseIds != null) {
      acceptedLids.addAll(Arrays.asList(acceptLicenseIds.split(","))); // $NON-NLS-1$
    }
    boolean automated = !acceptedLids.isEmpty();

    TreeSet<String> rejectedLids = new TreeSet<String>();
    TreeMap<String, License> lidToAccept = new TreeMap<String, License>();
    TreeMap<String, List<String>> lidPkgNames = new TreeMap<String, List<String>>();

    // Find the licenses needed. Include those already accepted.
    for (ArchiveInfo ai : archives) {
      License lic = getArchiveInfoLicense(ai);
      if (lic == null) {
        continue;
      }
      String lid = getLicenseId(lic);
      if (!acceptedLids.contains(lid)) {
        if (automated) {
          // Automatically reject those not already accepted
          rejectedLids.add(lid);
        } else {
          // Queue it to ask for it to be accepted
          lidToAccept.put(lid, lic);
          List<String> list = lidPkgNames.get(lid);
          if (list == null) {
            list = new ArrayList<String>();
            lidPkgNames.put(lid, list);
          }
          list.add(ai.getShortDescription());
        }
      }
    }

    // Ask for each license that needs to be asked manually for confirmation
    nextEntry:
    for (Map.Entry<String, License> entry : lidToAccept.entrySet()) {
      String lid = entry.getKey();
      License lic = entry.getValue();
      mSdkLog.info("-------------------------------\n");
      mSdkLog.info("License id: %1$s\n", lid);
      mSdkLog.info(
          "Used by: \n - %1$s\n", Joiner.on("\n  - ").skipNulls().join(lidPkgNames.get(lid)));
      mSdkLog.info("-------------------------------\n\n");
      mSdkLog.info("%1$s\n", lic.getLicense());

      int retries = numRetries;
      tryAgain:
      while (true) {
        try {
          mSdkLog.info("Do you accept the license '%1$s' [y/n]: ", lid);

          byte[] buffer = new byte[256];
          if (mSdkLog instanceof IReaderLogger) {
            ((IReaderLogger) mSdkLog).readLine(buffer);
          } else {
            System.in.read(buffer);
          }
          mSdkLog.info("\n");

          String reply = new String(buffer, Charsets.UTF_8);
          reply = reply.trim().toLowerCase(Locale.US);

          if ("y".equals(reply) || "yes".equals(reply)) {
            acceptedLids.add(lid);
            continue nextEntry;

          } else if ("n".equals(reply) || "no".equals(reply)) {
            break tryAgain;

          } else {
            mSdkLog.info("Unknown response '%1$s'.\n", reply);
            if (--retries == 0) {
              mSdkLog.info("Max number of retries exceeded. Rejecting '%1$s'\n", lid);
              break tryAgain;
            }
            continue tryAgain;
          }

        } catch (IOException e) {
          // Panic. Don't install anything.
          e.printStackTrace();
          return false;
        }
      }
      rejectedLids.add(lid);
    }

    // Finally remove all archive which license is rejected or not accepted.
    for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
      ArchiveInfo ai = it.next();
      License lic = getArchiveInfoLicense(ai);
      if (lic == null) {
        continue;
      }
      String lid = getLicenseId(lic);
      if (rejectedLids.contains(lid) || !acceptedLids.contains(lid)) {
        mSdkLog.info(
            "Package %1$s not installed due to rejected license '%2$s'.\n",
            ai.getShortDescription(), lid);
        it.remove();
      }
    }

    return !archives.isEmpty();
  }