Example #1
0
  private int doExecute(ExecutionContext context) throws IOException, InterruptedException {
    List<Predicate<Path>> pathPredicates = Lists.newArrayList();

    boolean canDownscale = imageScaler != null && imageScaler.isAvailable(context);
    LOG.info(
        "FilterResourcesStep: canDownscale: %s. imageScalar non-null: %s.",
        canDownscale, imageScaler != null);

    if (filterDrawables) {
      Preconditions.checkNotNull(drawableFinder);
      Set<Path> drawables =
          drawableFinder.findDrawables(inResDirToOutResDirMap.keySet(), filesystem);
      pathPredicates.add(
          ResourceFilters.createImageDensityFilter(
              drawables, Preconditions.checkNotNull(targetDensities), canDownscale));
    }

    final boolean localeFilterEnabled = !locales.isEmpty();
    if (localeFilterEnabled || enableStringWhitelisting) {
      pathPredicates.add(
          new Predicate<Path>() {
            @Override
            public boolean apply(Path path) {
              Matcher matcher =
                  NON_ENGLISH_STRINGS_FILE_PATH.matcher(MorePaths.pathWithUnixSeparators(path));
              if (!matcher.matches()) {
                return true;
              }

              if (enableStringWhitelisting) {
                return isPathWhitelisted(path);
              } else {
                Preconditions.checkState(localeFilterEnabled);
                String locale = matcher.group(1);
                if (matcher.group(2) != null) {
                  locale += "_" + matcher.group(2);
                }

                return locales.contains(locale);
              }
            }
          });
    }

    // Create filtered copies of all resource directories. These will be passed to aapt instead.
    filteredDirectoryCopier.copyDirs(
        filesystem, inResDirToOutResDirMap, Predicates.and(pathPredicates));

    // If an ImageScaler was specified, but only if it is available, try to apply it.
    if (canDownscale && filterDrawables) {
      scaleUnmatchedDrawables(context);
    }

    return 0;
  }
Example #2
0
 public FilterResourcesStep build() {
   Preconditions.checkNotNull(filesystem);
   Preconditions.checkNotNull(resourceFilter);
   LOG.info("FilterResourcesStep.Builder: resource filter: %s", resourceFilter);
   Preconditions.checkNotNull(inResDirToOutResDirMap);
   return new FilterResourcesStep(
       filesystem,
       inResDirToOutResDirMap,
       resourceFilter.isEnabled(),
       enableStringWhitelisting,
       whitelistedStringDirs,
       locales,
       DefaultFilteredDirectoryCopier.getInstance(),
       resourceFilter.getDensities(),
       DefaultDrawableFinder.getInstance(),
       resourceFilter.shouldDownscale()
           ? new ImageMagickScaler(filesystem.getRootPath())
           : null);
 }
