• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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