1 /* 2 * Copyright (C) 2019 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen; 18 19 import static com.google.common.base.CaseFormat.LOWER_CAMEL; 20 import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE; 21 import static com.google.common.base.Preconditions.checkArgument; 22 import static com.google.common.collect.Sets.immutableEnumSet; 23 import static dagger.internal.codegen.DaggerStreams.toImmutableSet; 24 import static dagger.internal.codegen.FeatureStatus.DISABLED; 25 import static dagger.internal.codegen.FeatureStatus.ENABLED; 26 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.EMIT_MODIFIABLE_METADATA_ANNOTATIONS; 27 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.EXPERIMENTAL_AHEAD_OF_TIME_SUBCOMPONENTS; 28 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.EXPERIMENTAL_ANDROID_MODE; 29 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.FAST_INIT; 30 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.FLOATING_BINDS_METHODS; 31 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.FORCE_USE_SERIALIZED_COMPONENT_IMPLEMENTATIONS; 32 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.FORMAT_GENERATED_SOURCE; 33 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT; 34 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.WARN_IF_INJECTION_FACTORY_NOT_GENERATED_UPSTREAM; 35 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Feature.WRITE_PRODUCER_NAME_IN_TOKEN; 36 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.KeyOnlyOption.HEADER_COMPILATION; 37 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.KeyOnlyOption.USE_GRADLE_INCREMENTAL_PROCESSING; 38 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.DISABLE_INTER_COMPONENT_SCOPE_VALIDATION; 39 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.EXPLICIT_BINDING_CONFLICTS_WITH_INJECT; 40 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.FULL_BINDING_GRAPH_VALIDATION; 41 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.MODULE_HAS_DIFFERENT_SCOPES_VALIDATION; 42 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.NULLABLE_VALIDATION; 43 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.PRIVATE_MEMBER_VALIDATION; 44 import static dagger.internal.codegen.ProcessingEnvironmentCompilerOptions.Validation.STATIC_MEMBER_VALIDATION; 45 import static dagger.internal.codegen.ValidationType.ERROR; 46 import static dagger.internal.codegen.ValidationType.NONE; 47 import static dagger.internal.codegen.ValidationType.WARNING; 48 import static java.util.stream.Collectors.joining; 49 import static java.util.stream.Stream.concat; 50 51 import com.google.common.base.Ascii; 52 import com.google.common.collect.ImmutableList; 53 import com.google.common.collect.ImmutableMap; 54 import com.google.common.collect.ImmutableSet; 55 import dagger.producers.Produces; 56 import java.util.Arrays; 57 import java.util.EnumSet; 58 import java.util.HashMap; 59 import java.util.Map; 60 import java.util.Optional; 61 import java.util.Set; 62 import java.util.stream.Stream; 63 import javax.annotation.processing.ProcessingEnvironment; 64 import javax.lang.model.element.TypeElement; 65 import javax.tools.Diagnostic; 66 67 final class ProcessingEnvironmentCompilerOptions extends CompilerOptions { 68 /** Returns a valid {@link CompilerOptions} parsed from the processing environment. */ create(ProcessingEnvironment processingEnvironment)69 static CompilerOptions create(ProcessingEnvironment processingEnvironment) { 70 return new ProcessingEnvironmentCompilerOptions(processingEnvironment).checkValid(); 71 } 72 73 private final ProcessingEnvironment processingEnvironment; 74 private final Map<EnumOption<?>, Object> enumOptions = new HashMap<>(); 75 private final Map<EnumOption<?>, ImmutableMap<String, ? extends Enum<?>>> allCommandLineOptions = 76 new HashMap<>(); 77 ProcessingEnvironmentCompilerOptions(ProcessingEnvironment processingEnvironment)78 private ProcessingEnvironmentCompilerOptions(ProcessingEnvironment processingEnvironment) { 79 this.processingEnvironment = processingEnvironment; 80 } 81 82 @Override usesProducers()83 boolean usesProducers() { 84 return processingEnvironment.getElementUtils().getTypeElement(Produces.class.getCanonicalName()) 85 != null; 86 } 87 88 @Override headerCompilation()89 boolean headerCompilation() { 90 return isEnabled(HEADER_COMPILATION); 91 } 92 93 @Override fastInit()94 boolean fastInit() { 95 return isEnabled(FAST_INIT); 96 } 97 98 @Override formatGeneratedSource()99 boolean formatGeneratedSource() { 100 return isEnabled(FORMAT_GENERATED_SOURCE); 101 } 102 103 @Override writeProducerNameInToken()104 boolean writeProducerNameInToken() { 105 return isEnabled(WRITE_PRODUCER_NAME_IN_TOKEN); 106 } 107 108 @Override nullableValidationKind()109 Diagnostic.Kind nullableValidationKind() { 110 return diagnosticKind(NULLABLE_VALIDATION); 111 } 112 113 @Override privateMemberValidationKind()114 Diagnostic.Kind privateMemberValidationKind() { 115 return diagnosticKind(PRIVATE_MEMBER_VALIDATION); 116 } 117 118 @Override staticMemberValidationKind()119 Diagnostic.Kind staticMemberValidationKind() { 120 return diagnosticKind(STATIC_MEMBER_VALIDATION); 121 } 122 123 @Override ignorePrivateAndStaticInjectionForComponent()124 boolean ignorePrivateAndStaticInjectionForComponent() { 125 return isEnabled(IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT); 126 } 127 128 @Override scopeCycleValidationType()129 ValidationType scopeCycleValidationType() { 130 return parseOption(DISABLE_INTER_COMPONENT_SCOPE_VALIDATION); 131 } 132 133 @Override warnIfInjectionFactoryNotGeneratedUpstream()134 boolean warnIfInjectionFactoryNotGeneratedUpstream() { 135 return isEnabled(WARN_IF_INJECTION_FACTORY_NOT_GENERATED_UPSTREAM); 136 } 137 138 @Override aheadOfTimeSubcomponents()139 boolean aheadOfTimeSubcomponents() { 140 return isEnabled(EXPERIMENTAL_AHEAD_OF_TIME_SUBCOMPONENTS); 141 } 142 143 @Override forceUseSerializedComponentImplementations()144 boolean forceUseSerializedComponentImplementations() { 145 return isEnabled(FORCE_USE_SERIALIZED_COMPONENT_IMPLEMENTATIONS); 146 } 147 148 @Override emitModifiableMetadataAnnotations()149 boolean emitModifiableMetadataAnnotations() { 150 return isEnabled(EMIT_MODIFIABLE_METADATA_ANNOTATIONS); 151 } 152 153 @Override useGradleIncrementalProcessing()154 boolean useGradleIncrementalProcessing() { 155 return isEnabled(USE_GRADLE_INCREMENTAL_PROCESSING); 156 } 157 158 @Override fullBindingGraphValidationType(TypeElement element)159 ValidationType fullBindingGraphValidationType(TypeElement element) { 160 return fullBindingGraphValidationType(); 161 } 162 fullBindingGraphValidationType()163 private ValidationType fullBindingGraphValidationType() { 164 return parseOption(FULL_BINDING_GRAPH_VALIDATION); 165 } 166 167 @Override moduleHasDifferentScopesDiagnosticKind()168 Diagnostic.Kind moduleHasDifferentScopesDiagnosticKind() { 169 return diagnosticKind(MODULE_HAS_DIFFERENT_SCOPES_VALIDATION); 170 } 171 172 @Override explicitBindingConflictsWithInjectValidationType()173 ValidationType explicitBindingConflictsWithInjectValidationType() { 174 return parseOption(EXPLICIT_BINDING_CONFLICTS_WITH_INJECT); 175 } 176 isEnabled(KeyOnlyOption keyOnlyOption)177 private boolean isEnabled(KeyOnlyOption keyOnlyOption) { 178 return processingEnvironment.getOptions().containsKey(keyOnlyOption.toString()); 179 } 180 isEnabled(Feature feature)181 private boolean isEnabled(Feature feature) { 182 return parseOption(feature).equals(ENABLED); 183 } 184 diagnosticKind(Validation validation)185 private Diagnostic.Kind diagnosticKind(Validation validation) { 186 return parseOption(validation).diagnosticKind().get(); 187 } 188 189 @SuppressWarnings("CheckReturnValue") checkValid()190 private ProcessingEnvironmentCompilerOptions checkValid() { 191 for (KeyOnlyOption keyOnlyOption : KeyOnlyOption.values()) { 192 isEnabled(keyOnlyOption); 193 } 194 for (Feature feature : Feature.values()) { 195 parseOption(feature); 196 } 197 for (Validation validation : Validation.values()) { 198 parseOption(validation); 199 } 200 noLongerRecognized(EXPERIMENTAL_ANDROID_MODE); 201 noLongerRecognized(FLOATING_BINDS_METHODS); 202 return this; 203 } 204 noLongerRecognized(CommandLineOption commandLineOption)205 private void noLongerRecognized(CommandLineOption commandLineOption) { 206 if (processingEnvironment.getOptions().containsKey(commandLineOption.toString())) { 207 processingEnvironment 208 .getMessager() 209 .printMessage( 210 Diagnostic.Kind.WARNING, commandLineOption + " is no longer recognized by Dagger"); 211 } 212 } 213 214 private interface CommandLineOption { 215 /** The key of the option (appears after "-A"). */ 216 @Override toString()217 String toString(); 218 219 /** 220 * Returns all aliases besides {@link #toString()}, such as old names for an option, in order of 221 * precedence. 222 */ aliases()223 default ImmutableList<String> aliases() { 224 return ImmutableList.of(); 225 } 226 227 /** All the command-line names for this option, in order of precedence. */ allNames()228 default Stream<String> allNames() { 229 return concat(Stream.of(toString()), aliases().stream()); 230 } 231 } 232 233 /** An option that can be set on the command line. */ 234 private interface EnumOption<E extends Enum<E>> extends CommandLineOption { 235 /** The default value for this option. */ defaultValue()236 E defaultValue(); 237 238 /** The valid values for this option. */ validValues()239 Set<E> validValues(); 240 } 241 242 enum KeyOnlyOption implements CommandLineOption { 243 HEADER_COMPILATION { 244 @Override toString()245 public String toString() { 246 return "experimental_turbine_hjar"; 247 } 248 }, 249 250 USE_GRADLE_INCREMENTAL_PROCESSING { 251 @Override toString()252 public String toString() { 253 return "dagger.gradle.incremental"; 254 } 255 }, 256 } 257 258 /** 259 * A feature that can be enabled or disabled on the command line by setting {@code -Akey=ENABLED} 260 * or {@code -Akey=DISABLED}. 261 */ 262 enum Feature implements EnumOption<FeatureStatus> { 263 FAST_INIT, 264 265 EXPERIMENTAL_ANDROID_MODE, 266 267 FORMAT_GENERATED_SOURCE, 268 269 WRITE_PRODUCER_NAME_IN_TOKEN, 270 271 WARN_IF_INJECTION_FACTORY_NOT_GENERATED_UPSTREAM, 272 273 IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT, 274 275 EXPERIMENTAL_AHEAD_OF_TIME_SUBCOMPONENTS, 276 277 FORCE_USE_SERIALIZED_COMPONENT_IMPLEMENTATIONS, 278 279 EMIT_MODIFIABLE_METADATA_ANNOTATIONS(ENABLED), 280 281 FLOATING_BINDS_METHODS, 282 ; 283 284 final FeatureStatus defaultValue; 285 Feature()286 Feature() { 287 this(DISABLED); 288 } 289 Feature(FeatureStatus defaultValue)290 Feature(FeatureStatus defaultValue) { 291 this.defaultValue = defaultValue; 292 } 293 294 @Override defaultValue()295 public FeatureStatus defaultValue() { 296 return defaultValue; 297 } 298 299 @Override validValues()300 public Set<FeatureStatus> validValues() { 301 return EnumSet.allOf(FeatureStatus.class); 302 } 303 304 @Override toString()305 public String toString() { 306 return optionName(this); 307 } 308 } 309 310 /** The diagnostic kind or validation type for a kind of validation. */ 311 enum Validation implements EnumOption<ValidationType> { 312 DISABLE_INTER_COMPONENT_SCOPE_VALIDATION(), 313 314 NULLABLE_VALIDATION(ERROR, WARNING), 315 316 PRIVATE_MEMBER_VALIDATION(ERROR, WARNING), 317 318 STATIC_MEMBER_VALIDATION(ERROR, WARNING), 319 320 /** Whether to validate full binding graphs for components, subcomponents, and modules. */ FULL_BINDING_GRAPH_VALIDATION(NONE, ERROR, WARNING)321 FULL_BINDING_GRAPH_VALIDATION(NONE, ERROR, WARNING) { 322 @Override 323 public ImmutableList<String> aliases() { 324 return ImmutableList.of("dagger.moduleBindingValidation"); 325 } 326 }, 327 328 /** 329 * How to report conflicting scoped bindings when validating partial binding graphs associated 330 * with modules. 331 */ 332 MODULE_HAS_DIFFERENT_SCOPES_VALIDATION(ERROR, WARNING), 333 334 /** 335 * How to report that an explicit binding in a subcomponent conflicts with an {@code @Inject} 336 * constructor used in an ancestor component. 337 */ 338 EXPLICIT_BINDING_CONFLICTS_WITH_INJECT(WARNING, ERROR, NONE), 339 ; 340 341 final ValidationType defaultType; 342 final ImmutableSet<ValidationType> validTypes; 343 Validation()344 Validation() { 345 this(ERROR, WARNING, NONE); 346 } 347 Validation(ValidationType defaultType, ValidationType... moreValidTypes)348 Validation(ValidationType defaultType, ValidationType... moreValidTypes) { 349 this.defaultType = defaultType; 350 this.validTypes = immutableEnumSet(defaultType, moreValidTypes); 351 } 352 353 @Override defaultValue()354 public ValidationType defaultValue() { 355 return defaultType; 356 } 357 358 @Override validValues()359 public Set<ValidationType> validValues() { 360 return validTypes; 361 } 362 363 @Override toString()364 public String toString() { 365 return optionName(this); 366 } 367 } 368 optionName(Enum<? extends EnumOption<?>> option)369 private static String optionName(Enum<? extends EnumOption<?>> option) { 370 return "dagger." + UPPER_UNDERSCORE.to(LOWER_CAMEL, option.name()); 371 } 372 373 /** The supported command-line options. */ supportedOptions()374 static ImmutableSet<String> supportedOptions() { 375 // need explicit type parameter to avoid a runtime stream error 376 return Stream.<CommandLineOption[]>of( 377 KeyOnlyOption.values(), Feature.values(), Validation.values()) 378 .flatMap(Arrays::stream) 379 .flatMap(CommandLineOption::allNames) 380 .collect(toImmutableSet()); 381 } 382 383 /** 384 * Returns the value for the option as set on the command line by any name, or the default value 385 * if not set. 386 * 387 * <p>If more than one name is used to set the value, but all names specify the same value, 388 * reports a warning and returns that value. 389 * 390 * <p>If more than one name is used to set the value, and not all names specify the same value, 391 * reports an error and returns the default value. 392 */ parseOption(EnumOption<T> option)393 private <T extends Enum<T>> T parseOption(EnumOption<T> option) { 394 @SuppressWarnings("unchecked") // we only put covariant values into the map 395 T value = (T) enumOptions.computeIfAbsent(option, this::parseOptionUncached); 396 return value; 397 } 398 parseOptionUncached(EnumOption<T> option)399 private <T extends Enum<T>> T parseOptionUncached(EnumOption<T> option) { 400 ImmutableMap<String, T> values = parseOptionWithAllNames(option); 401 402 // If no value is specified, return the default value. 403 if (values.isEmpty()) { 404 return option.defaultValue(); 405 } 406 407 // If all names have the same value, return that. 408 if (values.asMultimap().inverse().keySet().size() == 1) { 409 // Warn if an option was set with more than one name. That would be an error if the values 410 // differed. 411 if (values.size() > 1) { 412 reportUseOfDifferentNamesForOption(Diagnostic.Kind.WARNING, option, values.keySet()); 413 } 414 return values.values().asList().get(0); 415 } 416 417 // If different names have different values, report an error and return the default 418 // value. 419 reportUseOfDifferentNamesForOption(Diagnostic.Kind.ERROR, option, values.keySet()); 420 return option.defaultValue(); 421 } 422 reportUseOfDifferentNamesForOption( Diagnostic.Kind diagnosticKind, EnumOption<?> option, ImmutableSet<String> usedNames)423 private void reportUseOfDifferentNamesForOption( 424 Diagnostic.Kind diagnosticKind, EnumOption<?> option, ImmutableSet<String> usedNames) { 425 processingEnvironment 426 .getMessager() 427 .printMessage( 428 diagnosticKind, 429 String.format( 430 "Only one of the equivalent options (%s) should be used; prefer -A%s", 431 usedNames.stream().map(name -> "-A" + name).collect(joining(", ")), option)); 432 } 433 parseOptionWithAllNames( EnumOption<T> option)434 private <T extends Enum<T>> ImmutableMap<String, T> parseOptionWithAllNames( 435 EnumOption<T> option) { 436 @SuppressWarnings("unchecked") // map is covariant 437 ImmutableMap<String, T> aliasValues = 438 (ImmutableMap<String, T>) 439 allCommandLineOptions.computeIfAbsent(option, this::parseOptionWithAllNamesUncached); 440 return aliasValues; 441 } 442 parseOptionWithAllNamesUncached( EnumOption<T> option)443 private <T extends Enum<T>> ImmutableMap<String, T> parseOptionWithAllNamesUncached( 444 EnumOption<T> option) { 445 ImmutableMap.Builder<String, T> values = ImmutableMap.builder(); 446 getUsedNames(option) 447 .forEach( 448 name -> parseOptionWithName(option, name).ifPresent(value -> values.put(name, value))); 449 return values.build(); 450 } 451 parseOptionWithName(EnumOption<T> option, String key)452 private <T extends Enum<T>> Optional<T> parseOptionWithName(EnumOption<T> option, String key) { 453 checkArgument(processingEnvironment.getOptions().containsKey(key), "key %s not found", key); 454 String stringValue = processingEnvironment.getOptions().get(key); 455 if (stringValue == null) { 456 processingEnvironment 457 .getMessager() 458 .printMessage(Diagnostic.Kind.ERROR, "Processor option -A" + key + " needs a value"); 459 } else { 460 try { 461 T value = 462 Enum.valueOf(option.defaultValue().getDeclaringClass(), Ascii.toUpperCase(stringValue)); 463 if (option.validValues().contains(value)) { 464 return Optional.of(value); 465 } 466 } catch (IllegalArgumentException e) { 467 // handled below 468 } 469 processingEnvironment 470 .getMessager() 471 .printMessage( 472 Diagnostic.Kind.ERROR, 473 String.format( 474 "Processor option -A%s may only have the values %s " 475 + "(case insensitive), found: %s", 476 key, option.validValues(), stringValue)); 477 } 478 return Optional.empty(); 479 } 480 getUsedNames(CommandLineOption option)481 private Stream<String> getUsedNames(CommandLineOption option) { 482 return option.allNames().filter(name -> processingEnvironment.getOptions().containsKey(name)); 483 } 484 } 485