/* * Copyright 2012-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.cli; import com.facebook.buck.util.MoreStrings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.List; /** * In actual build files, Buck requires that build targets are well-formed, which means that they * must start with a "//", followed by a path, and ending with the name of a build rule, which must * be preceded by a colon. Although these are easy for Buck to parse, they are not always easy to * type from the command line. For this reason, we are more lenient with the format of build targets * when entered from the command line, keeping in mind that one of the easiest things for a user to * do is tab-complete file paths. For this reason, the "//" and ":" are added, when appropriate. For * example, if the following argument were specified on the command line when a build target was * expected: * * <pre> * src/com/facebook/orca * </pre> * * then this normalizer would convert it to: * * <pre> * //src/com/facebook/orca:orca * </pre> * * It would also normalize it to the same thing if it contained a trailing slash: * * <pre> * src/com/facebook/orca * </pre> * * Similarly, if the argument were: * * <pre> * src/com/facebook/orca:messenger * </pre> * * then this normalizer would convert it to: * * <pre> * //src/com/facebook/orca:messenger * </pre> * * This makes it easier to tab-complete to the directory with the desired build target, and then * append the name of the build target by typing it out. Note that because of how the normalizer * works, it makes sense to name the most commonly built target in the package as the same name as * the directory that contains it so that the entire target can be tab-completed when entered from * the command line. */ class CommandLineBuildTargetNormalizer { private final Function<String, ImmutableSet<String>> normalizer; CommandLineBuildTargetNormalizer(final BuckConfig buckConfig) { this.normalizer = arg -> { ImmutableSet<String> aliasValues = buckConfig.getBuildTargetForAliasAsString(arg); if (!aliasValues.isEmpty()) { return aliasValues; } else { return ImmutableSet.of(normalizeBuildTargetIdentifier(arg)); } }; } public ImmutableSet<String> normalize(String argument) { return normalizer.apply(argument); } public ImmutableList<String> normalizeAll(List<String> arguments) { // When transforming command-line arguments, first check to see whether it is an alias in the // BuckConfig. If so, return the value associated with the alias. Otherwise, try normalize(). ImmutableList.Builder<String> builder = new ImmutableList.Builder<>(); for (String argument : arguments) { builder.addAll(normalizer.apply(argument)); } return builder.build(); } @VisibleForTesting static String normalizeBuildTargetIdentifier(final String buildTargetFromCommandLine) { // Build rules in the root are weird, but they do happen. Special-case them. if (buildTargetFromCommandLine.startsWith("//:")) { return buildTargetFromCommandLine; } String target = buildTargetFromCommandLine; // Save the cell int targetSeparator = target.indexOf("//"); String cellName = ""; if (targetSeparator > 0) { cellName = target.substring(0, targetSeparator); target = target.substring(targetSeparator); } // Strip out the leading "//" if there is one to make it easier to normalize the // remaining target string. We'll add this back at the end. target = MoreStrings.stripPrefix(target, "//").orElse(target); // Add the colon, if necessary. int colonIndex = target.indexOf(':'); if (colonIndex < 0) { // Strip a trailing slash if there is no colon. if (target.endsWith("/")) { target = target.substring(0, target.length() - 1); } int lastSlashIndex = target.lastIndexOf('/'); if (lastSlashIndex < 0) { target = target + ':' + target; } else { target = target + ':' + target.substring(lastSlashIndex + 1); } } else if (colonIndex != 0) { char charBeforeColon = target.charAt(colonIndex - 1); if (charBeforeColon == '/') { target = target.substring(0, colonIndex - 1) + target.substring(colonIndex); } } return cellName + "//" + target; } }