aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWanying Ding <wanyingd@google.com>2024-02-08 11:43:06 -0800
committerDagger Team <dagger-dev+copybara@google.com>2024-02-08 11:46:02 -0800
commit09e5ac2d580399e783e8b83e761967ad0f01e227 (patch)
tree5333e8a392652024b24550805126b5d3110528df
parent348bd7507c5219734b6be8531a8796bec667ab38 (diff)
downloaddagger2-09e5ac2d580399e783e8b83e761967ad0f01e227.tar.gz
Include proguard rules in dagger core artifact so that LazyClassKeyMap's string keys can be obfuscated.
RELNOTES=n/a PiperOrigin-RevId: 605381957
-rw-r--r--java/dagger/BUILD2
-rw-r--r--java/dagger/internal/IdentifierNameString.java35
-rw-r--r--java/dagger/internal/KeepFieldType.java36
-rw-r--r--java/dagger/internal/LazyClassKeyMap.java131
-rw-r--r--java/dagger/internal/codegen/binding/MapKeys.java21
-rw-r--r--java/dagger/internal/codegen/javapoet/TypeNames.java9
-rw-r--r--java/dagger/internal/codegen/writing/ComponentImplementation.java6
-rw-r--r--java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java33
-rw-r--r--java/dagger/internal/codegen/writing/LazyClassKeyProviders.java117
-rw-r--r--java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java22
-rw-r--r--java/dagger/internal/codegen/writing/MapRequestRepresentation.java23
-rw-r--r--java/dagger/multibindings/LazyClassKey.java39
-rw-r--r--java/dagger/proguard.pro3
-rw-r--r--java/dagger/r8.pro3
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/build.gradle64
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/proguard-rules.pro5
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/androidTest/java/dagger/lazyclasskey/FlowerAppTest.java43
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/AndroidManifest.xml34
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Flower.java20
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerActivity.java52
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerComponent.java26
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerModule.java40
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Lily.java20
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Rose.java20
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/res/layout/flower_activity.xml26
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/app/src/main/res/values/strings.xml21
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/build.gradle58
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/gradle.properties12
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.jarbin0 -> 43462 bytes
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.properties7
-rwxr-xr-xjavatests/artifacts/dagger/lazyclasskey/gradlew249
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/gradlew.bat92
-rw-r--r--javatests/artifacts/dagger/lazyclasskey/settings.gradle2
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/build.gradle65
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/proguard-rules.pro3
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/androidTest/java/hilt/lazyclasskey/FlowerAppTest.java43
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/AndroidManifest.xml33
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Flower.java20
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerActivity.java52
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerApp.java24
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerModule.java43
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Lily.java20
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Rose.java20
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/layout/flower_activity.xml25
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/values/strings.xml20
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/build.gradle59
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/gradle.properties12
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.jarbin0 -> 63721 bytes
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.properties7
-rwxr-xr-xjavatests/artifacts/hilt-android/lazyclasskey/gradlew249
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/gradlew.bat92
-rw-r--r--javatests/artifacts/hilt-android/lazyclasskey/settings.gradle2
-rw-r--r--javatests/dagger/internal/codegen/LazyClassKeyMapBindingComponentProcessorTest.java191
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ClassKeyMapKey30
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey26
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_test.DaggerTestComponent99
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ClassKeyMapKey30
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey26
-rw-r--r--javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_test.DaggerTestComponent119
-rw-r--r--tools/maven.bzl109
-rwxr-xr-xutil/run-local-emulator-tests.sh2
61 files changed, 2675 insertions, 17 deletions
diff --git a/java/dagger/BUILD b/java/dagger/BUILD
index bbf7e24dd..918340825 100644
--- a/java/dagger/BUILD
+++ b/java/dagger/BUILD
@@ -47,6 +47,8 @@ gen_maven_artifact(
],
javadoc_root_packages = ["dagger"],
javadoc_srcs = [":javadoc-srcs"],
+ proguard_specs = [":proguard.pro"],
+ r8_specs = [":r8.pro"],
)
filegroup(
diff --git a/java/dagger/internal/IdentifierNameString.java b/java/dagger/internal/IdentifierNameString.java
new file mode 100644
index 000000000..7d4b1d9e4
--- /dev/null
+++ b/java/dagger/internal/IdentifierNameString.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.internal;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates the dagger generated class that requires applying -identifiernamestring rule.
+ *
+ * <p>When applied, all the strings that corresponds to a class name within the annotated class will
+ * be obfuscated if its corresponding class is obfuscated. This only works with r8. This annotation
+ */
+@Documented
+@Retention(CLASS)
+@Target(TYPE)
+public @interface IdentifierNameString {}
diff --git a/java/dagger/internal/KeepFieldType.java b/java/dagger/internal/KeepFieldType.java
new file mode 100644
index 000000000..090550210
--- /dev/null
+++ b/java/dagger/internal/KeepFieldType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.internal;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates the dagger generated field that requires keeping the field types.
+ *
+ * <p>Annotating a field with this annotation, the field type's class name won't be discarded or
+ * obfuscated when compiles with proguard. Note:This will cause the containing class to be kept, and
+ * only works with proguard.
+ */
+@Documented
+@Retention(CLASS)
+@Target(FIELD)
+public @interface KeepFieldType {}
diff --git a/java/dagger/internal/LazyClassKeyMap.java b/java/dagger/internal/LazyClassKeyMap.java
new file mode 100644
index 000000000..dabf86f91
--- /dev/null
+++ b/java/dagger/internal/LazyClassKeyMap.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.internal;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class keyed map that delegates to a string keyed map under the hood.
+ *
+ * <p>A {@code LazyClassKeyMap} is created for @LazyClassKey contributed map binding.
+ */
+public final class LazyClassKeyMap<V> implements Map<Class<?>, V> {
+ private final Map<String, V> delegate;
+
+ public static <V> Map<Class<?>, V> of(Map<String, V> delegate) {
+ return new LazyClassKeyMap<>(delegate);
+ }
+
+ private LazyClassKeyMap(Map<String, V> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public V get(Object key) {
+ if (!(key instanceof Class)) {
+ throw new IllegalArgumentException("Key must be a class");
+ }
+ return delegate.get(((Class<?>) key).getName());
+ }
+
+ @Override
+ public Set<Class<?>> keySet() {
+ // This method will load all class keys, therefore no need to use @LazyClassKey annotated
+ // bindings.
+ throw new UnsupportedOperationException(
+ "Maps created with @LazyClassKey do not support usage of keySet(). Consider @ClassKey"
+ + " instead.");
+ }
+
+ @Override
+ public Collection<V> values() {
+ return delegate.values();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ if (!(key instanceof Class)) {
+ throw new IllegalArgumentException("Key must be a class");
+ }
+ return delegate.containsKey(((Class<?>) key).getName());
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return delegate.containsValue(value);
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public Set<Map.Entry<Class<?>, V>> entrySet() {
+ // This method will load all class keys, therefore no need to use @LazyClassKey annotated
+ // bindings.
+ throw new UnsupportedOperationException(
+ "Maps created with @LazyClassKey do not support usage of entrySet(). Consider @ClassKey"
+ + " instead.");
+ }
+
+ // The dagger map binding should be a immutable map.
+ @Override
+ public V remove(Object key) {
+ throw new UnsupportedOperationException("Dagger map bindings are immutable");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("Dagger map bindings are immutable");
+ }
+
+ @Override
+ public V put(Class<?> key, V value) {
+ throw new UnsupportedOperationException("Dagger map bindings are immutable");
+ }
+
+ @Override
+ public void putAll(Map<? extends Class<?>, ? extends V> map) {
+ throw new UnsupportedOperationException("Dagger map bindings are immutable");
+ }
+
+ /** A factory for {@code LazyClassKeyMap}. */
+ public static class Factory<V> implements Provider<Map<Class<?>, V>> {
+ MapFactory<String, V> delegate;
+
+ public static <V> Factory<V> of(MapFactory<String, V> delegate) {
+ return new Factory<>(delegate);
+ }
+
+ private Factory(MapFactory<String, V> delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Map<Class<?>, V> get() {
+ return LazyClassKeyMap.of(delegate.get());
+ }
+ }
+}
diff --git a/java/dagger/internal/codegen/binding/MapKeys.java b/java/dagger/internal/codegen/binding/MapKeys.java
index c156dbb06..61ef9f2b2 100644
--- a/java/dagger/internal/codegen/binding/MapKeys.java
+++ b/java/dagger/internal/codegen/binding/MapKeys.java
@@ -199,5 +199,26 @@ public final class MapKeys {
.build());
}
+ /**
+ * Returns if this binding is a map binding and uses @LazyClassKey for contributing class keys.
+ *
+ * <p>@LazyClassKey won't co-exist with @ClassKey in the graph, since the same binding type cannot
+ * use more than one @MapKey annotation type and Dagger validation will fail.
+ */
+ public static boolean useLazyClassKey(Binding binding, BindingGraph graph) {
+ if (!binding.dependencies().isEmpty()) {
+ ContributionBinding contributionBinding =
+ graph.contributionBinding(binding.dependencies().iterator().next().key());
+ return contributionBinding.mapKey().isPresent()
+ && contributionBinding
+ .mapKey()
+ .get()
+ .xprocessing()
+ .getClassName()
+ .equals(TypeNames.LAZY_CLASS_KEY);
+ }
+ return false;
+ }
+
private MapKeys() {}
}
diff --git a/java/dagger/internal/codegen/javapoet/TypeNames.java b/java/dagger/internal/codegen/javapoet/TypeNames.java
index 7d77bf2d1..ea89fc033 100644
--- a/java/dagger/internal/codegen/javapoet/TypeNames.java
+++ b/java/dagger/internal/codegen/javapoet/TypeNames.java
@@ -53,6 +53,15 @@ public final class TypeNames {
public static final ClassName SUBCOMPONENT_FACTORY = SUBCOMPONENT.nestedClass("Factory");
// Dagger Internal classnames
+ public static final ClassName IDENTIFIER_NAME_STRING =
+ ClassName.get("dagger.internal", "IdentifierNameString");
+ public static final ClassName KEEP_FIELD_TYPE = ClassName.get("dagger.internal", "KeepFieldType");
+ public static final ClassName LAZY_CLASS_KEY =
+ ClassName.get("dagger.multibindings", "LazyClassKey");
+ public static final ClassName LAZY_CLASS_KEY_MAP =
+ ClassName.get("dagger.internal", "LazyClassKeyMap");
+ public static final ClassName LAZY_CLASS_KEY_MAP_FACTORY =
+ ClassName.get("dagger.internal", "LazyClassKeyMap", "Factory");
public static final ClassName DELEGATE_FACTORY =
ClassName.get("dagger.internal", "DelegateFactory");
diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java
index abc39f3d1..a41b1c793 100644
--- a/java/dagger/internal/codegen/writing/ComponentImplementation.java
+++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java
@@ -456,6 +456,7 @@ public final class ComponentImplementation {
private final UniqueNameSet assistedParamNames = new UniqueNameSet();
private final List<CodeBlock> initializations = new ArrayList<>();
private final SwitchingProviders switchingProviders;
+ private final LazyClassKeyProviders lazyClassKeyProviders;
private final Map<Key, CodeBlock> cancellations = new LinkedHashMap<>();
private final Map<XVariableElement, String> uniqueAssistedName = new LinkedHashMap<>();
private final List<CodeBlock> componentRequirementInitializations = new ArrayList<>();
@@ -472,6 +473,7 @@ public final class ComponentImplementation {
private ShardImplementation(ClassName name) {
this.name = name;
this.switchingProviders = new SwitchingProviders(this, processingEnv);
+ this.lazyClassKeyProviders = new LazyClassKeyProviders(this);
if (graph.componentDescriptor().isProduction()) {
claimMethodName(CANCELLATION_LISTENER_METHOD_NAME);
}
@@ -505,6 +507,10 @@ public final class ComponentImplementation {
return switchingProviders;
}
+ public LazyClassKeyProviders getLazyClassKeyProviders() {
+ return lazyClassKeyProviders;
+ }
+
/** Returns the {@link ComponentImplementation} that owns this shard. */
public ComponentImplementation getComponentImplementation() {
return ComponentImplementation.this;
diff --git a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
index e43d8446d..3eaf92db0 100644
--- a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
+++ b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java
@@ -26,10 +26,13 @@ import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XFiler;
import androidx.room.compiler.processing.XProcessingEnv;
import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.base.SourceFileGenerator;
import dagger.internal.codegen.binding.ContributionBinding;
import dagger.internal.codegen.binding.MapKeys;
+import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DaggerAnnotation;
import javax.inject.Inject;
/**
@@ -56,11 +59,31 @@ public final class InaccessibleMapKeyProxyGenerator
public ImmutableList<TypeSpec.Builder> topLevelTypes(ContributionBinding binding) {
return MapKeys.mapKeyFactoryMethod(binding, processingEnv)
.map(
- method ->
- classBuilder(MapKeys.mapKeyProxyClassName(binding))
- .addModifiers(PUBLIC, FINAL)
- .addMethod(constructorBuilder().addModifiers(PRIVATE).build())
- .addMethod(method))
+ method -> {
+ TypeSpec.Builder builder =
+ classBuilder(MapKeys.mapKeyProxyClassName(binding))
+ .addModifiers(PUBLIC, FINAL)
+ .addMethod(constructorBuilder().addModifiers(PRIVATE).build())
+ .addMethod(method);
+ // In proguard, we need to keep the classes referenced by @LazyClassKey, we do that by
+ // generating a field referencing the type, and then applying @KeepFieldType to the
+ // field. Here, we generate the field in the proxy class. For classes that are
+ // accessible from the dagger component, we generate fields in LazyClassKeyProvider.
+ // Note: the generated field should not be initialized to avoid class loading.
+ binding
+ .mapKey()
+ .map(DaggerAnnotation::xprocessing)
+ .filter(
+ mapKey ->
+ mapKey.getTypeElement().getClassName().equals(TypeNames.LAZY_CLASS_KEY))
+ .map(
+ mapKey ->
+ FieldSpec.builder(mapKey.getAsType("value").getTypeName(), "className")
+ .addAnnotation(TypeNames.KEEP_FIELD_TYPE)
+ .build())
+ .ifPresent(builder::addField);
+ return builder;
+ })
.map(ImmutableList::of)
.orElse(ImmutableList.of());
}
diff --git a/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java b/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java
new file mode 100644
index 000000000..2c2581172
--- /dev/null
+++ b/java/dagger/internal/codegen/writing/LazyClassKeyProviders.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.writing;
+
+import static dagger.internal.codegen.base.MapKeyAccessibility.isMapKeyAccessibleFrom;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.STATIC;
+
+import androidx.room.compiler.processing.XAnnotation;
+import com.google.common.base.Preconditions;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.FieldSpec;
+import com.squareup.javapoet.TypeSpec;
+import dagger.internal.codegen.base.UniqueNameSet;
+import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Keeps track of all providers for DaggerMap keys. */
+public final class LazyClassKeyProviders {
+ public static final String MAP_KEY_PROVIDER_NAME = "LazyClassKeyProvider";
+ private final ClassName mapKeyProviderType;
+ private final Map<Key, FieldSpec> entries = new HashMap<>();
+ private final Map<Key, FieldSpec> keepClassNamesFields = new HashMap<>();
+ private final UniqueNameSet uniqueFieldNames = new UniqueNameSet();
+ private final ShardImplementation shardImplementation;
+ private boolean providerAdded = false;
+
+ LazyClassKeyProviders(ShardImplementation shardImplementation) {
+ String name = shardImplementation.getUniqueClassName(MAP_KEY_PROVIDER_NAME);
+ mapKeyProviderType = shardImplementation.name().nestedClass(name);
+ this.shardImplementation = shardImplementation;
+ }
+
+ /** Returns a reference to a field in LazyClassKeyProvider that corresponds to this binding. */
+ CodeBlock getMapKeyExpression(Key key) {
+ // This is for avoid generating empty LazyClassKeyProvider in codegen tests
+ if (!providerAdded) {
+ shardImplementation.addTypeSupplier(this::build);
+ providerAdded = true;
+ }
+ if (!entries.containsKey(key)) {
+ addField(key);
+ }
+ return CodeBlock.of("$T.$N", mapKeyProviderType, entries.get(key));
+ }
+
+ private void addField(Key key) {
+ Preconditions.checkArgument(
+ key.multibindingContributionIdentifier().isPresent()
+ && key.multibindingContributionIdentifier()
+ .get()
+ .bindingMethod()
+ .xprocessing()
+ .hasAnnotation(TypeNames.LAZY_CLASS_KEY));
+ XAnnotation lazyClassKeyAnnotation =
+ key.multibindingContributionIdentifier()
+ .get()
+ .bindingMethod()
+ .xprocessing()
+ .getAnnotation(TypeNames.LAZY_CLASS_KEY);
+ ClassName lazyClassKey =
+ lazyClassKeyAnnotation.getAsType("value").getTypeElement().getClassName();
+ entries.put(
+ key,
+ FieldSpec.builder(
+ TypeNames.STRING,
+ uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_')))
+ // TODO(b/217435141): Leave the field as non-final. We will apply @IdentifierNameString
+ // on the field, which doesn't work well with static final fields.
+ .addModifiers(STATIC)
+ .initializer("$S", lazyClassKey.reflectionName())
+ .build());
+ // To be able to apply -includedescriptorclasses rule to keep the class names referenced by
+ // LazyClassKey, we need to generate fields that uses those classes as type in
+ // LazyClassKeyProvider. For types that are not accessible from the generated component, we
+ // generate fields in the proxy class.
+ // Note: the generated field should not be initialized to avoid class loading.
+ if (isMapKeyAccessibleFrom(lazyClassKeyAnnotation, shardImplementation.name().packageName())) {
+ keepClassNamesFields.put(
+ key,
+ FieldSpec.builder(
+ lazyClassKey,
+ uniqueFieldNames.getUniqueName(lazyClassKey.canonicalName().replace('.', '_')))
+ .addAnnotation(TypeNames.KEEP_FIELD_TYPE)
+ .build());
+ }
+ }
+
+ private TypeSpec build() {
+ TypeSpec.Builder builder =
+ TypeSpec.classBuilder(mapKeyProviderType)
+ .addAnnotation(TypeNames.IDENTIFIER_NAME_STRING)
+ .addModifiers(PRIVATE, STATIC, FINAL)
+ .addFields(entries.values())
+ .addFields(keepClassNamesFields.values());
+ return builder.build();
+ }
+}
diff --git a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
index 3512353e7..0ea382d1f 100644
--- a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
@@ -31,6 +31,7 @@ import dagger.assisted.AssistedInject;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.ContributionBinding;
+import dagger.internal.codegen.binding.MapKeys;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.DependencyRequest;
import java.util.stream.Stream;
@@ -42,6 +43,8 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr
private final ComponentImplementation componentImplementation;
private final BindingGraph graph;
private final ContributionBinding binding;
+ private final boolean useLazyClassKey;
+ private final LazyClassKeyProviders lazyClassKeyProviders;
@AssistedInject
MapFactoryCreationExpression(
@@ -55,6 +58,9 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr
this.binding = checkNotNull(binding);
this.componentImplementation = componentImplementation;
this.graph = graph;
+ this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph);
+ this.lazyClassKeyProviders =
+ componentImplementation.shardImplementation(binding).getLazyClassKeyProviders();
}
@Override
@@ -75,7 +81,7 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr
.getTypeName();
builder.add(
"<$T, $T>",
- mapType.keyType().getTypeName(),
+ useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(),
valueTypeName);
}
@@ -85,12 +91,20 @@ final class MapFactoryCreationExpression extends MultibindingFactoryCreationExpr
ContributionBinding contributionBinding = graph.contributionBinding(dependency.key());
builder.add(
".put($L, $L)",
- getMapKeyExpression(
- contributionBinding, componentImplementation.name(), processingEnv),
+ useLazyClassKey
+ ? lazyClassKeyProviders.getMapKeyExpression(dependency.key())
+ : getMapKeyExpression(
+ contributionBinding, componentImplementation.name(), processingEnv),
multibindingDependencyExpression(dependency));
}
- return builder.add(".build()").build();
+ return useLazyClassKey
+ ? CodeBlock.of(
+ "$T.<$T>of($L)",
+ TypeNames.LAZY_CLASS_KEY_MAP_FACTORY,
+ valueTypeName,
+ builder.add(".build()").build())
+ : builder.add(".build()").build();
}
@AssistedFactory
diff --git a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
index d42b6bfc7..f631d34ce 100644
--- a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
@@ -38,6 +38,7 @@ import dagger.internal.MapBuilder;
import dagger.internal.codegen.base.MapType;
import dagger.internal.codegen.binding.BindingGraph;
import dagger.internal.codegen.binding.ContributionBinding;
+import dagger.internal.codegen.binding.MapKeys;
import dagger.internal.codegen.binding.ProvisionBinding;
import dagger.internal.codegen.javapoet.Expression;
import dagger.internal.codegen.javapoet.TypeNames;
@@ -54,6 +55,8 @@ final class MapRequestRepresentation extends RequestRepresentation {
private final ProvisionBinding binding;
private final ImmutableMap<DependencyRequest, ContributionBinding> dependencies;
private final ComponentRequestRepresentations componentRequestRepresentations;
+ private final boolean useLazyClassKey;
+ private final LazyClassKeyProviders lazyClassKeyProviders;
@AssistedInject
MapRequestRepresentation(
@@ -69,12 +72,25 @@ final class MapRequestRepresentation extends RequestRepresentation {
this.componentRequestRepresentations = componentRequestRepresentations;
this.dependencies =
Maps.toMap(binding.dependencies(), dep -> graph.contributionBinding(dep.key()));
+ this.useLazyClassKey = MapKeys.useLazyClassKey(binding, graph);
+ this.lazyClassKeyProviders =
+ componentImplementation.shardImplementation(binding).getLazyClassKeyProviders();
}
@Override
Expression getDependencyExpression(ClassName requestingClass) {
MapType mapType = MapType.from(binding.key());
Expression dependencyExpression = getUnderlyingMapExpression(requestingClass);
+ // LazyClassKey is backed with a string map, therefore needs to be wrapped.
+ if (useLazyClassKey) {
+ return Expression.create(
+ dependencyExpression.type(),
+ CodeBlock.of(
+ "$T.<$T>of($L)",
+ TypeNames.LAZY_CLASS_KEY_MAP,
+ mapType.valueType().getTypeName(),
+ dependencyExpression.codeBlock()));
+ }
return dependencyExpression;
}
@@ -136,8 +152,9 @@ final class MapRequestRepresentation extends RequestRepresentation {
private CodeBlock keyAndValueExpression(DependencyRequest dependency, ClassName requestingClass) {
return CodeBlock.of(
"$L, $L",
- getMapKeyExpression(
- dependencies.get(dependency), requestingClass, processingEnv),
+ useLazyClassKey
+ ? lazyClassKeyProviders.getMapKeyExpression(dependency.key())
+ : getMapKeyExpression(dependencies.get(dependency), requestingClass, processingEnv),
componentRequestRepresentations
.getDependencyExpression(bindingRequest(dependency), requestingClass)
.codeBlock());
@@ -160,7 +177,7 @@ final class MapRequestRepresentation extends RequestRepresentation {
return isTypeAccessibleFrom(bindingKeyType, requestingClass.packageName())
? CodeBlock.of(
"<$T, $T>",
- mapType.keyType().getTypeName(),
+ useLazyClassKey ? TypeNames.STRING : mapType.keyType().getTypeName(),
mapType.valueType().getTypeName())
: CodeBlock.of("");
}
diff --git a/java/dagger/multibindings/LazyClassKey.java b/java/dagger/multibindings/LazyClassKey.java
new file mode 100644
index 000000000..da46984e3
--- /dev/null
+++ b/java/dagger/multibindings/LazyClassKey.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.multibindings;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import dagger.MapKey;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * A {@link MapKey} annotation for maps with {@code Class<?>} keys.
+ *
+ * <p>The difference from {@link ClassKey} is that dagger generates a string representation for the
+ * class to use under the hood, which prevents loading unused classes at runtime.
+ */
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
+@Retention(RUNTIME)
+@Documented
+@MapKey
+public @interface LazyClassKey {
+ Class<?> value();
+}
diff --git a/java/dagger/proguard.pro b/java/dagger/proguard.pro
new file mode 100644
index 000000000..1bfc94749
--- /dev/null
+++ b/java/dagger/proguard.pro
@@ -0,0 +1,3 @@
+-keepclasseswithmembers,includedescriptorclasses class * {
+ @dagger.internal.KeepFieldType <fields>;
+} \ No newline at end of file
diff --git a/java/dagger/r8.pro b/java/dagger/r8.pro
new file mode 100644
index 000000000..17e0ffe6d
--- /dev/null
+++ b/java/dagger/r8.pro
@@ -0,0 +1,3 @@
+-identifiernamestring @dagger.internal.IdentifierNameString class ** {
+ static java.lang.String *;
+} \ No newline at end of file
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/build.gradle b/javatests/artifacts/dagger/lazyclasskey/app/build.gradle
new file mode 100644
index 000000000..b44309afd
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/build.gradle
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+ id 'com.android.application'
+}
+
+android {
+
+ namespace 'dagger.lazyclasskey'
+ compileSdkVersion 33
+ defaultConfig {
+ applicationId 'dagger.lazyclasskey'
+ minSdk 16
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile(
+ 'proguard-android-optimize.txt'),
+ 'proguard-rules.pro'
+
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.errorprone:error_prone_annotations:2.15.0'
+
+ androidTestImplementation 'androidx.test:core:1.5.0-alpha02'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation "androidx.test:runner:1.5.2"
+ androidTestImplementation "androidx.test:rules:1.5.0"
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ // Dagger dependencies
+ implementation "com.google.dagger:dagger:$dagger_version"
+ annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/proguard-rules.pro b/javatests/artifacts/dagger/lazyclasskey/app/proguard-rules.pro
new file mode 100644
index 000000000..95980023c
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/proguard-rules.pro
@@ -0,0 +1,5 @@
+
+-dontwarn com.google.errorprone.annotations.MustBeClosed
+ # TODO(b/324097623) Remove the keep rules once test won't be affected by obfuscation
+-keep class kotlin.**
+-keep class javax.** { *; }
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/androidTest/java/dagger/lazyclasskey/FlowerAppTest.java b/javatests/artifacts/dagger/lazyclasskey/app/src/androidTest/java/dagger/lazyclasskey/FlowerAppTest.java
new file mode 100644
index 000000000..86e1401bd
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/androidTest/java/dagger/lazyclasskey/FlowerAppTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.withResourceName;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.startsWith;
+
+import android.content.Intent;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FlowerAppTest {
+ @Test
+ public void testFlowerAppWithR8DoesNotCrash() {
+ Intent mainIntent =
+ new Intent(ApplicationProvider.getApplicationContext(), FlowerActivity.class);
+ try (ActivityScenario<FlowerActivity> scenario = ActivityScenario.launch(mainIntent)) {
+ onView(withResourceName("flower_info"))
+ .check(matches(withText(startsWith(Lily.class.getSimpleName()))));
+ }
+ }
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/AndroidManifest.xml b/javatests/artifacts/dagger/lazyclasskey/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..cec40616b
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="dagger.lazyclasskey">
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34"/>
+ <application
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+ android:taskAffinity=""
+ android:label="@string/app_name">
+ <activity
+ android:name=".FlowerActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Flower.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Flower.java
new file mode 100644
index 000000000..34161a1f9
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Flower.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+/** Base class for flowers. */
+interface Flower {}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerActivity.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerActivity.java
new file mode 100644
index 000000000..005012733
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+import android.os.Bundle;
+import android.widget.TextView;
+import androidx.activity.ComponentActivity;
+import java.util.Locale;
+import java.util.Map;
+
+/** Displays flower price information. */
+public final class FlowerActivity extends ComponentActivity {
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Map<Class<?>, Integer> flowerPrices = DaggerFlowerComponent.create().getFlowerMap();
+ setContentView(R.layout.flower_activity);
+ if (!flowerPrices.containsKey(Rose.class)) {
+ throw new IllegalStateException("Rose price not found");
+ }
+ if (!flowerPrices.containsKey(Lily.class)) {
+ throw new IllegalStateException("Lily price not found");
+ }
+ ((TextView) findViewById(R.id.flower_info))
+ .setText(
+ String.format(
+ Locale.US,
+ "%s : %d dollar, %s : %d dollar",
+ Lily.class.getSimpleName(),
+ flowerPrices.get(Lily.class),
+ Rose.class.getSimpleName(),
+ flowerPrices.get(Rose.class)));
+ }
+
+ class ProguardClassNames {
+ Rose rose;
+ Lily lily;
+ }
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerComponent.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerComponent.java
new file mode 100644
index 000000000..1302126ed
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerComponent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+import dagger.Component;
+import java.util.Map;
+
+/** Dagger component for flower bindings. */
+@Component(modules = FlowerModule.class)
+public interface FlowerComponent {
+ Map<Class<?>, Integer> getFlowerMap();
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerModule.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerModule.java
new file mode 100644
index 000000000..4f9b61c0d
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/FlowerModule.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.LazyClassKey;
+
+/** Module for providing flower prices. */
+@Module
+abstract class FlowerModule {
+ @IntoMap
+ @LazyClassKey(Lily.class)
+ @Provides
+ static int lilyPrice() {
+ return 1;
+ }
+
+ @IntoMap
+ @LazyClassKey(Rose.class)
+ @Provides
+ static int rosePrice() {
+ return 2;
+ }
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Lily.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Lily.java
new file mode 100644
index 000000000..c8c21be78
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Lily.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+/** Stores info for Lily. */
+final class Lily implements Flower {}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Rose.java b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Rose.java
new file mode 100644
index 000000000..abdd87a50
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/java/dagger/lazyclasskey/Rose.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.lazyclasskey;
+
+/** Stores information for Rose. */
+final class Rose implements Flower {}
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/layout/flower_activity.xml b/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/layout/flower_activity.xml
new file mode 100644
index 000000000..400691d74
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/layout/flower_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:id="@+id/flower_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
+
diff --git a/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/values/strings.xml b/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..72ad12a27
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/app/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<resources>
+ <!-- The name of the application [CHAR LIMIT=25] -->
+ <string name="app_name">Flower Demo</string>
+</resources>
+
diff --git a/javatests/artifacts/dagger/lazyclasskey/build.gradle b/javatests/artifacts/dagger/lazyclasskey/build.gradle
new file mode 100644
index 000000000..a0d425259
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/build.gradle
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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.
+ */
+
+buildscript {
+ ext {
+ dagger_version = 'LOCAL-SNAPSHOT'
+ // AGP is set below 7.2.0 on purpose to be able to obfuscate debug apk.
+ agp_version = "7.1.2"
+ kotlin_version = '1.9.20'
+ }
+ repositories {
+ google()
+ mavenCentral()
+ mavenLocal()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:$agp_version"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ mavenLocal()
+ }
+}
+
+subprojects {
+ afterEvaluate {
+ dependencies {
+ // This is needed to align older versions of kotlin-stdlib.
+ // The main issue is that in v1.8.0 the jdk7 and jdk8 artifacts were
+ // merged into kotlin-stdlib, so without this alignment we end up
+ // getting duplicate classes by pulling in both artifacts.
+ // See: https://kotlinlang.org/docs/whatsnew18.html#usage-of-the-latest-kotlin-stdlib-version-in-transitive-dependencies
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version"))
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/javatests/artifacts/dagger/lazyclasskey/gradle.properties b/javatests/artifacts/dagger/lazyclasskey/gradle.properties
new file mode 100644
index 000000000..abca46e4f
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/gradle.properties
@@ -0,0 +1,12 @@
+android.useAndroidX=true
+android.enableJetifier=true
+
+# Enable and fail the build if an issue is found that disallows the
+# configuration cache. These options along with this app being built in
+# presubmit helps us cache changes that would cause config cache to be disabled
+# via the HiltGradlePlugin.
+org.gradle.unsafe.configuration-cache-problems=fail
+org.gradle.unsafe.configuration-cache.max-problems=0
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx2048m
diff --git a/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..d64cd4917
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..9623276bc
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/javatests/artifacts/dagger/lazyclasskey/gradlew b/javatests/artifacts/dagger/lazyclasskey/gradlew
new file mode 100755
index 000000000..1aa94a426
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/javatests/artifacts/dagger/lazyclasskey/gradlew.bat b/javatests/artifacts/dagger/lazyclasskey/gradlew.bat
new file mode 100644
index 000000000..93e3f59f1
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/javatests/artifacts/dagger/lazyclasskey/settings.gradle b/javatests/artifacts/dagger/lazyclasskey/settings.gradle
new file mode 100644
index 000000000..5bb49fa71
--- /dev/null
+++ b/javatests/artifacts/dagger/lazyclasskey/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'lazyclasskey'
+include('app')
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/build.gradle b/javatests/artifacts/hilt-android/lazyclasskey/app/build.gradle
new file mode 100644
index 000000000..0f854cb82
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/build.gradle
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+ id 'com.android.application'
+ id 'com.google.dagger.hilt.android'
+}
+
+android {
+
+ namespace 'hilt.lazyclasskey'
+ compileSdkVersion 33
+ defaultConfig {
+ applicationId 'hilt.lazyclasskey'
+ minSdk 16
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ debug {
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile(
+ 'proguard-android-optimize.txt'),
+ 'proguard-rules.pro'
+
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'com.google.errorprone:error_prone_annotations:2.15.0'
+
+ androidTestImplementation 'androidx.test:core:1.5.0-alpha02'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation "androidx.test:runner:1.5.2"
+ androidTestImplementation "androidx.test:rules:1.5.0"
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ // Hilt dependencies
+ implementation "com.google.dagger:hilt-android:$hilt_version"
+ annotationProcessor "com.google.dagger:hilt-compiler:$hilt_version"
+} \ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/proguard-rules.pro b/javatests/artifacts/hilt-android/lazyclasskey/app/proguard-rules.pro
new file mode 100644
index 000000000..d389c4339
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/proguard-rules.pro
@@ -0,0 +1,3 @@
+-dontwarn com.google.errorprone.annotations.MustBeClosed
+ # TODO(b/324097623) Remove the keep rules once test won't be affected by obfuscation
+-keep class kotlin.** \ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/androidTest/java/hilt/lazyclasskey/FlowerAppTest.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/androidTest/java/hilt/lazyclasskey/FlowerAppTest.java
new file mode 100644
index 000000000..a3cf1c349
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/androidTest/java/hilt/lazyclasskey/FlowerAppTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.withResourceName;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.startsWith;
+
+import android.content.Intent;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FlowerAppTest {
+ @Test
+ public void testFlowerAppWithR8DoesNotCrash() {
+ Intent mainIntent =
+ new Intent(ApplicationProvider.getApplicationContext(), FlowerActivity.class);
+ try (ActivityScenario<FlowerActivity> scenario = ActivityScenario.launch(mainIntent)) {
+ onView(withResourceName("flower_info"))
+ .check(matches(withText(startsWith(Lily.class.getSimpleName()))));
+ }
+ }
+}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/AndroidManifest.xml b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..9a6ab88d1
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="hilt.lazyclasskey">
+ <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34"/>
+ <application android:name=".FlowerApp"
+ android:taskAffinity=""
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+ android:label="@string/app_name">
+ <activity
+ android:name=".FlowerActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Flower.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Flower.java
new file mode 100644
index 000000000..f7d23fc28
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Flower.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+/** Base class for flowers. */
+interface Flower {}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerActivity.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerActivity.java
new file mode 100644
index 000000000..b85bde040
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import android.widget.TextView;
+import dagger.hilt.android.AndroidEntryPoint;
+import java.util.Locale;
+import java.util.Map;
+import javax.inject.Inject;
+
+/** Displays flower price information. */
+@AndroidEntryPoint
+public final class FlowerActivity extends AppCompatActivity {
+ @Inject Map<Class<?>, Integer> flowerPrices;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.flower_activity);
+ if (!flowerPrices.containsKey(Rose.class)) {
+ throw new IllegalStateException("Rose price not found");
+ }
+ if (!flowerPrices.containsKey(Lily.class)) {
+ throw new IllegalStateException("Lily price not found");
+ }
+ ((TextView) findViewById(R.id.flower_info))
+ .setText(
+ String.format(
+ Locale.US,
+ "%s : %d dollar, %s : %d dollar",
+ Lily.class.getSimpleName(),
+ flowerPrices.get(Lily.class),
+ Rose.class.getSimpleName(),
+ flowerPrices.get(Rose.class)));
+ }
+}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerApp.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerApp.java
new file mode 100644
index 000000000..337ae8821
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerApp.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+import android.app.Application;
+import dagger.hilt.android.HiltAndroidApp;
+
+/** The main app responsible for providing flower information. */
+@HiltAndroidApp
+public class FlowerApp extends Application {}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerModule.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerModule.java
new file mode 100644
index 000000000..3e04eac85
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/FlowerModule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.components.SingletonComponent;
+import dagger.multibindings.IntoMap;
+import dagger.multibindings.LazyClassKey;
+
+/** Module for providing flower prices. */
+@Module
+@InstallIn(SingletonComponent.class)
+abstract class FlowerModule {
+ @IntoMap
+ @LazyClassKey(Lily.class)
+ @Provides
+ static int lilyPrice() {
+ return 1;
+ }
+
+ @IntoMap
+ @LazyClassKey(Rose.class)
+ @Provides
+ static int rosePrice() {
+ return 2;
+ }
+}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Lily.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Lily.java
new file mode 100644
index 000000000..fc3831a9e
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Lily.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+/** Stores info for Lily. */
+final class Lily implements Flower {}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Rose.java b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Rose.java
new file mode 100644
index 000000000..695d8fb56
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/java/hilt/lazyclasskey/Rose.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 hilt.lazyclasskey;
+
+/** Stores information for Rose. */
+final class Rose implements Flower {}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/layout/flower_activity.xml b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/layout/flower_activity.xml
new file mode 100644
index 000000000..1c6f03f31
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/layout/flower_activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:id="@+id/flower_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/values/strings.xml b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..55a2258fc
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/app/src/main/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Dagger Authors.
+ ~
+ ~ 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.
+ -->
+<resources>
+ <!-- The name of the application [CHAR LIMIT=25] -->
+ <string name="app_name">Flower Demo</string>
+</resources>
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/build.gradle b/javatests/artifacts/hilt-android/lazyclasskey/build.gradle
new file mode 100644
index 000000000..14bbfbd37
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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.
+ */
+
+buildscript {
+ ext {
+ hilt_version = 'LOCAL-SNAPSHOT'
+ // AGP is set below 7.2.0 on purpose to be able to obfuscate debug apk.
+ agp_version = "7.1.2"
+ kotlin_version = '1.9.20'
+ }
+ repositories {
+ google()
+ mavenCentral()
+ mavenLocal()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:$agp_version"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ mavenLocal()
+ }
+}
+
+subprojects {
+ afterEvaluate {
+ dependencies {
+ // This is needed to align older versions of kotlin-stdlib.
+ // The main issue is that in v1.8.0 the jdk7 and jdk8 artifacts were
+ // merged into kotlin-stdlib, so without this alignment we end up
+ // getting duplicate classes by pulling in both artifacts.
+ // See: https://kotlinlang.org/docs/whatsnew18.html#usage-of-the-latest-kotlin-stdlib-version-in-transitive-dependencies
+ implementation(platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version"))
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/gradle.properties b/javatests/artifacts/hilt-android/lazyclasskey/gradle.properties
new file mode 100644
index 000000000..abca46e4f
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/gradle.properties
@@ -0,0 +1,12 @@
+android.useAndroidX=true
+android.enableJetifier=true
+
+# Enable and fail the build if an issue is found that disallows the
+# configuration cache. These options along with this app being built in
+# presubmit helps us cache changes that would cause config cache to be disabled
+# via the HiltGradlePlugin.
+org.gradle.unsafe.configuration-cache-problems=fail
+org.gradle.unsafe.configuration-cache.max-problems=0
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.jvmargs=-Xmx2048m
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..7f93135c4
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..9623276bc
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/gradlew b/javatests/artifacts/hilt-android/lazyclasskey/gradlew
new file mode 100755
index 000000000..1aa94a426
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/gradlew
@@ -0,0 +1,249 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/gradlew.bat b/javatests/artifacts/hilt-android/lazyclasskey/gradlew.bat
new file mode 100644
index 000000000..93e3f59f1
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/gradlew.bat
@@ -0,0 +1,92 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/javatests/artifacts/hilt-android/lazyclasskey/settings.gradle b/javatests/artifacts/hilt-android/lazyclasskey/settings.gradle
new file mode 100644
index 000000000..5bb49fa71
--- /dev/null
+++ b/javatests/artifacts/hilt-android/lazyclasskey/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'lazyclasskey'
+include('app')
diff --git a/javatests/dagger/internal/codegen/LazyClassKeyMapBindingComponentProcessorTest.java b/javatests/dagger/internal/codegen/LazyClassKeyMapBindingComponentProcessorTest.java
new file mode 100644
index 000000000..9937d05ed
--- /dev/null
+++ b/javatests/dagger/internal/codegen/LazyClassKeyMapBindingComponentProcessorTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+
+import androidx.room.compiler.processing.util.Source;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import dagger.testing.compile.CompilerTests;
+import dagger.testing.golden.GoldenFileRule;
+import java.util.Collection;
+import javax.tools.JavaFileObject;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LazyClassKeyMapBindingComponentProcessorTest {
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> parameters() {
+ return CompilerMode.TEST_PARAMETERS;
+ }
+
+ @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
+
+ private final CompilerMode compilerMode;
+
+ public LazyClassKeyMapBindingComponentProcessorTest(CompilerMode compilerMode) {
+ this.compilerMode = compilerMode;
+ }
+
+ // Cannot convert to use ksp as this test relies on AutoAnnotationProcessor.
+ @Test
+ public void mapBindingsWithInaccessibleKeys() throws Exception {
+ JavaFileObject mapKeys =
+ JavaFileObjects.forSourceLines(
+ "mapkeys.MapKeys",
+ "package mapkeys;",
+ "",
+ "import dagger.MapKey;",
+ "import dagger.multibindings.LazyClassKey;",
+ "",
+ "public class MapKeys {",
+ " @MapKey(unwrapValue = false)",
+ " public @interface ComplexKey {",
+ " Class<?>[] manyClasses();",
+ " Class<?> oneClass();",
+ " LazyClassKey annotation();",
+ " }",
+ "",
+ " interface Inaccessible {}",
+ "}");
+ JavaFileObject moduleFile =
+ JavaFileObjects.forSourceLines(
+ "mapkeys.MapModule",
+ "package mapkeys;",
+ "",
+ "import dagger.Binds;",
+ "import dagger.Module;",
+ "import dagger.Provides;",
+ "import dagger.multibindings.LazyClassKey;",
+ "import dagger.multibindings.IntoMap;",
+ "import java.util.Map;",
+ "import javax.inject.Provider;",
+ "",
+ "@Module",
+ "public interface MapModule {",
+ " @Provides @IntoMap @LazyClassKey(MapKeys.Inaccessible.class)",
+ " static int classKey() { return 1; }",
+ "",
+ " @Provides @IntoMap",
+ " @MapKeys.ComplexKey(",
+ " manyClasses = {java.lang.Object.class, java.lang.String.class},",
+ " oneClass = MapKeys.Inaccessible.class,",
+ " annotation = @LazyClassKey(java.lang.Object.class)",
+ " )",
+ " static int complexKeyWithInaccessibleValue() { return 1; }",
+ "",
+ " @Provides @IntoMap",
+ " @MapKeys.ComplexKey(",
+ " manyClasses = {MapKeys.Inaccessible.class, java.lang.String.class},",
+ " oneClass = java.lang.String.class,",
+ " annotation = @LazyClassKey(java.lang.Object.class)",
+ " )",
+ " static int complexKeyWithInaccessibleArrayValue() { return 1; }",
+ "",
+ " @Provides @IntoMap",
+ " @MapKeys.ComplexKey(",
+ " manyClasses = {java.lang.String.class},",
+ " oneClass = java.lang.String.class,",
+ " annotation = @LazyClassKey(MapKeys.Inaccessible.class)",
+ " )",
+ " static int complexKeyWithInaccessibleAnnotationValue() { return 1; }",
+ "}");
+ JavaFileObject componentFile =
+ JavaFileObjects.forSourceLines(
+ "test.TestComponent",
+ "package test;",
+ "",
+ "import dagger.Component;",
+ "import java.util.Map;",
+ "import javax.inject.Provider;",
+ "import mapkeys.MapKeys;",
+ "import mapkeys.MapModule;",
+ "",
+ "@Component(modules = MapModule.class)",
+ "interface TestComponent {",
+ " Map<Class<?>, Integer> classKey();",
+ " Provider<Map<Class<?>, Integer>> classKeyProvider();",
+ "",
+ " Map<MapKeys.ComplexKey, Integer> complexKey();",
+ " Provider<Map<MapKeys.ComplexKey, Integer>> complexKeyProvider();",
+ "}");
+ Compilation compilation =
+ Compilers.compilerWithOptions(compilerMode.javacopts())
+ .compile(mapKeys, moduleFile, componentFile);
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedSourceFile("test.DaggerTestComponent")
+ .hasSourceEquivalentTo(goldenFileRule.goldenFile("test.DaggerTestComponent"));
+ assertThat(compilation)
+ .generatedSourceFile(
+ "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey")
+ .hasSourceEquivalentTo(
+ goldenFileRule.goldenFile(
+ "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey"));
+ assertThat(compilation)
+ .generatedSourceFile("mapkeys.MapModule_ClassKeyMapKey")
+ .hasSourceEquivalentTo(goldenFileRule.goldenFile("mapkeys.MapModule_ClassKeyMapKey"));
+ }
+
+ @Test
+ public void lazyClassKeySimilarQualifiedName_doesNotConflict() throws Exception {
+ Source fooBar =
+ CompilerTests.javaSource("test.Foo_Bar", "package test;", "", "interface Foo_Bar {}");
+ Source fooBar2 =
+ CompilerTests.javaSource(
+ "test.Foo", "package test;", "", "interface Foo { interface Bar {} }");
+ Source mapKeyBindingsModule =
+ CompilerTests.javaSource(
+ "test.MapKeyBindingsModule",
+ "package test;",
+ "",
+ "import dagger.Module;",
+ "import dagger.Provides;",
+ "import dagger.multibindings.LazyClassKey;",
+ "import dagger.multibindings.IntoMap;",
+ "",
+ "@Module",
+ "public interface MapKeyBindingsModule {",
+ " @Provides @IntoMap @LazyClassKey(test.Foo_Bar.class)",
+ " static int classKey() { return 1; }",
+ "",
+ " @Provides @IntoMap @LazyClassKey(test.Foo.Bar.class)",
+ " static int classKey2() { return 1; }",
+ "}");
+
+ Source componentFile =
+ CompilerTests.javaSource(
+ "test.TestComponent",
+ "package test;",
+ "",
+ "import dagger.Component;",
+ "import java.util.Map;",
+ "",
+ "@Component(modules = MapKeyBindingsModule.class)",
+ "interface TestComponent {",
+ " Map<Class<?>, Integer> classKey();",
+ "}");
+ CompilerTests.daggerCompiler(fooBar, fooBar2, mapKeyBindingsModule, componentFile)
+ .withProcessingOptions(compilerMode.processorOptions())
+ .compile(subject -> subject.hasErrorCount(0));
+ }
+}
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ClassKeyMapKey b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ClassKeyMapKey
new file mode 100644
index 000000000..63a07ab64
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ClassKeyMapKey
@@ -0,0 +1,30 @@
+package mapkeys;
+
+import dagger.internal.DaggerGenerated;
+import dagger.internal.KeepFieldType;
+import javax.annotation.processing.Generated;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+public final class MapModule_ClassKeyMapKey {
+ @KeepFieldType
+ MapKeys.Inaccessible className;
+
+ private MapModule_ClassKeyMapKey() {
+ }
+
+ public static Class<?> create() {
+ return MapKeys.Inaccessible.class;
+ }
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey
new file mode 100644
index 000000000..3e00ffd48
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey
@@ -0,0 +1,26 @@
+package mapkeys;
+
+import dagger.internal.DaggerGenerated;
+import javax.annotation.processing.Generated;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+public final class MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey {
+ private MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey() {
+ }
+
+ public static MapKeys.ComplexKey create() {
+ return MapKeys_ComplexKeyCreator.createComplexKey(new Class[] {String.class}, String.class, MapKeys_ComplexKeyCreator.createLazyClassKey(MapKeys.Inaccessible.class));
+ }
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_test.DaggerTestComponent
new file mode 100644
index 000000000..eff6a40b8
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_DEFAULT_MODE_test.DaggerTestComponent
@@ -0,0 +1,99 @@
+package test;
+
+import com.google.common.collect.ImmutableMap;
+import dagger.internal.DaggerGenerated;
+import dagger.internal.IdentifierNameString;
+import dagger.internal.LazyClassKeyMap;
+import dagger.internal.MapFactory;
+import dagger.internal.Provider;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+import mapkeys.MapKeys;
+import mapkeys.MapModule;
+import mapkeys.MapModule_ClassKeyFactory;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueFactory;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleArrayValueFactory;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleArrayValueMapKey;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleValueFactory;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleValueMapKey;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+final class DaggerTestComponent {
+ private DaggerTestComponent() {
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static TestComponent create() {
+ return new Builder().build();
+ }
+
+ static final class Builder {
+ private Builder() {
+ }
+
+ public TestComponent build() {
+ return new TestComponentImpl();
+ }
+ }
+
+ private static final class TestComponentImpl implements TestComponent {
+ private final TestComponentImpl testComponentImpl = this;
+
+ private Provider<Map<Class<?>, Integer>> mapOfClassOfAndIntegerProvider;
+
+ private Provider<Map<MapKeys.ComplexKey, Integer>> mapOfComplexKeyAndIntegerProvider;
+
+ private TestComponentImpl() {
+
+ initialize();
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initialize() {
+ this.mapOfClassOfAndIntegerProvider = LazyClassKeyMap.Factory.<Integer>of(MapFactory.<String, Integer>builder(1).put(LazyClassKeyProvider.mapkeys_MapKeys_Inaccessible, MapModule_ClassKeyFactory.create()).build());
+ this.mapOfComplexKeyAndIntegerProvider = MapFactory.<MapKeys.ComplexKey, Integer>builder(3).put(MapModule_ComplexKeyWithInaccessibleValueMapKey.create(), MapModule_ComplexKeyWithInaccessibleValueFactory.create()).put(MapModule_ComplexKeyWithInaccessibleArrayValueMapKey.create(), MapModule_ComplexKeyWithInaccessibleArrayValueFactory.create()).put(MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey.create(), MapModule_ComplexKeyWithInaccessibleAnnotationValueFactory.create()).build();
+ }
+
+ @Override
+ public Map<Class<?>, Integer> classKey() {
+ return LazyClassKeyMap.<Integer>of(ImmutableMap.<String, Integer>of(LazyClassKeyProvider.mapkeys_MapKeys_Inaccessible, MapModule.classKey()));
+ }
+
+ @Override
+ public javax.inject.Provider<Map<Class<?>, Integer>> classKeyProvider() {
+ return mapOfClassOfAndIntegerProvider;
+ }
+
+ @Override
+ public Map<MapKeys.ComplexKey, Integer> complexKey() {
+ return ImmutableMap.<MapKeys.ComplexKey, Integer>of(MapModule_ComplexKeyWithInaccessibleValueMapKey.create(), MapModule.complexKeyWithInaccessibleValue(), MapModule_ComplexKeyWithInaccessibleArrayValueMapKey.create(), MapModule.complexKeyWithInaccessibleArrayValue(), MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey.create(), MapModule.complexKeyWithInaccessibleAnnotationValue());
+ }
+
+ @Override
+ public javax.inject.Provider<Map<MapKeys.ComplexKey, Integer>> complexKeyProvider() {
+ return mapOfComplexKeyAndIntegerProvider;
+ }
+
+ @IdentifierNameString
+ private static final class LazyClassKeyProvider {
+ static String mapkeys_MapKeys_Inaccessible = "mapkeys.MapKeys$Inaccessible";
+ }
+ }
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ClassKeyMapKey b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ClassKeyMapKey
new file mode 100644
index 000000000..63a07ab64
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ClassKeyMapKey
@@ -0,0 +1,30 @@
+package mapkeys;
+
+import dagger.internal.DaggerGenerated;
+import dagger.internal.KeepFieldType;
+import javax.annotation.processing.Generated;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+public final class MapModule_ClassKeyMapKey {
+ @KeepFieldType
+ MapKeys.Inaccessible className;
+
+ private MapModule_ClassKeyMapKey() {
+ }
+
+ public static Class<?> create() {
+ return MapKeys.Inaccessible.class;
+ }
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey
new file mode 100644
index 000000000..3e00ffd48
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey
@@ -0,0 +1,26 @@
+package mapkeys;
+
+import dagger.internal.DaggerGenerated;
+import javax.annotation.processing.Generated;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+public final class MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey {
+ private MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey() {
+ }
+
+ public static MapKeys.ComplexKey create() {
+ return MapKeys_ComplexKeyCreator.createComplexKey(new Class[] {String.class}, String.class, MapKeys_ComplexKeyCreator.createLazyClassKey(MapKeys.Inaccessible.class));
+ }
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_test.DaggerTestComponent
new file mode 100644
index 000000000..478b0101d
--- /dev/null
+++ b/javatests/dagger/internal/codegen/goldens/LazyClassKeyMapBindingComponentProcessorTest_mapBindingsWithInaccessibleKeys_FAST_INIT_MODE_test.DaggerTestComponent
@@ -0,0 +1,119 @@
+package test;
+
+import com.google.common.collect.ImmutableMap;
+import dagger.internal.DaggerGenerated;
+import dagger.internal.IdentifierNameString;
+import dagger.internal.LazyClassKeyMap;
+import dagger.internal.Provider;
+import java.util.Map;
+import javax.annotation.processing.Generated;
+import mapkeys.MapKeys;
+import mapkeys.MapModule;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleArrayValueMapKey;
+import mapkeys.MapModule_ComplexKeyWithInaccessibleValueMapKey;
+
+@DaggerGenerated
+@Generated(
+ value = "dagger.internal.codegen.ComponentProcessor",
+ comments = "https://dagger.dev"
+)
+@SuppressWarnings({
+ "unchecked",
+ "rawtypes",
+ "KotlinInternal",
+ "KotlinInternalInJava",
+ "cast"
+})
+final class DaggerTestComponent {
+ private DaggerTestComponent() {
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static TestComponent create() {
+ return new Builder().build();
+ }
+
+ static final class Builder {
+ private Builder() {
+ }
+
+ public TestComponent build() {
+ return new TestComponentImpl();
+ }
+ }
+
+ private static final class TestComponentImpl implements TestComponent {
+ private final TestComponentImpl testComponentImpl = this;
+
+ private Provider<Map<Class<?>, Integer>> mapOfClassOfAndIntegerProvider;
+
+ private Provider<Map<MapKeys.ComplexKey, Integer>> mapOfComplexKeyAndIntegerProvider;
+
+ private TestComponentImpl() {
+
+ initialize();
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private void initialize() {
+ this.mapOfClassOfAndIntegerProvider = new SwitchingProvider<>(testComponentImpl, 0);
+ this.mapOfComplexKeyAndIntegerProvider = new SwitchingProvider<>(testComponentImpl, 1);
+ }
+
+ @Override
+ public Map<Class<?>, Integer> classKey() {
+ return mapOfClassOfAndIntegerProvider.get();
+ }
+
+ @Override
+ public javax.inject.Provider<Map<Class<?>, Integer>> classKeyProvider() {
+ return mapOfClassOfAndIntegerProvider;
+ }
+
+ @Override
+ public Map<MapKeys.ComplexKey, Integer> complexKey() {
+ return mapOfComplexKeyAndIntegerProvider.get();
+ }
+
+ @Override
+ public javax.inject.Provider<Map<MapKeys.ComplexKey, Integer>> complexKeyProvider() {
+ return mapOfComplexKeyAndIntegerProvider;
+ }
+
+ private static final class SwitchingProvider<T> implements Provider<T> {
+ private final TestComponentImpl testComponentImpl;
+
+ private final int id;
+
+ SwitchingProvider(TestComponentImpl testComponentImpl, int id) {
+ this.testComponentImpl = testComponentImpl;
+ this.id = id;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get() {
+ switch (id) {
+ case 0: // java.util.Map<java.lang.Class<?>,java.lang.Integer>
+ return (T) LazyClassKeyMap.<Integer>of(ImmutableMap.<String, Integer>of(LazyClassKeyProvider.mapkeys_MapKeys_Inaccessible, MapModule.classKey()));
+
+ case 1: // java.util.Map<mapkeys.MapKeys.ComplexKey,java.lang.Integer>
+ return (T) ImmutableMap.<MapKeys.ComplexKey, Integer>of(MapModule_ComplexKeyWithInaccessibleValueMapKey.create(), MapModule.complexKeyWithInaccessibleValue(), MapModule_ComplexKeyWithInaccessibleArrayValueMapKey.create(), MapModule.complexKeyWithInaccessibleArrayValue(), MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey.create(), MapModule.complexKeyWithInaccessibleAnnotationValue());
+
+ default: throw new AssertionError(id);
+ }
+ }
+ }
+
+ @IdentifierNameString
+ private static final class LazyClassKeyProvider {
+ static String mapkeys_MapKeys_Inaccessible = "mapkeys.MapKeys$Inaccessible";
+ }
+ }
+}
+
diff --git a/tools/maven.bzl b/tools/maven.bzl
index 751cc4e4f..4fba1a53c 100644
--- a/tools/maven.bzl
+++ b/tools/maven.bzl
@@ -66,7 +66,9 @@ def gen_maven_artifact(
shaded_deps = None,
manifest = None,
lint_deps = None,
- proguard_and_r8_specs = None):
+ proguard_and_r8_specs = None,
+ r8_specs = None,
+ proguard_specs = None):
_gen_maven_artifact(
name,
artifact_name,
@@ -85,7 +87,9 @@ def gen_maven_artifact(
shaded_deps,
manifest,
lint_deps,
- proguard_and_r8_specs
+ proguard_and_r8_specs,
+ r8_specs,
+ proguard_specs
)
def _gen_maven_artifact(
@@ -106,7 +110,9 @@ def _gen_maven_artifact(
shaded_deps,
manifest,
lint_deps,
- proguard_and_r8_specs):
+ proguard_and_r8_specs,
+ r8_specs,
+ proguard_specs):
"""Generates the files required for a maven artifact.
This macro generates the following targets:
@@ -141,7 +147,11 @@ def _gen_maven_artifact(
manifest: The AndroidManifest.xml to bundle in when packaing an 'aar'.
lint_deps: The lint targets to be bundled in when packaging an 'aar'.
proguard_and_r8_specs: The proguard spec files to be bundled in when
- packaging an 'aar', which will be applied in both r8 and proguard.
+ packaging an 'aar', which will be applied in
+ both r8 and proguard.
+ r8_specs: The proguard spec files to be used only for r8 when packaging an 'jar'.
+ proguard_specs: The proguard spec files to be used only for proguard not r8 when
+ packaging an 'jar'.
"""
_validate_maven_deps(
@@ -218,12 +228,46 @@ def _gen_maven_artifact(
cmd = "cp $< $@",
)
else:
+ # (TODO/322873492) add support for passing in general proguard rule.
+ if r8_specs:
+ # Concatenate all r8 rules.
+ native.genrule(
+ name = name + "-r8",
+ srcs = r8_specs,
+ outs = [name + "-r8.txt"],
+ cmd = "cat $(SRCS) > $@",
+ )
+ r8_file = name + "-r8.txt"
+ else:
+ r8_file = None
+
+ if proguard_specs:
+ # Concatenate all proguard only rules.
+ native.genrule(
+ name = name + "-proguard-only",
+ srcs = proguard_specs,
+ outs = [name + "-proguard-only.txt"],
+ cmd = "cat $(SRCS) > $@",
+ )
+ proguard_only_file = name + "-proguard-only.txt"
+ else:
+ proguard_only_file = None
+
jarjar_library(
- name = name,
+ name = name + "-classes",
testonly = testonly,
jars = artifact_targets + shaded_deps,
merge_meta_inf_files = merge_meta_inf_files,
)
+ jar_name = name + "-classes.jar"
+
+ # Include r8 and proguard rules to dagger jar if there is one.
+ _package_r8_and_proguard_rule(
+ name = name,
+ artifactJar = jar_name,
+ r8Spec = r8_file,
+ proguardSpec = proguard_only_file,
+ )
jarjar_library(
name = name + "-src",
@@ -406,3 +450,58 @@ _package_android_library = rule(
"aar": "%{name}.aar",
},
)
+
+def _package_r8_and_proguard_rule_impl(ctx):
+ inputs = [ctx.file.artifactJar]
+ if ctx.file.r8Spec:
+ inputs.append(ctx.file.r8Spec)
+ if ctx.file.proguardSpec:
+ inputs.append(ctx.file.proguardSpec)
+ ctx.actions.run_shell(
+ inputs = inputs,
+ outputs = [ctx.outputs.jar],
+ command = """
+ TMPDIR="$(mktemp -d)"
+ cp {artifactJar} $TMPDIR/artifact.jar
+ if [[ -a {r8Spec} ]]; then
+ mkdir -p META-INF/com.android.tools/r8
+ cp {r8Spec} META-INF/com.android.tools/r8/r8.pro
+ jar uf $TMPDIR/artifact.jar META-INF/
+ fi
+ if [[ -a {proguardSpec} ]]; then
+ mkdir -p META-INF/com.android.tools/proguard
+ cp {proguardSpec} META-INF/com.android.tools/proguard/proguard.pro
+ jar uf $TMPDIR/artifact.jar META-INF/
+ fi
+ cp $TMPDIR/artifact.jar {outputFile}
+ """.format(
+ artifactJar = ctx.file.artifactJar.path,
+ r8Spec = ctx.file.r8Spec.path if ctx.file.r8Spec else "none",
+ proguardSpec = ctx.file.proguardSpec.path if ctx.file.proguardSpec else "none",
+ outputFile = ctx.outputs.jar.path,
+ ),
+ )
+
+_package_r8_and_proguard_rule = rule(
+ implementation = _package_r8_and_proguard_rule_impl,
+ attrs = {
+ "artifactJar": attr.label(
+ doc = "The library artifact jar to be updated.",
+ allow_single_file = True,
+ mandatory = True,
+ ),
+ "r8Spec": attr.label(
+ doc = "The r8.txt file to be merged with the artifact jar",
+ allow_single_file = True,
+ mandatory = False,
+ ),
+ "proguardSpec": attr.label(
+ doc = "The proguard-only.txt file to be merged with the artifact jar",
+ allow_single_file = True,
+ mandatory = False,
+ ),
+ },
+ outputs = {
+ "jar": "%{name}.jar",
+ },
+)
diff --git a/util/run-local-emulator-tests.sh b/util/run-local-emulator-tests.sh
index 7ec2e60d7..733f63744 100755
--- a/util/run-local-emulator-tests.sh
+++ b/util/run-local-emulator-tests.sh
@@ -9,6 +9,8 @@ readonly GRADLE_PROJECTS=(
"javatests/artifacts/hilt-android/simple"
"javatests/artifacts/hilt-android/simpleKotlin"
"javatests/artifacts/hilt-android/viewmodel"
+ "javatests/artifacts/hilt-android/lazyclasskey"
+ "javatests/artifacts/dagger/lazyclasskey"
)
for project in "${GRADLE_PROJECTS[@]}"; do
echo "Running gradle Android emulator tests for $project"