Java tutorial
/* * Copyright 2014-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.ConsoleEvent; import com.facebook.buck.io.MorePaths; import com.facebook.buck.rules.keys.SupportsInputBasedRuleKey; import com.facebook.buck.step.AbstractExecutionStep; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.StepExecutionResult; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.step.fs.SymlinkTreeStep; import com.facebook.buck.util.MoreMaps; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Multiset; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.stream.Stream; public class SymlinkTree extends AbstractBuildRuleWithResolver implements HasRuntimeDeps, RuleKeyAppendable, SupportsInputBasedRuleKey { private final Path root; private final ImmutableSortedMap<Path, SourcePath> links; public SymlinkTree(BuildRuleParams params, SourcePathResolver resolver, Path root, final ImmutableMap<Path, SourcePath> links) { super(params, resolver); Preconditions.checkState(!root.isAbsolute(), "Expected symlink tree root to be relative: %s", root); this.root = root; this.links = ImmutableSortedMap.copyOf(links); } public static SymlinkTree from(BuildRuleParams params, SourcePathResolver resolver, Path root, ImmutableMap<String, SourcePath> links) { return new SymlinkTree(params, resolver, root, MoreMaps.transformKeys(links, MorePaths.toPathFn(root.getFileSystem()))); } @Override public void appendToRuleKey(RuleKeyObjectSink sink) { sink.setReflectively("links", getLinksForRuleKey()); } /** * Because of cross-cell, multiple {@link SourcePath}s can resolve to the same relative path, * despite having distinct absolute paths. This presents a challenge for rules that require * gathering all of the inputs in one directory. * * @param sourcePaths set of SourcePaths to process * @param resolver resolver * @return a map that assigns a unique relative path to each of the SourcePaths. */ public static ImmutableBiMap<SourcePath, Path> resolveDuplicateRelativePaths( ImmutableSortedSet<SourcePath> sourcePaths, SourcePathResolver resolver) { // This serves a dual purpose - it keeps track of whether a particular relative path had been // assigned to a SourcePath and how many times a particular relative path had been seen. Multiset<Path> assignedPaths = HashMultiset.create(); ImmutableBiMap.Builder<SourcePath, Path> builder = ImmutableBiMap.builder(); List<SourcePath> conflicts = new ArrayList<>(); for (SourcePath sourcePath : sourcePaths) { Path relativePath = resolver.getRelativePath(sourcePath); if (!assignedPaths.contains(relativePath)) { builder.put(sourcePath, relativePath); assignedPaths.add(relativePath); } else { conflicts.add(sourcePath); } } for (SourcePath conflict : conflicts) { Path relativePath = resolver.getRelativePath(conflict); Path parent = MorePaths.getParentOrEmpty(relativePath); String extension = MorePaths.getFileExtension(relativePath); String name = MorePaths.getNameWithoutExtension(relativePath); while (true) { StringBuilder candidateName = new StringBuilder(name); candidateName.append('-'); int suffix = assignedPaths.count(relativePath); candidateName.append(suffix); if (!extension.isEmpty()) { candidateName.append('.'); candidateName.append(extension); } Path candidate = parent.resolve(candidateName.toString()); if (!assignedPaths.contains(candidate)) { assignedPaths.add(candidate); builder.put(conflict, candidate); break; } else { assignedPaths.add(relativePath); } } } return builder.build(); } @Override public ImmutableList<Step> getBuildSteps(BuildContext context, BuildableContext buildableContext) { return ImmutableList.of(getVerifyStep(), new MakeCleanDirectoryStep(getProjectFilesystem(), root), new SymlinkTreeStep(getProjectFilesystem(), root, getResolver().getMappedPaths(links))); } // Put the link map into the rule key, as if it changes at all, we need to // re-run it. private ImmutableSortedMap<String, NonHashableSourcePathContainer> getLinksForRuleKey() { ImmutableSortedMap.Builder<String, NonHashableSourcePathContainer> linksForRuleKeyBuilder = ImmutableSortedMap .naturalOrder(); for (Map.Entry<Path, SourcePath> entry : links.entrySet()) { linksForRuleKeyBuilder.put(entry.getKey().toString(), new NonHashableSourcePathContainer(entry.getValue())); } return linksForRuleKeyBuilder.build(); } @Override public Path getPathToOutput() { return root; } @VisibleForTesting protected Step getVerifyStep() { return new AbstractExecutionStep("verify_symlink_tree") { @Override public StepExecutionResult execute(ExecutionContext context) throws IOException { for (ImmutableMap.Entry<Path, SourcePath> entry : getLinks().entrySet()) { for (Path pathPart : entry.getKey()) { if (pathPart.toString().equals("..")) { context.getBuckEventBus().post(ConsoleEvent.create(Level.SEVERE, String.format("Path '%s' should not contain '%s'.", entry.getKey(), pathPart))); return StepExecutionResult.ERROR; } } } return StepExecutionResult.SUCCESS; } }; } // We don't cache symlinks because: // 1) We don't currently support caching symlinks. // 2) It's almost certainly always more expensive to cache them rather than just re-create them. // 3) The symlinks are absolute. @Override public boolean isCacheable() { return false; } @Override public Stream<SourcePath> getRuntimeDeps() { return links.values().stream(); } public Path getRoot() { return getProjectFilesystem().resolve(root); } public ImmutableMap<Path, SourcePath> getLinks() { return links; } }