/* * Copyright 2013-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.io.ProjectFilesystem; import com.facebook.buck.rules.RuleKeyAppendable; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.immutables.value.Value; /** * Represents the command line options that should be passed to javac. Note that the options do not * include either the classpath or the directory for storing class files. */ @Value.Immutable @BuckStyleImmutable abstract class AbstractJavacOptions implements RuleKeyAppendable { // Default combined source and target level. public static final String TARGETED_JAVA_VERSION = "7"; /** The method in which the compiler output is spooled. */ public enum SpoolMode { /** * Writes the compiler output directly to a .jar file while retaining the intermediate .class * files in memory. If {@link com.facebook.buck.jvm.java.JavaLibraryDescription.Arg} * postprocessClassesCommands are present, the builder will resort to writing .class files to * disk by necessity. */ DIRECT_TO_JAR, /** * Writes the intermediate .class files from the compiler output to disk which is later packed * up into a .jar file. */ INTERMEDIATE_TO_DISK, } @Value.Default protected SpoolMode getSpoolMode() { return SpoolMode.INTERMEDIATE_TO_DISK; } @Value.Default protected boolean isProductionBuild() { return false; } @Value.Default protected boolean isVerbose() { return false; } @Value.Default public String getSourceLevel() { return TARGETED_JAVA_VERSION; } @VisibleForTesting @Value.Default public String getTargetLevel() { return TARGETED_JAVA_VERSION; } @Value.Default public AnnotationProcessingParams getAnnotationProcessingParams() { return AnnotationProcessingParams.EMPTY; } public abstract Set<String> getSafeAnnotationProcessors(); public abstract List<String> getExtraArguments(); public abstract Set<Pattern> getClassesToRemoveFromJar(); protected abstract Optional<String> getBootclasspath(); protected abstract Map<String, String> getSourceToBootclasspath(); protected boolean isDebug() { return !isProductionBuild(); } @Value.Default public boolean trackClassUsage() { return false; } @Value.Default public JavacCompilationMode getCompilationMode() { return JavacCompilationMode.FULL; } public void validateOptions(Function<String, Boolean> classpathChecker) throws IOException { if (getBootclasspath().isPresent()) { String bootclasspath = getBootclasspath().get(); try { if (!classpathChecker.apply(bootclasspath)) { throw new IOException( String.format("Bootstrap classpath %s contains no valid entries", bootclasspath)); } } catch (UncheckedIOException e) { throw e.getCause(); } } } public void appendOptionsTo( OptionsConsumer optionsConsumer, SourcePathResolver pathResolver, ProjectFilesystem filesystem) { // Add some standard options. optionsConsumer.addOptionValue("source", getSourceLevel()); optionsConsumer.addOptionValue("target", getTargetLevel()); // Set the sourcepath to stop us reading source files out of jars by mistake. optionsConsumer.addOptionValue("sourcepath", ""); if (isDebug()) { optionsConsumer.addFlag("g"); } if (isVerbose()) { optionsConsumer.addFlag("verbose"); } // Override the bootclasspath if Buck is building Java code for Android. if (getBootclasspath().isPresent()) { optionsConsumer.addOptionValue("bootclasspath", getBootclasspath().get()); } else { String bcp = getSourceToBootclasspath().get(getSourceLevel()); if (bcp != null) { optionsConsumer.addOptionValue("bootclasspath", bcp); } } // Add annotation processors. AnnotationProcessingParams annotationProcessingParams = getAnnotationProcessingParams(); if (!annotationProcessingParams.isEmpty()) { // Specify where to generate sources so IntelliJ can pick them up. Path generateTo = annotationProcessingParams.getGeneratedSourceFolderName(); if (generateTo != null) { //noinspection ConstantConditions optionsConsumer.addOptionValue("s", filesystem.resolve(generateTo).toString()); } ImmutableList<ResolvedJavacPluginProperties> annotationProcessors = annotationProcessingParams.getAnnotationProcessors(filesystem, pathResolver); // Specify processorpath to search for processors. optionsConsumer.addOptionValue( "processorpath", annotationProcessors .stream() .map(ResolvedJavacPluginProperties::getClasspath) .flatMap(Arrays::stream) .distinct() .map(URL::toString) .collect(Collectors.joining(File.pathSeparator))); // Specify names of processors. optionsConsumer.addOptionValue( "processor", annotationProcessors .stream() .map(ResolvedJavacPluginProperties::getProcessorNames) .flatMap(Collection::stream) .collect(Collectors.joining(","))); // Add processor parameters. for (String parameter : annotationProcessingParams.getParameters()) { optionsConsumer.addFlag("A" + parameter); } if (annotationProcessingParams.getProcessOnly()) { optionsConsumer.addFlag("proc:only"); } } else { // Disable automatic annotation processor lookup optionsConsumer.addFlag("proc:none"); } // Add extra arguments. optionsConsumer.addExtras(getExtraArguments()); } @Override public void appendToRuleKey(RuleKeyObjectSink sink) { sink.setReflectively("sourceLevel", getSourceLevel()) .setReflectively("targetLevel", getTargetLevel()) .setReflectively("extraArguments", Joiner.on(',').join(getExtraArguments())) .setReflectively("debug", isDebug()) .setReflectively("bootclasspath", getBootclasspath()) .setReflectively("annotationProcessingParams", getAnnotationProcessingParams()) .setReflectively("spoolMode", getSpoolMode()) .setReflectively("trackClassUsage", trackClassUsage()) .setReflectively("compilationMode", getCompilationMode()); } static JavacOptions.Builder builderForUseInJavaBuckConfig() { return JavacOptions.builder(); } public static JavacOptions.Builder builder(JavacOptions options) { JavacOptions.Builder builder = JavacOptions.builder(); return builder.from(options); } public final Optional<Path> getGeneratedSourceFolderName() { return Optional.ofNullable(getAnnotationProcessingParams().getGeneratedSourceFolderName()); } }