Java tutorial
/* * Copyright 2013-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.rules; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.LogEvent; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.util.DefaultDirectoryTraverser; import com.facebook.buck.util.DirectoryTraversal; import com.facebook.buck.util.DirectoryTraverser; import com.facebook.buck.util.ProjectFilesystem; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.JsonArray; import com.google.gson.JsonPrimitive; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; /** * Utility for recording the paths to the output files generated by a build rule, as well as any * metadata about those output files. This data will be packaged up into an artifact that will be * stored in the cache. The metadata will also be written to disk so it can be read on a subsequent * build by an {@link OnDiskBuildInfo}. */ public class BuildInfoRecorder { private static final DirectoryTraverser DEFAULT_DIRECTORY_TRAVERSER = new DefaultDirectoryTraverser(); private final BuildTarget buildTarget; private final Path pathToMetadataDirectory; private final ProjectFilesystem projectFilesystem; private final Map<String, String> metadataToWrite; private final RuleKey ruleKey; /** * Every value in this set is a path relative to the project root. */ private final Set<Path> pathsToOutputFiles; private final Set<Path> pathsToOutputDirectories; private final DirectoryTraverser directoryTraverser; BuildInfoRecorder(BuildTarget buildTarget, ProjectFilesystem projectFilesystem, RuleKey ruleKey, RuleKey rukeKeyWithoutDeps) { this(buildTarget, projectFilesystem, ruleKey, rukeKeyWithoutDeps, DEFAULT_DIRECTORY_TRAVERSER); } BuildInfoRecorder(BuildTarget buildTarget, ProjectFilesystem projectFilesystem, RuleKey ruleKey, RuleKey rukeKeyWithoutDeps, DirectoryTraverser directoryTraverser) { this.buildTarget = Preconditions.checkNotNull(buildTarget); this.pathToMetadataDirectory = BuildInfo.getPathToMetadataDirectory(buildTarget); this.projectFilesystem = Preconditions.checkNotNull(projectFilesystem); this.metadataToWrite = Maps.newHashMap(); metadataToWrite.put(BuildInfo.METADATA_KEY_FOR_RULE_KEY, Preconditions.checkNotNull(ruleKey).toString()); metadataToWrite.put(BuildInfo.METADATA_KEY_FOR_RULE_KEY_WITHOUT_DEPS, Preconditions.checkNotNull(rukeKeyWithoutDeps).toString()); this.ruleKey = ruleKey; this.pathsToOutputFiles = Sets.newHashSet(); this.pathsToOutputDirectories = Sets.newHashSet(); this.directoryTraverser = directoryTraverser; } /** * Writes the metadata currently stored in memory to the directory returned by * {@link BuildInfo#getPathToMetadataDirectory(BuildTarget)}. */ public void writeMetadataToDisk(boolean clearExistingMetadata) throws IOException { if (clearExistingMetadata) { projectFilesystem.rmdir(pathToMetadataDirectory); } projectFilesystem.mkdirs(pathToMetadataDirectory); for (Map.Entry<String, String> entry : metadataToWrite.entrySet()) { projectFilesystem.writeContentsToPath(entry.getValue(), pathToMetadataDirectory.resolve(entry.getKey())); } } /** * This key/value pair is stored in memory until {@link #writeMetadataToDisk(boolean)} is invoked. */ public void addMetadata(String key, String value) { metadataToWrite.put(Preconditions.checkNotNull(key), Preconditions.checkNotNull(value)); } public void addMetadata(String key, Iterable<String> value) { JsonArray values = new JsonArray(); for (String str : value) { values.add(new JsonPrimitive(str)); } addMetadata(key, values.toString()); } /** * Creates a zip file of the metadata and recorded artifacts and stores it in the artifact cache. */ public void performUploadToArtifactCache(ArtifactCache artifactCache, BuckEventBus eventBus) { // Skip all of this if caching is disabled. Although artifactCache.store() will be a noop, // building up the zip is wasted I/O. if (!artifactCache.isStoreSupported()) { return; } ImmutableSet.Builder<Path> pathsToIncludeInZipBuilder = ImmutableSet.<Path>builder() .addAll(Iterables.transform(metadataToWrite.keySet(), new Function<String, Path>() { @Override public Path apply(String key) { return pathToMetadataDirectory.resolve(key); } })).addAll(pathsToOutputFiles); try { for (Path outputDirectory : pathsToOutputDirectories) { pathsToIncludeInZipBuilder.addAll(getEntries(outputDirectory)); } } catch (IOException e) { throw new RuntimeException(e); } ImmutableSet<Path> pathsToIncludeInZip = pathsToIncludeInZipBuilder.build(); File zip; try { zip = File.createTempFile(buildTarget.getFullyQualifiedName().replace('/', '_'), ".zip"); projectFilesystem.createZip(pathsToIncludeInZip, zip); } catch (IOException e) { eventBus.post(LogEvent.info("Failed to create zip for %s containing:\n%s", buildTarget, Joiner.on('\n').join(ImmutableSortedSet.copyOf(pathsToIncludeInZip)))); e.printStackTrace(); return; } artifactCache.store(ruleKey, zip); zip.delete(); } private List<Path> getEntries(final Path outputDirectory) throws IOException { final ImmutableList.Builder<Path> entries = ImmutableList.builder(); DirectoryTraversal traversal = new DirectoryTraversal( projectFilesystem.getFileForRelativePath(outputDirectory)) { @Override public void visit(File file, String relativePath) throws IOException { entries.add(outputDirectory.resolve(relativePath)); } }; directoryTraverser.traverse(traversal); return entries.build(); } /** * Fetches the artifact associated with the {@link #buildTarget} for this class and writes it to * the specified {@code outputFile}. */ public CacheResult fetchArtifactForBuildable(File outputFile, ArtifactCache artifactCache) { Preconditions.checkNotNull(outputFile); return artifactCache.fetch(ruleKey, outputFile); } /** * @param pathToArtifact Relative path to the project root. */ public void recordArtifact(Path pathToArtifact) { Preconditions.checkNotNull(pathToArtifact); pathsToOutputFiles.add(pathToArtifact); } public void recordArtifactsInDirectory(Path pathToArtifactsDirectory) { Preconditions.checkNotNull(pathToArtifactsDirectory); pathsToOutputDirectories.add(pathToArtifactsDirectory); } @Nullable @VisibleForTesting String getMetadataFor(String key) { return metadataToWrite.get(key); } }