/* * 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.config.CellConfig; import com.facebook.buck.event.BuckEventListener; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.log.LogConfigSetup; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.BuildTargetParser; import com.facebook.buck.parser.BuildTargetPatternParser; import com.facebook.buck.parser.BuildTargetPatternTargetNodeParser; import com.facebook.buck.parser.TargetNodeSpec; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.RelativeCellName; import com.facebook.buck.rules.RuleKey; import com.facebook.buck.rules.TargetGraphAndBuildTargets; import com.facebook.buck.rules.keys.DefaultRuleKeyCache; import com.facebook.buck.rules.keys.EventPostingRuleKeyCacheScope; import com.facebook.buck.rules.keys.RuleKeyCacheRecycler; import com.facebook.buck.rules.keys.RuleKeyCacheScope; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.util.DefaultProcessExecutor; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.concurrent.ConcurrencyLimit; import com.facebook.buck.versions.VersionException; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import javax.annotation.Nullable; import org.kohsuke.args4j.Option; public abstract class AbstractCommand implements Command { private static final String HELP_LONG_ARG = "--help"; private static final String NO_CACHE_LONG_ARG = "--no-cache"; private static final String OUTPUT_TEST_EVENTS_TO_FILE_LONG_ARG = "--output-test-events-to-file"; private static final String PROFILE_PARSER_LONG_ARG = "--profile-buck-parser"; private static final String NUM_THREADS_LONG_ARG = "--num-threads"; /** * This value should never be read. {@link VerbosityParser} should be used instead. args4j * requires that all options that could be passed in are listed as fields, so we include this * field so that {@code --verbose} is universally available to all commands. */ @Option( name = VerbosityParser.VERBOSE_LONG_ARG, aliases = {VerbosityParser.VERBOSE_SHORT_ARG}, usage = "Specify a number between 0 and 8. '-v 1' is default, '-v 8' is most verbose." ) @SuppressWarnings("PMD.UnusedPrivateField") private int verbosityLevel = -1; @Option(name = NUM_THREADS_LONG_ARG, aliases = "-j", usage = "Default is 1.25 * num processors.") @Nullable private Integer numThreads = null; @Option( name = "--config", aliases = {"-c"}, usage = "" ) private Map<String, String> configOverrides = Maps.newLinkedHashMap(); @Override public CellConfig getConfigOverrides() { CellConfig.Builder builder = CellConfig.builder(); // Parse command-line config overrides. for (Map.Entry<String, String> entry : configOverrides.entrySet()) { List<String> key = Splitter.on("//").limit(2).splitToList(entry.getKey()); RelativeCellName cellName = RelativeCellName.ALL_CELLS_SPECIAL_NAME; String configKey = key.get(0); if (key.size() == 2) { // Here we explicitly take the whole string as the cell name. We don't support transitive // path overrides for cells. if (key.get(0).length() == 0) { cellName = RelativeCellName.ROOT_CELL_NAME; } else { cellName = RelativeCellName.of(ImmutableSet.of(key.get(0))); } configKey = key.get(1); } int separatorIndex = configKey.lastIndexOf('.'); if (separatorIndex < 0 || separatorIndex == configKey.length() - 1) { throw new HumanReadableException( "Invalid config override \"%s=%s\" Expected <section>.<field>=<value>.", configKey, entry.getValue()); } String value = entry.getValue(); // If the value is empty, un-set the config if (value == null) { value = ""; } // Overrides for locations of transitive children of cells are weird as the order of overrides // can affect the result (for example `-c a/b/c.k=v -c a/b//repositories.c=foo` causes an // interesting problem as the a/b/c cell gets created as a side-effect of the first override, // but the second override wants to change its identity). // It's generally a better idea to use the .buckconfig.local mechanism when overriding // repositories anyway, so here we simply disallow them. String section = configKey.substring(0, separatorIndex); if (section.equals("repositories")) { throw new HumanReadableException( "Overriding repository locations from the command line " + "is not supported. Please place a .buckconfig.local in the appropriate location and " + "use that instead."); } String field = configKey.substring(separatorIndex + 1); builder.put(cellName, section, field, value); } if (numThreads != null) { builder.put( RelativeCellName.ALL_CELLS_SPECIAL_NAME, "build", "threads", String.valueOf(numThreads)); } if (noCache) { builder.put(RelativeCellName.ALL_CELLS_SPECIAL_NAME, "cache", "mode", ""); } return builder.build(); } @Override public LogConfigSetup getLogConfig() { return LogConfigSetup.DEFAULT_SETUP; } @Option( name = NO_CACHE_LONG_ARG, usage = "Whether to ignore the [cache] declared in .buckconfig." ) private boolean noCache = false; @Nullable @Option( name = OUTPUT_TEST_EVENTS_TO_FILE_LONG_ARG, aliases = {"--output-events-to-file"}, usage = "Serialize test-related event-bus events to the given file " + "as line-oriented JSON objects." ) private String eventsOutputPath = null; @Option( name = PROFILE_PARSER_LONG_ARG, usage = "Enable profiling of buck.py internals (not the target being compiled) in the debug" + "log and trace." ) private boolean enableParserProfiling = false; @Option(name = HELP_LONG_ARG, usage = "Prints the available options and exits.") private boolean help = false; /** @return {code true} if the {@code [cache]} in {@code .buckconfig} should be ignored. */ public boolean isNoCache() { return noCache; } public Optional<Path> getEventsOutputPath() { if (eventsOutputPath == null) { return Optional.empty(); } else { return Optional.of(Paths.get(eventsOutputPath)); } } @Override public void printUsage(PrintStream stream) { stream.println("Options:"); new AdditionalOptionsCmdLineParser(this).printUsage(stream); stream.println(); } @Override public OptionalInt runHelp(PrintStream stream) { if (help) { printUsage(stream); return OptionalInt.of(1); } else { return OptionalInt.empty(); } } @Override public final int run(CommandRunnerParams params) throws IOException, InterruptedException { if (help) { printUsage(params.getConsole().getStdErr()); return 1; } if (params.getConsole().getAnsi().isAnsiTerminal()) { ImmutableList<String> motd = params.getBuckConfig().getMessageOfTheDay(); if (!motd.isEmpty()) { for (String line : motd) { params.getBuckEventBus().post(ConsoleEvent.info(line)); } } } return runWithoutHelp(params); } public abstract int runWithoutHelp(CommandRunnerParams params) throws IOException, InterruptedException; protected CommandLineBuildTargetNormalizer getCommandLineBuildTargetNormalizer( BuckConfig buckConfig) { return new CommandLineBuildTargetNormalizer(buckConfig); } public boolean getEnableParserProfiling() { return enableParserProfiling; } public ImmutableList<TargetNodeSpec> parseArgumentsAsTargetNodeSpecs( BuckConfig config, Iterable<String> targetsAsArgs) { ImmutableList.Builder<TargetNodeSpec> specs = ImmutableList.builder(); CommandLineTargetNodeSpecParser parser = new CommandLineTargetNodeSpecParser(config, new BuildTargetPatternTargetNodeParser()); for (String arg : targetsAsArgs) { specs.addAll(parser.parse(config.getCellPathResolver(), arg)); } return specs.build(); } /** * @param cellNames * @param buildTargetNames The build targets to parse, represented as strings. * @return A set of {@link BuildTarget}s for the input buildTargetNames. */ protected ImmutableSet<BuildTarget> getBuildTargets( CellPathResolver cellNames, Iterable<String> buildTargetNames) { ImmutableSet.Builder<BuildTarget> buildTargets = ImmutableSet.builder(); // Parse all of the build targets specified by the user. for (String buildTargetName : buildTargetNames) { buildTargets.add( BuildTargetParser.INSTANCE.parse( buildTargetName, BuildTargetPatternParser.fullyQualified(), cellNames)); } return buildTargets.build(); } protected ExecutionContext createExecutionContext(CommandRunnerParams params) { return ExecutionContext.builder() .setConsole(params.getConsole()) .setAndroidPlatformTargetSupplier(params.getAndroidPlatformTargetSupplier()) .setBuckEventBus(params.getBuckEventBus()) .setPlatform(params.getPlatform()) .setEnvironment(params.getEnvironment()) .setJavaPackageFinder(params.getJavaPackageFinder()) .setExecutors(params.getExecutors()) .setCellPathResolver(params.getCell().getCellPathResolver()) .setProcessExecutor(new DefaultProcessExecutor(params.getConsole())) .build(); } public ConcurrencyLimit getConcurrencyLimit(BuckConfig buckConfig) { return new ConcurrencyLimit( buckConfig.getNumThreads(), buckConfig.getResourceAllocationFairness(), buckConfig.getManagedThreadCount(), buckConfig.getDefaultResourceAmounts(), buckConfig.getMaximumResourceAmounts()); } @Override public boolean isSourceControlStatsGatheringEnabled() { return false; } TargetGraphAndBuildTargets toVersionedTargetGraph( CommandRunnerParams params, TargetGraphAndBuildTargets targetGraphAndBuildTargets) throws VersionException, InterruptedException { return params .getVersionedTargetGraphCache() .toVersionedTargetGraph( params.getBuckEventBus(), params.getBuckConfig(), params.getTypeCoercerFactory(), targetGraphAndBuildTargets); } @Override public Iterable<BuckEventListener> getEventListeners() { return ImmutableList.of(); } RuleKeyCacheScope<RuleKey> getDefaultRuleKeyCacheScope( CommandRunnerParams params, RuleKeyCacheRecycler.SettingsAffectingCache settings) { return params .getDefaultRuleKeyFactoryCacheRecycler() // First try to get the cache from the recycler. .map(recycler -> recycler.withRecycledCache(params.getBuckEventBus(), settings)) // Otherwise, create a new one. .orElseGet( () -> new EventPostingRuleKeyCacheScope<>( params.getBuckEventBus(), new DefaultRuleKeyCache<>())); } }