/* * 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.jvm.java; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.config.ConfigView; import com.facebook.buck.model.Either; import com.facebook.buck.util.HumanReadableException; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import java.io.File; import java.nio.file.Path; import java.util.Map; import java.util.Optional; /** A java-specific "view" of BuckConfig. */ public class JavaBuckConfig implements ConfigView<BuckConfig> { public static final String SECTION = "java"; public static final String PROPERTY_COMPILE_AGAINST_ABIS = "compile_against_abis"; private final BuckConfig delegate; // Interface for reflection-based ConfigView to instantiate this class. public static JavaBuckConfig of(BuckConfig delegate) { return new JavaBuckConfig(delegate); } private JavaBuckConfig(BuckConfig delegate) { this.delegate = delegate; } @Override public BuckConfig getDelegate() { return delegate; } public JavaOptions getDefaultJavaOptions() { return JavaOptions.builder().setJavaPath(getPathToExecutable("java")).build(); } public JavaOptions getDefaultJavaOptionsForTests() { Optional<Path> javaTestPath = getPathToExecutable("java_for_tests"); if (javaTestPath.isPresent()) { return JavaOptions.builder().setJavaPath(javaTestPath).build(); } return getDefaultJavaOptions(); } public JavacOptions getDefaultJavacOptions() { JavacOptions.Builder builder = JavacOptions.builderForUseInJavaBuckConfig(); Optional<String> sourceLevel = delegate.getValue(SECTION, "source_level"); if (sourceLevel.isPresent()) { builder.setSourceLevel(sourceLevel.get()); } Optional<String> targetLevel = delegate.getValue(SECTION, "target_level"); if (targetLevel.isPresent()) { builder.setTargetLevel(targetLevel.get()); } ImmutableList<String> extraArguments = delegate.getListWithoutComments(SECTION, "extra_arguments"); ImmutableList<String> safeAnnotationProcessors = delegate.getListWithoutComments(SECTION, "safe_annotation_processors"); Optional<AbstractJavacOptions.SpoolMode> spoolMode = delegate.getEnum(SECTION, "jar_spool_mode", AbstractJavacOptions.SpoolMode.class); if (spoolMode.isPresent()) { builder.setSpoolMode(spoolMode.get()); } builder.setTrackClassUsage(trackClassUsage()); AbiGenerationMode abiGenerationMode = getAbiGenerationMode(); switch (abiGenerationMode) { case CLASS: case SOURCE_WITH_DEPS: builder.setCompilationMode(Javac.CompilationMode.FULL); break; case MIGRATING_TO_SOURCE: builder.setCompilationMode(Javac.CompilationMode.FULL_CHECKING_REFERENCES); break; case SOURCE: builder.setCompilationMode(Javac.CompilationMode.FULL_ENFORCING_REFERENCES); break; } ImmutableMap<String, String> allEntries = delegate.getEntriesForSection(SECTION); ImmutableMap.Builder<String, String> bootclasspaths = ImmutableMap.builder(); for (Map.Entry<String, String> entry : allEntries.entrySet()) { if (entry.getKey().startsWith("bootclasspath-")) { bootclasspaths.put(entry.getKey().substring("bootclasspath-".length()), entry.getValue()); } } return builder .putAllSourceToBootclasspath(bootclasspaths.build()) .addAllExtraArguments(extraArguments) .setSafeAnnotationProcessors(safeAnnotationProcessors) .build(); } public boolean shouldGenerateAbisFromSource() { AbiGenerationMode abiGenerationMode = getAbiGenerationMode(); return abiGenerationMode == AbiGenerationMode.SOURCE || abiGenerationMode == AbiGenerationMode.SOURCE_WITH_DEPS; } private AbiGenerationMode getAbiGenerationMode() { return delegate .getEnum(SECTION, "abi_generation_mode", AbiGenerationMode.class) .orElse(AbiGenerationMode.CLASS); } public ImmutableSet<String> getSrcRoots() { return ImmutableSet.copyOf(delegate.getListWithoutComments(SECTION, "src_roots")); } public DefaultJavaPackageFinder createDefaultJavaPackageFinder() { return DefaultJavaPackageFinder.createDefaultJavaPackageFinder(getSrcRoots()); } public boolean trackClassUsage() { // This is just to make it possible to turn off dep-based rulekeys in case anything goes wrong // and can be removed when we're sure class usage tracking and dep-based keys for Java // work fine. Optional<Boolean> trackClassUsage = delegate.getBoolean(SECTION, "track_class_usage"); if (trackClassUsage.isPresent() && !trackClassUsage.get()) { return false; } final Javac.Source javacSource = getJavacSpec().getJavacSource(); return (javacSource == Javac.Source.JAR || javacSource == Javac.Source.JDK); } public JavacSpec getJavacSpec() { return JavacSpec.builder() .setJavacPath( getJavacPath().isPresent() ? Optional.of(Either.ofLeft(getJavacPath().get())) : Optional.empty()) .setJavacJarPath(delegate.getSourcePath("tools", "javac_jar")) .setJavacLocation( delegate .getEnum(SECTION, "location", Javac.Location.class) .orElse(Javac.Location.IN_PROCESS)) .setCompilerClassName(delegate.getValue("tools", "compiler_class_name")) .build(); } @VisibleForTesting Optional<Path> getJavacPath() { return getPathToExecutable("javac"); } private Optional<Path> getPathToExecutable(String executableName) { Optional<Path> path = delegate.getPath("tools", executableName); if (path.isPresent()) { File file = path.get().toFile(); if (!file.canExecute()) { throw new HumanReadableException(executableName + " is not executable: " + file.getPath()); } return Optional.of(file.toPath()); } return Optional.empty(); } public boolean shouldCacheBinaries() { return delegate.getBooleanValue(SECTION, "cache_binaries", true); } public Optional<Integer> getDxThreadCount() { return delegate.getInteger(SECTION, "dx_threads"); } /** * Controls a special verification mode that generates ABIs both from source and from class files * and diffs them. This is a test hook for use during development of the source ABI feature. This * only has meaning when {@link #getAbiGenerationMode()} is one of the source modes. */ public SourceAbiVerificationMode getSourceAbiVerificationMode() { if (!shouldGenerateAbisFromSource()) { return SourceAbiVerificationMode.OFF; } return delegate .getEnum(SECTION, "source_abi_verification_mode", SourceAbiVerificationMode.class) // TODO(jkeljo): Flip the default to OFF when the feature is considered debugged .orElse(SourceAbiVerificationMode.LOG); } public boolean shouldCompileAgainstAbis() { return delegate.getBooleanValue(SECTION, PROPERTY_COMPILE_AGAINST_ABIS, false); } public enum AbiGenerationMode { /** Generate ABIs by stripping .class files */ CLASS, /** Generate ABIs by parsing .java files with dependency ABIs available */ SOURCE_WITH_DEPS, /** * Output warnings for things that aren't legal when generating ABIs from source without * dependency ABIs */ MIGRATING_TO_SOURCE, /** * Generate ABIs by parsing .java files without dependency ABIs available (has some limitations) */ SOURCE, } public enum SourceAbiVerificationMode { /** Don't verify ABI jars. */ OFF, /** Generate ABI jars from classes and from source. Log any differences. */ LOG, /** Generate ABI jars from classes and from source. Fail on differences. */ FAIL, } }