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; }
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); }
/** * 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; } }