Example #3
0
  /**
   * Query Watchman for file change events. If too many events are pending or an error occurs an
   * overflow event is posted to the EventBus signalling that events may have been lost (and so
   * typically caches must be cleared to avoid inconsistency). Interruptions and IOExceptions are
   * propagated to callers, but typically if overflow events are handled conservatively by
   * subscribers then no other remedial action is required.
   */
  @Override
  public void postEvents(BuckEventBus buckEventBus) throws IOException, InterruptedException {
    ProcessExecutor.LaunchedProcess watchmanProcess =
        processExecutor.launchProcess(
            ProcessExecutorParams.builder()
                .addCommand("watchman", "--server-encoding=json", "--no-pretty", "-j")
                .build());
    try {
      LOG.debug("Writing query to Watchman: %s", query);
      watchmanProcess.getOutputStream().write(query.getBytes(Charsets.US_ASCII));
      watchmanProcess.getOutputStream().close();
      LOG.debug("Parsing JSON output from Watchman");
      final long parseStartTimeMillis = clock.currentTimeMillis();
      InputStream jsonInput = watchmanProcess.getInputStream();
      if (LOG.isVerboseEnabled()) {
        byte[] fullResponse = ByteStreams.toByteArray(jsonInput);
        jsonInput.close();
        jsonInput = new ByteArrayInputStream(fullResponse);
        LOG.verbose("Full JSON: " + new String(fullResponse, Charsets.UTF_8).trim());
      }
      JsonParser jsonParser = objectMapper.getJsonFactory().createJsonParser(jsonInput);
      PathEventBuilder builder = new PathEventBuilder();
      JsonToken token = jsonParser.nextToken();
      /*
       * Watchman returns changes as an array of JSON objects with potentially unstable key
       * ordering:
       * {
       *     "files": [
       *     {
       *         "new": false,
       *         "exists": true,
       *         "name": "bin/buckd",
       *     },
       *     ]
       * }
       * A simple way to parse these changes is to collect the relevant values from each object
       * in a builder and then build an event when the end of a JSON object is reached. When the end
       * of the enclosing JSON object is processed the builder will not contain a complete event, so
       * the object end token will be ignored.
       */
      int eventCount = 0;
      while (token != null) {
        boolean shouldOverflow = false;
        if (eventCount > overflow) {
          LOG.warn(
              "Received too many events from Watchmen (%d > overflow max %d), posting overflow "
                  + "event and giving up.",
              eventCount, overflow);
          shouldOverflow = true;
        } else {
          long elapsedMillis = clock.currentTimeMillis() - parseStartTimeMillis;
          if (elapsedMillis >= timeoutMillis) {
            LOG.warn(
                "Parsing took too long (timeout %d ms), posting overflow event and giving up.",
                timeoutMillis);
            shouldOverflow = true;
          }
        }

        if (shouldOverflow) {
          postWatchEvent(createOverflowEvent());
          processExecutor.destroyLaunchedProcess(watchmanProcess);
          processExecutor.waitForLaunchedProcess(watchmanProcess);
          return;
        }

        switch (token) {
          case FIELD_NAME:
            String fieldName = jsonParser.getCurrentName();
            switch (fieldName) {
              case "is_fresh_instance":
                // Force caches to be invalidated --- we have no idea what's happening.
                Boolean newInstance = jsonParser.nextBooleanValue();
                if (newInstance) {
                  LOG.info(
                      "Fresh watchman instance detected. "
                          + "Posting overflow event to flush caches.");
                  postWatchEvent(createOverflowEvent());
                }
                break;

              case "name":
                builder.setPath(Paths.get(jsonParser.nextTextValue()));
                break;
              case "new":
                if (jsonParser.nextBooleanValue()) {
                  builder.setCreationEvent();
                }
                break;
              case "exists":
                if (!jsonParser.nextBooleanValue()) {
                  builder.setDeletionEvent();
                }
                break;
              case "error":
                WatchmanWatcherException e =
                    new WatchmanWatcherException(jsonParser.nextTextValue());
                LOG.error(
                    e, "Error in Watchman output. Posting an overflow event to flush the caches");
                postWatchEvent(createOverflowEvent());
                throw e;
              case "warning":
                String message = jsonParser.nextTextValue();
                buckEventBus.post(
                    ConsoleEvent.warning("Watchman has produced a warning: %s", message));
                LOG.warn("Watchman has produced a warning: %s", message);
                break;
            }
            break;
          case END_OBJECT:
            if (builder.canBuild()) {
              postWatchEvent(builder.build());
              ++eventCount;
            }
            builder = new PathEventBuilder();
            break;
            // $CASES-OMITTED$
          default:
            break;
        }
        token = jsonParser.nextToken();
      }
      int watchmanExitCode;
      LOG.debug("Posted %d Watchman events. Waiting for subprocess to exit...", eventCount);
      watchmanExitCode = processExecutor.waitForLaunchedProcess(watchmanProcess);
      if (watchmanExitCode != 0) {
        LOG.error("Watchman exited with error code %d", watchmanExitCode);
        postWatchEvent(createOverflowEvent()); // Events may have been lost, signal overflow.
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        ByteStreams.copy(watchmanProcess.getErrorStream(), buffer);
        throw new WatchmanWatcherException(
            "Watchman failed with exit code " + watchmanExitCode + ": " + buffer.toString());
      } else {
        LOG.debug("Watchman exited cleanly.");
      }
    } catch (InterruptedException e) {
      LOG.warn(e, "Killing Watchman process on interrupted exception");
      postWatchEvent(createOverflowEvent()); // Events may have been lost, signal overflow.
      processExecutor.destroyLaunchedProcess(watchmanProcess);
      processExecutor.waitForLaunchedProcess(watchmanProcess);
      Thread.currentThread().interrupt();
      throw e;
    } catch (IOException e) {
      LOG.error(e, "Killing Watchman process on I/O exception");
      postWatchEvent(createOverflowEvent()); // Events may have been lost, signal overflow.
      processExecutor.destroyLaunchedProcess(watchmanProcess);
      processExecutor.waitForLaunchedProcess(watchmanProcess);
      throw e;
    }
  }