/* * 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.python; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.RuleKeyAppendable; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; import java.util.Set; import org.immutables.value.Value; @Value.Immutable(builder = false) @BuckStyleImmutable abstract class AbstractPythonPackageComponents implements RuleKeyAppendable { private static final PythonPackageComponents EMPTY = PythonPackageComponents.of( /* modules */ ImmutableMap.of(), /* resources */ ImmutableMap.of(), /* nativeLibraries */ ImmutableMap.of(), /* prebuiltLibraries */ ImmutableSet.of(), /* zipSafe */ Optional.empty()); // Python modules as map of their module name to location of the source. @Value.Parameter public abstract Map<Path, SourcePath> getModules(); // Resources to include in the package. @Value.Parameter public abstract Map<Path, SourcePath> getResources(); // Native libraries to include in the package. @Value.Parameter public abstract Map<Path, SourcePath> getNativeLibraries(); // Pre-built python libraries (eggs, wheels). Note that source distributions // will not work! @Value.Parameter public abstract Set<SourcePath> getPrebuiltLibraries(); @Value.Parameter public abstract Optional<Boolean> isZipSafe(); @Override public final void appendToRuleKey(RuleKeyObjectSink sink) { // Hash all the input components here so we can detect changes in both input file content // and module name mappings. // TODO(agallagher): Change the types of these fields from Map to SortedMap so that we don't // have to do all this weird stuff to ensure the key is stable. Please update // getInputsToCompareToOutput() as well once this is fixed. for (ImmutableMap.Entry<String, Map<Path, SourcePath>> part : ImmutableMap.of( "module", getModules(), "resource", getResources(), "nativeLibraries", getNativeLibraries()) .entrySet()) { for (Path name : ImmutableSortedSet.copyOf(part.getValue().keySet())) { sink.setReflectively(part.getKey() + ":" + name, part.getValue().get(name)); } } } /** @return whether there are any native libraries included in these components. */ public boolean hasNativeCode(CxxPlatform cxxPlatform) { for (Path module : getModules().keySet()) { if (module.toString().endsWith(cxxPlatform.getSharedLibraryExtension())) { return true; } } return false; } public static PythonPackageComponents of() { return EMPTY; } public ImmutableCollection<BuildRule> getDeps(SourcePathRuleFinder ruleFinder) { ImmutableList.Builder<BuildRule> deps = ImmutableList.builder(); deps.addAll(ruleFinder.filterBuildRuleInputs(getModules().values())); deps.addAll(ruleFinder.filterBuildRuleInputs(getResources().values())); deps.addAll(ruleFinder.filterBuildRuleInputs(getNativeLibraries().values())); deps.addAll(ruleFinder.filterBuildRuleInputs(getPrebuiltLibraries())); return deps.build(); } /** * A helper class to construct a PythonPackageComponents instance which throws human readable * error messages on duplicates. */ public static class Builder { // A description of the entity that is building this PythonPackageComponents instance. private final BuildTarget owner; // The actual maps holding the components. private final Map<Path, SourcePath> modules = new HashMap<>(); private final Map<Path, SourcePath> resources = new HashMap<>(); private final Map<Path, SourcePath> nativeLibraries = new HashMap<>(); private final Set<SourcePath> prebuiltLibraries = new LinkedHashSet<>(); private Optional<Boolean> zipSafe = Optional.empty(); // Bookkeeping used to for error handling in the presence of duplicate // entries. These data structures map the components named above to the // entities that provided them. private final Map<Path, BuildTarget> moduleSources = new HashMap<>(); private final Map<Path, BuildTarget> resourceSources = new HashMap<>(); private final Map<Path, BuildTarget> nativeLibrarySources = new HashMap<>(); public Builder(BuildTarget owner) { this.owner = owner; } private HumanReadableException createDuplicateError( String type, Path destination, BuildTarget sourceA, BuildTarget sourceB) { return new HumanReadableException( "%s: found duplicate entries for %s %s when creating python package (%s and %s)", owner, type, destination, sourceA, sourceB); } private Builder add( String type, Map<Path, SourcePath> builder, Map<Path, BuildTarget> sourceDescs, Path destination, SourcePath source, BuildTarget sourceDesc) { SourcePath existing = builder.put(destination, source); if (existing != null && !existing.equals(source)) { throw createDuplicateError( type, destination, sourceDesc, Preconditions.checkNotNull(sourceDescs.get(destination))); } sourceDescs.put(destination, sourceDesc); return this; } private Builder add( String type, Map<Path, SourcePath> builder, Map<Path, BuildTarget> sourceDescs, Map<Path, SourcePath> toAdd, BuildTarget sourceDesc) { for (Map.Entry<Path, SourcePath> ent : toAdd.entrySet()) { add(type, builder, sourceDescs, ent.getKey(), ent.getValue(), sourceDesc); } return this; } public Builder addModule(Path destination, SourcePath source, BuildTarget from) { return add("module", modules, moduleSources, destination, source, from); } public Builder addModules(Map<Path, SourcePath> sources, BuildTarget from) { return add("module", modules, moduleSources, sources, from); } public Builder addResources(Map<Path, SourcePath> sources, BuildTarget from) { return add("resource", resources, resourceSources, sources, from); } public Builder addNativeLibraries(Path destination, SourcePath source, BuildTarget from) { return add( "native library", nativeLibraries, nativeLibrarySources, destination, source, from); } public Builder addNativeLibraries(Map<Path, SourcePath> sources, BuildTarget from) { return add("native library", nativeLibraries, nativeLibrarySources, sources, from); } public Builder addPrebuiltLibraries(Set<SourcePath> sources) { prebuiltLibraries.addAll(sources); return this; } public Builder addComponent(PythonPackageComponents other, BuildTarget from) { addModules(other.getModules(), from); addResources(other.getResources(), from); addNativeLibraries(other.getNativeLibraries(), from); addPrebuiltLibraries(other.getPrebuiltLibraries()); addZipSafe(other.isZipSafe()); return this; } public Builder addZipSafe(Optional<Boolean> zipSafe) { if (!this.zipSafe.isPresent() && !zipSafe.isPresent()) { return this; } this.zipSafe = Optional.of(this.zipSafe.orElse(true) && zipSafe.orElse(true)); return this; } public PythonPackageComponents build() { return PythonPackageComponents.of( ImmutableMap.copyOf(modules), ImmutableMap.copyOf(resources), ImmutableMap.copyOf(nativeLibraries), ImmutableSet.copyOf(prebuiltLibraries), zipSafe); } } }