/* * Copyright 2016-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.parser; import static java.nio.charset.StandardCharsets.UTF_8; import com.facebook.buck.event.PerfEventId; import com.facebook.buck.event.SimplePerfEvent; import com.facebook.buck.json.JsonObjectHashing; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuckVersion; import com.facebook.buck.model.BuildFileTree; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavored; import com.facebook.buck.model.UnflavoredBuildTarget; import com.facebook.buck.rules.BuckPyFunction; import com.facebook.buck.rules.BuildRuleType; import com.facebook.buck.rules.Cell; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.TargetNode; import com.facebook.buck.rules.TargetNodeFactory; import com.facebook.buck.rules.VisibilityPattern; import com.facebook.buck.rules.coercer.ConstructorArgMarshaller; import com.facebook.buck.rules.coercer.ParamInfoException; import com.facebook.buck.util.HumanReadableException; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import java.io.IOException; import java.nio.file.Path; import java.util.Map; import java.util.Optional; /** * Creates {@link TargetNode} instances from raw data coming in form the {@link * com.facebook.buck.json.ProjectBuildFileParser}. */ public class DefaultParserTargetNodeFactory implements ParserTargetNodeFactory<TargetNode<?, ?>> { private static final Logger LOG = Logger.get(DefaultParserTargetNodeFactory.class); private final ConstructorArgMarshaller marshaller; private final Optional<LoadingCache<Cell, BuildFileTree>> buildFileTrees; private final TargetNodeListener<TargetNode<?, ?>> nodeListener; private final TargetNodeFactory targetNodeFactory; private DefaultParserTargetNodeFactory( ConstructorArgMarshaller marshaller, Optional<LoadingCache<Cell, BuildFileTree>> buildFileTrees, TargetNodeListener<TargetNode<?, ?>> nodeListener, TargetNodeFactory targetNodeFactory) { this.marshaller = marshaller; this.buildFileTrees = buildFileTrees; this.nodeListener = nodeListener; this.targetNodeFactory = targetNodeFactory; } public static ParserTargetNodeFactory<TargetNode<?, ?>> createForParser( ConstructorArgMarshaller marshaller, LoadingCache<Cell, BuildFileTree> buildFileTrees, TargetNodeListener<TargetNode<?, ?>> nodeListener, TargetNodeFactory targetNodeFactory) { return new DefaultParserTargetNodeFactory( marshaller, Optional.of(buildFileTrees), nodeListener, targetNodeFactory); } public static ParserTargetNodeFactory<TargetNode<?, ?>> createForDistributedBuild( ConstructorArgMarshaller marshaller, TargetNodeFactory targetNodeFactory) { return new DefaultParserTargetNodeFactory( marshaller, Optional.empty(), (buildFile, node) -> { // No-op. }, targetNodeFactory); } @Override public TargetNode<?, ?> createTargetNode( Cell cell, Path buildFile, BuildTarget target, Map<String, Object> rawNode, Function<PerfEventId, SimplePerfEvent.Scope> perfEventScope) { BuildRuleType buildRuleType = parseBuildRuleTypeFromRawRule(cell, rawNode); // Because of the way that the parser works, we know this can never return null. Description<?> description = cell.getDescription(buildRuleType); UnflavoredBuildTarget unflavoredBuildTarget = target.getUnflavoredBuildTarget(); if (target.isFlavored()) { if (description instanceof Flavored) { if (!((Flavored) description).hasFlavors(ImmutableSet.copyOf(target.getFlavors()))) { throw UnexpectedFlavorException.createWithSuggestions(cell, target); } } else { LOG.warn( "Target %s (type %s) must implement the Flavored interface " + "before we can check if it supports flavors: %s", unflavoredBuildTarget, buildRuleType, target.getFlavors()); throw new HumanReadableException( "Target %s (type %s) does not currently support flavors (tried %s)", unflavoredBuildTarget, buildRuleType, target.getFlavors()); } } UnflavoredBuildTarget unflavoredBuildTargetFromRawData = RawNodeParsePipeline.parseBuildTargetFromRawRule( cell.getRoot(), cell.getCanonicalName(), rawNode, buildFile); if (!unflavoredBuildTarget.equals(unflavoredBuildTargetFromRawData)) { throw new IllegalStateException( String.format( "Inconsistent internal state, target from data: %s, expected: %s, raw data: %s", unflavoredBuildTargetFromRawData, unflavoredBuildTarget, Joiner.on(',').withKeyValueSeparator("->").join(rawNode))); } Cell targetCell = cell.getCell(target); Object constructorArg; try { ImmutableSet.Builder<BuildTarget> declaredDeps = ImmutableSet.builder(); ImmutableSet<VisibilityPattern> visibilityPatterns; ImmutableSet<VisibilityPattern> withinViewPatterns; try (SimplePerfEvent.Scope scope = perfEventScope.apply(PerfEventId.of("MarshalledConstructorArg"))) { constructorArg = marshaller.populate( targetCell.getCellPathResolver(), targetCell.getFilesystem(), target, description.getConstructorArgType(), declaredDeps, rawNode); visibilityPatterns = ConstructorArgMarshaller.populateVisibilityPatterns( targetCell.getCellPathResolver(), "visibility", rawNode.get("visibility"), target); withinViewPatterns = ConstructorArgMarshaller.populateVisibilityPatterns( targetCell.getCellPathResolver(), "within_view", rawNode.get("within_view"), target); } try (SimplePerfEvent.Scope scope = perfEventScope.apply(PerfEventId.of("CreatedTargetNode"))) { Hasher hasher = Hashing.sha1().newHasher(); hasher.putString(BuckVersion.getVersion(), UTF_8); JsonObjectHashing.hashJsonObject(hasher, rawNode); TargetNode<?, ?> node = targetNodeFactory.createFromObject( hasher.hash(), description, constructorArg, targetCell.getFilesystem(), target, declaredDeps.build(), visibilityPatterns, withinViewPatterns, targetCell.getCellPathResolver()); if (buildFileTrees.isPresent() && cell.isEnforcingBuckPackageBoundaries(target.getBasePath())) { enforceBuckPackageBoundaries( target, buildFileTrees.get().getUnchecked(targetCell), node.getInputs()); } nodeListener.onCreate(buildFile, node); return node; } } catch (NoSuchBuildTargetException e) { throw new HumanReadableException(e); } catch (ParamInfoException e) { throw new HumanReadableException(e, "%s: %s", target, e.getMessage()); } catch (IOException e) { throw new HumanReadableException(e.getMessage(), e); } } protected void enforceBuckPackageBoundaries( BuildTarget target, BuildFileTree buildFileTree, ImmutableSet<Path> paths) { Path basePath = target.getBasePath(); for (Path path : paths) { if (!basePath.toString().isEmpty() && !path.startsWith(basePath)) { throw new HumanReadableException( "'%s' in '%s' refers to a parent directory.", basePath.relativize(path), target); } Optional<Path> ancestor = buildFileTree.getBasePathOfAncestorTarget(path); // It should not be possible for us to ever get an Optional.empty() for this because that // would require one of two conditions: // 1) The source path references parent directories, which we check for above. // 2) You don't have a build file above this file, which is impossible if it is referenced in // a build file *unless* you happen to be referencing something that is ignored. if (!ancestor.isPresent()) { throw new HumanReadableException( "'%s' in '%s' crosses a buck package boundary. This is probably caused by " + "specifying one of the folders in '%s' in your .buckconfig under `project.ignore`.", path, target, path); } if (!ancestor.get().equals(basePath)) { throw new HumanReadableException( "'%s' in '%s' crosses a buck package boundary. This file is owned by '%s'. Find " + "the owning rule that references '%s', and use a reference to that rule instead " + "of referencing the desired file directly.", path, target, ancestor.get(), path); } } } private static BuildRuleType parseBuildRuleTypeFromRawRule(Cell cell, Map<String, Object> map) { String type = (String) Preconditions.checkNotNull(map.get(BuckPyFunction.TYPE_PROPERTY_NAME)); return cell.getBuildRuleType(type); } }