Java tutorial
/* * 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.event.BuckEventBus; import com.facebook.buck.event.ThrowableConsoleEvent; import com.facebook.buck.io.MorePaths; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.java.DefaultJavaPackageFinder; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.parser.BuildTargetParseException; import com.facebook.buck.parser.BuildTargetParser; import com.facebook.buck.parser.BuildTargetPatternParser; import com.facebook.buck.rules.ArtifactCache; import com.facebook.buck.rules.BuildDependencies; import com.facebook.buck.rules.BuildTargetSourcePath; import com.facebook.buck.rules.CassandraArtifactCache; import com.facebook.buck.rules.DirArtifactCache; import com.facebook.buck.rules.HttpArtifactCache; import com.facebook.buck.rules.MultiArtifactCache; import com.facebook.buck.rules.NoopArtifactCache; import com.facebook.buck.rules.PathSourcePath; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.util.Ansi; import com.facebook.buck.util.AnsiEnvironmentChecking; import com.facebook.buck.util.BuckConstant; import com.facebook.buck.util.FileHashCache; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.environment.Platform; import com.facebook.buck.util.unit.SizeUnit; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.hash.Hashing; import com.google.common.io.CharStreams; import com.google.common.io.Files; import com.netflix.astyanax.connectionpool.exceptions.ConnectionException; import com.squareup.okhttp.ConnectionPool; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Response; import org.ini4j.Ini; import org.ini4j.Profile.Section; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * Structured representation of data read from a {@code .buckconfig} file. */ @Beta @Immutable public class BuckConfig { private static final String DEFAULT_BUCK_CONFIG_FILE_NAME = ".buckconfig"; public static final String DEFAULT_BUCK_CONFIG_OVERRIDE_FILE_NAME = ".buckconfig.local"; private static final String ALIAS_SECTION_HEADER = "alias"; /** * This pattern is designed so that a fully-qualified build target cannot be a valid alias name * and vice-versa. */ private static final Pattern ALIAS_PATTERN = Pattern.compile("[a-zA-Z_-][a-zA-Z0-9_-]*"); @VisibleForTesting static final String BUCK_BUCKD_DIR_KEY = "buck.buckd_dir"; private static final String DEFAULT_CACHE_DIR = "buck-cache"; private static final String DEFAULT_DIR_CACHE_MODE = CacheMode.readwrite.name(); private static final String DEFAULT_CASSANDRA_PORT = "9160"; private static final String DEFAULT_CASSANDRA_MODE = CacheMode.readwrite.name(); private static final String DEFAULT_CASSANDRA_TIMEOUT_SECONDS = "10"; private static final String DEFAULT_MAX_TRACES = "25"; private static final String DEFAULT_HTTP_URL = "http://localhost:8080"; private static final String DEFAULT_HTTP_CACHE_MODE = CacheMode.readwrite.name(); private static final String DEFAULT_HTTP_CACHE_TIMEOUT_SECONDS = "10"; private final ImmutableMap<String, ImmutableMap<String, String>> sectionsToEntries; private final ImmutableMap<String, BuildTarget> aliasToBuildTargetMap; private final ImmutableMap<String, Path> repoNamesToPaths; private final ProjectFilesystem projectFilesystem; private final BuildTargetParser buildTargetParser; private final Platform platform; private final ImmutableMap<String, String> environment; private enum ArtifactCacheNames { dir, cassandra, http } private enum CacheMode { readonly(false), readwrite(true),; private final boolean doStore; private CacheMode(boolean doStore) { this.doStore = doStore; } } @VisibleForTesting BuckConfig(Map<String, Map<String, String>> sectionsToEntries, ProjectFilesystem projectFilesystem, BuildTargetParser buildTargetParser, Platform platform, ImmutableMap<String, String> environment, ImmutableMap<String, Path> repoNamesToPaths) { this.projectFilesystem = projectFilesystem; this.buildTargetParser = buildTargetParser; ImmutableMap.Builder<String, ImmutableMap<String, String>> sectionsToEntriesBuilder = ImmutableMap .builder(); for (Map.Entry<String, Map<String, String>> entry : sectionsToEntries.entrySet()) { sectionsToEntriesBuilder.put(entry.getKey(), ImmutableMap.copyOf(entry.getValue())); } this.sectionsToEntries = sectionsToEntriesBuilder.build(); // We could create this Map on demand; however, in practice, it is almost always needed when // BuckConfig is needed because CommandLineBuildTargetNormalizer needs it. this.aliasToBuildTargetMap = createAliasToBuildTargetMap(this.getEntriesForSection(ALIAS_SECTION_HEADER), buildTargetParser); this.repoNamesToPaths = repoNamesToPaths; this.platform = platform; this.environment = environment; } /** * Takes a sequence of {@code .buckconfig} files and loads them, in order, to create a * {@code BuckConfig} object. Each successive file that is loaded has the ability to override * definitions from a previous file. * @param projectFilesystem project for which the {@link BuckConfig} is being created. * @param files The sequence of {@code .buckconfig} files to load. */ public static BuckConfig createFromFiles(ProjectFilesystem projectFilesystem, Iterable<File> files, Platform platform, ImmutableMap<String, String> environment) throws IOException { BuildTargetParser buildTargetParser = new BuildTargetParser(); if (Iterables.isEmpty(files)) { return new BuckConfig(ImmutableMap.<String, Map<String, String>>of(), projectFilesystem, buildTargetParser, platform, environment, ImmutableMap.<String, Path>of()); } // Convert the Files to Readers. ImmutableList.Builder<Reader> readers = ImmutableList.builder(); for (File file : files) { readers.add(Files.newReader(file, Charsets.UTF_8)); } return createFromReaders(readers.build(), projectFilesystem, buildTargetParser, platform, environment); } /** * @return whether {@code aliasName} conforms to the pattern for a valid alias name. This does not * indicate whether it is an alias that maps to a build target in a BuckConfig. */ private static boolean isValidAliasName(String aliasName) { return ALIAS_PATTERN.matcher(aliasName).matches(); } public static void validateAliasName(String aliasName) throws HumanReadableException { validateAgainstAlias(aliasName, "Alias"); } public static void validateLabelName(String aliasName) throws HumanReadableException { validateAgainstAlias(aliasName, "Label"); } private static void validateAgainstAlias(String aliasName, String fieldName) { if (isValidAliasName(aliasName)) { return; } if (aliasName.isEmpty()) { throw new HumanReadableException("%s cannot be the empty string.", fieldName); } throw new HumanReadableException("Not a valid %s: %s.", fieldName.toLowerCase(), aliasName); } @VisibleForTesting static Map<String, Map<String, String>> createFromReaders(Iterable<Reader> readers) throws IOException { Ini ini = new Ini(); for (Reader reader : readers) { // The data contained by reader need to be processed twice (first during validation, then // when merging into ini), so read the data into a string that can be used as the source of // two StringReaders. try (Reader r = reader) { String iniString = CharStreams.toString(r); validateReader(new StringReader(iniString)); ini.load(new StringReader(iniString)); } } Map<String, Map<String, String>> sectionsToEntries = Maps.newHashMap(); for (String sectionName : ini.keySet()) { ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); Section section = ini.get(sectionName); for (String propertyName : section.keySet()) { String propertyValue = section.get(propertyName); builder.put(propertyName, propertyValue); } ImmutableMap<String, String> sectionToEntries = builder.build(); sectionsToEntries.put(sectionName, sectionToEntries); } return sectionsToEntries; } private static void validateReader(Reader reader) throws IOException { // Verify that within each ini file, no section has the same key specified more than once. Ini ini = new Ini(); ini.load(reader); for (String sectionName : ini.keySet()) { Section section = ini.get(sectionName); for (String propertyName : section.keySet()) { if (section.getAll(propertyName).size() > 1) { throw new HumanReadableException( "Duplicate definition for %s in the [%s] section of your .buckconfig or " + ".buckconfig.local.", propertyName, sectionName); } } } } @VisibleForTesting static BuckConfig createFromReaders(Iterable<Reader> readers, ProjectFilesystem projectFilesystem, BuildTargetParser buildTargetParser, Platform platform, ImmutableMap<String, String> environment) throws IOException { Map<String, Map<String, String>> sectionsToEntries = createFromReaders(readers); ImmutableMap<String, Path> repoNamesToPaths = createRepoNamesToPaths(projectFilesystem, sectionsToEntries); return new BuckConfig(sectionsToEntries, projectFilesystem, buildTargetParser, platform, environment, repoNamesToPaths); } public ImmutableMap<String, String> getEntriesForSection(String section) { ImmutableMap<String, String> entries = sectionsToEntries.get(section); if (entries != null) { return entries; } else { return ImmutableMap.of(); } } /** * A set of paths to subtrees that do not contain source files, build files or files that could * affect either (buck-out, .idea, .buckd, buck-cache, .git, etc.). May return absolute paths * as well as relative paths. */ public ImmutableSet<Path> getIgnorePaths() { final ImmutableMap<String, String> projectConfig = getEntriesForSection("project"); final String ignoreKey = "ignore"; ImmutableSet.Builder<Path> builder = ImmutableSet.builder(); builder.add(Paths.get(BuckConstant.BUCK_OUTPUT_DIRECTORY)); builder.add(Paths.get(".idea")); Path buckdDir = Paths.get(System.getProperty(BUCK_BUCKD_DIR_KEY, ".buckd")); Path cacheDir = getCacheDir(); for (Path path : ImmutableList.of(buckdDir, cacheDir)) { if (!path.toString().isEmpty()) { builder.add(path); } } if (projectConfig.containsKey(ignoreKey)) { builder.addAll(Lists.transform(asListWithoutComments(projectConfig.get(ignoreKey)), MorePaths.TO_PATH)); } // Normalize paths in order to eliminate trailing '/' characters and whatnot. return builder.build(); } /** * ini4j leaves things that look like comments in the values of entries in the file. Generally, * we don't want to include these in our parameters, so filter them out where necessary. In an INI * file, the comment separator is ";", but some parsers (ini4j included) use "#" too. This method * handles both cases. * * @return An {@link ImmutableList} containing all entries that don't look like comments, or the * empty list if there are no values of if {@code value} is null. */ private ImmutableList<String> asListWithoutComments(@Nullable String value) { if (value == null) { return ImmutableList.of(); } Iterable<String> allValues = Splitter.on(',').omitEmptyStrings().trimResults().split(value); return FluentIterable.from(allValues).filter(new Predicate<String>() { @Override public boolean apply(String input) { // Reject if the first printable character is an ini comment char (';' or '#') return !Pattern.compile("^\\s*[#;]").matcher(input).find(); } }).toList(); } public ImmutableList<String> asListWithoutComments(Optional<String> value) { return asListWithoutComments(value.orNull()); } @Nullable public String getBuildTargetForAlias(String alias) { BuildTarget buildTarget = aliasToBuildTargetMap.get(alias); if (buildTarget != null) { return buildTarget.getFullyQualifiedName(); } else { return null; } } public BuildTarget getBuildTargetForFullyQualifiedTarget(String target) { return buildTargetParser.parse(target, BuildTargetPatternParser.fullyQualified(buildTargetParser)); } /** * @return the parsed BuildTarget in the given section and field, if set. */ public Optional<BuildTarget> getBuildTarget(String section, String field) { Optional<String> target = getValue(section, field); return target.isPresent() ? Optional.of(getBuildTargetForFullyQualifiedTarget(target.get())) : Optional.<BuildTarget>absent(); } /** * @return the parsed BuildTarget in the given section and field. */ public BuildTarget getRequiredBuildTarget(String section, String field) { Optional<BuildTarget> target = getBuildTarget(section, field); return required(section, field, target); } public <T extends Enum<T>> Optional<T> getEnum(String section, String field, Class<T> clazz) { Optional<String> value = getValue(section, field); if (!value.isPresent()) { return Optional.absent(); } try { return Optional.of(Enum.valueOf(clazz, value.get().toUpperCase(Locale.ROOT))); } catch (IllegalArgumentException e) { throw new HumanReadableException(".buckconfig: %s:%s must be one of %s (was %s)", section, field, clazz.getEnumConstants(), value.get()); } } public <T extends Enum<T>> T getRequiredEnum(String section, String field, Class<T> clazz) { Optional<T> value = getEnum(section, field, clazz); return required(section, field, value); } /** * @return a {@link SourcePath} identified by a @{link BuildTarget} or {@link Path} reference * by the given section:field, if set. */ public Optional<SourcePath> getSourcePath(String section, String field) { Optional<String> value = getValue(section, field); if (!value.isPresent()) { return Optional.absent(); } try { BuildTarget target = getBuildTargetForFullyQualifiedTarget(value.get()); return Optional.<SourcePath>of(new BuildTargetSourcePath(projectFilesystem, target)); } catch (BuildTargetParseException e) { checkPathExists(value.get(), String.format("Overridden %s:%s path not found: ", section, field)); return Optional.<SourcePath>of(new PathSourcePath(projectFilesystem, Paths.get(value.get()))); } } /** * @return a {@link SourcePath} identified by a @{link BuildTarget} or {@link Path} reference * by the given section:field. */ public SourcePath getRequiredSourcePath(String section, String field) { Optional<SourcePath> path = getSourcePath(section, field); return required(section, field, path); } /** * In a {@link BuckConfig}, an alias can either refer to a fully-qualified build target, or an * alias defined earlier in the {@code alias} section. The mapping produced by this method * reflects the result of resolving all aliases as values in the {@code alias} section. */ private static ImmutableMap<String, BuildTarget> createAliasToBuildTargetMap( ImmutableMap<String, String> rawAliasMap, BuildTargetParser buildTargetParser) { // We use a LinkedHashMap rather than an ImmutableMap.Builder because we want both (1) order to // be preserved, and (2) the ability to inspect the Map while building it up. LinkedHashMap<String, BuildTarget> aliasToBuildTarget = Maps.newLinkedHashMap(); for (Map.Entry<String, String> aliasEntry : rawAliasMap.entrySet()) { String alias = aliasEntry.getKey(); validateAliasName(alias); // Determine whether the mapping is to a build target or to an alias. String value = aliasEntry.getValue(); BuildTarget buildTarget; if (isValidAliasName(value)) { buildTarget = aliasToBuildTarget.get(value); if (buildTarget == null) { throw new HumanReadableException("No alias for: %s.", value); } } else { // Here we parse the alias values with a BuildTargetParser to be strict. We could be looser // and just grab everything between "//" and ":" and assume it's a valid base path. buildTarget = buildTargetParser.parse(value, BuildTargetPatternParser.fullyQualified(buildTargetParser)); } aliasToBuildTarget.put(alias, buildTarget); } return ImmutableMap.copyOf(aliasToBuildTarget); } /** * Create a map of {@link BuildTarget} base paths to aliases. Note that there may be more than * one alias to a base path, so the first one listed in the .buckconfig will be chosen. */ public ImmutableMap<Path, String> getBasePathToAliasMap() { ImmutableMap<String, String> aliases = sectionsToEntries.get(ALIAS_SECTION_HEADER); if (aliases == null) { return ImmutableMap.of(); } // Build up the Map with an ordinary HashMap because we need to be able to check whether the Map // already contains the key before inserting. Map<Path, String> basePathToAlias = Maps.newHashMap(); for (Map.Entry<String, BuildTarget> entry : aliasToBuildTargetMap.entrySet()) { String alias = entry.getKey(); BuildTarget buildTarget = entry.getValue(); Path basePath = buildTarget.getBasePath(); if (!basePathToAlias.containsKey(basePath)) { basePathToAlias.put(basePath, alias); } } return ImmutableMap.copyOf(basePathToAlias); } public ImmutableSet<String> getAliases() { return this.aliasToBuildTargetMap.keySet(); } public long getDefaultTestTimeoutMillis() { return Long.parseLong(getValue("test", "timeout").or("0")); } public boolean isTreatingAssumptionsAsErrors() { return getBooleanValue("test", "assumptions-are-errors", false); } public int getMaxTraces() { return Integer.parseInt(getValue("log", "max_traces").or(DEFAULT_MAX_TRACES)); } public boolean getRestartAdbOnFailure() { return Boolean.parseBoolean(getValue("adb", "adb_restart_on_failure").or("true")); } public boolean getFlushEventsBeforeExit() { return getBooleanValue("daemon", "flush_events_before_exit", false); } public ImmutableSet<String> getListenerJars() { return ImmutableSet.copyOf(asListWithoutComments(getValue("extensions", "listeners"))); } public ImmutableSet<String> getSrcRoots() { return ImmutableSet.copyOf(asListWithoutComments(getValue("java", "src_roots"))); } @VisibleForTesting DefaultJavaPackageFinder createDefaultJavaPackageFinder() { Set<String> srcRoots = getSrcRoots(); return DefaultJavaPackageFinder.createDefaultJavaPackageFinder(srcRoots); } /** * Return Strings so as to avoid a dependency on {@link LabelSelector}! */ ImmutableList<String> getDefaultRawExcludedLabelSelectors() { Optional<String> excludedRulesOptional = getValue("test", "excluded_labels"); return asListWithoutComments(excludedRulesOptional); } @Beta Optional<BuildDependencies> getBuildDependencies() { Optional<String> buildDependenciesOptional = getValue("build", "build_dependencies"); if (buildDependenciesOptional.isPresent()) { try { return Optional.of(BuildDependencies.valueOf(buildDependenciesOptional.get())); } catch (IllegalArgumentException e) { throw new HumanReadableException( "%s is not a valid value for build_dependencies. Must be one of: %s", buildDependenciesOptional.get(), Joiner.on(", ").join(BuildDependencies.values())); } } else { return Optional.absent(); } } /** * Create an Ansi object appropriate for the current output. First respect the user's * preferences, if set. Next, respect any default provided by the caller. (This is used by buckd * to tell the daemon about the client's terminal.) Finally, allow the Ansi class to autodetect * whether the current output is a tty. * @param defaultColor Default value provided by the caller (e.g. the client of buckd) */ public Ansi createAnsi(Optional<String> defaultColor) { String color = getValue("color", "ui").or(defaultColor).or("auto"); switch (color) { case "false": case "never": return Ansi.withoutTty(); case "true": case "always": return Ansi.forceTty(); case "auto": default: return new Ansi(AnsiEnvironmentChecking.environmentSupportsAnsiEscapes(platform, environment)); } } public ArtifactCache createArtifactCache(Optional<String> currentWifiSsid, BuckEventBus buckEventBus, FileHashCache fileHashCache) { ImmutableList<String> modes = getArtifactCacheModes(); if (modes.isEmpty()) { return new NoopArtifactCache(); } ImmutableList.Builder<ArtifactCache> builder = ImmutableList.builder(); try { for (String mode : modes) { switch (ArtifactCacheNames.valueOf(mode)) { case dir: ArtifactCache dirArtifactCache = createDirArtifactCache(); buckEventBus.register(dirArtifactCache); builder.add(dirArtifactCache); break; case cassandra: ArtifactCache cassandraArtifactCache = createCassandraArtifactCache(currentWifiSsid, buckEventBus, fileHashCache); if (cassandraArtifactCache != null) { builder.add(cassandraArtifactCache); } break; case http: ArtifactCache httpArtifactCache = createHttpArtifactCache(); builder.add(httpArtifactCache); break; } } } catch (IllegalArgumentException e) { throw new HumanReadableException("Unusable cache.mode: '%s'", modes.toString()); } ImmutableList<ArtifactCache> artifactCaches = builder.build(); if (artifactCaches.size() == 1) { // Don't bother wrapping a single artifact cache in MultiArtifactCache. return artifactCaches.get(0); } else { return new MultiArtifactCache(artifactCaches); } } ImmutableList<String> getArtifactCacheModes() { return asListWithoutComments(getValue("cache", "mode")); } /** * @return the depth of a local build chain which should trigger skipping the cache. */ public Optional<Long> getSkipLocalBuildChainDepth() { return getLong("cache", "skip_local_build_chain_depth"); } @VisibleForTesting Path getCacheDir() { String cacheDir = getValue("cache", "dir").or(DEFAULT_CACHE_DIR); Path pathToCacheDir = resolvePathThatMayBeOutsideTheProjectFilesystem(Paths.get(cacheDir)); return Preconditions.checkNotNull(pathToCacheDir); } @Nullable public Path resolvePathThatMayBeOutsideTheProjectFilesystem(@Nullable Path path) { if (path == null) { return path; } if (path.isAbsolute()) { return path; } Path expandedPath = MorePaths.expandHomeDir(path); return projectFilesystem.getAbsolutifier().apply(expandedPath); } public Optional<Long> getCacheDirMaxSizeBytes() { return getValue("cache", "dir_max_size").transform(new Function<String, Long>() { @Override public Long apply(String input) { return SizeUnit.parseBytes(input); } }); } private ArtifactCache createDirArtifactCache() { Path cacheDir = getCacheDir(); File dir = cacheDir.toFile(); boolean doStore = readCacheMode("dir_mode", DEFAULT_DIR_CACHE_MODE); try { return new DirArtifactCache(dir, doStore, getCacheDirMaxSizeBytes()); } catch (IOException e) { throw new HumanReadableException("Failure initializing artifact cache directory: %s", dir); } } /** * Clients should use {@link #createArtifactCache(Optional, BuckEventBus, FileHashCache)} unless * it is expected that the user has defined a {@code cassandra} cache, and that it should be used * exclusively. */ @Nullable CassandraArtifactCache createCassandraArtifactCache(Optional<String> currentWifiSsid, BuckEventBus buckEventBus, FileHashCache fileHashCache) { // cache.blacklisted_wifi_ssids ImmutableSet<String> blacklistedWifi = ImmutableSet .copyOf(asListWithoutComments(getValue("cache", "blacklisted_wifi_ssids"))); if (currentWifiSsid.isPresent() && blacklistedWifi.contains(currentWifiSsid.get())) { // We're connected to a wifi hotspot that has been explicitly blacklisted from connecting to // Cassandra. return null; } // cache.cassandra_mode final boolean doStore = readCacheMode("cassandra_mode", DEFAULT_CASSANDRA_MODE); // cache.hosts String cacheHosts = getValue("cache", "hosts").or(""); // cache.port int port = Integer.parseInt(getValue("cache", "port").or(DEFAULT_CASSANDRA_PORT)); // cache.connection_timeout_seconds int timeoutSeconds = Integer .parseInt(getValue("cache", "connection_timeout_seconds").or(DEFAULT_CASSANDRA_TIMEOUT_SECONDS)); try { return new CassandraArtifactCache(cacheHosts, port, timeoutSeconds, doStore, buckEventBus, fileHashCache); } catch (ConnectionException e) { buckEventBus.post(ThrowableConsoleEvent.create(e, "Cassandra cache connection failure.")); return null; } } private String getLocalhost() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { return "<unknown>"; } } private ArtifactCache createHttpArtifactCache() { URL url; try { url = new URL(getValue("cache", "http_url").or(DEFAULT_HTTP_URL)); } catch (MalformedURLException e) { throw new HumanReadableException(e, "Malformed [cache]http_url: %s", e.getMessage()); } int timeoutSeconds = Integer .parseInt(getValue("cache", "http_timeout_seconds").or(DEFAULT_HTTP_CACHE_TIMEOUT_SECONDS)); boolean doStore = readCacheMode("http_mode", DEFAULT_HTTP_CACHE_MODE); // Setup the defaut client to use. OkHttpClient client = new OkHttpClient(); final String localhost = getLocalhost(); client.networkInterceptors().add(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { return chain.proceed(chain.request().newBuilder() .addHeader("X-BuckCache-User", System.getProperty("user.name", "<unknown>")) .addHeader("X-BuckCache-Host", localhost).build()); } }); client.setConnectTimeout(timeoutSeconds, TimeUnit.SECONDS); client.setConnectionPool(new ConnectionPool( // It's important that this number is greater than the `-j` parallelism, // as if it's too small, we'll overflow the reusable connection pool and // start spamming new connections. While this isn't the best location, // the other current option is setting this wherever we construct a `Build` // object and have access to the `-j` argument. However, since that is // created in several places leave it here for now. /* maxIdleConnections */ 200, /* keepAliveDurationMs */ TimeUnit.MINUTES.toMillis(5))); // For fetches, use a client with a read timeout. OkHttpClient fetchClient = client.clone(); fetchClient.setReadTimeout(timeoutSeconds, TimeUnit.SECONDS); return new HttpArtifactCache(fetchClient, client, url, doStore, projectFilesystem, Hashing.crc32()); } private boolean readCacheMode(String fieldName, String defaultValue) { String cacheMode = getValue("cache", fieldName).or(defaultValue); final boolean doStore; try { doStore = CacheMode.valueOf(cacheMode).doStore; } catch (IllegalArgumentException e) { throw new HumanReadableException("Unusable cache.%s: '%s'", fieldName, cacheMode); } return doStore; } public Optional<String> getAndroidTarget() { return getValue("android", "target"); } public Optional<String> getNdkVersion() { return getValue("ndk", "ndk_version"); } public Optional<String> getValue(String sectionName, String propertyName) { ImmutableMap<String, String> properties = this.getEntriesForSection(sectionName); return Optional.fromNullable(properties.get(propertyName)); } public Optional<Long> getLong(String sectionName, String propertyName) { Optional<String> value = getValue(sectionName, propertyName); return value.isPresent() ? Optional.of(Long.valueOf(value.get())) : Optional.<Long>absent(); } public boolean getBooleanValue(String sectionName, String propertyName, boolean defaultValue) { Map<String, String> entries = getEntriesForSection(sectionName); if (!entries.containsKey(propertyName)) { return defaultValue; } String answer = Preconditions.checkNotNull(entries.get(propertyName)); switch (answer.toLowerCase()) { case "yes": case "true": return true; case "no": case "false": return false; default: throw new HumanReadableException("Unknown value for %s in [%s]: %s; should be yes/no true/false!", propertyName, sectionName); } } private <T> T required(String section, String field, Optional<T> value) { if (!value.isPresent()) { throw new HumanReadableException(String.format(".buckconfig: %s:%s must be set", section, field)); } return value.get(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof BuckConfig)) { return false; } BuckConfig that = (BuckConfig) obj; return Objects.equal(this.sectionsToEntries, that.sectionsToEntries); } @Override public int hashCode() { return Objects.hashCode(sectionsToEntries); } public ImmutableMap<String, String> getEnvironment() { return environment; } public String[] getEnv(String propertyName, String separator) { String value = getEnvironment().get(propertyName); if (value == null) { value = ""; } return value.split(separator); } /** * Returns the path to the platform specific aapt executable that is overridden by the current * project. If not specified, the Android platform aapt will be used. */ public Optional<Path> getAaptOverride() { Optional<String> pathString = getValue("tools", "aapt"); if (!pathString.isPresent()) { return Optional.absent(); } String platformDir; if (platform == Platform.LINUX) { platformDir = "linux"; } else if (platform == Platform.MACOS) { platformDir = "mac"; } else if (platform == Platform.WINDOWS) { platformDir = "windows"; } else { return Optional.absent(); } Path pathToAapt = Paths.get(pathString.get(), platformDir, "aapt"); return checkPathExists(pathToAapt.toString(), "Overridden aapt path not found: "); } /** * @return the path for the given section and property. */ public Optional<Path> getPath(String sectionName, String name) { return getPath(sectionName, name, true); } public Optional<Path> getPath(String sectionName, String name, boolean isRepoRootRelative) { Optional<String> pathString = getValue(sectionName, name); return pathString.isPresent() ? isRepoRootRelative ? checkPathExists(pathString.get(), String.format("Overridden %s:%s path not found: ", sectionName, name)) : Optional.of(Paths.get(pathString.get())) : Optional.<Path>absent(); } public Optional<Path> checkPathExists(String pathString, String errorMsg) { Path path = Paths.get(pathString); if (projectFilesystem.exists(path)) { return Optional.of(projectFilesystem.getPathForRelativePath(path)); } throw new HumanReadableException(errorMsg + path); } private static ImmutableMap<String, Path> createRepoNamesToPaths(ProjectFilesystem filesystem, Map<String, Map<String, String>> sectionsToEntries) throws IOException { @Nullable Map<String, String> repositoryConfigs = sectionsToEntries.get("repositories"); if (repositoryConfigs == null) { return ImmutableMap.of(); } ImmutableMap.Builder<String, Path> repositoryPaths = ImmutableMap.builder(); for (String name : repositoryConfigs.keySet()) { String pathString = repositoryConfigs.get(name); Path canonicalPath = filesystem.resolve(Paths.get(pathString)).toRealPath(); repositoryPaths.put(name, canonicalPath); } return repositoryPaths.build(); } public ImmutableMap<String, Path> getRepositoryPaths() { return repoNamesToPaths; } /** * @param projectFilesystem The directory that is the root of the project being built. */ public static BuckConfig createDefaultBuckConfig(ProjectFilesystem projectFilesystem, Platform platform, ImmutableMap<String, String> environment) throws IOException { ImmutableList.Builder<File> configFileBuilder = ImmutableList.builder(); File configFile = projectFilesystem.getFileForRelativePath(DEFAULT_BUCK_CONFIG_FILE_NAME); if (configFile.isFile()) { configFileBuilder.add(configFile); } File overrideConfigFile = projectFilesystem.getFileForRelativePath(DEFAULT_BUCK_CONFIG_OVERRIDE_FILE_NAME); if (overrideConfigFile.isFile()) { configFileBuilder.add(overrideConfigFile); } ImmutableList<File> configFiles = configFileBuilder.build(); return BuckConfig.createFromFiles(projectFilesystem, configFiles, platform, environment); } }