diff options
author | Colin Cross <ccross@android.com> | 2021-05-11 21:35:40 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-05-11 21:35:40 +0000 |
commit | 33911b6754a26d9cb5892dee587d2bd71de405af (patch) | |
tree | 960edeb544f68559c068d85754598f9450c5a9fe | |
parent | e5b0c32d47957d571b8c4401dd31abcf276b38fc (diff) | |
parent | 5b1c4ba531b7e50f96c1063e30a62be01cb43974 (diff) | |
download | dagger2-33911b6754a26d9cb5892dee587d2bd71de405af.tar.gz |
Merge commit 'upstream/dagger-2.35.1^' am: 399f4f6113 am: c6f4971ee0 am: 849b6a83fd am: 5b1c4ba531
Original change: https://android-review.googlesource.com/c/platform/external/dagger2/+/1697792
Change-Id: Icec28249d477fddd9ae53cb1ffc13e2dfd569419
304 files changed, 14418 insertions, 2158 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 328d4b1df..6c818cc98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,12 +5,43 @@ on: branches: - master pull_request: + branches: + - master + +env: + # Our Bazel builds currently rely on JDK 8. + USE_JAVA_VERSION: '8' + # Our Bazel builds currently rely on 3.7.1. The version is set via + # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk. + USE_BAZEL_VERSION: '3.7.1' jobs: + validate-latest-dagger-version: + name: 'Validate Dagger version' + runs-on: ubuntu-latest + steps: + # Cancel previous runs on the same branch to avoid unnecessary parallel + # runs of the same job. See https://github.com/google/go-github/pull/1821 + - name: Cancel previous + uses: styfle/cancel-workflow-action@0.8.0 + with: + access_token: ${{ github.token }} + - name: 'Check out gh-pages repository' + uses: actions/checkout@v2 + with: + ref: 'refs/heads/gh-pages' + path: gh-pages + - name: 'Validate latest Dagger version' + run: ./gh-pages/.github/scripts/validate-latest-dagger-version.sh gh-pages/_config.yml bazel-test: name: 'Bazel tests' + needs: validate-latest-dagger-version runs-on: ubuntu-latest steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v1 + with: + java-version: '${{ env.USE_JAVA_VERSION }}' - name: 'Check out repository' uses: actions/checkout@v2 - name: 'Cache local Maven repository' @@ -39,7 +70,7 @@ jobs: restore-keys: | ${{ runner.os }}-gradle- - name: 'Run Bazel tests' - run: bazel test --test_output=errors //... + run: bazel test --test_output=errors //... --keep_going shell: bash - name: 'Install local snapshot' run: ./util/install-local-snapshot.sh @@ -100,6 +131,12 @@ jobs: - name: 'Gradle Android local tests (AGP ${{ matrix.agp }})' run: ./util/run-local-gradle-android-tests.sh "${{ matrix.agp }}" shell: bash + - name: 'Upload test reports (AGP ${{ matrix.agp }})' + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: tests-reports-agp-${{ matrix.agp }} + path: ${{ github.workspace }}/**/build/reports/tests/* artifact-android-emulator-tests: name: 'Artifact Android emulator tests (API 30)' needs: bazel-test @@ -130,6 +167,12 @@ jobs: api-level: 30 target: google_apis script: ./util/run-local-emulator-tests.sh + - name: 'Upload test reports (API 30)' + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: androidTests-reports-api-30 + path: ${{ github.workspace }}/**/build/reports/androidTests/connected/* publish-snapshot: name: 'Publish snapshot' # TODO(bcorso): Consider also waiting on artifact-android-emulator-tests @@ -138,6 +181,10 @@ jobs: if: github.event_name == 'push' && github.repository == 'google/dagger' && github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: + - name: 'Install Java ${{ env.USE_JAVA_VERSION }}' + uses: actions/setup-java@v1 + with: + java-version: '${{ env.USE_JAVA_VERSION }}' - name: 'Check out repository' uses: actions/checkout@v2 - name: 'Cache local Maven repository' @@ -186,8 +233,8 @@ jobs: # See https://github.com/marketplace/actions/android-emulator-runner runs-on: macos-latest strategy: - matrix: - api-level: [16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 29] + matrix: # Run on 16 (PreL), 21 (L), and 26 (O). + api-level: [16, 21, 26] steps: - name: 'Check out repository' uses: actions/checkout@v2 @@ -212,3 +259,9 @@ jobs: api-level: ${{ matrix.api-level }} target: google_apis script: ./util/run-local-emulator-tests.sh + - name: 'Upload test reports (API ${{ matrix.api-level }})' + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: androidTests-report-api-${{ matrix.api-level }} + path: ${{ github.workspace }}/**/build/reports/androidTests/connected/* @@ -97,6 +97,22 @@ jarjar_library( ], ) +android_library( + name = "android_local_test_exports", + exports = [ + # TODO(bcorso): see if we can remove jsr250 dep from autovalue to prevent this. + "@javax_annotation_jsr250_api", # For @Generated + "@maven//:org_robolectric_shadows_framework", # For ActivityController + "@maven//:androidx_lifecycle_lifecycle_common", # For Lifecycle.State + "@maven//:androidx_activity_activity", # For ComponentActivity + "@maven//:androidx_test_core", # For ApplicationProvider + "@maven//:androidx_test_ext_junit", + "@maven//:org_robolectric_annotations", + "@maven//:org_robolectric_robolectric", + "@robolectric//bazel:android-all", + ], +) + # coalesced javadocs used for the gh-pages site javadoc_library( name = "user-docs", @@ -12,6 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +############################# +# Load nested repository +############################# + # Declare the nested workspace so that the top-level workspace doesn't try to # traverse it when calling `bazel build //...` local_repository( @@ -19,7 +25,9 @@ local_repository( path = "examples/bazel", ) -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +############################# +# Load Bazel-Common repository +############################# http_archive( name = "google_bazel_common", @@ -32,16 +40,9 @@ load("@google_bazel_common//:workspace_defs.bzl", "google_common_workspace_rules google_common_workspace_rules() -RULES_JVM_EXTERNAL_TAG = "2.7" - -RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74" - -http_archive( - name = "rules_jvm_external", - sha256 = RULES_JVM_EXTERNAL_SHA, - strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, - url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, -) +############################# +# Load Protobuf dependencies +############################# # rules_python and zlib are required by protobuf. # TODO(ronshapiro): Figure out if zlib is in fact necessary, or if proto can depend on the @@ -65,6 +66,27 @@ http_archive( urls = ["https://github.com/madler/zlib/archive/v1.2.11.tar.gz"], ) +############################# +# Load Robolectric repository +############################# + +ROBOLECTRIC_VERSION = "4.4" + +http_archive( + name = "robolectric", + sha256 = "d4f2eb078a51f4e534ebf5e18b6cd4646d05eae9b362ac40b93831bdf46112c7", + strip_prefix = "robolectric-bazel-%s" % ROBOLECTRIC_VERSION, + urls = ["https://github.com/robolectric/robolectric-bazel/archive/%s.tar.gz" % ROBOLECTRIC_VERSION], +) + +load("@robolectric//bazel:robolectric.bzl", "robolectric_repositories") + +robolectric_repositories() + +############################# +# Load Kotlin repository +############################# + RULES_KOTLIN_COMMIT = "2c283821911439e244285b5bfec39148e7d90e21" RULES_KOTLIN_SHA = "b04cd539e7e3571745179da95069586b6fa76a64306b24bb286154e652010608" @@ -96,6 +118,21 @@ kotlin_repositories(compiler_release = KOTLINC_RELEASE) register_toolchains("//:kotlin_toolchain") +############################# +# Load Maven dependencies +############################# + +RULES_JVM_EXTERNAL_TAG = "2.7" + +RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) + load("@rules_jvm_external//:defs.bzl", "maven_install") ANDROID_LINT_VERSION = "26.6.2" @@ -104,14 +141,16 @@ maven_install( artifacts = [ "androidx.annotation:annotation:1.1.0", "androidx.appcompat:appcompat:1.2.0", - "androidx.activity:activity:1.1.0", - "androidx.fragment:fragment:1.2.5", - "androidx.lifecycle:lifecycle-viewmodel:2.2.0", - "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0", + "androidx.activity:activity:1.2.2", + "androidx.fragment:fragment:1.3.2", + "androidx.lifecycle:lifecycle-common:2.3.1", + "androidx.lifecycle:lifecycle-viewmodel:2.3.1", + "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1", "androidx.multidex:multidex:2.0.1", "androidx.savedstate:savedstate:1.0.0", "androidx.test:monitor:1.1.1", "androidx.test:core:1.1.0", + "androidx.test.ext:junit:1.1.2", "com.google.auto:auto-common:0.11", "com.android.support:appcompat-v7:25.0.0", "com.android.support:support-annotations:25.0.0", @@ -126,9 +165,11 @@ maven_install( "com.android.tools:testutils:%s" % ANDROID_LINT_VERSION, "com.github.tschuchortdev:kotlin-compile-testing:1.2.8", "com.google.guava:guava:27.1-android", + "junit:junit:4.13", "org.jetbrains.kotlin:kotlin-stdlib:%s" % KOTLIN_VERSION, - "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.1.0", - "org.robolectric:robolectric:4.3.1", + "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.2.0", + "org.robolectric:robolectric:4.4", + "org.robolectric:shadows-framework:4.4", # For ActivityController ], repositories = [ "https://repo1.maven.org/maven2", @@ -137,6 +178,10 @@ maven_install( ], ) +############################# +# Load Bazel Skylib rules +############################# + BAZEL_SKYLIB_VERSION = "1.0.2" BAZEL_SKYLIB_SHA = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44" diff --git a/build_defs.bzl b/build_defs.bzl index 932ca086a..915f0a056 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -25,7 +25,4 @@ SOURCE_7_TARGET_7 = [ "1.7", ] -POM_VERSION = "2.29.1" - -# DO NOT remove the comment on the next line. It's used in deploy-to-maven-central.sh -POM_VERSION_ALPHA = POM_VERSION + "-alpha" +POM_VERSION = "${project.version}" diff --git a/java/dagger/android/support/BUILD b/java/dagger/android/support/BUILD index 03846b577..8afa703d4 100644 --- a/java/dagger/android/support/BUILD +++ b/java/dagger/android/support/BUILD @@ -41,9 +41,13 @@ android_library( "//:dagger_with_compiler", "//java/dagger/android", "@google_bazel_common//third_party/java/error_prone:annotations", + "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_appcompat_appcompat", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/java/dagger/android/support/DaggerAppCompatActivity.java b/java/dagger/android/support/DaggerAppCompatActivity.java index da5b17313..3f0469ed6 100644 --- a/java/dagger/android/support/DaggerAppCompatActivity.java +++ b/java/dagger/android/support/DaggerAppCompatActivity.java @@ -17,10 +17,10 @@ package dagger.android.support; import android.os.Bundle; -import androidx.annotation.ContentView; -import androidx.annotation.LayoutRes; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.annotation.ContentView; +import androidx.annotation.LayoutRes; import dagger.android.AndroidInjection; import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; diff --git a/java/dagger/android/support/DaggerFragment.java b/java/dagger/android/support/DaggerFragment.java index 887451154..e705b3c69 100644 --- a/java/dagger/android/support/DaggerFragment.java +++ b/java/dagger/android/support/DaggerFragment.java @@ -17,9 +17,9 @@ package dagger.android.support; import android.content.Context; +import androidx.fragment.app.Fragment; import androidx.annotation.ContentView; import androidx.annotation.LayoutRes; -import androidx.fragment.app.Fragment; import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; import dagger.android.HasAndroidInjector; diff --git a/java/dagger/hilt/BUILD b/java/dagger/hilt/BUILD index 94af18a91..3d12c91e7 100644 --- a/java/dagger/hilt/BUILD +++ b/java/dagger/hilt/BUILD @@ -13,7 +13,7 @@ # limitations under the License. load("//tools:maven.bzl", "gen_maven_artifact") -load("//:build_defs.bzl", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") # Description: # A library that wraps the Dagger API to make DI usage and testing easier. @@ -46,11 +46,14 @@ java_library( # TODO(bcorso): Consider using a separate processor to validate @EntryPoint. "//java/dagger/hilt/processor/internal/aggregateddeps:plugin", ], + proguard_specs = ["proguard-rules.pro"], deps = [ ":generates_root_input", ":package_info", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:generated_component", + "//java/dagger/hilt/internal:preconditions", + "//java/dagger/hilt/internal:test_singleton_component", "@google_bazel_common//third_party/java/jsr305_annotations", ], ) @@ -162,7 +165,6 @@ filegroup( "//java/dagger/hilt/android/processor/internal/androidentrypoint:srcs_filegroup", "//java/dagger/hilt/android/processor/internal/bindvalue:srcs_filegroup", "//java/dagger/hilt/android/processor/internal/customtestapplication:srcs_filegroup", - "//java/dagger/hilt/android/processor/internal/uninstallmodules:srcs_filegroup", "//java/dagger/hilt/android/processor/internal/viewmodel:srcs_filegroup", "//java/dagger/hilt/processor:srcs_filegroup", "//java/dagger/hilt/processor/internal:srcs_filegroup", @@ -173,12 +175,13 @@ filegroup( "//java/dagger/hilt/processor/internal/generatesrootinput:srcs_filegroup", "//java/dagger/hilt/processor/internal/originatingelement:srcs_filegroup", "//java/dagger/hilt/processor/internal/root:srcs_filegroup", + "//java/dagger/hilt/processor/internal/uninstallmodules:srcs_filegroup", ], ) java_library( name = "artifact-core-lib", - tags = ["maven_coordinates=com.google.dagger:hilt-core:" + POM_VERSION_ALPHA], + tags = ["maven_coordinates=com.google.dagger:hilt-core:" + POM_VERSION], exports = [ ":define_component", ":entry_point", @@ -194,7 +197,7 @@ java_library( gen_maven_artifact( name = "artifact-core", - artifact_coordinates = "com.google.dagger:hilt-core:" + POM_VERSION_ALPHA, + artifact_coordinates = "com.google.dagger:hilt-core:" + POM_VERSION, artifact_name = "Hilt Core", artifact_target = ":artifact-core-lib", artifact_target_libs = [ @@ -210,6 +213,7 @@ gen_maven_artifact( "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:generated_component", "//java/dagger/hilt/internal:preconditions", + "//java/dagger/hilt/internal:test_singleton_component", "//java/dagger/hilt/internal:unsafe_casts", "//java/dagger/hilt/internal/aliasof", "//java/dagger/hilt/internal/definecomponent", diff --git a/java/dagger/hilt/EntryPoints.java b/java/dagger/hilt/EntryPoints.java index 32e231212..3db77f903 100644 --- a/java/dagger/hilt/EntryPoints.java +++ b/java/dagger/hilt/EntryPoints.java @@ -18,10 +18,14 @@ package dagger.hilt; import dagger.hilt.internal.GeneratedComponent; import dagger.hilt.internal.GeneratedComponentManager; +import dagger.hilt.internal.Preconditions; +import dagger.hilt.internal.TestSingletonComponent; +import java.lang.annotation.Annotation; import javax.annotation.Nonnull; /** Static utility methods for accessing objects through entry points. */ public final class EntryPoints { + private static final String EARLY_ENTRY_POINT = "dagger.hilt.android.EarlyEntryPoint"; /** * Returns the entry point interface given a component or component manager. Note that this @@ -39,11 +43,20 @@ public final class EntryPoints { @Nonnull public static <T> T get(Object component, Class<T> entryPoint) { if (component instanceof GeneratedComponent) { + if (component instanceof TestSingletonComponent) { + // @EarlyEntryPoint only has an effect in test environment, so we shouldn't fail in + // non-test cases. In addition, some of the validation requires the use of reflection, which + // we don't want to do in non-test cases anyway. + Preconditions.checkState( + !hasAnnotationReflection(entryPoint, EARLY_ENTRY_POINT), + "Interface, %s, annotated with @EarlyEntryPoint should be called with " + + "EarlyEntryPoints.get() rather than EntryPoints.get()", + entryPoint.getCanonicalName()); + } // Unsafe cast. There is no way for this method to know that the correct component was used. return entryPoint.cast(component); } else if (component instanceof GeneratedComponentManager) { - // Unsafe cast. There is no way for this method to know that the correct component was used. - return entryPoint.cast(((GeneratedComponentManager<?>) component).generatedComponent()); + return get(((GeneratedComponentManager<?>) component).generatedComponent(), entryPoint); } else { throw new IllegalStateException( String.format( @@ -52,5 +65,15 @@ public final class EntryPoints { } } + // Note: This method uses reflection but it should only be called in test environments. + private static boolean hasAnnotationReflection(Class<?> clazz, String annotationName) { + for (Annotation annotation : clazz.getAnnotations()) { + if (annotation.annotationType().getCanonicalName().contentEquals(annotationName)) { + return true; + } + } + return false; + } + private EntryPoints() {} } diff --git a/java/dagger/hilt/android/BUILD b/java/dagger/hilt/android/BUILD index 47d7e678c..d8833e77c 100644 --- a/java/dagger/hilt/android/BUILD +++ b/java/dagger/hilt/android/BUILD @@ -14,7 +14,7 @@ # Description: # A library based on Hilt that provides standard components and automated injection for Android. -load("//:build_defs.bzl", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -41,8 +41,12 @@ android_library( "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:generated_entry_point", "//java/dagger/hilt/internal:preconditions", + "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], deps = [ ":package_info", @@ -59,18 +63,28 @@ android_library( "//java/dagger/hilt/processor/internal/root:plugin", ], exports = [ + ":activity_retained_lifecycle", "//:dagger_with_compiler", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", "//java/dagger/hilt/android/internal/modules", + "//java/dagger/hilt/android/scopes", "//java/dagger/hilt/codegen:originating_element", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:generated_component", "//java/dagger/hilt/internal:generated_entry_point", + "//java/dagger/hilt/internal/aggregatedroot", + "//java/dagger/hilt/internal/processedrootsentinel", "//java/dagger/hilt/migration:disable_install_in_check", + "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], deps = [ ":package_info", @@ -85,7 +99,11 @@ android_library( ":package_info", "//java/dagger/hilt:entry_point", "@google_bazel_common//third_party/java/jsr305_annotations", + "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) @@ -99,16 +117,27 @@ android_library( ) android_library( - name = "artifact-lib", - tags = ["maven_coordinates=com.google.dagger:hilt-android:" + POM_VERSION_ALPHA], + name = "early_entry_point", + srcs = [ + "EarlyEntryPoint.java", + "EarlyEntryPoints.java", + ], + exported_plugins = [ + "//java/dagger/hilt/processor/internal/aggregateddeps:plugin", + "//java/dagger/hilt/processor/internal/earlyentrypoint:processor", + ], + proguard_specs = ["proguard-rules.pro"], exports = [ - ":android_entry_point", - ":entry_point_accessors", - ":hilt_android_app", + "//java/dagger/hilt/android/internal/earlyentrypoint", + ], + deps = [ ":package_info", - "//java/dagger/hilt:artifact-core-lib", - "//java/dagger/hilt/android/migration:optional_inject", - "//java/dagger/lint:lint-android-artifact-lib", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/internal:component_manager", + "//java/dagger/hilt/internal:preconditions", + "//java/dagger/hilt/internal:test_singleton_component_manager", + "@google_bazel_common//third_party/java/jsr305_annotations", ], ) @@ -120,9 +149,24 @@ java_library( ], ) +android_library( + name = "artifact-lib", + tags = ["maven_coordinates=com.google.dagger:hilt-android:" + POM_VERSION], + exports = [ + ":android_entry_point", + ":early_entry_point", + ":entry_point_accessors", + ":hilt_android_app", + ":package_info", + "//java/dagger/hilt:artifact-core-lib", + "//java/dagger/hilt/android/migration:optional_inject", + "//java/dagger/lint:lint-android-artifact-lib", + ], +) + gen_maven_artifact( name = "artifact", - artifact_coordinates = "com.google.dagger:hilt-android:" + POM_VERSION_ALPHA, + artifact_coordinates = "com.google.dagger:hilt-android:" + POM_VERSION, artifact_name = "Hilt Android", artifact_target = ":artifact-lib", artifact_target_libs = [ @@ -130,12 +174,13 @@ gen_maven_artifact( "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android:android_entry_point", "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:early_entry_point", "//java/dagger/hilt/android:package_info", "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/components:view_model_component", "//java/dagger/hilt/android/components:package_info", "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", + "//java/dagger/hilt/android/internal/earlyentrypoint", "//java/dagger/hilt/android/internal/lifecycle", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/managers:component_supplier", @@ -147,16 +192,18 @@ gen_maven_artifact( "//java/dagger/hilt/android/qualifiers", "//java/dagger/hilt/android/qualifiers:package_info", "//java/dagger/hilt/android/scopes", - "//java/dagger/hilt/android/scopes:activity_retained_scoped", - "//java/dagger/hilt/android/scopes:view_model_scoped", "//java/dagger/hilt/android/scopes:package_info", "//java/dagger/hilt/internal:component_entry_point", "//java/dagger/hilt/internal:generated_entry_point", + "//java/dagger/hilt/internal:test_singleton_component_manager", + "//java/dagger/hilt/internal/aggregatedroot:aggregatedroot", + "//java/dagger/hilt/internal/processedrootsentinel:processedrootsentinel", ], artifact_target_maven_deps = [ "androidx.activity:activity", "androidx.annotation:annotation", "androidx.fragment:fragment", + "androidx.lifecycle:lifecycle-common", "androidx.lifecycle:lifecycle-viewmodel", "androidx.lifecycle:lifecycle-viewmodel-savedstate", "androidx.savedstate:savedstate", @@ -183,7 +230,10 @@ gen_maven_artifact( manifest = "AndroidManifest.xml", packaging = "aar", proguard_specs = [ + "//java/dagger/hilt:proguard-rules.pro", + ":proguard-rules.pro", "//java/dagger/hilt/android/lifecycle:proguard-rules.pro", + "//java/dagger/hilt/internal:proguard-rules.pro", ], ) diff --git a/java/dagger/hilt/android/EarlyEntryPoint.java b/java/dagger/hilt/android/EarlyEntryPoint.java new file mode 100644 index 000000000..2a9845e2a --- /dev/null +++ b/java/dagger/hilt/android/EarlyEntryPoint.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import dagger.internal.Beta; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * An escape hatch for when a Hilt entry point usage needs to be called before the singleton + * component is available in a Hilt test. + * + * <p>Warning: Please see documentation for more details: + * https://dagger.dev/hilt/early-entry-point + * + * <p>Usage: + * + * <p>To enable an existing entry point to be called early in a Hilt test, replace its + * {@link dagger.hilt.EntryPoint} annotation with {@link EarlyEntryPoint}. (Note that, + * {@link EarlyEntryPoint} is only allowed on entry points installed in the + * {@link dagger.hilt.components.SingletonComponent}). + * + * <pre><code> + * @EarlyEntryPoint // <- This replaces @EntryPoint + * @InstallIn(SingletonComponent.class) + * interface FooEntryPoint { + * Foo getFoo(); + * } + * </code></pre> + * + * <p>Then, replace any of the corresponding usages of {@link dagger.hilt.EntryPoints} with + * {@link EarlyEntryPoints}, as shown below: + * + * <pre><code> + * // EarlyEntryPoints.get() must be used with entry points annotated with @EarlyEntryPoint + * // This entry point can now be called at any point during a test, e.g. in Application.onCreate(). + * Foo foo = EarlyEntryPoints.get(appContext, FooEntryPoint.class).getFoo(); + * </code></pre> + */ +@Beta +@Retention(RUNTIME) // Needs to be runtime for checks in EntryPoints and EarlyEntryPoints. +@Target(ElementType.TYPE) +public @interface EarlyEntryPoint {} diff --git a/java/dagger/hilt/android/EarlyEntryPoints.java b/java/dagger/hilt/android/EarlyEntryPoints.java new file mode 100644 index 000000000..e71401995 --- /dev/null +++ b/java/dagger/hilt/android/EarlyEntryPoints.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import android.content.Context; +import dagger.hilt.EntryPoints; +import dagger.hilt.internal.GeneratedComponentManagerHolder; +import dagger.hilt.internal.Preconditions; +import dagger.hilt.internal.TestSingletonComponentManager; +import dagger.internal.Beta; +import java.lang.annotation.Annotation; +import javax.annotation.Nonnull; + +/** Static utility methods for accessing entry points annotated with {@link EarlyEntryPoint}. */ +@Beta +public final class EarlyEntryPoints { + + /** + * Returns the early entry point interface given a component manager holder. Note that this + * performs an unsafe cast and so callers should be sure that the given component/component + * manager matches the early entry point interface that is given. + * + * @param applicationContext The application context. + * @param entryPoint The interface marked with {@link EarlyEntryPoint}. The {@link + * dagger.hilt.InstallIn} annotation on this entry point should match the component argument + * above. + */ + // Note that the input is not statically declared to be a Component or ComponentManager to make + // this method easier to use, since most code will use this with an Application or Context type. + @Nonnull + public static <T> T get(Context applicationContext, Class<T> entryPoint) { + applicationContext = applicationContext.getApplicationContext(); + Preconditions.checkState( + applicationContext instanceof GeneratedComponentManagerHolder, + "Expected application context to implement GeneratedComponentManagerHolder. " + + "Check that you're passing in an application context that uses Hilt."); + Object componentManager = + ((GeneratedComponentManagerHolder) applicationContext).componentManager(); + if (componentManager instanceof TestSingletonComponentManager) { + Preconditions.checkState( + hasAnnotationReflection(entryPoint, EarlyEntryPoint.class), + "%s should be called with EntryPoints.get() rather than EarlyEntryPoints.get()", + entryPoint.getCanonicalName()); + Object earlyComponent = + ((TestSingletonComponentManager) componentManager).earlySingletonComponent(); + return entryPoint.cast(earlyComponent); + } + + // @EarlyEntryPoint only has an effect in test environment, so if this is not a test we + // delegate to EntryPoints. + return EntryPoints.get(applicationContext, entryPoint); + } + + // Note: This method uses reflection but it should only be called in test environments. + private static boolean hasAnnotationReflection( + Class<?> clazz, Class<? extends Annotation> annotationClazz) { + for (Annotation annotation : clazz.getAnnotations()) { + if (annotation.annotationType().equals(annotationClazz)) { + return true; + } + } + return false; + } + + private EarlyEntryPoints() {} +} diff --git a/java/dagger/hilt/android/components/BUILD b/java/dagger/hilt/android/components/BUILD index 3a307d145..655ccd199 100644 --- a/java/dagger/hilt/android/components/BUILD +++ b/java/dagger/hilt/android/components/BUILD @@ -25,6 +25,7 @@ android_library( "FragmentComponent.java", "ServiceComponent.java", "ViewComponent.java", + "ViewModelComponent.java", "ViewWithFragmentComponent.java", ], exports = [ @@ -34,20 +35,7 @@ android_library( ":package_info", "//java/dagger/hilt:define_component", "//java/dagger/hilt/android/scopes", - "//java/dagger/hilt/android/scopes:activity_retained_scoped", "//java/dagger/hilt/components", - "@google_bazel_common//third_party/java/jsr330_inject", - ], -) - -android_library( - name = "view_model_component", - srcs = ["ViewModelComponent.java"], - deps = [ - ":components", - ":package_info", - "//java/dagger/hilt:define_component", - "//java/dagger/hilt/android/scopes:view_model_scoped", ], ) diff --git a/java/dagger/hilt/android/internal/builders/BUILD b/java/dagger/hilt/android/internal/builders/BUILD index 0e282be5f..9693f79c5 100644 --- a/java/dagger/hilt/android/internal/builders/BUILD +++ b/java/dagger/hilt/android/internal/builders/BUILD @@ -24,9 +24,10 @@ android_library( "//:dagger_with_compiler", "//java/dagger/hilt:define_component", "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/components:view_model_component", "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java b/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java new file mode 100644 index 000000000..124f831f3 --- /dev/null +++ b/java/dagger/hilt/android/internal/earlyentrypoint/AggregatedEarlyEntryPoint.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal.earlyentrypoint; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; + +/** Holds aggregated data about {@link dagger.hilt.android.EarlyEntryPoint} elements. */ +@Retention(CLASS) +public @interface AggregatedEarlyEntryPoint { + + /** Returns the entry point annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */ + String earlyEntryPoint(); +} diff --git a/java/dagger/hilt/android/internal/earlyentrypoint/BUILD b/java/dagger/hilt/android/internal/earlyentrypoint/BUILD new file mode 100644 index 000000000..ab1478afe --- /dev/null +++ b/java/dagger/hilt/android/internal/earlyentrypoint/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# Description: +# A processor that aggregates metadata about Hilt @EarlyEntryPoint annotations + +package(default_visibility = ["//:src"]) + +java_library( + name = "earlyentrypoint", + srcs = ["AggregatedEarlyEntryPoint.java"], +) diff --git a/java/dagger/hilt/android/internal/lifecycle/BUILD b/java/dagger/hilt/android/internal/lifecycle/BUILD index b3ecfc22e..17812c904 100644 --- a/java/dagger/hilt/android/internal/lifecycle/BUILD +++ b/java/dagger/hilt/android/internal/lifecycle/BUILD @@ -25,12 +25,12 @@ android_library( "//java/dagger/hilt:entry_point", "//java/dagger/hilt:install_in", "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/components:view_model_component", "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/qualifiers", "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", "@maven//:androidx_lifecycle_lifecycle_viewmodel", "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", "@maven//:androidx_savedstate_savedstate", diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java index 427822dbb..448847cd3 100644 --- a/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java +++ b/java/dagger/hilt/android/internal/lifecycle/DefaultViewModelFactories.java @@ -50,10 +50,11 @@ public final class DefaultViewModelFactories { * * <p>Do not use except in Hilt generated code! */ - public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity) { + public static ViewModelProvider.Factory getActivityFactory(ComponentActivity activity, + ViewModelProvider.Factory delegateFactory) { return EntryPoints.get(activity, ActivityEntryPoint.class) .getHiltInternalFactoryFactory() - .fromActivity(activity); + .fromActivity(activity, delegateFactory); } /** @@ -61,10 +62,11 @@ public final class DefaultViewModelFactories { * * <p>Do not use except in Hilt generated code! */ - public static ViewModelProvider.Factory getFragmentFactory(Fragment fragment) { + public static ViewModelProvider.Factory getFragmentFactory( + Fragment fragment, ViewModelProvider.Factory delegateFactory) { return EntryPoints.get(fragment, FragmentEntryPoint.class) .getHiltInternalFactoryFactory() - .fromFragment(fragment); + .fromFragment(fragment, delegateFactory); } /** Internal factory for the Hilt ViewModel Factory. */ @@ -73,33 +75,28 @@ public final class DefaultViewModelFactories { private final Application application; private final Set<String> keySet; private final ViewModelComponentBuilder viewModelComponentBuilder; - @Nullable private final ViewModelProvider.Factory defaultActivityFactory; - @Nullable private final ViewModelProvider.Factory defaultFragmentFactory; @Inject InternalFactoryFactory( Application application, @HiltViewModelMap.KeySet Set<String> keySet, - ViewModelComponentBuilder viewModelComponentBuilder, - // These default factory bindings are temporary for the transition of deprecating - // the Hilt ViewModel extension for the built-in support - @DefaultActivityViewModelFactory Set<ViewModelProvider.Factory> defaultActivityFactorySet, - @DefaultFragmentViewModelFactory Set<ViewModelProvider.Factory> defaultFragmentFactorySet) { + ViewModelComponentBuilder viewModelComponentBuilder) { this.application = application; this.keySet = keySet; this.viewModelComponentBuilder = viewModelComponentBuilder; - this.defaultActivityFactory = getFactoryFromSet(defaultActivityFactorySet); - this.defaultFragmentFactory = getFactoryFromSet(defaultFragmentFactorySet); } - ViewModelProvider.Factory fromActivity(ComponentActivity activity) { - return getHiltViewModelFactory(activity, + ViewModelProvider.Factory fromActivity( + ComponentActivity activity, ViewModelProvider.Factory delegateFactory) { + return getHiltViewModelFactory( + activity, activity.getIntent() != null ? activity.getIntent().getExtras() : null, - defaultActivityFactory); + delegateFactory); } - ViewModelProvider.Factory fromFragment(Fragment fragment) { - return getHiltViewModelFactory(fragment, fragment.getArguments(), defaultFragmentFactory); + ViewModelProvider.Factory fromFragment( + Fragment fragment, ViewModelProvider.Factory delegateFactory) { + return getHiltViewModelFactory(fragment, fragment.getArguments(), delegateFactory); } private ViewModelProvider.Factory getHiltViewModelFactory( @@ -112,24 +109,6 @@ public final class DefaultViewModelFactories { return new HiltViewModelFactory( owner, defaultArgs, keySet, delegate, viewModelComponentBuilder); } - - @Nullable - private static ViewModelProvider.Factory getFactoryFromSet(Set<ViewModelProvider.Factory> set) { - // A multibinding set is used instead of BindsOptionalOf because Optional is not available in - // Android until API 24 and we don't want to have Guava as a transitive dependency. - if (set.isEmpty()) { - return null; - } - if (set.size() > 1) { - throw new IllegalStateException( - "At most one default view model factory is expected. Found " + set); - } - ViewModelProvider.Factory factory = set.iterator().next(); - if (factory == null) { - throw new IllegalStateException("Default view model factory must not be null."); - } - return factory; - } } /** The activity module to declare the optional factories. */ @@ -139,14 +118,6 @@ public final class DefaultViewModelFactories { @Multibinds @HiltViewModelMap.KeySet abstract Set<String> viewModelKeys(); - - @Multibinds - @DefaultActivityViewModelFactory - Set<ViewModelProvider.Factory> defaultActivityViewModelFactory(); - - @Multibinds - @DefaultFragmentViewModelFactory - Set<ViewModelProvider.Factory> defaultFragmentViewModelFactory(); } /** The activity entry point to retrieve the factory. */ diff --git a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java index 3b0d3bc43..443894127 100644 --- a/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java +++ b/java/dagger/hilt/android/internal/lifecycle/HiltViewModelFactory.java @@ -16,6 +16,7 @@ package dagger.hilt.android.internal.lifecycle; +import android.app.Activity; import androidx.lifecycle.AbstractSavedStateViewModelFactory; import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModel; @@ -28,6 +29,7 @@ import dagger.Module; import dagger.hilt.EntryPoint; import dagger.hilt.EntryPoints; import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; import dagger.hilt.android.components.ViewModelComponent; import dagger.hilt.android.internal.builders.ViewModelComponentBuilder; import dagger.multibindings.Multibinds; @@ -109,4 +111,28 @@ public final class HiltViewModelFactory implements ViewModelProvider.Factory { return delegateFactory.create(modelClass); } } + + @EntryPoint + @InstallIn(ActivityComponent.class) + interface ActivityCreatorEntryPoint { + @HiltViewModelMap.KeySet + Set<String> getViewModelKeys(); + ViewModelComponentBuilder getViewModelComponentBuilder(); + } + + public static ViewModelProvider.Factory createInternal( + @NonNull Activity activity, + @NonNull SavedStateRegistryOwner owner, + @Nullable Bundle defaultArgs, + @NonNull ViewModelProvider.Factory delegateFactory) { + ActivityCreatorEntryPoint entryPoint = + EntryPoints.get(activity, ActivityCreatorEntryPoint.class); + return new HiltViewModelFactory( + owner, + defaultArgs, + entryPoint.getViewModelKeys(), + delegateFactory, + entryPoint.getViewModelComponentBuilder() + ); + } } diff --git a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java index 2d4a72bdd..0af3dcd11 100644 --- a/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ActivityRetainedComponentManager.java @@ -18,6 +18,8 @@ package dagger.hilt.android.internal.managers; import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; +import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.activity.ComponentActivity; @@ -82,23 +84,26 @@ final class ActivityRetainedComponentManager private final Object componentLock = new Object(); ActivityRetainedComponentManager(ComponentActivity activity) { - this.viewModelProvider = - new ViewModelProvider( - activity, - new ViewModelProvider.Factory() { - @NonNull - @Override - @SuppressWarnings("unchecked") - public <T extends ViewModel> T create(@NonNull Class<T> aClass) { - ActivityRetainedComponent component = - EntryPoints.get( - activity.getApplication(), - ActivityRetainedComponentBuilderEntryPoint.class) - .retainedComponentBuilder() - .build(); - return (T) new ActivityRetainedComponentViewModel(component); - } - }); + this.viewModelProvider = getViewModelProvider(activity, activity.getApplication()); + } + + private ViewModelProvider getViewModelProvider( + ViewModelStoreOwner owner, Context applicationContext) { + return new ViewModelProvider( + owner, + new ViewModelProvider.Factory() { + @NonNull + @Override + @SuppressWarnings("unchecked") + public <T extends ViewModel> T create(@NonNull Class<T> aClass) { + ActivityRetainedComponent component = + EntryPoints.get( + applicationContext, ActivityRetainedComponentBuilderEntryPoint.class) + .retainedComponentBuilder() + .build(); + return (T) new ActivityRetainedComponentViewModel(component); + } + }); } @Override diff --git a/java/dagger/hilt/android/internal/managers/BUILD b/java/dagger/hilt/android/internal/managers/BUILD index 3bc8df144..76613101e 100644 --- a/java/dagger/hilt/android/internal/managers/BUILD +++ b/java/dagger/hilt/android/internal/managers/BUILD @@ -40,17 +40,17 @@ android_library( "//java/dagger/hilt:install_in", "//java/dagger/hilt/android:activity_retained_lifecycle", "//java/dagger/hilt/android/components", - "//java/dagger/hilt/android/components:view_model_component", "//java/dagger/hilt/android/internal", "//java/dagger/hilt/android/internal/builders", - "//java/dagger/hilt/android/scopes:activity_retained_scoped", - "//java/dagger/hilt/android/scopes:view_model_scoped", + "//java/dagger/hilt/android/scopes", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java index cb2ece0c8..78614950a 100644 --- a/java/dagger/hilt/android/internal/managers/ViewComponentManager.java +++ b/java/dagger/hilt/android/internal/managers/ViewComponentManager.java @@ -16,6 +16,9 @@ package dagger.hilt.android.internal.managers; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; import android.content.Context; import android.content.ContextWrapper; import androidx.fragment.app.Fragment; @@ -104,7 +107,7 @@ public final class ViewComponentManager implements GeneratedComponentManager<Obj if (context instanceof FragmentContextWrapper) { FragmentContextWrapper fragmentContextWrapper = (FragmentContextWrapper) context; - return (GeneratedComponentManager<?>) fragmentContextWrapper.fragment; + return (GeneratedComponentManager<?>) fragmentContextWrapper.getFragment(); } else if (allowMissing) { // We didn't find anything, so return null if we're not supposed to fail. // The rest of the logic is just about getting a good error message. @@ -165,22 +168,41 @@ public final class ViewComponentManager implements GeneratedComponentManager<Obj * * <p>A wrapper class to expose the {@link Fragment} to the views they're inflating. */ - // This is only non-final for the account override public static final class FragmentContextWrapper extends ContextWrapper { + private Fragment fragment; private LayoutInflater baseInflater; private LayoutInflater inflater; - public final Fragment fragment; - - public FragmentContextWrapper(Context base, Fragment fragment) { + private final LifecycleEventObserver fragmentLifecycleObserver = + new LifecycleEventObserver() { + @Override + public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { + if (event == Lifecycle.Event.ON_DESTROY) { + // Prevent the fragment from leaking if the view outlives the fragment. + // See https://github.com/google/dagger/issues/2070 + FragmentContextWrapper.this.fragment = null; + FragmentContextWrapper.this.baseInflater = null; + FragmentContextWrapper.this.inflater = null; + } + } + }; + + FragmentContextWrapper(Context base, Fragment fragment) { super(Preconditions.checkNotNull(base)); this.baseInflater = null; this.fragment = Preconditions.checkNotNull(fragment); + this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver); } - public FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) { + FragmentContextWrapper(LayoutInflater baseInflater, Fragment fragment) { super(Preconditions.checkNotNull(Preconditions.checkNotNull(baseInflater).getContext())); this.baseInflater = baseInflater; this.fragment = Preconditions.checkNotNull(fragment); + this.fragment.getLifecycle().addObserver(fragmentLifecycleObserver); + } + + Fragment getFragment() { + Preconditions.checkNotNull(fragment, "The fragment has already been destroyed."); + return fragment; } @Override diff --git a/java/dagger/hilt/android/internal/modules/BUILD b/java/dagger/hilt/android/internal/modules/BUILD index a36e23746..c1b639dda 100644 --- a/java/dagger/hilt/android/internal/modules/BUILD +++ b/java/dagger/hilt/android/internal/modules/BUILD @@ -28,6 +28,9 @@ android_library( "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/java/dagger/hilt/android/internal/testing/BUILD b/java/dagger/hilt/android/internal/testing/BUILD index b0bc361c7..98a273d12 100644 --- a/java/dagger/hilt/android/internal/testing/BUILD +++ b/java/dagger/hilt/android/internal/testing/BUILD @@ -21,7 +21,6 @@ java_library( name = "test_injector", testonly = 1, srcs = [ - "TestApplicationInjector.java", "TestInjector.java", ], ) @@ -37,15 +36,23 @@ android_library( ) android_library( + name = "early_test_singleton_component_creator", + testonly = 1, + srcs = ["EarlySingletonComponentCreator.java"], +) + +android_library( name = "test_application_component_manager", testonly = 1, srcs = ["TestApplicationComponentManager.java"], deps = [ + ":early_test_singleton_component_creator", ":test_component_data", ":test_injector", "//java/dagger/hilt/android/testing:on_component_ready_runner", "//java/dagger/hilt/internal:component_manager", "//java/dagger/hilt/internal:preconditions", + "//java/dagger/hilt/internal:test_singleton_component_manager", "@maven//:junit_junit", ], ) @@ -67,6 +74,9 @@ android_library( name = "test_application_component_manager_holder", testonly = 1, srcs = ["TestApplicationComponentManagerHolder.java"], + deps = [ + "//java/dagger/hilt/internal:component_manager", + ], ) android_library( diff --git a/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java new file mode 100644 index 000000000..8e61e4aff --- /dev/null +++ b/java/dagger/hilt/android/internal/testing/EarlySingletonComponentCreator.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal.testing; + +import java.lang.reflect.InvocationTargetException; + +/** Creates a test's early component. */ +public abstract class EarlySingletonComponentCreator { + private static final String EARLY_SINGLETON_COMPONENT_CREATOR_IMPL = + "dagger.hilt.android.internal.testing.EarlySingletonComponentCreatorImpl"; + + static Object createComponent() { + try { + return Class.forName(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL) + .asSubclass(EarlySingletonComponentCreator.class) + .getDeclaredConstructor() + .newInstance() + .create(); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InstantiationException + | InvocationTargetException e) { + throw new RuntimeException( + "The EarlyComponent was requested but does not exist. Check that you have annotated " + + "your test class with @HiltAndroidTest and that the processor is running over your " + + "test.", + e); + } + } + + /** Creates the early test component. */ + abstract Object create(); +} diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java index 187887022..c2051170d 100644 --- a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java +++ b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManager.java @@ -21,7 +21,7 @@ import dagger.hilt.android.testing.OnComponentReadyRunner; import dagger.hilt.android.testing.OnComponentReadyRunner.OnComponentReadyRunnerHolder; import dagger.hilt.internal.GeneratedComponentManager; import dagger.hilt.internal.Preconditions; -import java.lang.reflect.InvocationTargetException; +import dagger.hilt.internal.TestSingletonComponentManager; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -35,17 +35,18 @@ import org.junit.runner.Description; * <p>A manager for the creation of components that live in the test Application. */ public final class TestApplicationComponentManager - implements GeneratedComponentManager<Object>, OnComponentReadyRunnerHolder { + implements TestSingletonComponentManager, OnComponentReadyRunnerHolder { - // This is a generated class that we always generate in a known location. - private static final String TEST_COMPONENT_DATA_SUPPLIER_IMPL = - "dagger.hilt.android.internal.testing.TestComponentDataSupplierImpl"; + private final Object earlyComponentLock = new Object(); + private volatile Object earlyComponent = null; - private final Application application; - private final Map<Class<?>, TestComponentData> testComponentDataSupplier; + private final Object testComponentDataLock = new Object(); + private volatile TestComponentData testComponentData; + private final Application application; private final AtomicReference<Object> component = new AtomicReference<>(); private final AtomicReference<Description> hasHiltTestRule = new AtomicReference<>(); + // TODO(bcorso): Consider using a lock here rather than ConcurrentHashMap to avoid b/37042460. private final Map<Class<?>, Object> registeredModules = new ConcurrentHashMap<>(); private final AtomicReference<Boolean> autoAddModuleEnabled = new AtomicReference<>(); private final AtomicReference<DelayedComponentState> delayedComponentState = @@ -75,24 +76,18 @@ public final class TestApplicationComponentManager public TestApplicationComponentManager(Application application) { this.application = application; - try { - this.testComponentDataSupplier = - Class.forName(TEST_COMPONENT_DATA_SUPPLIER_IMPL) - .asSubclass(TestComponentDataSupplier.class) - .getDeclaredConstructor() - .newInstance() - .get(); - } catch (ClassNotFoundException - | NoSuchMethodException - | IllegalAccessException - | InstantiationException - | InvocationTargetException e) { - throw new RuntimeException( - "Hilt classes generated from @HiltAndroidTest are missing. Check that you have annotated " - + "your test class with @HiltAndroidTest and that the processor is running over your " - + "test", - e); + } + + @Override + public Object earlySingletonComponent() { + if (earlyComponent == null) { + synchronized (earlyComponentLock) { + if (earlyComponent == null) { + earlyComponent = EarlySingletonComponentCreator.createComponent(); + } + } } + return earlyComponent; } @Override @@ -149,6 +144,9 @@ public final class TestApplicationComponentManager testInstance == null, "The Hilt BindValue instance cannot be set before Hilt's test rule has run."); Preconditions.checkState( + testComponentData == null, + "The testComponentData instance cannot be set before Hilt's test rule has run."); + Preconditions.checkState( registeredModules.isEmpty(), "The Hilt registered modules cannot be set before Hilt's test rule has run."); Preconditions.checkState( @@ -171,6 +169,7 @@ public final class TestApplicationComponentManager component.set(null); hasHiltTestRule.set(null); testInstance = null; + testComponentData = null; registeredModules.clear(); autoAddModuleEnabled.set(null); delayedComponentState.set(DelayedComponentState.NOT_DELAYED); @@ -308,7 +307,14 @@ public final class TestApplicationComponentManager } private TestComponentData testComponentData() { - return testComponentDataSupplier.get(testClass()); + if (testComponentData == null) { + synchronized (testComponentDataLock) { + if (testComponentData == null) { + testComponentData = TestComponentDataSupplier.get(testClass()); + } + } + } + return testComponentData; } private Class<?> testClass() { diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java index a8695c4fe..4be5829eb 100644 --- a/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java +++ b/java/dagger/hilt/android/internal/testing/TestApplicationComponentManagerHolder.java @@ -16,9 +16,8 @@ package dagger.hilt.android.internal.testing; +import dagger.hilt.internal.GeneratedComponentManagerHolder; + /** For use by Hilt internally only! Returns the component manager. */ -public interface TestApplicationComponentManagerHolder { - // Returns {@link Object} so that we do not expose {@code TestApplicationComponentManager} to - // clients. Framework code should explicitly cast to {@code TestApplicationComponentManager}. - Object componentManager(); -} +// TODO(bcorso):Consider deleting this interface and just using GeneratedComponentManagerHolder +public interface TestApplicationComponentManagerHolder extends GeneratedComponentManagerHolder {} diff --git a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java index e39073f9a..b6f8b0b45 100644 --- a/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java +++ b/java/dagger/hilt/android/internal/testing/TestComponentDataSupplier.java @@ -16,11 +16,50 @@ package dagger.hilt.android.internal.testing; -import java.util.Map; +import java.lang.reflect.InvocationTargetException; -/** Stores the {@link TestComponentData} for all Hilt test classes. */ +/** Stores the {@link TestComponentData} for a Hilt test class. */ public abstract class TestComponentDataSupplier { - /** Returns a map of {@link TestComponentData} keyed by test class. */ - protected abstract Map<Class<?>, TestComponentData> get(); + /** Returns a {@link TestComponentData}. */ + protected abstract TestComponentData get(); + + static TestComponentData get(Class<?> testClass) { + String generatedClassName = getEnclosedClassName(testClass) + "_TestComponentDataSupplier"; + try { + return Class.forName(generatedClassName) + .asSubclass(TestComponentDataSupplier.class) + .getDeclaredConstructor() + .newInstance() + .get(); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InstantiationException + | InvocationTargetException e) { + throw new RuntimeException( + String.format( + "Hilt test, %s, is missing generated file: %s. Check that the test class is " + + " annotated with @HiltAndroidTest and that the processor is running over your" + + " test.", + testClass.getSimpleName(), + generatedClassName), + e); + } + } + + private static String getEnclosedClassName(Class<?> testClass) { + StringBuilder sb = new StringBuilder(); + Class<?> currClass = testClass; + while (currClass != null) { + Class<?> enclosingClass = currClass.getEnclosingClass(); + if (enclosingClass != null) { + sb.insert(0, "_" + currClass.getSimpleName()); + } else { + sb.insert(0, currClass.getCanonicalName()); + } + currClass = enclosingClass; + } + return sb.toString(); + } } diff --git a/java/dagger/hilt/android/internal/testing/root/BUILD b/java/dagger/hilt/android/internal/testing/root/BUILD new file mode 100644 index 000000000..96b161e32 --- /dev/null +++ b/java/dagger/hilt/android/internal/testing/root/BUILD @@ -0,0 +1,23 @@ +# Copyright (C) 2021 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. + +# Description: +# Internal Hilt android testing root + +package(default_visibility = ["//:src"]) + +android_library( + name = "default", + srcs = ["Default.java"], +) diff --git a/java/dagger/hilt/android/internal/testing/root/Default.java b/java/dagger/hilt/android/internal/testing/root/Default.java new file mode 100644 index 000000000..0aa1b16bc --- /dev/null +++ b/java/dagger/hilt/android/internal/testing/root/Default.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal.testing.root; + +/** + * This is internal code. Do not depend on this class directly. + * + * <p>This is a "default" root (used only in tests) that generates a component without any test + * specific dependencies. + */ +final class Default {} diff --git a/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java b/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java new file mode 100644 index 000000000..0ab1f316f --- /dev/null +++ b/java/dagger/hilt/android/internal/uninstallmodules/AggregatedUninstallModules.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal.uninstallmodules; + +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; + +/** Holds aggregated data about {@link dagger.hilt.android.testing.UninstallModules} elements. */ +@Retention(CLASS) +public @interface AggregatedUninstallModules { + + /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */ + String test(); + + /** Returns the list of modules to uninstall. */ + String[] uninstallModules(); +} diff --git a/java/dagger/hilt/android/internal/uninstallmodules/BUILD b/java/dagger/hilt/android/internal/uninstallmodules/BUILD new file mode 100644 index 000000000..583964b53 --- /dev/null +++ b/java/dagger/hilt/android/internal/uninstallmodules/BUILD @@ -0,0 +1,24 @@ +# Copyright (C) 2021 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. + +# Description: +# A processor that aggregates metadata about Hilt @UninstallModules annotations + +package(default_visibility = ["//:src"]) + +java_library( + name = "uninstallmodules", + testonly = 1, + srcs = ["AggregatedUninstallModules.java"], +) diff --git a/java/dagger/hilt/android/lifecycle/BUILD b/java/dagger/hilt/android/lifecycle/BUILD index fd805102c..25b3e5e53 100644 --- a/java/dagger/hilt/android/lifecycle/BUILD +++ b/java/dagger/hilt/android/lifecycle/BUILD @@ -25,7 +25,7 @@ android_library( ], proguard_specs = ["proguard-rules.pro"], exports = [ - "//java/dagger/hilt/android/components:view_model_component", + "//java/dagger/hilt/android/components", "//java/dagger/hilt/android/internal/lifecycle", ], deps = [ diff --git a/java/dagger/hilt/android/migration/BUILD b/java/dagger/hilt/android/migration/BUILD index ade47c37f..a42f0ac65 100644 --- a/java/dagger/hilt/android/migration/BUILD +++ b/java/dagger/hilt/android/migration/BUILD @@ -33,6 +33,9 @@ android_library( "@maven//:androidx_activity_activity", "@maven//:androidx_annotation_annotation", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle index 274ecbead..480c6f0a4 100644 --- a/java/dagger/hilt/android/plugin/build.gradle +++ b/java/dagger/hilt/android/plugin/build.gradle @@ -17,6 +17,7 @@ buildscript { repositories { google() + mavenCentral() jcenter() } } @@ -29,6 +30,7 @@ plugins { repositories { google() + mavenCentral() jcenter() } diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt index e066f48a1..3bbf1e190 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt @@ -81,6 +81,7 @@ internal class AndroidEntryPointClassTransformer( if (entry.isClassFile()) { val clazz = classPool.makeClass(input, false) transformed = transformClassToOutput(clazz) || transformed + clazz.detach() } entry = input.nextEntry } @@ -99,7 +100,9 @@ internal class AndroidEntryPointClassTransformer( "Invalid file, '$inputFile' is not a class." } val clazz = inputFile.inputStream().use { classPool.makeClass(it, false) } - return transformClassToOutput(clazz) + val transformed = transformClassToOutput(clazz) + clazz.detach() + return transformed } private fun transformClassToOutput(clazz: CtClass): Boolean { diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt index e26edb5fd..a82826ed8 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt @@ -32,10 +32,12 @@ import com.android.build.gradle.api.UnitTestVariant import dagger.hilt.android.plugin.util.CopyTransform import dagger.hilt.android.plugin.util.SimpleAGPVersion import java.io.File +import javax.inject.Inject import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.attributes.Attribute +import org.gradle.api.provider.ProviderFactory /** * A Gradle plugin that checks if the project is an Android project and if so, registers a @@ -45,7 +47,9 @@ import org.gradle.api.attributes.Attribute * classes annotated with `@AndroidEntryPoint` since the registered transform by this plugin will * update the superclass. */ -class HiltGradlePlugin : Plugin<Project> { +class HiltGradlePlugin @Inject constructor( + val providers: ProviderFactory +) : Plugin<Project> { override fun apply(project: Project) { var configured = false project.plugins.withType(AndroidBasePlugin::class.java) { @@ -127,6 +131,7 @@ class HiltGradlePlugin : Plugin<Project> { } } + @Suppress("UnstableApiUsage") private fun configureVariantCompileClasspath( project: Project, hiltExtension: HiltExtension, @@ -160,7 +165,7 @@ class HiltGradlePlugin : Plugin<Project> { "android.injected.build.model.only.versioned", // Sent by AS 2.4+ "android.injected.build.model.feature.full.dependencies", // Sent by AS 2.4+ "android.injected.build.model.v2", // Sent by AS 4.2+ - ).any { project.properties.containsKey(it) } + ).any { providers.gradleProperty(it).forUseAtConfigurationTime().isPresent } ) { // Do not configure compile classpath when AndroidStudio is building the model (syncing) // otherwise it will cause a freeze. diff --git a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt index 158043124..339c83e1a 100644 --- a/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt +++ b/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt @@ -1,7 +1,5 @@ package dagger.hilt.android.plugin.util -import com.android.Version - /** * Simple Android Gradle Plugin version class since there is no public API one. b/175816217 */ @@ -20,7 +18,18 @@ internal data class SimpleAGPVersion( companion object { - val ANDROID_GRADLE_PLUGIN_VERSION by lazy { parse(Version.ANDROID_GRADLE_PLUGIN_VERSION) } + // TODO(danysantiago): Migrate to AndroidPluginVersion once it is available (b/175816217) + val ANDROID_GRADLE_PLUGIN_VERSION by lazy { + val clazz = + findClass("com.android.Version") + ?: findClass("com.android.builder.model.Version") + if (clazz != null) { + return@lazy parse(clazz.getField("ANDROID_GRADLE_PLUGIN_VERSION").get(null) as String) + } + error( + "Unable to obtain AGP version. It is likely that the AGP version being used is too old." + ) + } fun parse(version: String?) = tryParse(version) ?: error("Unable to parse AGP version: $version") @@ -37,5 +46,11 @@ internal data class SimpleAGPVersion( return SimpleAGPVersion(parts[0].toInt(), parts[1].toInt()) } + + private fun findClass(fqName: String) = try { + Class.forName(fqName) + } catch (ex: ClassNotFoundException) { + null + } } } diff --git a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt index a003ab516..cab50aab0 100644 --- a/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt +++ b/java/dagger/hilt/android/plugin/src/test/kotlin/IncrementalProcessorTest.kt @@ -157,28 +157,22 @@ class IncrementalProcessorTest { genActivityInjector2 = File(projectRoot, "$GEN_SRC_DIR/simple/Activity2_GeneratedInjector.java") genAppInjectorDeps = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/simple_SimpleApp_GeneratedInjectorModuleDeps.java" + "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.java" ) genActivityInjectorDeps1 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Activity1_GeneratedInjectorModuleDeps.java" + "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.java" ) genActivityInjectorDeps2 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Activity2_GeneratedInjectorModuleDeps.java" + "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.java" ) genModuleDeps1 = File( projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Module1ModuleDeps.java" - ) - genModuleDeps2 = File( - projectRoot, - "$GEN_SRC_DIR/hilt_aggregated_deps/simple_Module2ModuleDeps.java" - ) - genHiltComponents = File( - projectRoot, - "$GEN_SRC_DIR/simple/SimpleApp_HiltComponents.java" + "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module1.java" ) + genModuleDeps2 = File(projectRoot, "$GEN_SRC_DIR/hilt_aggregated_deps/_simple_Module2.java") + genHiltComponents = File(projectRoot, "$GEN_SRC_DIR/simple/SimpleApp_HiltComponents.java") genDaggerHiltApplicationComponent = File( projectRoot, "$GEN_SRC_DIR/simple/DaggerSimpleApp_HiltComponents_SingletonC.java" @@ -203,24 +197,18 @@ class IncrementalProcessorTest { ) classGenAppInjectorDeps = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/simple_SimpleApp_GeneratedInjectorModuleDeps.class" + "$CLASS_DIR/hilt_aggregated_deps/_simple_SimpleApp_GeneratedInjector.class" ) classGenActivityInjectorDeps1 = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/simple_Activity1_GeneratedInjectorModuleDeps.class" + "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity1_GeneratedInjector.class" ) classGenActivityInjectorDeps2 = File( projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/simple_Activity2_GeneratedInjectorModuleDeps.class" - ) - classGenModuleDeps1 = File( - projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/simple_Module1ModuleDeps.class" - ) - classGenModuleDeps2 = File( - projectRoot, - "$CLASS_DIR/hilt_aggregated_deps/simple_Module2ModuleDeps.class" + "$CLASS_DIR/hilt_aggregated_deps/_simple_Activity2_GeneratedInjector.class" ) + classGenModuleDeps1 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module1.class") + classGenModuleDeps2 = File(projectRoot, "$CLASS_DIR/hilt_aggregated_deps/_simple_Module2.class") classGenHiltComponents = File( projectRoot, "$CLASS_DIR/simple/SimpleApp_HiltComponents.class" diff --git a/java/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/processor/BUILD index e116c4443..ccf21033d 100644 --- a/java/dagger/hilt/android/processor/BUILD +++ b/java/dagger/hilt/android/processor/BUILD @@ -15,14 +15,14 @@ # Description: # Hilt android processors. -load("//:build_defs.bzl", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) java_library( name = "artifact-lib", - tags = ["maven_coordinates=com.google.dagger:hilt-android-compiler:" + POM_VERSION_ALPHA], + tags = ["maven_coordinates=com.google.dagger:hilt-android-compiler:" + POM_VERSION], visibility = ["//visibility:private"], exports = [ "//java/dagger/hilt/processor:artifact-lib-shared", @@ -31,23 +31,23 @@ java_library( gen_maven_artifact( name = "artifact", - artifact_coordinates = "com.google.dagger:hilt-android-compiler:" + POM_VERSION_ALPHA, + artifact_coordinates = "com.google.dagger:hilt-android-compiler:" + POM_VERSION, artifact_name = "Hilt Android Processor", artifact_target = ":artifact-lib", artifact_target_libs = [ "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/android/processor/internal:utils", "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators", - "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options", "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata", "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib", "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib", "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib", - "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:components", @@ -55,17 +55,22 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", + "//java/dagger/hilt/processor/internal/aggregateddeps:pkg_private_metadata", "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", "//java/dagger/hilt/processor/internal/aliasof:alias_ofs", "//java/dagger/hilt/processor/internal/aliasof:processor_lib", "//java/dagger/hilt/processor/internal/definecomponent:define_components", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", + "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", + "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", "//java/dagger/hilt/processor/internal/root:processor_lib", "//java/dagger/hilt/processor/internal/root:root_metadata", "//java/dagger/hilt/processor/internal/root:root_type", + "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", + "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", ], artifact_target_maven_deps = [ "com.google.auto:auto-common", diff --git a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java index 915ae519f..d37092003 100644 --- a/java/dagger/hilt/android/processor/internal/AndroidClassNames.java +++ b/java/dagger/hilt/android/processor/internal/AndroidClassNames.java @@ -113,5 +113,10 @@ public final class AndroidClassNames { public static final ClassName SAVED_STATE_HANDLE = get("androidx.lifecycle", "SavedStateHandle"); + public static final ClassName ON_CONTEXT_AVAILABLE_LISTENER = + get("androidx.activity.contextaware", "OnContextAvailableListener"); + public static final ClassName INJECT_VIA_ON_CONTEXT_AVAILABLE_LISTENER = + get("dagger.hilt.android", "InjectViaOnContextAvailableListener"); + private AndroidClassNames() {} } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java index ce9ad14c5..86fbaa712 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java @@ -17,9 +17,9 @@ package dagger.hilt.android.processor.internal.androidentrypoint; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.hilt.android.processor.internal.AndroidClassNames; @@ -30,7 +30,6 @@ import javax.lang.model.element.Modifier; /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */ public final class ActivityGenerator { - private final ProcessingEnvironment env; private final AndroidEntryPointMetadata metadata; private final ClassName generatedClassName; @@ -56,9 +55,11 @@ public final class ActivityGenerator { Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); Processors.addGeneratedAnnotation(builder, env, getClass()); - Generators.copyConstructors(metadata.baseElement(), builder); - builder.addMethod(onCreate()); - + Generators.copyConstructors( + metadata.baseElement(), + CodeBlock.builder().addStatement("_initHiltInternal()").build(), + builder); + builder.addMethod(init()); metadata.baseElement().getTypeParameters().stream() .map(TypeVariableName::get) @@ -79,29 +80,36 @@ public final class ActivityGenerator { .writeTo(env.getFiler()); } - // @CallSuper - // @Override - // protected void onCreate(@Nullable Bundle savedInstanceState) { - // inject(); - // super.onCreate(savedInstanceState); + // private void init() { + // addOnContextAvailableListener(new OnContextAvailableListener() { + // @Override + // public void onContextAvailable(Context context) { + // inject(); + // } + // }); // } - private MethodSpec onCreate() { - return MethodSpec.methodBuilder("onCreate") - .addAnnotation(AndroidClassNames.CALL_SUPER) - .addAnnotation(Override.class) - .addModifiers(Modifier.PROTECTED) - .addParameter( - ParameterSpec.builder(AndroidClassNames.BUNDLE, "savedInstanceState") - .addAnnotation(AndroidClassNames.NULLABLE) + private MethodSpec init() { + return MethodSpec.methodBuilder("_initHiltInternal") + .addModifiers(Modifier.PRIVATE) + .addStatement( + "addOnContextAvailableListener($L)", + TypeSpec.anonymousClassBuilder("") + .addSuperinterface(AndroidClassNames.ON_CONTEXT_AVAILABLE_LISTENER) + .addMethod( + MethodSpec.methodBuilder("onContextAvailable") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addParameter(AndroidClassNames.CONTEXT, "context") + .addStatement("inject()") + .build()) .build()) - .addStatement("inject()") - .addStatement("super.onCreate(savedInstanceState)") .build(); } // @Override // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { - // return DefaultViewModelFactories.getActivityFactory(this); + // return DefaultViewModelFactories.getActivityFactory( + // this, super.getDefaultViewModelProviderFactory()); // } private MethodSpec getDefaultViewModelProviderFactory() { return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") @@ -109,7 +117,7 @@ public final class ActivityGenerator { .addModifiers(Modifier.PUBLIC) .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY) .addStatement( - "return $T.getActivityFactory(this)", + "return $T.getActivityFactory(this, super.getDefaultViewModelProviderFactory())", AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) .build(); } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java index e5868f861..c94f6a977 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java @@ -16,7 +16,7 @@ package dagger.hilt.android.processor.internal.androidentrypoint; -import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_ANDROID_SUPERCLASS_VALIDATION; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperclassValidationDisabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.common.MoreElements; @@ -245,7 +245,7 @@ public abstract class AndroidEntryPointMetadata { final TypeElement baseElement; final ClassName generatedClassName; boolean requiresBytecodeInjection = - DISABLE_ANDROID_SUPERCLASS_VALIDATION.get(env) + isAndroidSuperclassValidationDisabled(androidEntryPointElement, env) && MoreTypes.isTypeOf(Void.class, androidEntryPointClassValue.asType()); if (requiresBytecodeInjection) { baseElement = MoreElements.asType(env.getTypeUtils().asElement(androidEntryPointElement.getSuperclass())); diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java index 7bb9b9afe..cd3290b48 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java @@ -44,11 +44,6 @@ public final class AndroidEntryPointProcessor extends BaseProcessor { } @Override - public Set<String> getSupportedOptions() { - return HiltCompilerOptions.getProcessorOptions(); - } - - @Override public boolean delayErrors() { return true; } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java index f16e06dee..8d63cdbea 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java @@ -38,11 +38,13 @@ public final class ApplicationGenerator { private final ProcessingEnvironment env; private final AndroidEntryPointMetadata metadata; private final ClassName wrapperClassName; + private final ComponentNames componentNames; public ApplicationGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) { this.env = env; this.metadata = metadata; - wrapperClassName = metadata.generatedClassName(); + this.wrapperClassName = metadata.generatedClassName(); + this.componentNames = ComponentNames.withoutRenaming(); } // @Generated("ApplicationGenerator") @@ -107,7 +109,7 @@ public final class ApplicationGenerator { // } private TypeSpec creatorType() { ClassName component = - ComponentNames.generatedComponent( + componentNames.generatedComponent( metadata.elementClassName(), AndroidClassNames.SINGLETON_COMPONENT); return TypeSpec.anonymousClassBuilder("") .addSuperinterface(AndroidClassNames.COMPONENT_SUPPLIER) diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD index 55e9ddc02..efbf9e47a 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD @@ -35,11 +35,9 @@ java_library( srcs = ["AndroidEntryPointProcessor.java"], deps = [ ":android_generators", - ":compiler_options", ":metadata", "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:base_processor", - "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/auto:service", "@google_bazel_common//third_party/java/incap", @@ -81,9 +79,9 @@ java_library( "AndroidEntryPointMetadata.java", ], deps = [ - ":compiler_options", "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:components", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processor_errors", @@ -98,11 +96,6 @@ java_library( ], ) -java_library( - name = "compiler_options", - srcs = ["HiltCompilerOptions.java"], -) - filegroup( name = "srcs_filegroup", srcs = glob(["*"]), diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java index 4ef479f46..81b2b6156 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java @@ -202,7 +202,8 @@ public final class FragmentGenerator { // @Override // public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { - // return DefaultViewModelFactories.getFragmentFactory(this); + // return DefaultViewModelFactories.getFragmentFactory( + // this, super.getDefaultViewModelProviderFactory()); // } private MethodSpec getDefaultViewModelProviderFactory() { return MethodSpec.methodBuilder("getDefaultViewModelProviderFactory") @@ -210,7 +211,7 @@ public final class FragmentGenerator { .addModifiers(Modifier.PUBLIC) .returns(AndroidClassNames.VIEW_MODEL_PROVIDER_FACTORY) .addStatement( - "return $T.getFragmentFactory(this)", + "return $T.getFragmentFactory(this, super.getDefaultViewModelProviderFactory())", AndroidClassNames.DEFAULT_VIEW_MODEL_FACTORIES) .build(); } diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java index daadd032d..91df537c9 100644 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java +++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java @@ -319,6 +319,12 @@ final class Generators { .endControlFlow(); } + // Only add @Override if an ancestor extends a generated Hilt class. + // When using bytecode injection, this isn't always guaranteed. + if (metadata.overridesAndroidEntryPointClass() + && ancestorExtendsGeneratedHiltClass(metadata)) { + methodSpecBuilder.addAnnotation(Override.class); + } typeSpecBuilder.addField(injectedField(metadata)); switch (metadata.androidType()) { @@ -326,12 +332,6 @@ final class Generators { case FRAGMENT: case VIEW: case SERVICE: - // Only add @Override if an ancestor extends a generated Hilt class. - // When using bytecode injection, this isn't always guaranteed. - if (metadata.overridesAndroidEntryPointClass() - && ancestorExtendsGeneratedHiltClass(metadata)) { - methodSpecBuilder.addAnnotation(Override.class); - } methodSpecBuilder .beginControlFlow("if (!injected)") .addStatement("injected = true") diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java deleted file mode 100644 index 577410d81..000000000 --- a/java/dagger/hilt/android/processor/internal/androidentrypoint/HiltCompilerOptions.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.android.processor.internal.androidentrypoint; - -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.processing.ProcessingEnvironment; - -/** Hilt annotation processor options. */ -// TODO(danysantiago): Consider consolidating with Dagger compiler options logic. -// TODO(user): Move this class to dagger/hilt/processor/internal -public final class HiltCompilerOptions { - - /** Processor options which can have true or false values. */ - public enum BooleanOption { - /** - * Flag that disables validating the superclass of @AndroidEntryPoint are Hilt_ generated, - * classes. This flag is to be used internally by the Gradle plugin, enabling the bytecode - * transformation to change the superclass. - */ - DISABLE_ANDROID_SUPERCLASS_VALIDATION( - "android.internal.disableAndroidSuperclassValidation", false), - - /** Flag that disables check on modules to be annotated with @InstallIn. */ - DISABLE_MODULES_HAVE_INSTALL_IN_CHECK("disableModulesHaveInstallInCheck", false); - - private final String name; - private final boolean defaultValue; - - BooleanOption(String name, boolean defaultValue) { - this.name = name; - this.defaultValue = defaultValue; - } - - public boolean get(ProcessingEnvironment env) { - String value = env.getOptions().get(getQualifiedName()); - if (value == null) { - return defaultValue; - } - // TODO(danysantiago): Strictly verify input, either 'true' or 'false' and nothing else. - return Boolean.parseBoolean(value); - } - - public String getQualifiedName() { - return "dagger.hilt." + name; - } - } - - public static Set<String> getProcessorOptions() { - return Arrays.stream(BooleanOption.values()) - .map(BooleanOption::getQualifiedName) - .collect(Collectors.toSet()); - } - - private HiltCompilerOptions() {} -} diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java index 51b7ef466..4f7f1bd6f 100644 --- a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java +++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java @@ -109,7 +109,7 @@ final class CustomTestApplicationGenerator { return MethodSpec.methodBuilder("componentManager") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .returns(TypeName.OBJECT) + .returns(ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT)) .addStatement("return $N", COMPONENT_MANAGER) .build(); } diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt index 846f7d261..1bc2e93bf 100644 --- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt +++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt @@ -93,6 +93,11 @@ internal class ViewModelModuleGenerator( component = AndroidClassNames.VIEW_MODEL_COMPONENT ) .addModifiers(Modifier.ABSTRACT) + .addMethod( + MethodSpec.constructorBuilder() + .addModifiers(Modifier.PRIVATE) + .build() + ) .addMethod(getViewModelBindsMethod()) .build() diff --git a/java/dagger/hilt/android/proguard-rules.pro b/java/dagger/hilt/android/proguard-rules.pro new file mode 100644 index 000000000..6fd3a82ea --- /dev/null +++ b/java/dagger/hilt/android/proguard-rules.pro @@ -0,0 +1,3 @@ +# Keep for the reflective cast done in EntryPoints. +# See b/183070411#comment4 for more info. +-keep,allowobfuscation,allowshrinking @dagger.hilt.android.EarlyEntryPoint class *
\ No newline at end of file diff --git a/java/dagger/hilt/android/scopes/BUILD b/java/dagger/hilt/android/scopes/BUILD index e74ac9e38..5abc27e09 100644 --- a/java/dagger/hilt/android/scopes/BUILD +++ b/java/dagger/hilt/android/scopes/BUILD @@ -20,9 +20,11 @@ package(default_visibility = ["//:src"]) android_library( name = "scopes", srcs = [ + "ActivityRetainedScoped.java", "ActivityScoped.java", "FragmentScoped.java", "ServiceScoped.java", + "ViewModelScoped.java", "ViewScoped.java", ], deps = [ @@ -31,24 +33,6 @@ android_library( ], ) -android_library( - name = "activity_retained_scoped", - srcs = ["ActivityRetainedScoped.java"], - deps = [ - ":package_info", - "@google_bazel_common//third_party/java/jsr330_inject", - ], -) - -android_library( - name = "view_model_scoped", - srcs = ["ViewModelScoped.java"], - deps = [ - ":package_info", - "@google_bazel_common//third_party/java/jsr330_inject", - ], -) - java_library( name = "package_info", srcs = ["package-info.java"], diff --git a/java/dagger/hilt/android/testing/BUILD b/java/dagger/hilt/android/testing/BUILD index 47db287e5..93d4ceb94 100644 --- a/java/dagger/hilt/android/testing/BUILD +++ b/java/dagger/hilt/android/testing/BUILD @@ -14,7 +14,7 @@ # Description: # Testing libraries for Hilt Android. -load("//:build_defs.bzl", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -57,14 +57,21 @@ android_library( "//java/dagger/hilt/android/internal/builders", "//java/dagger/hilt/android/internal/managers", "//java/dagger/hilt/android/internal/modules", + "//java/dagger/hilt/android/internal/testing:early_test_singleton_component_creator", "//java/dagger/hilt/android/internal/testing:test_application_component_manager", "//java/dagger/hilt/android/internal/testing:test_application_component_manager_holder", + "//java/dagger/hilt/android/internal/testing:test_component_data", "//java/dagger/hilt/android/internal/testing:test_injector", + "//java/dagger/hilt/android/internal/testing/root:default", "//java/dagger/hilt/android/scopes", "//java/dagger/hilt/internal:component_entry_point", "//java/dagger/hilt/internal:component_manager", + "//java/dagger/hilt/internal:generated_component", "//java/dagger/hilt/internal:generated_entry_point", "//java/dagger/hilt/internal:preconditions", + "//java/dagger/hilt/internal:test_singleton_component", + "//java/dagger/hilt/internal/aggregatedroot", + "//java/dagger/hilt/internal/processedrootsentinel", "//java/dagger/hilt/migration:disable_install_in_check", "@maven//:androidx_annotation_annotation", "@maven//:androidx_multidex_multidex", @@ -123,10 +130,14 @@ android_library( testonly = 1, srcs = ["UninstallModules.java"], exported_plugins = [ - "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor", + "//java/dagger/hilt/processor/internal/uninstallmodules:processor", + ], + exports = [ + "//java/dagger/hilt/android/internal/uninstallmodules", ], deps = [ ":package_info", + "//java/dagger/hilt:generates_root_input", ], ) @@ -163,7 +174,7 @@ java_library( android_library( name = "artifact-lib", testonly = 1, - tags = ["maven_coordinates=com.google.dagger:hilt-android-testing:" + POM_VERSION_ALPHA], + tags = ["maven_coordinates=com.google.dagger:hilt-android-testing:" + POM_VERSION], exports = [ ":bind_value", ":custom_test_application", @@ -178,15 +189,18 @@ android_library( gen_maven_artifact( name = "artifact", testonly = 1, - artifact_coordinates = "com.google.dagger:hilt-android-testing:" + POM_VERSION_ALPHA, + artifact_coordinates = "com.google.dagger:hilt-android-testing:" + POM_VERSION, artifact_name = "Hilt Android Testing", artifact_target = ":artifact-lib", artifact_target_libs = [ + "//java/dagger/hilt/android/internal/testing:early_test_singleton_component_creator", "//java/dagger/hilt/android/internal/testing:mark_that_rules_ran_rule", "//java/dagger/hilt/android/internal/testing:test_application_component_manager", "//java/dagger/hilt/android/internal/testing:test_application_component_manager_holder", "//java/dagger/hilt/android/internal/testing:test_component_data", "//java/dagger/hilt/android/internal/testing:test_injector", + "//java/dagger/hilt/android/internal/testing/root:default", + "//java/dagger/hilt/android/internal/uninstallmodules:uninstallmodules", "//java/dagger/hilt/android/testing:bind_value", "//java/dagger/hilt/android/testing:custom_test_application", "//java/dagger/hilt/android/testing:hilt_android_rule", @@ -202,6 +216,7 @@ gen_maven_artifact( "androidx.activity:activity", "androidx.annotation:annotation", "androidx.fragment:fragment", + "androidx.lifecycle:lifecycle-common", "androidx.lifecycle:lifecycle-viewmodel", "androidx.lifecycle:lifecycle-viewmodel-savedstate", "androidx.multidex:multidex", diff --git a/java/dagger/hilt/android/testing/HiltTestApplication.java b/java/dagger/hilt/android/testing/HiltTestApplication.java index 97eb4cbb2..293bfda4a 100644 --- a/java/dagger/hilt/android/testing/HiltTestApplication.java +++ b/java/dagger/hilt/android/testing/HiltTestApplication.java @@ -40,7 +40,7 @@ public final class HiltTestApplication extends MultiDexApplication } @Override - public final Object componentManager() { + public final GeneratedComponentManager<Object> componentManager() { return componentManager; } diff --git a/java/dagger/hilt/android/testing/UninstallModules.java b/java/dagger/hilt/android/testing/UninstallModules.java index 6480c10f5..607d76a68 100644 --- a/java/dagger/hilt/android/testing/UninstallModules.java +++ b/java/dagger/hilt/android/testing/UninstallModules.java @@ -16,6 +16,7 @@ package dagger.hilt.android.testing; +import dagger.hilt.GeneratesRootInput; import java.lang.annotation.ElementType; import java.lang.annotation.Target; @@ -43,6 +44,7 @@ import java.lang.annotation.Target; * } * </code></pre> */ +@GeneratesRootInput @Target({ElementType.TYPE}) public @interface UninstallModules { diff --git a/java/dagger/hilt/internal/BUILD b/java/dagger/hilt/internal/BUILD index dc245d1d7..ad4dbb5d4 100644 --- a/java/dagger/hilt/internal/BUILD +++ b/java/dagger/hilt/internal/BUILD @@ -18,10 +18,20 @@ package(default_visibility = ["//:src"]) java_library( + name = "test_singleton_component", + srcs = ["TestSingletonComponent.java"], + deps = [":generated_component"], +) + +java_library( name = "generated_component", - srcs = [ - "GeneratedComponent.java", - ], + srcs = ["GeneratedComponent.java"], +) + +java_library( + name = "test_singleton_component_manager", + srcs = ["TestSingletonComponentManager.java"], + deps = [":component_manager"], ) java_library( @@ -53,12 +63,14 @@ java_library( java_library( name = "component_entry_point", srcs = ["ComponentEntryPoint.java"], + proguard_specs = ["proguard-rules.pro"], deps = ["//java/dagger/hilt:generates_root_input"], ) java_library( name = "generated_entry_point", srcs = ["GeneratedEntryPoint.java"], + proguard_specs = ["proguard-rules.pro"], deps = ["//java/dagger/hilt:generates_root_input"], ) diff --git a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java b/java/dagger/hilt/internal/TestSingletonComponent.java index c7ff5c9aa..730b70bef 100644 --- a/java/dagger/hilt/android/internal/testing/TestApplicationInjector.java +++ b/java/dagger/hilt/internal/TestSingletonComponent.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,11 +14,7 @@ * limitations under the License. */ -package dagger.hilt.android.internal.testing; +package dagger.hilt.internal; -/** - * Interface to expose a method for members injection for use in tests. - */ -public interface TestApplicationInjector<T> { - void injectApp(T t); -} +/** A marker that the given component is a test {@code SingletonComponent}. */ +public interface TestSingletonComponent extends GeneratedComponent {} diff --git a/java/dagger/hilt/internal/TestSingletonComponentManager.java b/java/dagger/hilt/internal/TestSingletonComponentManager.java new file mode 100644 index 000000000..316a008cb --- /dev/null +++ b/java/dagger/hilt/internal/TestSingletonComponentManager.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 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.hilt.internal; + +/** A marker that the given component manager is for an {@link TestSingletonComponent}. */ +public interface TestSingletonComponentManager extends GeneratedComponentManager<Object> { + Object earlySingletonComponent(); +} diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java index 996705376..b53ee7258 100644 --- a/java/dagger/hilt/android/internal/lifecycle/DefaultFragmentViewModelFactory.java +++ b/java/dagger/hilt/internal/aggregatedroot/AggregatedRoot.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,16 +14,21 @@ * limitations under the License. */ -package dagger.hilt.android.internal.lifecycle; +package dagger.hilt.internal.aggregatedroot; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import javax.inject.Qualifier; -/** Qualifier for the default view model factory used by @AndroidEntryPoint annotated fragments. */ -@Qualifier +/** + * An annotation used to aggregate {@link dagger.hilt.android.HiltAndroidApp} and {@link + * dagger.hilt.android.testing.HiltAndroidTest} roots. + */ +@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) -public @interface DefaultFragmentViewModelFactory {} +public @interface AggregatedRoot { + String root(); + + Class<?> rootAnnotation(); +} diff --git a/java/dagger/hilt/internal/aggregatedroot/BUILD b/java/dagger/hilt/internal/aggregatedroot/BUILD new file mode 100644 index 000000000..0a7263a28 --- /dev/null +++ b/java/dagger/hilt/internal/aggregatedroot/BUILD @@ -0,0 +1,28 @@ +# Copyright (C) 2021 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. + +# Description: +# The annotation for aggregating information about Hilt roots. + +package(default_visibility = ["//:src"]) + +java_library( + name = "aggregatedroot", + srcs = ["AggregatedRoot.java"], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/internal/aliasof/BUILD b/java/dagger/hilt/internal/aliasof/BUILD index 3e96ed4d9..13d4364c3 100644 --- a/java/dagger/hilt/internal/aliasof/BUILD +++ b/java/dagger/hilt/internal/aliasof/BUILD @@ -17,7 +17,7 @@ package(default_visibility = ["//:src"]) -android_library( +java_library( name = "aliasof", srcs = ["AliasOfPropagatedData.java"], ) diff --git a/java/dagger/hilt/internal/processedrootsentinel/BUILD b/java/dagger/hilt/internal/processedrootsentinel/BUILD new file mode 100644 index 000000000..70b72a6ab --- /dev/null +++ b/java/dagger/hilt/internal/processedrootsentinel/BUILD @@ -0,0 +1,28 @@ +# Copyright (C) 2021 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. + +# Description: +# The annotation for aggregating information about processed Hilt roots. + +package(default_visibility = ["//:src"]) + +java_library( + name = "processedrootsentinel", + srcs = ["ProcessedRootSentinel.java"], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/android/internal/lifecycle/DefaultActivityViewModelFactory.java b/java/dagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel.java index becce8cdb..e5e0b1b08 100644 --- a/java/dagger/hilt/android/internal/lifecycle/DefaultActivityViewModelFactory.java +++ b/java/dagger/hilt/internal/processedrootsentinel/ProcessedRootSentinel.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Dagger Authors. + * Copyright (C) 2021 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. @@ -14,16 +14,17 @@ * limitations under the License. */ -package dagger.hilt.android.internal.lifecycle; +package dagger.hilt.internal.processedrootsentinel; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import javax.inject.Qualifier; -/** Qualifier for the default view model factory used by @AndroidEntryPoint annotated activities. */ -@Qualifier +/** An annotation used to aggregate sentinels for processed roots. */ +@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) -@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER}) -public @interface DefaultActivityViewModelFactory {} +public @interface ProcessedRootSentinel { + /** Returns the set of roots processed in a previous build. */ + String[] roots(); +} diff --git a/java/dagger/hilt/internal/proguard-rules.pro b/java/dagger/hilt/internal/proguard-rules.pro new file mode 100644 index 000000000..2607ba1f3 --- /dev/null +++ b/java/dagger/hilt/internal/proguard-rules.pro @@ -0,0 +1,4 @@ +# Keep for the reflective cast done in EntryPoints. +# See b/183070411#comment4 for more info. +-keep,allowobfuscation,allowshrinking @dagger.hilt.internal.ComponentEntryPoint class * +-keep,allowobfuscation,allowshrinking @dagger.hilt.internal.GeneratedEntryPoint class *
\ No newline at end of file diff --git a/java/dagger/hilt/processor/BUILD b/java/dagger/hilt/processor/BUILD index 87adcf34f..1f75d0a34 100644 --- a/java/dagger/hilt/processor/BUILD +++ b/java/dagger/hilt/processor/BUILD @@ -15,7 +15,7 @@ # Description: # Hilt android processors. -load("//:build_defs.bzl", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") load("//tools:maven.bzl", "gen_maven_artifact") package(default_visibility = ["//:src"]) @@ -27,22 +27,23 @@ java_library( "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib", "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib", "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib", - "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib", "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", "//java/dagger/hilt/processor/internal/aliasof:processor_lib", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", + "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/internal/codegen:processor", ], ) java_library( name = "artifact-lib", - tags = ["maven_coordinates=com.google.dagger:hilt-compiler:" + POM_VERSION_ALPHA], + tags = ["maven_coordinates=com.google.dagger:hilt-compiler:" + POM_VERSION], visibility = ["//visibility:private"], exports = [ ":artifact-lib-shared", @@ -51,23 +52,23 @@ java_library( gen_maven_artifact( name = "artifact", - artifact_coordinates = "com.google.dagger:hilt-compiler:" + POM_VERSION_ALPHA, + artifact_coordinates = "com.google.dagger:hilt-compiler:" + POM_VERSION, artifact_name = "Hilt Processor", artifact_target = ":artifact-lib", artifact_target_libs = [ "//java/dagger/hilt/android/processor/internal:android_classnames", "//java/dagger/hilt/android/processor/internal:utils", "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators", - "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options", "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata", "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib", "//java/dagger/hilt/android/processor/internal/bindvalue:bind_value_processor_lib", "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:validation_plugin_lib", - "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib", "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:components", @@ -75,17 +76,22 @@ gen_maven_artifact( "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies", + "//java/dagger/hilt/processor/internal/aggregateddeps:pkg_private_metadata", "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", "//java/dagger/hilt/processor/internal/aliasof:alias_ofs", "//java/dagger/hilt/processor/internal/aliasof:processor_lib", "//java/dagger/hilt/processor/internal/definecomponent:define_components", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", + "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", + "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", "//java/dagger/hilt/processor/internal/root:processor_lib", "//java/dagger/hilt/processor/internal/root:root_metadata", "//java/dagger/hilt/processor/internal/root:root_type", + "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", + "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", ], artifact_target_maven_deps = [ "com.google.auto:auto-common", diff --git a/java/dagger/hilt/processor/internal/AggregatedElements.java b/java/dagger/hilt/processor/internal/AggregatedElements.java new file mode 100644 index 000000000..8ee820fbd --- /dev/null +++ b/java/dagger/hilt/processor/internal/AggregatedElements.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** Utility class for aggregating metadata. */ +public final class AggregatedElements { + + /** Returns all aggregated elements in the aggregating package after validating them. */ + public static ImmutableSet<TypeElement> from( + String aggregatingPackage, ClassName aggregatingAnnotation, Elements elements) { + PackageElement packageElement = elements.getPackageElement(aggregatingPackage); + + if (packageElement == null) { + return ImmutableSet.of(); + } + + ImmutableSet<TypeElement> aggregatedElements = + packageElement.getEnclosedElements().stream() + .map(MoreElements::asType) + .collect(toImmutableSet()); + + ProcessorErrors.checkState( + !aggregatedElements.isEmpty(), + packageElement, + "No dependencies found. Did you remove code in package %s?", + packageElement); + + for (TypeElement aggregatedElement : aggregatedElements) { + ProcessorErrors.checkState( + Processors.hasAnnotation(aggregatedElement, aggregatingAnnotation), + aggregatedElement, + "Expected element, %s, to be annotated with @%s, but only found: %s.", + aggregatedElement.getSimpleName(), + aggregatingAnnotation, + aggregatedElement.getAnnotationMirrors()); + } + + return aggregatedElements; + } + + private AggregatedElements() {} +} diff --git a/java/dagger/hilt/processor/internal/AnnotationValues.java b/java/dagger/hilt/processor/internal/AnnotationValues.java index 584d8f950..9ebeeebca 100644 --- a/java/dagger/hilt/processor/internal/AnnotationValues.java +++ b/java/dagger/hilt/processor/internal/AnnotationValues.java @@ -18,15 +18,19 @@ package dagger.hilt.processor.internal; import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; +import static com.google.auto.common.MoreTypes.asTypeElement; import static com.google.common.base.Preconditions.checkNotNull; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.AnnotationValueVisitor; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; @@ -87,6 +91,18 @@ public final class AnnotationValues { } } + /** Returns a class array value as a set of {@link TypeElement}. */ + public static ImmutableSet<TypeElement> getTypeElements(AnnotationValue value) { + return getAnnotationValues(value).stream() + .map(AnnotationValues::getTypeElement) + .collect(toImmutableSet()); + } + + /** Returns a class value as a {@link TypeElement}. */ + public static TypeElement getTypeElement(AnnotationValue value) { + return asTypeElement(getTypeMirror(value)); + } + /** * Returns the value as a VariableElement. * @@ -96,6 +112,13 @@ public final class AnnotationValues { return EnumVisitor.INSTANCE.visit(value); } + /** Returns a string array value as a set of strings. */ + public static ImmutableSet<String> getStrings(AnnotationValue value) { + return getAnnotationValues(value).stream() + .map(AnnotationValues::getString) + .collect(toImmutableSet()); + } + /** * Returns the value as a string. * @@ -105,6 +128,15 @@ public final class AnnotationValues { return valueOfType(value, String.class); } + /** + * Returns the value as a boolean. + * + * @throws IllegalArgumentException if the value is not a boolean. + */ + public static boolean getBoolean(AnnotationValue value) { + return valueOfType(value, Boolean.class); + } + private static <T> T valueOfType(AnnotationValue annotationValue, Class<T> type) { Object value = annotationValue.getValue(); if (!type.isInstance(value)) { diff --git a/java/dagger/hilt/processor/internal/BUILD b/java/dagger/hilt/processor/internal/BUILD index baff1d823..978655dea 100644 --- a/java/dagger/hilt/processor/internal/BUILD +++ b/java/dagger/hilt/processor/internal/BUILD @@ -24,6 +24,7 @@ java_library( "ProcessorErrorHandler.java", ], deps = [ + ":compiler_options", ":processor_errors", ":processors", "//java/dagger/internal/guava:base", @@ -62,11 +63,9 @@ java_library( ":processor_errors", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/codegen/kotlin", - "//java/dagger/internal/codegen/langmodel", "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr305_annotations", "@google_bazel_common//third_party/java/jsr330_inject", "@maven//:com_google_auto_auto_common", "@maven//:org_jetbrains_kotlin_kotlin_stdlib", @@ -90,25 +89,34 @@ java_library( "ComponentNames.java", ], deps = [ + ":classnames", ":processors", + "//java/dagger/internal/guava:base", + "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/javapoet", ], ) java_library( - name = "component_descriptor", + name = "aggregated_elements", srcs = [ - "ComponentDescriptor.java", - "ComponentGenerator.java", - "ComponentTree.java", + "AggregatedElements.java", ], deps = [ - ":classnames", + ":processor_errors", ":processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", + "@google_bazel_common//third_party/java/javapoet", + "@maven//:com_google_auto_auto_common", + ], +) + +java_library( + name = "component_descriptor", + srcs = ["ComponentDescriptor.java"], + deps = [ + "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/auto:value", "@google_bazel_common//third_party/java/javapoet", ], @@ -143,6 +151,16 @@ java_library( ], ) +java_library( + name = "compiler_options", + srcs = ["HiltCompilerOptions.java"], + deps = [ + ":processor_errors", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/javapoet", + ], +) + filegroup( name = "srcs_filegroup", srcs = glob(["*"]), diff --git a/java/dagger/hilt/processor/internal/BadInputException.java b/java/dagger/hilt/processor/internal/BadInputException.java index d9617688e..f57a34a5e 100644 --- a/java/dagger/hilt/processor/internal/BadInputException.java +++ b/java/dagger/hilt/processor/internal/BadInputException.java @@ -36,6 +36,11 @@ public final class BadInputException extends RuntimeException { this.badElements = ImmutableList.copyOf(badElements); } + public BadInputException(String message) { + super(message); + this.badElements = ImmutableList.of(); + } + public ImmutableList<Element> getBadElements() { return badElements; } diff --git a/java/dagger/hilt/processor/internal/BaseProcessor.java b/java/dagger/hilt/processor/internal/BaseProcessor.java index 4961cd570..1a63f8b47 100644 --- a/java/dagger/hilt/processor/internal/BaseProcessor.java +++ b/java/dagger/hilt/processor/internal/BaseProcessor.java @@ -96,6 +96,15 @@ public abstract class BaseProcessor extends AbstractProcessor { private Messager messager; private ProcessorErrorHandler errorHandler; + @Override + public final Set<String> getSupportedOptions() { + // This is declared here rather than in the actual processors because KAPT will issue a + // warning if any used option is not unsupported. This can happen when there is a module + // which uses Hilt but lacks any @AndroidEntryPoint annotations. + // See: https://github.com/google/dagger/issues/2040 + return HiltCompilerOptions.getProcessorOptions(); + } + /** Used to perform initialization before each round of processing. */ protected void preRoundProcess(RoundEnvironment roundEnv) {}; diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java index 234ea7b45..093e1b3d5 100644 --- a/java/dagger/hilt/processor/internal/ClassNames.java +++ b/java/dagger/hilt/processor/internal/ClassNames.java @@ -22,6 +22,26 @@ import com.squareup.javapoet.ClassName; /** Holder for commonly used class names. */ public final class ClassNames { + public static final String AGGREGATED_ROOT_PACKAGE = + "dagger.hilt.internal.aggregatedroot.codegen"; + public static final ClassName AGGREGATED_ROOT = + get("dagger.hilt.internal.aggregatedroot", "AggregatedRoot"); + public static final String PROCESSED_ROOT_SENTINEL_PACKAGE = + "dagger.hilt.internal.processedrootsentinel.codegen"; + public static final ClassName PROCESSED_ROOT_SENTINEL = + get("dagger.hilt.internal.processedrootsentinel", "ProcessedRootSentinel"); + + public static final String AGGREGATED_EARLY_ENTRY_POINT_PACKAGE = + "dagger.hilt.android.internal.earlyentrypoint.codegen"; + public static final ClassName AGGREGATED_EARLY_ENTRY_POINT = + get("dagger.hilt.android.internal.earlyentrypoint", "AggregatedEarlyEntryPoint"); + public static final ClassName EARLY_ENTRY_POINT = get("dagger.hilt.android", "EarlyEntryPoint"); + + public static final String AGGREGATED_UNINSTALL_MODULES_PACKAGE = + "dagger.hilt.android.internal.uninstallmodules.codegen"; + public static final ClassName AGGREGATED_UNINSTALL_MODULES = + get("dagger.hilt.android.internal.uninstallmodules", "AggregatedUninstallModules"); + public static final ClassName ORIGINATING_ELEMENT = get("dagger.hilt.codegen", "OriginatingElement"); public static final ClassName AGGREGATED_DEPS = @@ -32,9 +52,11 @@ public final class ClassNames { get("dagger.hilt.internal", "GeneratedComponentManager"); public static final ClassName GENERATED_COMPONENT_MANAGER_HOLDER = get("dagger.hilt.internal", "GeneratedComponentManagerHolder"); - public static final ClassName IGNORE_MODULES = + public static final ClassName UNINSTALL_MODULES = get("dagger.hilt.android.testing", "UninstallModules"); + public static final String DEFINE_COMPONENT_CLASSES_PACKAGE = + "dagger.hilt.processor.internal.definecomponent.codegen"; public static final ClassName DEFINE_COMPONENT = get("dagger.hilt", "DefineComponent"); public static final ClassName DEFINE_COMPONENT_BUILDER = get("dagger.hilt", "DefineComponent", "Builder"); @@ -79,6 +101,8 @@ public final class ClassNames { public static final ClassName ALIAS_OF = get("dagger.hilt.migration", "AliasOf"); public static final ClassName ALIAS_OF_PROPAGATED_DATA = get("dagger.hilt.internal.aliasof", "AliasOfPropagatedData"); + public static final String ALIAS_OF_PROPAGATED_DATA_PACKAGE = + "dagger.hilt.processor.internal.aliasof.codegen"; public static final ClassName GENERATES_ROOT_INPUT = get("dagger.hilt", "GeneratesRootInput"); public static final ClassName GENERATES_ROOT_INPUT_PROPAGATED_DATA = @@ -122,12 +146,12 @@ public final class ClassNames { get("dagger.hilt.android.internal.managers", "ComponentSupplier"); public static final ClassName APPLICATION_CONTEXT_MODULE = get("dagger.hilt.android.internal.modules", "ApplicationContextModule"); + public static final ClassName DEFAULT_ROOT = + ClassName.get("dagger.hilt.android.internal.testing.root", "Default"); public static final ClassName INTERNAL_TEST_ROOT = get("dagger.hilt.android.internal.testing", "InternalTestRoot"); public static final ClassName TEST_INJECTOR = get("dagger.hilt.android.internal.testing", "TestInjector"); - public static final ClassName TEST_APPLICATION_INJECTOR = - get("dagger.hilt.android.internal.testing", "TestApplicationInjector"); public static final ClassName TEST_APPLICATION_COMPONENT_MANAGER = get("dagger.hilt.android.internal.testing", "TestApplicationComponentManager"); public static final ClassName TEST_APPLICATION_COMPONENT_MANAGER_HOLDER = @@ -152,6 +176,8 @@ public final class ClassNames { get("dagger.hilt.android.testing", "BindValueIntoSet"); public static final ClassName APPLICATION_CONTEXT = get("dagger.hilt.android.qualifiers", "ApplicationContext"); + public static final ClassName TEST_SINGLETON_COMPONENT = + get("dagger.hilt.internal", "TestSingletonComponent"); public static final ClassName TEST_COMPONENT_DATA = get("dagger.hilt.android.internal.testing", "TestComponentData"); public static final ClassName TEST_COMPONENT_DATA_SUPPLIER = diff --git a/java/dagger/hilt/processor/internal/ComponentNames.java b/java/dagger/hilt/processor/internal/ComponentNames.java index fab4d1995..eeaa5f44b 100644 --- a/java/dagger/hilt/processor/internal/ComponentNames.java +++ b/java/dagger/hilt/processor/internal/ComponentNames.java @@ -16,7 +16,25 @@ package dagger.hilt.processor.internal; +import static java.lang.Character.isUpperCase; +import static java.lang.String.format; +import static java.util.Comparator.comparing; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimaps; import com.squareup.javapoet.ClassName; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; /** * Utility class for getting the generated component name. @@ -24,15 +42,45 @@ import com.squareup.javapoet.ClassName; * <p>This should not be used externally. */ public final class ComponentNames { - private ComponentNames() {} + private static final Splitter QUALIFIED_NAME_SPLITTER = Splitter.on('.'); + + private final boolean renameTestComponents; + private final String destinationPackage; + private final ImmutableMap<ClassName, String> simpleNameByClassName; + + public static ComponentNames withoutRenaming() { + return new ComponentNames( + /*renameTestComponents=*/ false, /*destinationPackage=*/ null, ImmutableMap.of()); + } + + public static ComponentNames withRenamingIntoPackage( + String destinationPackage, ImmutableList<TypeElement> roots) { + ImmutableMap.Builder<ClassName, String> builder = ImmutableMap.builder(); + ImmutableListMultimap<String, TypeElement> rootsBySimpleName = + Multimaps.index(roots, typeElement -> typeElement.getSimpleName().toString()); + rootsBySimpleName.asMap().values().stream() + .map(ComponentNames::disambiguateConflictingSimpleNames) + .forEach(builder::putAll); + return new ComponentNames(/*renameTestComponents=*/ true, destinationPackage, builder.build()); + } + + private ComponentNames( + boolean renameTestComponents, + String destinationPackage, + ImmutableMap<ClassName, String> simpleNameByClassName) { + this.renameTestComponents = renameTestComponents; + this.destinationPackage = destinationPackage; + this.simpleNameByClassName = simpleNameByClassName; + } /** Returns the name of the generated component wrapper. */ - public static ClassName generatedComponentsWrapper(ClassName root) { - return Processors.append(Processors.getEnclosedClassName(root), "_HiltComponents"); + public ClassName generatedComponentsWrapper(ClassName root) { + return Processors.append( + Processors.getEnclosedClassName(maybeRenameComponent(root)), "_HiltComponents"); } /** Returns the name of the generated component. */ - public static ClassName generatedComponent(ClassName root, ClassName component) { + public ClassName generatedComponent(ClassName root, ClassName component) { return generatedComponentsWrapper(root).nestedClass(componentName(component)); } @@ -50,4 +98,82 @@ public final class ComponentNames { // Note: This uses regex matching so we only match if the name ends in "Component" return Processors.getEnclosedName(component).replaceAll("Component$", "C"); } + + /** + * Rewrites the provided HiltAndroidTest-annotated class name using the shared component + * directory. + */ + private ClassName maybeRenameComponent(ClassName className) { + return (renameTestComponents && !className.equals(ClassNames.DEFAULT_ROOT)) + ? ClassName.get(destinationPackage, dedupeSimpleName(className)) + : className; + } + + /** + * Derives a new generated component base name, should the simple names of two roots have + * conflicting simple names. + * + * <p>This is lifted nearly verbatim (albeit with new different struct types) from {@link + * dagger.internal.codegen.writing.SubcomponentNames}. + */ + private String dedupeSimpleName(ClassName className) { + Preconditions.checkState( + simpleNameByClassName.containsKey(className), + "Class name %s not found in simple name map", + className.canonicalName()); + return simpleNameByClassName.get(className); + } + + private static ImmutableMap<ClassName, String> disambiguateConflictingSimpleNames( + Collection<TypeElement> rootsWithConflictingNames) { + // If there's only 1 root there's nothing to disambiguate so return the simple name. + if (rootsWithConflictingNames.size() == 1) { + TypeElement root = Iterables.getOnlyElement(rootsWithConflictingNames); + return ImmutableMap.of(ClassName.get(root), root.getSimpleName().toString()); + } + + // There are conflicting simple names, so disambiguate them with a unique prefix. + // We keep them small to fix https://github.com/google/dagger/issues/421. + // Sorted in order to guarantee determinism if this is invoked by different processors. + ImmutableList<TypeElement> sortedRootsWithConflictingNames = + ImmutableList.sortedCopyOf( + comparing(typeElement -> typeElement.getQualifiedName().toString()), + rootsWithConflictingNames); + Set<String> usedNames = new HashSet<>(); + ImmutableMap.Builder<ClassName, String> uniqueNames = ImmutableMap.builder(); + for (TypeElement root : sortedRootsWithConflictingNames) { + String basePrefix = uniquingPrefix(root); + String uniqueName = basePrefix; + for (int differentiator = 2; !usedNames.add(uniqueName); differentiator++) { + uniqueName = basePrefix + differentiator; + } + uniqueNames.put(ClassName.get(root), format("%s_%s", uniqueName, root.getSimpleName())); + } + return uniqueNames.build(); + } + + /** Returns a prefix that could make the component's simple name more unique. */ + private static String uniquingPrefix(TypeElement typeElement) { + String containerName = typeElement.getEnclosingElement().getSimpleName().toString(); + + // If parent element looks like a class, use its initials as a prefix. + if (!containerName.isEmpty() && isUpperCase(containerName.charAt(0))) { + return CharMatcher.javaLowerCase().removeFrom(containerName); + } + + // Not in a normally named class. Prefix with the initials of the elements leading here. + Name qualifiedName = typeElement.getQualifiedName(); + Iterator<String> pieces = QUALIFIED_NAME_SPLITTER.split(qualifiedName).iterator(); + StringBuilder b = new StringBuilder(); + + while (pieces.hasNext()) { + String next = pieces.next(); + if (pieces.hasNext()) { + b.append(next.charAt(0)); + } + } + + // Note that a top level class in the root package will be prefixed "$_". + return b.length() > 0 ? b.toString() : "$"; + } } diff --git a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java new file mode 100644 index 000000000..0d248239b --- /dev/null +++ b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2020 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.hilt.processor.internal; + +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** Hilt annotation processor options. */ +// TODO(danysantiago): Consider consolidating with Dagger compiler options logic. +public final class HiltCompilerOptions { + + /** + * Returns {@code true} if the superclass validation is disabled for + * {@link dagger.hilt.android.AndroidEntryPoint}-annotated classes. + * + * This flag is for internal use only! The superclass validation checks that the super class is a + * generated {@code Hilt_} class. This flag is disabled by the Hilt Gradle plugin to enable + * bytecode transformation to change the superclass. + */ + public static boolean isAndroidSuperclassValidationDisabled( + TypeElement element, ProcessingEnvironment env) { + BooleanOption option = BooleanOption.DISABLE_ANDROID_SUPERCLASS_VALIDATION; + return option.get(env); + } + + /** + * Returns {@code true} if cross-compilation root validation is disabled. + * + * <p>This flag should rarely be needed, but may be used for legacy/migration purposes if + * tests require the use of {@link dagger.hilt.android.HiltAndroidApp} rather than + * {@link dagger.hilt.android.testing.HiltAndroidTest}. + * + * <p>Note that Hilt still does validation within a single compilation unit. In particular, + * a compilation unit that contains a {@code HiltAndroidApp} usage cannot have other + * {@code HiltAndroidApp} or {@code HiltAndroidTest} usages in the same compilation unit. + */ + public static boolean isCrossCompilationRootValidationDisabled( + ImmutableSet<TypeElement> rootElements, ProcessingEnvironment env) { + BooleanOption option = BooleanOption.DISABLE_CROSS_COMPILATION_ROOT_VALIDATION; + return option.get(env); + } + + /** Returns {@code true} if the check for {@link dagger.hilt.InstallIn} is disabled. */ + public static boolean isModuleInstallInCheckDisabled(ProcessingEnvironment env) { + return BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(env); + } + + /** + * Returns {@code true} of unit tests should try to share generated components, rather than using + * separate generated components per Hilt test root. + * + * <p>Tests that provide their own test bindings (e.g. using {@link + * dagger.hilt.android.testing.BindValue} or a test {@link dagger.Module}) cannot use the shared + * component. In these cases, a component will be generated for the test. + */ + public static boolean isSharedTestComponentsEnabled(ProcessingEnvironment env) { + return BooleanOption.SHARE_TEST_COMPONENTS.get(env); + } + + /** Processor options which can have true or false values. */ + private enum BooleanOption { + DISABLE_ANDROID_SUPERCLASS_VALIDATION( + "android.internal.disableAndroidSuperclassValidation", false), + + DISABLE_CROSS_COMPILATION_ROOT_VALIDATION("disableCrossCompilationRootValidation", false), + + DISABLE_MODULES_HAVE_INSTALL_IN_CHECK("disableModulesHaveInstallInCheck", false), + + SHARE_TEST_COMPONENTS("shareTestComponents", false); + + private final String name; + private final boolean defaultValue; + + BooleanOption(String name, boolean defaultValue) { + this.name = name; + this.defaultValue = defaultValue; + } + + boolean get(ProcessingEnvironment env) { + String value = env.getOptions().get(getQualifiedName()); + if (value == null) { + return defaultValue; + } + // TODO(danysantiago): Strictly verify input, either 'true' or 'false' and nothing else. + return Boolean.parseBoolean(value); + } + + String getQualifiedName() { + return "dagger.hilt." + name; + } + } + + public static Set<String> getProcessorOptions() { + return Arrays.stream(BooleanOption.values()) + .map(BooleanOption::getQualifiedName) + .collect(Collectors.toSet()); + } +} diff --git a/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java b/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java index ec5ddf871..64d5892c4 100644 --- a/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java +++ b/java/dagger/hilt/processor/internal/KotlinMetadataUtils.java @@ -20,7 +20,11 @@ import dagger.Component; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import javax.inject.Singleton; -/** A single-use provider of {@link KotlinMetadataUtil}. */ +/** + * A single-use provider of {@link KotlinMetadataUtil}. Since the returned util has a cache, it is + * better to reuse the same instance as much as possible, except for going across processor rounds + * because the cache contains elements. + */ // TODO(erichang): Revert this, should be wrapped with a Dagger module. public final class KotlinMetadataUtils { diff --git a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java index 460e8a946..2ecc0f098 100644 --- a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java +++ b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java @@ -62,6 +62,9 @@ final class ProcessorErrorHandler { if (t instanceof BadInputException) { BadInputException badInput = (BadInputException) t; + if (badInput.getBadElements().isEmpty()) { + hiltErrors.add(HiltError.of(badInput.getMessage())); + } for (Element element : badInput.getBadElements()) { hiltErrors.add(HiltError.of(badInput.getMessage(), element)); } diff --git a/java/dagger/hilt/processor/internal/ProcessorErrors.java b/java/dagger/hilt/processor/internal/ProcessorErrors.java index b1578daa3..d75bb625e 100644 --- a/java/dagger/hilt/processor/internal/ProcessorErrors.java +++ b/java/dagger/hilt/processor/internal/ProcessorErrors.java @@ -36,6 +36,49 @@ public final class ProcessorErrors { * involving any parameters to the calling method. * * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws BadInputException if {@code expression} is false + */ + public static void checkState( + boolean expression, + @Nullable Object errorMessage) { + if (!expression) { + throw new BadInputException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws BadInputException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or + * {@code errorMessageArgs} is null (don't let this happen) + */ + @FormatMethod + public static void checkState( + boolean expression, + @Nullable @FormatString String errorMessageTemplate, + @Nullable Object... errorMessageArgs) { + if (!expression) { + throw new BadInputException(String.format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression * @param badElement the element that was at fault * @param errorMessage the exception message to use if the check fails; will be converted to a * string using {@link String#valueOf(Object)} diff --git a/java/dagger/hilt/processor/internal/Processors.java b/java/dagger/hilt/processor/internal/Processors.java index b33c19d67..8740bd6a0 100644 --- a/java/dagger/hilt/processor/internal/Processors.java +++ b/java/dagger/hilt/processor/internal/Processors.java @@ -17,10 +17,12 @@ package dagger.hilt.processor.internal; import static com.google.auto.common.MoreElements.asPackage; +import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.asVariable; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static javax.lang.model.element.Modifier.ABSTRACT; +import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.AnnotationMirrors; @@ -41,6 +43,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; @@ -48,6 +51,7 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.extension.DaggerStreams; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; +import java.io.IOException; import java.lang.annotation.Annotation; import java.util.LinkedHashSet; import java.util.List; @@ -88,6 +92,26 @@ public final class Processors { private static final String JAVA_CLASS = "java.lang.Class"; + public static void generateAggregatingClass( + String aggregatingPackage, + AnnotationSpec aggregatingAnnotation, + TypeElement element, + Class<?> generatedAnnotationClass, + ProcessingEnvironment env) throws IOException { + ClassName name = ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(element)); + TypeSpec.Builder builder = + TypeSpec.classBuilder(name) + .addModifiers(PUBLIC) + .addOriginatingElement(element) + .addAnnotation(aggregatingAnnotation) + .addJavadoc("This class should only be referenced by generated code!") + .addJavadoc("This class aggregates information across multiple compilations.\n");; + + addGeneratedAnnotation(builder, env, generatedAnnotationClass); + + JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler()); + } + /** Returns a map from {@link AnnotationMirror} attribute name to {@link AnnotationValue}s */ public static ImmutableMap<String, AnnotationValue> getAnnotationValues(Elements elements, AnnotationMirror annotation) { @@ -511,6 +535,14 @@ public final class Processors { return ClassName.get(className.packageName(), getEnclosedName(className)); } + /** + * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with + * {@code _}. + */ + public static ClassName getEnclosedClassName(TypeElement typeElement) { + return getEnclosedClassName(ClassName.get(typeElement)); + } + /** Returns the fully qualified class name, with _ instead of . */ public static String getFullyQualifiedEnclosedClassName(ClassName className) { return className.packageName().replace('.', '_') + getEnclosedName(className); @@ -893,7 +925,10 @@ public final class Processors { return ElementFilter.methodsIn(elements.getAllMembers(module)).stream() .filter(Processors::isBindingMethod) .map(ExecutableElement::getModifiers) - .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)); + .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC)) + // TODO(erichang): Getting a new KotlinMetadataUtil each time isn't great here, but until + // we have some sort of dependency management it will be difficult to share the instance. + && !KotlinMetadataUtils.getMetadataUtil().isObjectOrCompanionObjectClass(module); } private static boolean isBindingMethod(ExecutableElement method) { @@ -936,5 +971,27 @@ public final class Processors { : typeName; } + public static Optional<TypeElement> getOriginatingTestElement( + Element element, Elements elements) { + TypeElement topLevelType = getOriginatingTopLevelType(element, elements); + return hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST) + ? Optional.of(asType(topLevelType)) + : Optional.empty(); + } + + private static TypeElement getOriginatingTopLevelType(Element element, Elements elements) { + TypeElement topLevelType = getTopLevelType(element); + if (hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) { + return getOriginatingTopLevelType( + getAnnotationClassValue( + elements, + getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT), + "topLevelClass"), + elements); + } + + return topLevelType; + } + private Processors() {} } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java index bcc2dfe04..84fb5a1ee 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java @@ -19,8 +19,6 @@ package dagger.hilt.processor.internal.aggregateddeps; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.Processors; import java.io.IOException; import java.util.Optional; @@ -59,20 +57,8 @@ final class AggregatedDepsGenerator { } void generate() throws IOException { - ClassName name = - ClassName.get( - AGGREGATING_PACKAGE, Processors.getFullEnclosedName(dependency) + "ModuleDeps"); - TypeSpec.Builder generator = - TypeSpec.classBuilder(name.simpleName()) - .addOriginatingElement(dependency) - .addAnnotation(aggregatedDepsAnnotation()) - .addJavadoc("Generated class to pass information through multiple javac runs.\n"); - - Processors.addGeneratedAnnotation(generator, processingEnv, getClass()); - - JavaFile.builder(name.packageName(), generator.build()) - .build() - .writeTo(processingEnv.getFiler()); + Processors.generateAggregatingClass( + AGGREGATING_PACKAGE, aggregatedDepsAnnotation(), dependency, getClass(), processingEnv); } private AnnotationSpec aggregatedDepsAnnotation() { diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java new file mode 100644 index 000000000..09e1651fe --- /dev/null +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.aggregateddeps; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.Iterables.getOnlyElement; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * A class that represents the values stored in an {@link + * dagger.hilt.processor.internal.aggregateddeps.AggregatedDeps} annotation. + */ +@AutoValue +abstract class AggregatedDepsMetadata { + private static final String AGGREGATED_DEPS_PACKAGE = "hilt_aggregated_deps"; + + enum DependencyType { + MODULE, + ENTRY_POINT, + COMPONENT_ENTRY_POINT + } + + abstract Optional<TypeElement> testElement(); + + abstract ImmutableSet<TypeElement> componentElements(); + + abstract DependencyType dependencyType(); + + abstract TypeElement dependency(); + + abstract ImmutableSet<TypeElement> replacedDependencies(); + + /** Returns all aggregated deps in the aggregating package. */ + public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) { + return AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static AggregatedDepsMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_AggregatedDepsMetadata( + getTestElement(values.get("test"), elements), + getComponents(values.get("components"), elements), + getDependencyType( + values.get("modules"), + values.get("entryPoints"), + values.get("componentEntryPoints")), + getDependency( + values.get("modules"), + values.get("entryPoints"), + values.get("componentEntryPoints"), + elements), + getReplacedDependencies(values.get("replaces"), elements)); + } + + private static Optional<TypeElement> getTestElement( + AnnotationValue testValue, Elements elements) { + checkNotNull(testValue); + String test = AnnotationValues.getString(testValue); + return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test)); + } + + private static ImmutableSet<TypeElement> getComponents( + AnnotationValue componentsValue, Elements elements) { + checkNotNull(componentsValue); + ImmutableSet<TypeElement> componentNames = + AnnotationValues.getAnnotationValues(componentsValue).stream() + .map(AnnotationValues::getString) + .map( + // This is a temporary hack to map the old ApplicationComponent to the new + // SingletonComponent. Technically, this is only needed for backwards compatibility + // with libraries using the old processor since new processors should convert to the + // new SingletonComponent when generating the metadata class. + componentName -> + componentName.contentEquals( + "dagger.hilt.android.components.ApplicationComponent") + ? ClassNames.SINGLETON_COMPONENT.canonicalName() + : componentName) + .map(elements::getTypeElement) + .collect(toImmutableSet()); + checkState(!componentNames.isEmpty()); + return componentNames; + } + + private static DependencyType getDependencyType( + AnnotationValue modulesValue, + AnnotationValue entryPointsValue, + AnnotationValue componentEntryPointsValue) { + checkNotNull(modulesValue); + checkNotNull(entryPointsValue); + checkNotNull(componentEntryPointsValue); + + ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder(); + if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) { + dependencyTypes.add(DependencyType.MODULE); + } + if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) { + dependencyTypes.add(DependencyType.ENTRY_POINT); + } + if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) { + dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT); + } + return getOnlyElement(dependencyTypes.build()); + } + + private static TypeElement getDependency( + AnnotationValue modulesValue, + AnnotationValue entryPointsValue, + AnnotationValue componentEntryPointsValue, + Elements elements) { + checkNotNull(modulesValue); + checkNotNull(entryPointsValue); + checkNotNull(componentEntryPointsValue); + + return elements.getTypeElement( + AnnotationValues.getString( + getOnlyElement( + ImmutableSet.<AnnotationValue>builder() + .addAll(AnnotationValues.getAnnotationValues(modulesValue)) + .addAll(AnnotationValues.getAnnotationValues(entryPointsValue)) + .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue)) + .build()))); + } + + private static ImmutableSet<TypeElement> getReplacedDependencies( + AnnotationValue replacedDependenciesValue, Elements elements) { + // Allow null values to support libraries using a Hilt version before @TestInstallIn was added + return replacedDependenciesValue == null + ? ImmutableSet.of() + : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream() + .map(AnnotationValues::getString) + .map(elements::getTypeElement) + .map(replacedDep -> getPublicDependency(replacedDep, elements)) + .collect(toImmutableSet()); + } + + /** Returns the public Hilt wrapper module, or the module itself if its already public. */ + private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) { + return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE) + .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString())) + .orElse(dependency); + } +} diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java index 58c938f88..151401e60 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java @@ -20,7 +20,7 @@ import static com.google.auto.common.AnnotationMirrors.getAnnotationValue; import static com.google.auto.common.MoreElements.asType; import static com.google.auto.common.MoreElements.getPackage; import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.hilt.android.processor.internal.androidentrypoint.HiltCompilerOptions.BooleanOption.DISABLE_MODULES_HAVE_INSTALL_IN_CHECK; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.ElementKind.CLASS; @@ -37,10 +37,8 @@ import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.BaseProcessor; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Components; -import dagger.hilt.processor.internal.KotlinMetadataUtils; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.Processors; -import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -65,6 +63,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS = ImmutableSet.of( ClassNames.ENTRY_POINT, + ClassNames.EARLY_ENTRY_POINT, ClassNames.GENERATED_ENTRY_POINT, ClassNames.COMPONENT_ENTRY_POINT); @@ -166,7 +165,10 @@ public final class AggregatedDepsProcessor extends BaseProcessor { // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by // calling a visible empty constructor. ProcessorErrors.checkState( - !daggerRequiresModuleInstance(module) || hasVisibleEmptyConstructor(module), + // Skip ApplicationContextModule, since Hilt manages this module internally. + ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) + || !Processors.requiresModuleInstance(getElementUtils(), module) + || hasVisibleEmptyConstructor(module), module, "Modules that need to be instantiated by Hilt must have a visible, empty constructor."); @@ -186,13 +188,14 @@ public final class AggregatedDepsProcessor extends BaseProcessor { ImmutableList<TypeElement> replacedModules = ImmutableList.of(); if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) { - Optional<TypeElement> originatingTestElement = getOriginatingTestElement(module); + Optional<TypeElement> originatingTestElement = + Processors.getOriginatingTestElement(module, getElementUtils()); ProcessorErrors.checkState( !originatingTestElement.isPresent(), // TODO(b/152801981): this should really error on the annotation value module, "@TestInstallIn modules cannot be nested in (or originate from) a " - + "@HiltAndroidTest-annotated class: %s", + + "@HiltAndroidTest-annotated class: %s", originatingTestElement .map(testElement -> testElement.getQualifiedName().toString()) .orElse("")); @@ -262,7 +265,10 @@ public final class AggregatedDepsProcessor extends BaseProcessor { // Prevent users from uninstalling test-specific @InstallIn modules. ImmutableList<TypeElement> replacedTestSpecificInstallIn = replacedModules.stream() - .filter(replacedModule -> getOriginatingTestElement(replacedModule).isPresent()) + .filter( + replacedModule -> + Processors.getOriginatingTestElement(replacedModule, getElementUtils()) + .isPresent()) .collect(toImmutableList()); ProcessorErrors.checkState( @@ -304,6 +310,28 @@ public final class AggregatedDepsProcessor extends BaseProcessor { element); TypeElement entryPoint = asType(element); + if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) { + ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element); + ProcessorErrors.checkState( + components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)), + element, + "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s", + components); + + Optional<TypeElement> optionalTestElement = + Processors.getOriginatingTestElement(element, getElementUtils()); + ProcessorErrors.checkState( + !optionalTestElement.isPresent(), + element, + "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) " + + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion " + + "with other, test-specific entry points.", + asType(element).getQualifiedName().toString(), + optionalTestElement + .map(testElement -> testElement.getQualifiedName().toString()) + .orElse("")); + } + generateAggregatedDeps( entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT) ? "componentEntryPoints" @@ -333,7 +361,8 @@ public final class AggregatedDepsProcessor extends BaseProcessor { .generate(); } } else { - Optional<ClassName> testName = getOriginatingTestElement(element).map(ClassName::get); + Optional<ClassName> testName = + Processors.getOriginatingTestElement(element, getElementUtils()).map(ClassName::get); new AggregatedDepsGenerator( key, element, testName, components, replacedModules, getProcessingEnv()) .generate(); @@ -362,25 +391,6 @@ public final class AggregatedDepsProcessor extends BaseProcessor { return Optional.of(getOnlyElement(usedAnnotations)); } - private Optional<TypeElement> getOriginatingTestElement(Element element) { - TypeElement topLevelType = getOriginatingTopLevelType(element); - return Processors.hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST) - ? Optional.of(asType(topLevelType)) - : Optional.empty(); - } - - private TypeElement getOriginatingTopLevelType(Element element) { - TypeElement topLevelType = Processors.getTopLevelType(element); - if (Processors.hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) { - return getOriginatingTopLevelType( - Processors.getAnnotationClassValue( - getElementUtils(), - Processors.getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT), - "topLevelClass")); - } - return topLevelType; - } - private static boolean isValidKind(Element element) { // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue // an error here because javac already has and we don't want to spam the user. @@ -388,7 +398,7 @@ public final class AggregatedDepsProcessor extends BaseProcessor { } private boolean installInCheckDisabled(Element element) { - return DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(getProcessingEnv()) + return isModuleInstallInCheckDisabled(getProcessingEnv()) || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK); } @@ -435,27 +445,6 @@ public final class AggregatedDepsProcessor extends BaseProcessor { || name.contentEquals("javax.annotation.processing.Generated"); } - private static boolean daggerRequiresModuleInstance(TypeElement module) { - return !module.getModifiers().contains(ABSTRACT) - && !hasOnlyStaticProvides(module) - // Skip ApplicationContextModule, since Hilt manages this module internally. - && !ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)) - // Skip Kotlin object modules since all their provision methods are static - && !isKotlinObject(module); - } - - private static boolean isKotlinObject(TypeElement type) { - KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil(); - return metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type); - } - - private static boolean hasOnlyStaticProvides(TypeElement module) { - // TODO(erichang): Check for @Produces too when we have a producers story - return ElementFilter.methodsIn(module.getEnclosedElements()).stream() - .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES)) - .allMatch(method -> method.getModifiers().contains(STATIC)); - } - private static boolean hasVisibleEmptyConstructor(TypeElement type) { List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements()); return constructors.isEmpty() diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD index ebbc941bc..5da2241f5 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD +++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD @@ -39,14 +39,13 @@ java_library( "AggregatedDepsGenerator.java", "AggregatedDepsProcessor.java", "PkgPrivateEntryPointGenerator.java", - "PkgPrivateMetadata.java", "PkgPrivateModuleGenerator.java", ], deps = [ - "//:dagger_with_compiler", - "//java/dagger/hilt/android/processor/internal/androidentrypoint:compiler_options", + ":pkg_private_metadata", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:components", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processor_errors", @@ -55,10 +54,21 @@ java_library( "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/auto:service", - "@google_bazel_common//third_party/java/auto:value", "@google_bazel_common//third_party/java/incap", "@google_bazel_common//third_party/java/javapoet", - "@google_bazel_common//third_party/java/jsr250_annotations", + "@maven//:com_google_auto_auto_common", + ], +) + +java_library( + name = "pkg_private_metadata", + srcs = ["PkgPrivateMetadata.java"], + deps = [ + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:kotlin", + "//java/dagger/hilt/processor/internal:processors", + "@google_bazel_common//third_party/java/auto:value", + "@google_bazel_common//third_party/java/javapoet", "@maven//:com_google_auto_auto_common", ], ) @@ -66,14 +76,17 @@ java_library( java_library( name = "component_dependencies", srcs = [ + "AggregatedDepsMetadata.java", "ComponentDependencies.java", ], deps = [ - ":processor_lib", + ":pkg_private_metadata", + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:component_descriptor", - "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata", + "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata", "//java/dagger/internal/codegen/extension", "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java index 23ed148c0..897ffd144 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java @@ -16,36 +16,17 @@ package dagger.hilt.processor.internal.aggregateddeps; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.Iterables.getOnlyElement; -import static dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsGenerator.AGGREGATING_PACKAGE; -import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; -import com.google.common.collect.SetMultimap; import com.squareup.javapoet.ClassName; -import dagger.hilt.processor.internal.AnnotationValues; -import dagger.hilt.processor.internal.BadInputException; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.Processors; -import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies.AggregatedDepMetadata.DependencyType; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.PackageElement; +import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata; +import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -65,6 +46,23 @@ public abstract class ComponentDependencies { /** Returns the component entry point associated with the given a component. */ public abstract Dependencies componentEntryPoints(); + /** Returns the set of early entry points */ + public abstract ImmutableSet<ClassName> earlyEntryPoints(); + + /** Returns {@code true} if any entry points are annotated with {@code EarlyEntryPoints}. */ + public boolean hasEarlyEntryPoints() { + return !earlyEntryPoints().isEmpty(); + } + + /** + * Returns {@code true} if the test binds or uninstalls test-specific bindings that would prevent + * it from sharing components with other test roots. + */ + public final boolean includesTestDeps(ClassName root) { + return modules().testDeps().keySet().stream().anyMatch((key) -> key.test().equals(root)) + || modules().uninstalledTestDeps().containsKey(root); + } + @AutoValue.Builder abstract static class Builder { abstract Dependencies.Builder modulesBuilder(); @@ -73,12 +71,9 @@ public abstract class ComponentDependencies { abstract Dependencies.Builder componentEntryPointsBuilder(); - abstract ComponentDependencies autoBuild(); + abstract ImmutableSet.Builder<ClassName> earlyEntryPointsBuilder(); - ComponentDependencies build(Elements elements) { - validateModules(modulesBuilder().build(), elements); - return autoBuild(); - } + abstract ComponentDependencies build(); } /** A key used for grouping a test dependency by both its component and test name. */ @@ -123,6 +118,17 @@ public abstract class ComponentDependencies { /** Returns the global uninstalled test deps. */ abstract ImmutableSet<TypeElement> globalUninstalledTestDeps(); + /** Returns the dependencies to be installed in the global singleton component. */ + ImmutableSet<TypeElement> getGlobalSingletonDeps() { + return ImmutableSet.<TypeElement>builder() + .addAll( + globalDeps().get(ClassNames.SINGLETON_COMPONENT).stream() + .filter(dep -> !globalUninstalledTestDeps().contains(dep)) + .collect(toImmutableSet())) + .addAll(globalTestDeps().get(ClassNames.SINGLETON_COMPONENT)) + .build(); + } + /** Returns the dependencies to be installed in the given component for the given root. */ public ImmutableSet<TypeElement> get(ClassName component, ClassName root, boolean isTestRoot) { if (!isTestRoot) { @@ -175,14 +181,10 @@ public abstract class ComponentDependencies { */ public static ComponentDependencies from( ImmutableSet<ComponentDescriptor> descriptors, Elements elements) { - Map<String, ComponentDescriptor> descriptorLookup = descriptorLookupMap(descriptors); - ImmutableList<AggregatedDepMetadata> metadatas = - getAggregatedDeps(elements).stream() - .map(deps -> AggregatedDepMetadata.create(deps, descriptorLookup, elements)) - .collect(toImmutableList()); - + ImmutableSet<ClassName> componentNames = + descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet()); ComponentDependencies.Builder componentDependencies = ComponentDependencies.builder(); - for (AggregatedDepMetadata metadata : metadatas) { + for (AggregatedDepsMetadata metadata : AggregatedDepsMetadata.from(elements)) { Dependencies.Builder builder = null; switch (metadata.dependencyType()) { case MODULE: @@ -195,265 +197,46 @@ public abstract class ComponentDependencies { builder = componentDependencies.componentEntryPointsBuilder(); break; } - - for (ComponentDescriptor componentDescriptor : metadata.componentDescriptors()) { - ClassName component = componentDescriptor.component(); + for (TypeElement componentElement : metadata.componentElements()) { + ClassName componentName = ClassName.get(componentElement); + checkState( + componentNames.contains(componentName), "%s is not a valid Component.", componentName); if (metadata.testElement().isPresent()) { // In this case the @InstallIn or @TestInstallIn applies to only the given test root. ClassName test = ClassName.get(metadata.testElement().get()); - builder.testDepsBuilder().put(TestDepKey.of(component, test), metadata.dependency()); + builder.testDepsBuilder().put(TestDepKey.of(componentName, test), metadata.dependency()); builder.uninstalledTestDepsBuilder().putAll(test, metadata.replacedDependencies()); } else { // In this case the @InstallIn or @TestInstallIn applies to all roots if (!metadata.replacedDependencies().isEmpty()) { // If there are replacedDependencies() it means this is a @TestInstallIn - builder.globalTestDepsBuilder().put(component, metadata.dependency()); + builder.globalTestDepsBuilder().put(componentName, metadata.dependency()); builder.globalUninstalledTestDepsBuilder().addAll(metadata.replacedDependencies()); } else { - builder.globalDepsBuilder().put(component, metadata.dependency()); + builder.globalDepsBuilder().put(componentName, metadata.dependency()); } } } } - // Collect all @UninstallModules. - // TODO(b/176438516): Filter @UninstallModules at the root. - metadatas.stream() - .filter(metadata -> metadata.testElement().isPresent()) - .map(metadata -> metadata.testElement().get()) - .distinct() - .filter(testElement -> Processors.hasAnnotation(testElement, ClassNames.IGNORE_MODULES)) + AggregatedUninstallModulesMetadata.from(elements) .forEach( - testElement -> + metadata -> componentDependencies .modulesBuilder() .uninstalledTestDepsBuilder() .putAll( - ClassName.get(testElement), getUninstalledModules(testElement, elements))); - - return componentDependencies.build(elements); - } - - private static ImmutableMap<String, ComponentDescriptor> descriptorLookupMap( - ImmutableSet<ComponentDescriptor> descriptors) { - ImmutableMap.Builder<String, ComponentDescriptor> builder = ImmutableMap.builder(); - for (ComponentDescriptor descriptor : descriptors) { - // This is a temporary hack to map the old ApplicationComponent to the new SingletonComponent. - // Technically, this is only needed for backwards compatibility with libraries using the old - // processor since new processors should convert to the new SingletonComponent when generating - // the metadata class. - if (descriptor.component().equals(ClassNames.SINGLETON_COMPONENT)) { - builder.put("dagger.hilt.android.components.ApplicationComponent", descriptor); - } - builder.put(descriptor.component().toString(), descriptor); - } - return builder.build(); - } - - // Validate that the @UninstallModules doesn't contain any test modules. - private static Dependencies validateModules(Dependencies moduleDeps, Elements elements) { - SetMultimap<ClassName, TypeElement> invalidTestModules = HashMultimap.create(); - moduleDeps.testDeps().entries().stream() - .filter( - e -> moduleDeps.uninstalledTestDeps().containsEntry(e.getKey().test(), e.getValue())) - .forEach(e -> invalidTestModules.put(e.getKey().test(), e.getValue())); - - // Currently we don't have a good way to throw an error for all tests, so we sort (to keep the - // error reporting order stable) and then choose the first test. - // TODO(bcorso): Consider using ProcessorErrorHandler directly to report all errors at once? - Optional<ClassName> invalidTest = - invalidTestModules.keySet().stream() - .min((test1, test2) -> test1.toString().compareTo(test2.toString())); - if (invalidTest.isPresent()) { - throw new BadInputException( - String.format( - "@UninstallModules on test, %s, should not containing test modules, " - + "but found: %s", - invalidTest.get(), - invalidTestModules.get(invalidTest.get()).stream() - // Sort modules to keep stable error messages. - .sorted((test1, test2) -> test1.toString().compareTo(test2.toString())) - .collect(toImmutableList())), - elements.getTypeElement(invalidTest.get().toString())); - } - return moduleDeps; - } - - private static ImmutableSet<TypeElement> getUninstalledModules( - TypeElement testElement, Elements elements) { - ImmutableList<TypeElement> userUninstallModules = - Processors.getAnnotationClassValues( - elements, - Processors.getAnnotationMirror(testElement, ClassNames.IGNORE_MODULES), - "value"); - - // For pkg-private modules, find the generated wrapper class and uninstall that instead. - return userUninstallModules.stream() - .map(uninstallModule -> getPublicDependency(uninstallModule, elements)) - .collect(toImmutableSet()); - } - - /** Returns the public Hilt wrapper module, or the module itself if its already public. */ - private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) { - return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE) - .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString())) - .orElse(dependency); - } - - /** Returns the top-level elements of the aggregated deps package. */ - private static ImmutableList<AnnotationMirror> getAggregatedDeps(Elements elements) { - PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE); - checkState( - packageElement != null, - "Couldn't find package %s. Did you mark your @Module classes with @InstallIn annotations?", - AGGREGATING_PACKAGE); - - List<? extends Element> aggregatedDepsElements = packageElement.getEnclosedElements(); - checkState( - !aggregatedDepsElements.isEmpty(), - "No dependencies found. Did you mark your @Module classes with @InstallIn annotations?"); - - ImmutableList.Builder<AnnotationMirror> builder = ImmutableList.builder(); - for (Element element : aggregatedDepsElements) { - ProcessorErrors.checkState( - element.getKind() == ElementKind.CLASS, - element, - "Only classes may be in package %s. Did you add custom code in the package?", - AGGREGATING_PACKAGE); - - AnnotationMirror aggregatedDeps = - Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS); - ProcessorErrors.checkState( - aggregatedDeps != null, - element, - "Classes in package %s must be annotated with @AggregatedDeps: %s. Found: %s.", - AGGREGATING_PACKAGE, - element.getSimpleName(), - element.getAnnotationMirrors()); - - builder.add(aggregatedDeps); - } - return builder.build(); - } - - @AutoValue - abstract static class AggregatedDepMetadata { - static AggregatedDepMetadata create( - AnnotationMirror aggregatedDeps, - Map<String, ComponentDescriptor> descriptorLookup, - Elements elements) { - ImmutableMap<String, AnnotationValue> aggregatedDepsValues = - Processors.getAnnotationValues(elements, aggregatedDeps); - - return new AutoValue_ComponentDependencies_AggregatedDepMetadata( - getTestElement(aggregatedDepsValues.get("test"), elements), - getComponents(aggregatedDepsValues.get("components"), descriptorLookup), - getDependencyType( - aggregatedDepsValues.get("modules"), - aggregatedDepsValues.get("entryPoints"), - aggregatedDepsValues.get("componentEntryPoints")), - getDependency( - aggregatedDepsValues.get("modules"), - aggregatedDepsValues.get("entryPoints"), - aggregatedDepsValues.get("componentEntryPoints"), - elements), - getReplacedDependencies(aggregatedDepsValues.get("replaces"), elements)); - } - - enum DependencyType { - MODULE, - ENTRY_POINT, - COMPONENT_ENTRY_POINT - } - - abstract Optional<TypeElement> testElement(); - - abstract ImmutableList<ComponentDescriptor> componentDescriptors(); - - abstract DependencyType dependencyType(); - - abstract TypeElement dependency(); - - abstract ImmutableSet<TypeElement> replacedDependencies(); - - private static Optional<TypeElement> getTestElement( - AnnotationValue testValue, Elements elements) { - checkNotNull(testValue); - String test = AnnotationValues.getString(testValue); - return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test)); - } - - private static ImmutableList<ComponentDescriptor> getComponents( - AnnotationValue componentsValue, Map<String, ComponentDescriptor> descriptorLookup) { - checkNotNull(componentsValue); - ImmutableList<String> componentNames = - AnnotationValues.getAnnotationValues(componentsValue).stream() - .map(AnnotationValues::getString) - .collect(toImmutableList()); - - checkState(!componentNames.isEmpty()); - ImmutableList.Builder<ComponentDescriptor> components = ImmutableList.builder(); - for (String componentName : componentNames) { - checkState( - descriptorLookup.containsKey(componentName), - "%s is not a valid Component. Did you add or remove code in package %s?", - componentName, - AGGREGATING_PACKAGE); - components.add(descriptorLookup.get(componentName)); - } - return components.build(); - } - - private static DependencyType getDependencyType( - AnnotationValue modulesValue, - AnnotationValue entryPointsValue, - AnnotationValue componentEntryPointsValue) { - checkNotNull(modulesValue); - checkNotNull(entryPointsValue); - checkNotNull(componentEntryPointsValue); - - ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder(); - if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) { - dependencyTypes.add(DependencyType.MODULE); - } - if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) { - dependencyTypes.add(DependencyType.ENTRY_POINT); - } - if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) { - dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT); - } - return getOnlyElement(dependencyTypes.build()); - } - - private static TypeElement getDependency( - AnnotationValue modulesValue, - AnnotationValue entryPointsValue, - AnnotationValue componentEntryPointsValue, - Elements elements) { - checkNotNull(modulesValue); - checkNotNull(entryPointsValue); - checkNotNull(componentEntryPointsValue); - - return elements.getTypeElement( - AnnotationValues.getString( - getOnlyElement( - ImmutableList.<AnnotationValue>builder() - .addAll(AnnotationValues.getAnnotationValues(modulesValue)) - .addAll(AnnotationValues.getAnnotationValues(entryPointsValue)) - .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue)) - .build()))); - } - - private static ImmutableSet<TypeElement> getReplacedDependencies( - AnnotationValue replacedDependenciesValue, Elements elements) { - // Allow null values to support libraries using a Hilt version before @TestInstallIn was added - return replacedDependenciesValue == null - ? ImmutableSet.of() - : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream() - .map(AnnotationValues::getString) - .map(elements::getTypeElement) - .map(replacedDep -> getPublicDependency(replacedDep, elements)) - .collect(toImmutableSet()); - } + ClassName.get(metadata.testElement()), + metadata.uninstallModuleElements().stream() + .map(module -> PkgPrivateMetadata.publicModule(module, elements)) + .collect(toImmutableSet()))); + + AggregatedEarlyEntryPointMetadata.from(elements).stream() + .map(AggregatedEarlyEntryPointMetadata::earlyEntryPoint) + .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements)) + .map(ClassName::get) + .forEach(componentDependencies.earlyEntryPointsBuilder()::add); + + return componentDependencies.build(); } } diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java index 66f175151..ff344a651 100644 --- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java +++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java @@ -24,16 +24,35 @@ import com.google.auto.value.AutoValue; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.KotlinMetadataUtils; import dagger.hilt.processor.internal.Processors; import java.util.Optional; import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; /** PkgPrivateModuleMetadata contains a set of utilities for processing package private modules. */ @AutoValue -abstract class PkgPrivateMetadata { +public abstract class PkgPrivateMetadata { + /** Returns the public Hilt wrapped type or the type itself if it is already public. */ + public static TypeElement publicModule(TypeElement element, Elements elements) { + return publicDep(element, elements, ClassNames.MODULE); + } + + /** Returns the public Hilt wrapped type or the type itself if it is already public. */ + public static TypeElement publicEarlyEntryPoint(TypeElement element, Elements elements) { + return publicDep(element, elements, ClassNames.EARLY_ENTRY_POINT); + } + + private static TypeElement publicDep( + TypeElement element, Elements elements, ClassName annotation) { + return of(elements, element, annotation) + .map(PkgPrivateMetadata::generatedClassName) + .map(ClassName::canonicalName) + .map(elements::getTypeElement) + .orElse(element); + } + private static final String PREFIX = "HiltWrapper_"; /** Returns the base class name of the elemenet. */ @@ -63,9 +82,11 @@ abstract class PkgPrivateMetadata { * Returns an Optional PkgPrivateMetadata requiring Hilt processing, otherwise returns an empty * Optional. */ - static Optional<PkgPrivateMetadata> of(Elements elements, Element element, ClassName annotation) { + static Optional<PkgPrivateMetadata> of( + Elements elements, TypeElement element, ClassName annotation) { // If this is a public element no wrapping is needed - if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC) { + if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC + && !KotlinMetadataUtils.getMetadataUtil().isVisibilityInternal(element)) { return Optional.empty(); } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java index 6efd6431e..02e7f5d56 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java @@ -16,6 +16,7 @@ package dagger.hilt.processor.internal.aliasof; +import static com.google.auto.common.MoreElements.asType; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; import com.google.auto.service.AutoService; @@ -48,13 +49,14 @@ public final class AliasOfProcessor extends BaseProcessor { "%s should only be used on scopes." + " However, it was found annotating %s", annotation, element); + AnnotationMirror annotationMirror = Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF); - Element defineComponentScope = + TypeElement defineComponentScope = Processors.getAnnotationClassValue(getElementUtils(), annotationMirror, "value"); - new AliasOfPropagatedDataGenerator(getProcessingEnv(), element, defineComponentScope) + new AliasOfPropagatedDataGenerator(getProcessingEnv(), asType(element), defineComponentScope) .generate(); } } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java index 75c4c15ac..1d7edf285 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java @@ -17,43 +17,37 @@ package dagger.hilt.processor.internal.aliasof; import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; import java.io.IOException; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; /** Generates resource files for {@link dagger.hilt.migration.AliasOf}. */ final class AliasOfPropagatedDataGenerator { private final ProcessingEnvironment processingEnv; - private final Element aliasScope; - private final Element defineComponentScope; + private final TypeElement aliasScope; + private final TypeElement defineComponentScope; AliasOfPropagatedDataGenerator( - ProcessingEnvironment processingEnv, Element aliasScope, Element defineComponentScope) { + ProcessingEnvironment processingEnv, + TypeElement aliasScope, + TypeElement defineComponentScope) { this.processingEnv = processingEnv; this.aliasScope = aliasScope; this.defineComponentScope = defineComponentScope; } void generate() throws IOException { - TypeSpec.Builder generator = - TypeSpec.classBuilder(Processors.getFullEnclosedName(aliasScope)) - .addOriginatingElement(aliasScope) - .addAnnotation( - AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA) + Processors.generateAggregatingClass( + ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE, + AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA) .addMember("defineComponentScope", "$T.class", defineComponentScope) .addMember("alias", "$T.class", aliasScope) - .build()) - .addJavadoc("Generated class for aggregating scope aliases. \n"); - - Processors.addGeneratedAnnotation(generator, processingEnv, getClass()); - - JavaFile.builder(AliasOfs.AGGREGATING_PACKAGE, generator.build()) - .build() - .writeTo(processingEnv.getFiler()); + .build(), + aliasScope, + getClass(), + processingEnv); } } diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java new file mode 100644 index 000000000..30a2c70c6 --- /dev/null +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.aliasof; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * A class that represents the values stored in an {@link + * dagger.hilt.internal.aliasof.AliasOfPropagatedData} annotation. + */ +@AutoValue +abstract class AliasOfPropagatedDataMetadata { + + abstract TypeElement defineComponentScopeElement(); + + abstract TypeElement aliasElement(); + + static ImmutableSet<AliasOfPropagatedDataMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE, + ClassNames.ALIAS_OF_PROPAGATED_DATA, + elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static AliasOfPropagatedDataMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_AliasOfPropagatedDataMetadata( + AnnotationValues.getTypeElement(values.get("defineComponentScope")), + AnnotationValues.getTypeElement(values.get("alias"))); + } +} diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java index 930a72e27..18951bdf9 100644 --- a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java +++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java @@ -16,23 +16,11 @@ package dagger.hilt.processor.internal.aliasof; -import static com.google.common.base.Suppliers.memoize; -import com.google.common.base.Preconditions; -import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.ClassName; -import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.Processors; -import java.util.List; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.PackageElement; -import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; /** @@ -40,69 +28,33 @@ import javax.lang.model.util.Elements; * to scopes they are alias of. */ public final class AliasOfs { - static final String AGGREGATING_PACKAGE = AliasOfs.class.getPackage().getName() + ".codegen"; + public static AliasOfs create(Elements elements, ImmutableSet<ClassName> defineComponentScopes) { + ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder(); + AliasOfPropagatedDataMetadata.from(elements) + .forEach( + metadata -> { + ClassName defineComponentScopeName = + ClassName.get(metadata.defineComponentScopeElement()); + ClassName aliasScopeName = ClassName.get(metadata.aliasElement()); + ProcessorErrors.checkState( + defineComponentScopes.contains(defineComponentScopeName), + metadata.aliasElement(), + "The scope %s cannot be an alias for %s. You can only have aliases of a scope" + + " defined directly on a @DefineComponent type.", + aliasScopeName, + defineComponentScopeName); + builder.put(defineComponentScopeName, aliasScopeName); + }); + return new AliasOfs(builder.build()); + } - private final ProcessingEnvironment processingEnvironment; - private final ImmutableSet<ClassName> defineComponentScopes; - private final Supplier<ImmutableMultimap<ClassName, ClassName>> aliases = - memoize(() -> getAliases()); + private final ImmutableSetMultimap<ClassName, ClassName> defineComponentScopeToAliases; - public AliasOfs( - ProcessingEnvironment processingEnvironment, ImmutableSet<ClassName> defineComponentScopes) { - this.defineComponentScopes = defineComponentScopes; - this.processingEnvironment = processingEnvironment; + private AliasOfs(ImmutableSetMultimap<ClassName, ClassName> defineComponentScopeToAliases) { + this.defineComponentScopeToAliases = defineComponentScopeToAliases; } public ImmutableSet<ClassName> getAliasesFor(ClassName defineComponentScope) { - return ImmutableSet.copyOf(aliases.get().get(defineComponentScope)); - } - - private ImmutableMultimap<ClassName, ClassName> getAliases() { - Elements elements = processingEnvironment.getElementUtils(); - PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE); - if (packageElement == null) { - return ImmutableMultimap.of(); - } - List<? extends Element> scopeAliasElements = packageElement.getEnclosedElements(); - Preconditions.checkState( - !scopeAliasElements.isEmpty(), "No scope aliases Found in package %s.", packageElement); - - ImmutableMultimap.Builder<ClassName, ClassName> builder = ImmutableMultimap.builder(); - for (Element element : scopeAliasElements) { - ProcessorErrors.checkState( - element.getKind() == ElementKind.CLASS, - element, - "Only classes may be in package %s. Did you add custom code in the package?", - packageElement); - - AnnotationMirror annotationMirror = - Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA); - - ProcessorErrors.checkState( - annotationMirror != null, - element, - "Classes in package %s must be annotated with @%s: %s." - + " Found: %s. Files in this package are generated, did you add custom code in the" - + " package? ", - packageElement, - ClassNames.ALIAS_OF_PROPAGATED_DATA, - element.getSimpleName(), - element.getAnnotationMirrors()); - - TypeElement defineComponentScope = - Processors.getAnnotationClassValue(elements, annotationMirror, "defineComponentScope"); - TypeElement alias = Processors.getAnnotationClassValue(elements, annotationMirror, "alias"); - - Preconditions.checkState( - defineComponentScopes.contains(ClassName.get(defineComponentScope)), - "The scope %s cannot be an alias for %s. You can only have aliases of a scope defined" - + " directly on a @DefineComponent type.", - ClassName.get(alias), - ClassName.get(defineComponentScope)); - - builder.put(ClassName.get(defineComponentScope), ClassName.get(alias)); - } - - return builder.build(); + return defineComponentScopeToAliases.get(defineComponentScope); } } diff --git a/java/dagger/hilt/processor/internal/aliasof/BUILD b/java/dagger/hilt/processor/internal/aliasof/BUILD index d3f90f2fa..ffd0c9ae8 100644 --- a/java/dagger/hilt/processor/internal/aliasof/BUILD +++ b/java/dagger/hilt/processor/internal/aliasof/BUILD @@ -31,7 +31,6 @@ java_library( "AliasOfPropagatedDataGenerator.java", ], deps = [ - ":alias_ofs", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", @@ -40,20 +39,24 @@ java_library( "@google_bazel_common//third_party/java/auto:service", "@google_bazel_common//third_party/java/incap", "@google_bazel_common//third_party/java/javapoet", + "@maven//:com_google_auto_auto_common", ], ) java_library( name = "alias_ofs", srcs = [ + "AliasOfPropagatedDataMetadata.java", "AliasOfs.java", ], deps = [ + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", - "//java/dagger/internal/guava:base", + "//java/dagger/internal/codegen/extension", "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/auto:value", "@google_bazel_common//third_party/java/javapoet", ], ) diff --git a/java/dagger/hilt/processor/internal/definecomponent/BUILD b/java/dagger/hilt/processor/internal/definecomponent/BUILD index 7edec1cba..43f4dfda0 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/BUILD +++ b/java/dagger/hilt/processor/internal/definecomponent/BUILD @@ -45,18 +45,18 @@ java_library( name = "define_components", srcs = [ "DefineComponentBuilderMetadatas.java", + "DefineComponentClassesMetadata.java", "DefineComponentMetadatas.java", "DefineComponents.java", ], deps = [ + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "//java/dagger/internal/codegen/extension", - "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", - "//java/dagger/internal/guava:graph", "@google_bazel_common//third_party/java/auto:value", "@google_bazel_common//third_party/java/javapoet", "@maven//:com_google_auto_auto_common", diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java new file mode 100644 index 000000000..36ac28fb3 --- /dev/null +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.definecomponent; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.ProcessorErrors; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * A class that represents the values stored in an {@link + * dagger.hilt.internal.definecomponent.DefineComponentClasses} annotation. + */ +@AutoValue +abstract class DefineComponentClassesMetadata { + + /** + * Returns the element annotated with {@code dagger.hilt.internal.definecomponent.DefineComponent} + * or {@code dagger.hilt.internal.definecomponent.DefineComponent.Builder}. + */ + abstract TypeElement element(); + + /** Returns {@code true} if this element represents a component. */ + abstract boolean isComponent(); + + /** Returns {@code true} if this element represents a component builder. */ + boolean isComponentBuilder() { + return !isComponent(); + } + + static ImmutableSet<DefineComponentClassesMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, + ClassNames.DEFINE_COMPONENT_CLASSES, + elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static DefineComponentClassesMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + String componentName = AnnotationValues.getString(values.get("component")); + String builderName = AnnotationValues.getString(values.get("builder")); + + ProcessorErrors.checkState( + !(componentName.isEmpty() && builderName.isEmpty()), + element, + "@DefineComponentClasses missing both `component` and `builder` members."); + + ProcessorErrors.checkState( + componentName.isEmpty() || builderName.isEmpty(), + element, + "@DefineComponentClasses should not include both `component` and `builder` members."); + + boolean isComponent = !componentName.isEmpty(); + String componentOrBuilderName = isComponent ? componentName : builderName; + TypeElement componentOrBuilderElement = elements.getTypeElement(componentOrBuilderName); + ProcessorErrors.checkState( + componentOrBuilderElement != null, + componentOrBuilderElement, + "%s.%s(), has invalid value: `%s`.", + ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), + isComponent ? "component" : "builder", + componentOrBuilderName); + return new AutoValue_DefineComponentClassesMetadata(componentOrBuilderElement, isComponent); + } +} diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java index aa69e40e1..60864c2c2 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java @@ -163,15 +163,25 @@ final class DefineComponentMetadatas { ? Optional.empty() : Optional.of(get(parent, childPath)); + ClassName componentClassName = ClassName.get(component); + ProcessorErrors.checkState( parentComponent.isPresent() - || ClassName.get(component).equals(ClassNames.SINGLETON_COMPONENT), + || componentClassName.equals(ClassNames.SINGLETON_COMPONENT), component, "@DefineComponent %s is missing a parent declaration.\n" + "Please declare the parent, for example: @DefineComponent(parent =" + " SingletonComponent.class)", component); + ProcessorErrors.checkState( + componentClassName.equals(ClassNames.SINGLETON_COMPONENT) + || !componentClassName.simpleName().equals(ClassNames.SINGLETON_COMPONENT.simpleName()), + component, + "Cannot have a component with the same simple name as the reserved %s: %s", + ClassNames.SINGLETON_COMPONENT.simpleName(), + componentClassName); + return new AutoValue_DefineComponentMetadatas_DefineComponentMetadata( component, scopes, parentComponent); } diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java index e0bfca930..f7e54a0a7 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java @@ -22,8 +22,6 @@ import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.BaseProcessor; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; @@ -70,18 +68,13 @@ public final class DefineComponentProcessor extends BaseProcessor { } private void generateFile(String member, TypeElement typeElement) throws IOException { - TypeSpec.Builder builder = - TypeSpec.interfaceBuilder(Processors.getFullEnclosedName(typeElement)) - .addOriginatingElement(typeElement) - .addAnnotation( - AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES) - .addMember(member, "$S", typeElement.getQualifiedName()) - .build()); - - Processors.addGeneratedAnnotation(builder, processingEnv, getClass()); - - JavaFile.builder(DefineComponents.AGGREGATING_PACKAGE, builder.build()) - .build() - .writeTo(processingEnv.getFiler()); + Processors.generateAggregatingClass( + ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, + AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES) + .addMember(member, "$S", typeElement.getQualifiedName()) + .build(), + typeElement, + getClass(), + getProcessingEnv()); } } diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java index 0b330111a..efa501191 100644 --- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java +++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java @@ -16,32 +16,22 @@ package dagger.hilt.processor.internal.definecomponent; -import static com.google.auto.common.AnnotationMirrors.getAnnotationElementAndValue; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; -import com.google.auto.common.MoreElements; -import com.google.auto.value.AutoValue; import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; import com.squareup.javapoet.ClassName; -import dagger.hilt.processor.internal.AnnotationValues; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ComponentTree; import dagger.hilt.processor.internal.ProcessorErrors; -import dagger.hilt.processor.internal.Processors; import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata; import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; @@ -50,8 +40,6 @@ import javax.lang.model.util.Elements; * DefineComponentBuilderMetadata}. */ public final class DefineComponents { - static final String AGGREGATING_PACKAGE = - DefineComponents.class.getPackage().getName() + ".codegen"; public static DefineComponents create() { return new DefineComponents(); @@ -89,14 +77,28 @@ public final class DefineComponents { return builder.build(); } - /** Returns the {@link ComponentTree} from the aggregated {@link ComponentDescriptor}s. */ - public ComponentTree getComponentTree(Elements elements) { - AggregatedMetadata aggregatedMetadata = - AggregatedMetadata.from(elements, componentMetadatas, componentBuilderMetadatas); + /** Returns the set of aggregated {@link ComponentDescriptor}s. */ + public ImmutableSet<ComponentDescriptor> getComponentDescriptors(Elements elements) { + ImmutableSet<DefineComponentClassesMetadata> aggregatedMetadatas = + DefineComponentClassesMetadata.from(elements); + + ImmutableSet<DefineComponentMetadata> components = + aggregatedMetadatas.stream() + .filter(DefineComponentClassesMetadata::isComponent) + .map(DefineComponentClassesMetadata::element) + .map(componentMetadatas::get) + .collect(toImmutableSet()); + + ImmutableSet<DefineComponentBuilderMetadata> builders = + aggregatedMetadatas.stream() + .filter(DefineComponentClassesMetadata::isComponentBuilder) + .map(DefineComponentClassesMetadata::element) + .map(componentBuilderMetadatas::get) + .collect(toImmutableSet()); + ListMultimap<DefineComponentMetadata, DefineComponentBuilderMetadata> builderMultimap = ArrayListMultimap.create(); - aggregatedMetadata.builders() - .forEach(builder -> builderMultimap.put(builder.componentMetadata(), builder)); + builders.forEach(builder -> builderMultimap.put(builder.componentMetadata(), builder)); // Check that there are not multiple builders per component for (DefineComponentMetadata componentMetadata : builderMultimap.keySet()) { @@ -119,10 +121,9 @@ public final class DefineComponents { Map<DefineComponentMetadata, DefineComponentBuilderMetadata> builderMap = new LinkedHashMap<>(); builderMultimap.entries().forEach(e -> builderMap.put(e.getKey(), e.getValue())); - - return ComponentTree.from(aggregatedMetadata.components().stream() + return components.stream() .map(componentMetadata -> toComponentDescriptor(componentMetadata, builderMap)) - .collect(toImmutableSet())); + .collect(toImmutableSet()); } private static ComponentDescriptor toComponentDescriptor( @@ -146,80 +147,4 @@ public final class DefineComponents { return builder.build(); } - - @AutoValue - abstract static class AggregatedMetadata { - /** Returns the aggregated metadata for {@link DefineComponentClasses#component()}. */ - abstract ImmutableList<DefineComponentMetadata> components(); - - /** Returns the aggregated metadata for {@link DefineComponentClasses#builder()}. */ - abstract ImmutableList<DefineComponentBuilderMetadata> builders(); - - static AggregatedMetadata from( - Elements elements, - DefineComponentMetadatas componentMetadatas, - DefineComponentBuilderMetadatas componentBuilderMetadatas) { - PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE); - - if (packageElement == null) { - return new AutoValue_DefineComponents_AggregatedMetadata( - ImmutableList.of(), ImmutableList.of()); - } - - ImmutableList.Builder<DefineComponentMetadata> components = ImmutableList.builder(); - ImmutableList.Builder<DefineComponentBuilderMetadata> builders = ImmutableList.builder(); - for (Element element : packageElement.getEnclosedElements()) { - ProcessorErrors.checkState( - MoreElements.isType(element), - element, - "Only types may be in package %s. Did you add custom code in the package?", - packageElement); - - TypeElement typeElement = MoreElements.asType(element); - ProcessorErrors.checkState( - Processors.hasAnnotation(typeElement, ClassNames.DEFINE_COMPONENT_CLASSES), - typeElement, - "Class, %s, must be annotated with @%s. Found: %s.", - typeElement, - ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), - typeElement.getAnnotationMirrors()); - - Optional<TypeElement> component = defineComponentClass(elements, typeElement, "component"); - Optional<TypeElement> builder = defineComponentClass(elements, typeElement, "builder"); - ProcessorErrors.checkState( - component.isPresent() || builder.isPresent(), - typeElement, - "@DefineComponentClasses missing both `component` and `builder` members."); - - component.map(componentMetadatas::get).ifPresent(components::add); - builder.map(componentBuilderMetadatas::get).ifPresent(builders::add); - } - - return new AutoValue_DefineComponents_AggregatedMetadata( - components.build(), builders.build()); - } - - private static Optional<TypeElement> defineComponentClass( - Elements elements, Element element, String annotationMember) { - AnnotationMirror mirror = - Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES); - AnnotationValue value = getAnnotationElementAndValue(mirror, annotationMember).getValue(); - String className = AnnotationValues.getString(value); - - if (className.isEmpty()) { // The default value. - return Optional.empty(); - } - - TypeElement type = elements.getTypeElement(className); - ProcessorErrors.checkState( - type != null, - element, - "%s.%s(), has invalid value: `%s`.", - ClassNames.DEFINE_COMPONENT_CLASSES.simpleName(), - annotationMember, - className); - - return Optional.of(type); - } - } } diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java new file mode 100644 index 000000000..ae341189c --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.earlyentrypoint; + +import com.squareup.javapoet.AnnotationSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** + * Generates an {@link dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint} + * annotation. + */ +final class AggregatedEarlyEntryPointGenerator { + + private final ProcessingEnvironment env; + private final TypeElement earlyEntryPoint; + + AggregatedEarlyEntryPointGenerator(TypeElement earlyEntryPoint, ProcessingEnvironment env) { + this.earlyEntryPoint = earlyEntryPoint; + this.env = env; + } + + void generate() throws IOException { + Processors.generateAggregatingClass( + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE, + AnnotationSpec.builder(ClassNames.AGGREGATED_EARLY_ENTRY_POINT) + .addMember("earlyEntryPoint", "$S", earlyEntryPoint.getQualifiedName()) + .build(), + earlyEntryPoint, + getClass(), + env); + } +} diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java new file mode 100644 index 000000000..ed347bd17 --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.earlyentrypoint; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * A class that represents the values stored in an {@link + * dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation. + */ +@AutoValue +public abstract class AggregatedEarlyEntryPointMetadata { + + /** Returns the element annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */ + public abstract TypeElement earlyEntryPoint(); + + /** Returns all aggregated deps in the aggregating package. */ + public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE, + ClassNames.AGGREGATED_EARLY_ENTRY_POINT, + elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static AggregatedEarlyEntryPointMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_EARLY_ENTRY_POINT); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_AggregatedEarlyEntryPointMetadata( + elements.getTypeElement(AnnotationValues.getString(values.get("earlyEntryPoint")))); + } +} diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD new file mode 100644 index 000000000..3573e8603 --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD @@ -0,0 +1,63 @@ +# Copyright (C) 2020 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. + +# Description: +# A processor for @dagger.hilt.android.EarlyEntryPoint. + +package(default_visibility = ["//:src"]) + +java_plugin( + name = "processor", + generates_api = 1, + processor_class = "dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor", + deps = [":processor_lib"], +) + +java_library( + name = "processor_lib", + srcs = [ + "AggregatedEarlyEntryPointGenerator.java", + "EarlyEntryPointProcessor.java", + ], + deps = [ + "//java/dagger/hilt/processor/internal:base_processor", + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/auto:service", + "@google_bazel_common//third_party/java/incap", + "@google_bazel_common//third_party/java/javapoet", + "@maven//:com_google_auto_auto_common", + ], +) + +java_library( + name = "aggregated_early_entry_point_metadata", + srcs = [ + "AggregatedEarlyEntryPointMetadata.java", + ], + deps = [ + "//java/dagger/hilt/processor/internal:aggregated_elements", + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/auto:value", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java new file mode 100644 index 000000000..848fa319d --- /dev/null +++ b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.earlyentrypoint; + +import static com.google.auto.common.MoreElements.asType; +import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.BaseProcessor; +import dagger.hilt.processor.internal.ClassNames; +import javax.annotation.processing.Processor; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; + +/** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */ +@IncrementalAnnotationProcessor(ISOLATING) +@AutoService(Processor.class) +public final class EarlyEntryPointProcessor extends BaseProcessor { + + @Override + public ImmutableSet<String> getSupportedAnnotationTypes() { + return ImmutableSet.of(ClassNames.EARLY_ENTRY_POINT.toString()); + } + + @Override + public void processEach(TypeElement annotation, Element element) throws Exception { + new AggregatedEarlyEntryPointGenerator(asType(element), getProcessingEnv()).generate(); + } +} diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java new file mode 100644 index 000000000..722b99b72 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import com.squareup.javapoet.AnnotationSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** Generates an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. */ +final class AggregatedRootGenerator { + private final TypeElement rootElement; + private final TypeElement rootAnnotation; + private final ProcessingEnvironment processingEnv; + + AggregatedRootGenerator( + TypeElement rootElement, TypeElement rootAnnotation, ProcessingEnvironment processingEnv) { + this.rootElement = rootElement; + this.rootAnnotation = rootAnnotation; + this.processingEnv = processingEnv; + } + + void generate() throws IOException { + Processors.generateAggregatingClass( + ClassNames.AGGREGATED_ROOT_PACKAGE, + AnnotationSpec.builder(ClassNames.AGGREGATED_ROOT) + .addMember("root", "$S", rootElement.getQualifiedName()) + .addMember("rootAnnotation", "$T.class", rootAnnotation) + .build(), + rootElement, + getClass(), + processingEnv); + } +} diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java new file mode 100644 index 000000000..961689b7c --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * Represents the values stored in an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. + */ +@AutoValue +abstract class AggregatedRootMetadata { + + /** Returns the element that was annotated with the root annotation. */ + abstract TypeElement rootElement(); + + /** Returns the root annotation as an element. */ + abstract TypeElement rootAnnotation(); + + static ImmutableSet<AggregatedRootMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static AggregatedRootMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ROOT); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_AggregatedRootMetadata( + elements.getTypeElement(AnnotationValues.getString(values.get("root"))), + AnnotationValues.getTypeElement(values.get("rootAnnotation"))); + } +} diff --git a/java/dagger/hilt/processor/internal/root/BUILD b/java/dagger/hilt/processor/internal/root/BUILD index 709eb7f5f..5ce762fa6 100644 --- a/java/dagger/hilt/processor/internal/root/BUILD +++ b/java/dagger/hilt/processor/internal/root/BUILD @@ -29,11 +29,14 @@ java_plugin( java_library( name = "processor_lib", srcs = [ + "AggregatedRootGenerator.java", + "ComponentGenerator.java", + "EarlySingletonComponentCreatorGenerator.java", + "ProcessedRootSentinelGenerator.java", "RootFileFormatter.java", "RootGenerator.java", "RootProcessor.java", "TestComponentDataGenerator.java", - "TestComponentDataSupplierGenerator.java", "TestInjectorGenerator.java", ], deps = [ @@ -41,6 +44,7 @@ java_library( ":root_type", "//java/dagger/hilt/processor/internal:base_processor", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:component_names", "//java/dagger/hilt/processor/internal:processor_errors", @@ -62,13 +66,18 @@ java_library( java_library( name = "root_metadata", srcs = [ + "AggregatedRootMetadata.java", + "ComponentTree.java", + "ProcessedRootSentinelMetadata.java", "Root.java", "RootMetadata.java", "TestRootMetadata.java", ], deps = [ ":root_type", + "//java/dagger/hilt/processor/internal:aggregated_elements", "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:compiler_options", "//java/dagger/hilt/processor/internal:component_descriptor", "//java/dagger/hilt/processor/internal:kotlin", "//java/dagger/hilt/processor/internal:processor_errors", @@ -79,6 +88,7 @@ java_library( "//java/dagger/internal/codegen/kotlin", "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", + "//java/dagger/internal/guava:graph", "@google_bazel_common//third_party/java/auto:value", "@google_bazel_common//third_party/java/javapoet", "@maven//:com_google_auto_auto_common", @@ -90,7 +100,6 @@ java_library( srcs = ["RootType.java"], deps = [ "//java/dagger/hilt/processor/internal:classnames", - "//java/dagger/hilt/processor/internal:processor_errors", "//java/dagger/hilt/processor/internal:processors", "@google_bazel_common//third_party/java/javapoet", ], diff --git a/java/dagger/hilt/processor/internal/ComponentGenerator.java b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java index 3a4bf1e78..0e8d0ad80 100644 --- a/java/dagger/hilt/processor/internal/ComponentGenerator.java +++ b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.hilt.processor.internal; +package dagger.hilt.processor.internal.root; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static java.util.Comparator.comparing; @@ -28,6 +28,8 @@ import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; @@ -36,11 +38,9 @@ import java.util.Optional; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; /** Generates a Dagger component or subcomponent interface. */ -// TODO(bcorso): Make this non-public -public final class ComponentGenerator { +final class ComponentGenerator { private static final Joiner JOINER = Joiner.on("."); private static final Comparator<ClassName> SIMPLE_NAME_SORTER = Comparator.comparing((ClassName c) -> JOINER.join(c.simpleNames())) @@ -49,7 +49,6 @@ public final class ComponentGenerator { private final ProcessingEnvironment processingEnv; private final ClassName name; - private final TypeElement rootElement; private final Optional<ClassName> superclass; private final ImmutableList<ClassName> modules; private final ImmutableList<TypeName> entryPoints; @@ -61,7 +60,6 @@ public final class ComponentGenerator { public ComponentGenerator( ProcessingEnvironment processingEnv, ClassName name, - TypeElement rootElement, Optional<ClassName> superclass, Set<? extends ClassName> modules, Set<? extends TypeName> entryPoints, @@ -71,7 +69,6 @@ public final class ComponentGenerator { Optional<TypeSpec> componentBuilder) { this.processingEnv = processingEnv; this.name = name; - this.rootElement = rootElement; this.superclass = superclass; this.modules = modules.stream().sorted(SIMPLE_NAME_SORTER).collect(toImmutableList()); this.entryPoints = entryPoints.stream().sorted(TYPE_NAME_SORTER).collect(toImmutableList()); @@ -81,25 +78,24 @@ public final class ComponentGenerator { this.componentBuilder = componentBuilder; } - public TypeSpec generate() throws IOException { - TypeSpec.Builder generator = + public TypeSpec.Builder typeSpecBuilder() throws IOException { + TypeSpec.Builder builder = TypeSpec.classBuilder(name) // Public because components from a scope below must reference to create .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) - .addOriginatingElement(rootElement) .addAnnotation(getComponentAnnotation()); - componentBuilder.ifPresent(generator::addType); + componentBuilder.ifPresent(builder::addType); - scopes.forEach(generator::addAnnotation); + scopes.forEach(builder::addAnnotation); - addEntryPoints(generator); + addEntryPoints(builder); - superclass.ifPresent(generator::superclass); + superclass.ifPresent(builder::superclass); - generator.addAnnotations(extraAnnotations); + builder.addAnnotations(extraAnnotations); - return generator.build(); + return builder; } /** Returns the component annotation with the list of modules to install for the component. */ @@ -160,7 +156,6 @@ public final class ComponentGenerator { Processors.getEnclosedClassName(name), "_EntryPointPartition" + partitionIndex); TypeSpec.Builder builder = TypeSpec.interfaceBuilder(partitionName) - .addOriginatingElement(rootElement) .addModifiers(Modifier.ABSTRACT) .addSuperinterfaces(partition); diff --git a/java/dagger/hilt/processor/internal/ComponentTree.java b/java/dagger/hilt/processor/internal/root/ComponentTree.java index 6d2137a02..fe3d4b133 100644 --- a/java/dagger/hilt/processor/internal/ComponentTree.java +++ b/java/dagger/hilt/processor/internal/root/ComponentTree.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.hilt.processor.internal; +package dagger.hilt.processor.internal.root; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; @@ -27,17 +27,18 @@ import com.google.common.graph.Graphs; import com.google.common.graph.ImmutableGraph; import com.google.common.graph.MutableGraph; import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.ComponentDescriptor; import java.util.HashMap; import java.util.Map; import java.util.Set; /** A representation of the full tree of scopes. */ -public final class ComponentTree { +final class ComponentTree { private final ImmutableGraph<ComponentDescriptor> graph; private final ComponentDescriptor root; /** Creates a new tree from a set of descriptors. */ - public static ComponentTree from(Set<ComponentDescriptor> descriptors) { + static ComponentTree from(Set<ComponentDescriptor> descriptors) { MutableGraph<ComponentDescriptor> graph = GraphBuilder.directed().allowsSelfLoops(false).build(); @@ -88,19 +89,19 @@ public final class ComponentTree { root = Iterables.getOnlyElement(roots); } - public ImmutableSet<ComponentDescriptor> getComponentDescriptors() { + ImmutableSet<ComponentDescriptor> getComponentDescriptors() { return ImmutableSet.copyOf(graph.nodes()); } - public ImmutableSet<ComponentDescriptor> childrenOf(ComponentDescriptor componentDescriptor) { + ImmutableSet<ComponentDescriptor> childrenOf(ComponentDescriptor componentDescriptor) { return ImmutableSet.copyOf(graph.successors(componentDescriptor)); } - public ImmutableGraph<ComponentDescriptor> graph() { + ImmutableGraph<ComponentDescriptor> graph() { return graph; } - public ComponentDescriptor root() { + ComponentDescriptor root() { return root; } } diff --git a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java new file mode 100644 index 000000000..1dbda3982 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; + +/** Generator for the {@code EarlySingletonComponentCreator}. */ +final class EarlySingletonComponentCreatorGenerator { + private static final ClassName EARLY_SINGLETON_COMPONENT_CREATOR = + ClassName.get("dagger.hilt.android.internal.testing", "EarlySingletonComponentCreator"); + private static final ClassName EARLY_SINGLETON_COMPONENT_CREATOR_IMPL = + ClassName.get( + "dagger.hilt.android.internal.testing", "EarlySingletonComponentCreatorImpl"); + private static final ClassName DEFAULT_COMPONENT_IMPL = + ClassName.get( + "dagger.hilt.android.internal.testing.root", "DaggerDefault_HiltComponents_SingletonC"); + + static void generate(ProcessingEnvironment env) throws IOException { + TypeSpec.Builder builder = + TypeSpec.classBuilder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL) + .superclass(EARLY_SINGLETON_COMPONENT_CREATOR) + .addMethod( + MethodSpec.methodBuilder("create") + .returns(ClassName.OBJECT) + .addStatement( + "return $T.builder()\n" + + ".applicationContextModule(new $T($T.getApplicationContext()))\n" + + ".build()", + DEFAULT_COMPONENT_IMPL, + ClassNames.APPLICATION_CONTEXT_MODULE, + ClassNames.APPLICATION_PROVIDER) + .build()); + + Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString()); + + JavaFile.builder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL.packageName(), builder.build()) + .build() + .writeTo(env.getFiler()); + } + + private EarlySingletonComponentCreatorGenerator() {} +} diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java new file mode 100644 index 000000000..87efa20a1 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import com.squareup.javapoet.AnnotationSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** Generates an {@link dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel}. */ +final class ProcessedRootSentinelGenerator { + private final TypeElement processedRoot; + private final ProcessingEnvironment processingEnv; + + ProcessedRootSentinelGenerator(TypeElement processedRoot, ProcessingEnvironment processingEnv) { + this.processedRoot = processedRoot; + this.processingEnv = processingEnv; + } + + void generate() throws IOException { + Processors.generateAggregatingClass( + ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE, + AnnotationSpec.builder(ClassNames.PROCESSED_ROOT_SENTINEL) + .addMember("roots", "$S", processedRoot.getQualifiedName()) + .build(), + processedRoot, + getClass(), + processingEnv); + } +} diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java new file mode 100644 index 000000000..d60015495 --- /dev/null +++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * Represents the values stored in an {@link + * dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel}. + */ +@AutoValue +abstract class ProcessedRootSentinelMetadata { + + /** Returns the processed root elements. */ + abstract ImmutableSet<TypeElement> rootElements(); + + static ImmutableSet<ProcessedRootSentinelMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE, + ClassNames.PROCESSED_ROOT_SENTINEL, + elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static ProcessedRootSentinelMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.PROCESSED_ROOT_SENTINEL); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_ProcessedRootSentinelMetadata( + AnnotationValues.getStrings(values.get("roots")).stream() + .map(elements::getTypeElement) + .collect(toImmutableSet())); + } +} diff --git a/java/dagger/hilt/processor/internal/root/Root.java b/java/dagger/hilt/processor/internal/root/Root.java index 981636d62..24440b0c9 100644 --- a/java/dagger/hilt/processor/internal/root/Root.java +++ b/java/dagger/hilt/processor/internal/root/Root.java @@ -19,6 +19,7 @@ package dagger.hilt.processor.internal.root; import com.google.auto.common.MoreElements; import com.google.auto.value.AutoValue; import com.squareup.javapoet.ClassName; +import dagger.hilt.processor.internal.ClassNames; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; @@ -26,18 +27,37 @@ import javax.lang.model.element.TypeElement; /** Metadata for a root element that can trigger the {@link RootProcessor}. */ @AutoValue abstract class Root { + /** + * Creates the default root for this (test) build compilation. + * + * <p>A default root installs only the global {@code InstallIn} and {@code TestInstallIn} + * dependencies. Test-specific dependencies are not installed in the default root. + * + * <p>The default root is used for two purposes: + * + * <ul> + * <li>To inject {@code EarlyEntryPoint} annotated interfaces. + * <li>To inject tests that only depend on global dependencies + * </ul> + */ + static Root createDefaultRoot(ProcessingEnvironment env) { + TypeElement rootElement = + env.getElementUtils().getTypeElement(ClassNames.DEFAULT_ROOT.canonicalName()); + return new AutoValue_Root(rootElement, /*isTestRoot=*/ true); + } + /** Creates a {@plainlink Root root} for the given {@plainlink Element element}. */ static Root create(Element element, ProcessingEnvironment env) { TypeElement rootElement = MoreElements.asType(element); - return new AutoValue_Root(RootType.of(env, rootElement), rootElement); + return new AutoValue_Root(rootElement, RootType.of(rootElement).isTestRoot()); } - /** Returns the type of the root {@code element}. */ - abstract RootType type(); - /** Returns the root element that should be used with processing. */ abstract TypeElement element(); + /** Returns {@code true} if this is a test root. */ + abstract boolean isTestRoot(); + /** Returns the class name of the root element. */ ClassName classname() { return ClassName.get(element()); @@ -48,7 +68,8 @@ abstract class Root { return element().toString(); } - boolean isTestRoot() { - return type().isTestRoot(); + /** Returns {@code true} if this uses the default root. */ + boolean isDefaultRoot() { + return classname().equals(ClassNames.DEFAULT_ROOT); } } diff --git a/java/dagger/hilt/processor/internal/root/RootGenerator.java b/java/dagger/hilt/processor/internal/root/RootGenerator.java index c2c55e6c4..f87fbd90b 100644 --- a/java/dagger/hilt/processor/internal/root/RootGenerator.java +++ b/java/dagger/hilt/processor/internal/root/RootGenerator.java @@ -16,6 +16,7 @@ package dagger.hilt.processor.internal.root; +import static com.google.common.base.Preconditions.checkState; import static dagger.hilt.processor.internal.Processors.toClassNames; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.Modifier.ABSTRACT; @@ -35,11 +36,11 @@ import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ComponentGenerator; import dagger.hilt.processor.internal.ComponentNames; -import dagger.hilt.processor.internal.ComponentTree; import dagger.hilt.processor.internal.Processors; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Modifier; @@ -47,20 +48,27 @@ import javax.lang.model.element.Modifier; /** Generates components and any other classes needed for a root. */ final class RootGenerator { - static void generate(RootMetadata metadata, ProcessingEnvironment env) throws IOException { + static void generate( + RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env) + throws IOException { new RootGenerator( - RootMetadata.copyWithNewTree( - metadata, - filterDescriptors(metadata.componentTree())), - env).generateComponents(); + RootMetadata.copyWithNewTree(metadata, filterDescriptors(metadata.componentTree())), + componentNames, + env) + .generateComponents(); } private final RootMetadata metadata; private final ProcessingEnvironment env; private final Root root; + private final Map<String, Integer> simpleComponentNamesToDedupeSuffix = new HashMap<>(); + private final Map<ComponentDescriptor, ClassName> componentNameMap = new HashMap<>(); + private final ComponentNames componentNames; - private RootGenerator(RootMetadata metadata, ProcessingEnvironment env) { + private RootGenerator( + RootMetadata metadata, ComponentNames componentNames, ProcessingEnvironment env) { this.metadata = metadata; + this.componentNames = componentNames; this.env = env; this.root = metadata.root(); } @@ -68,8 +76,9 @@ final class RootGenerator { private void generateComponents() throws IOException { // TODO(bcorso): Consider moving all of this logic into ComponentGenerator? + ClassName componentsWrapperClassName = getComponentsWrapperClassName(); TypeSpec.Builder componentsWrapper = - TypeSpec.classBuilder(getComponentsWrapperClassName()) + TypeSpec.classBuilder(componentsWrapperClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build()); @@ -93,7 +102,6 @@ final class RootGenerator { new ComponentGenerator( env, getComponentClassName(componentDescriptor), - root.element(), Optional.empty(), modules, metadata.entryPoints(componentDescriptor.component()), @@ -101,11 +109,14 @@ final class RootGenerator { ImmutableList.of(), componentAnnotation(componentDescriptor), componentBuilder(componentDescriptor)) - .generate().toBuilder().addModifiers(Modifier.STATIC).build()); + .typeSpecBuilder() + .addModifiers(Modifier.STATIC) + .build()); } RootFileFormatter.write( - JavaFile.builder(root.classname().packageName(), componentsWrapper.build()).build(), + JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build()) + .build(), env.getFiler()); } @@ -130,7 +141,7 @@ final class RootGenerator { } private ImmutableMap<ComponentDescriptor, ClassName> subcomponentBuilderModules( - TypeSpec.Builder componentsWrapper) throws IOException { + TypeSpec.Builder componentsWrapper) { ImmutableMap.Builder<ComponentDescriptor, ClassName> modules = ImmutableMap.builder(); for (ComponentDescriptor descriptor : metadata.componentTree().getComponentDescriptors()) { // Root component builders don't have subcomponent builder modules @@ -151,7 +162,7 @@ final class RootGenerator { // @Binds FooSubcomponentInterfaceBuilder bind(FooSubcomponent.Builder builder); // } private TypeSpec subcomponentBuilderModule( - ClassName componentName, ClassName builderName, ClassName moduleName) throws IOException { + ClassName componentName, ClassName builderName, ClassName moduleName) { TypeSpec.Builder subcomponentBuilderModule = TypeSpec.interfaceBuilder(moduleName) .addOriginatingElement(root.element()) @@ -210,10 +221,38 @@ final class RootGenerator { } private ClassName getComponentsWrapperClassName() { - return ComponentNames.generatedComponentsWrapper(root.classname()); + return componentNames.generatedComponentsWrapper(root.classname()); } private ClassName getComponentClassName(ComponentDescriptor componentDescriptor) { - return ComponentNames.generatedComponent(root.classname(), componentDescriptor.component()); + if (componentNameMap.containsKey(componentDescriptor)) { + return componentNameMap.get(componentDescriptor); + } + + // Disallow any component names with the same name as our SingletonComponent because we treat + // that component specially and things may break. + checkState( + componentDescriptor.component().equals(ClassNames.SINGLETON_COMPONENT) + || !componentDescriptor.component().simpleName().equals( + ClassNames.SINGLETON_COMPONENT.simpleName()), + "Cannot have a component with the same simple name as the reserved %s: %s", + ClassNames.SINGLETON_COMPONENT.simpleName(), + componentDescriptor.component()); + + ClassName generatedComponent = + componentNames.generatedComponent(root.classname(), componentDescriptor.component()); + + Integer suffix = simpleComponentNamesToDedupeSuffix.get(generatedComponent.simpleName()); + if (suffix != null) { + // If an entry exists, use the suffix in the map and the replace it with the value incremented + generatedComponent = Processors.append(generatedComponent, String.valueOf(suffix)); + simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), suffix + 1); + } else { + // Otherwise, just add an entry for any possible future duplicates + simpleComponentNamesToDedupeSuffix.put(generatedComponent.simpleName(), 2); + } + + componentNameMap.put(componentDescriptor, generatedComponent); + return generatedComponent; } } diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java index 4c43f1d61..b39b590ef 100644 --- a/java/dagger/hilt/processor/internal/root/RootMetadata.java +++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java @@ -16,20 +16,22 @@ package dagger.hilt.processor.internal.root; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Suppliers.memoize; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ComponentDescriptor; -import dagger.hilt.processor.internal.ComponentTree; import dagger.hilt.processor.internal.KotlinMetadataUtils; import dagger.hilt.processor.internal.Processors; import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; @@ -53,15 +55,34 @@ public final class RootMetadata { ComponentTree componentTree, ComponentDependencies deps, ProcessingEnvironment env) { - RootMetadata metadata = new RootMetadata(root, componentTree, deps, env); - metadata.validate(); - return metadata; + return createInternal(root, ImmutableList.of(), componentTree, deps, env); + } + + static RootMetadata createForDefaultRoot( + Root root, + ImmutableList<RootMetadata> rootsUsingDefaultComponents, + ComponentTree componentTree, + ComponentDependencies deps, + ProcessingEnvironment env) { + checkState(root.isDefaultRoot()); + return createInternal(root, rootsUsingDefaultComponents, componentTree, deps, env); + } + + static RootMetadata copyWithNewTree(RootMetadata other, ComponentTree componentTree) { + return createInternal( + other.root, other.rootsUsingDefaultComponents, componentTree, other.deps, other.env); } - static RootMetadata copyWithNewTree( - RootMetadata other, - ComponentTree componentTree) { - return create(other.root, componentTree, other.deps, other.env); + private static RootMetadata createInternal( + Root root, + ImmutableList<RootMetadata> rootsUsingDefaultComponents, + ComponentTree componentTree, + ComponentDependencies deps, + ProcessingEnvironment env) { + RootMetadata metadata = + new RootMetadata(root, componentTree, deps, rootsUsingDefaultComponents, env); + metadata.validate(); + return metadata; } private final Root root; @@ -69,6 +90,7 @@ public final class RootMetadata { private final Elements elements; private final ComponentTree componentTree; private final ComponentDependencies deps; + private final ImmutableList<RootMetadata> rootsUsingDefaultComponents; private final Supplier<ImmutableSetMultimap<ClassName, ClassName>> scopesByComponent = memoize(this::getScopesByComponentUncached); private final Supplier<TestRootMetadata> testRootMetadata = @@ -78,12 +100,14 @@ public final class RootMetadata { Root root, ComponentTree componentTree, ComponentDependencies deps, + ImmutableList<RootMetadata> rootsUsingDefaultComponents, ProcessingEnvironment env) { this.root = root; this.env = env; this.elements = env.getElementUtils(); this.componentTree = componentTree; this.deps = deps; + this.rootsUsingDefaultComponents = rootsUsingDefaultComponents; } public Root root() { @@ -102,9 +126,25 @@ public final class RootMetadata { return deps.modules().get(componentName, root.classname(), root.isTestRoot()); } + /** + * Returns {@code true} if this is a test root that provides no test-specific dependencies or sets + * other options that would prevent it from sharing components with other test roots. + */ + // TODO(groakley): Allow more tests to share modules, e.g. tests that uninstall the same module. + // In that case, this might instead return which shared dep grouping should be used. + public boolean canShareTestComponents() { + return isSharedTestComponentsEnabled(env) + && root.isTestRoot() + && !deps.includesTestDeps(root.classname()); + } + public ImmutableSet<TypeName> entryPoints(ClassName componentName) { return ImmutableSet.<TypeName>builder() .addAll(getUserDefinedEntryPoints(componentName)) + .add( + root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT) + ? ClassNames.TEST_SINGLETON_COMPONENT + : ClassNames.GENERATED_COMPONENT) .add(componentName) .build(); } @@ -128,6 +168,7 @@ public final class RootMetadata { } public TestRootMetadata testRootMetadata() { + checkState(!root.isDefaultRoot(), "The default root does not have TestRootMetadata!"); return testRootMetadata.get(); } @@ -149,7 +190,7 @@ public final class RootMetadata { for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) { ClassName componentName = componentDescriptor.component(); for (TypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) { - if (root.type().isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) { + if (root.isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) { env.getMessager() .printMessage( Diagnostic.Kind.ERROR, @@ -157,7 +198,7 @@ public final class RootMetadata { + "static provision methods or have a visible, no-arg constructor. Found: " + extraModule.getQualifiedName(), root.element()); - } else if (!root.type().isTestRoot()) { + } else if (!root.isTestRoot()) { env.getMessager() .printMessage( Diagnostic.Kind.ERROR, @@ -172,10 +213,19 @@ public final class RootMetadata { private ImmutableSet<TypeName> getUserDefinedEntryPoints(ClassName componentName) { ImmutableSet.Builder<TypeName> entryPointSet = ImmutableSet.builder(); - entryPointSet.add(ClassNames.GENERATED_COMPONENT); - for (TypeElement element : - deps.entryPoints().get(componentName, root.classname(), root.isTestRoot())) { - entryPointSet.add(ClassName.get(element)); + if (root.isDefaultRoot() && !rootsUsingDefaultComponents.isEmpty()) { + // Add entry points for shared component + rootsUsingDefaultComponents.stream() + .flatMap(metadata -> metadata.entryPoints(componentName).stream()) + .forEach(entryPointSet::add); + } else if (root.isDefaultRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)) { + // We only do this for SingletonComponent because EarlyEntryPoints can only be installed + // in the SingletonComponent. + deps.earlyEntryPoints().forEach(entryPointSet::add); + } else { + deps.entryPoints().get(componentName, root.classname(), root.isTestRoot()).stream() + .map(ClassName::get) + .forEach(entryPointSet::add); } return entryPointSet.build(); } @@ -188,7 +238,7 @@ public final class RootMetadata { .flatMap(descriptor -> descriptor.scopes().stream()) .collect(toImmutableSet()); - AliasOfs aliasOfs = new AliasOfs(env, defineComponentScopes); + AliasOfs aliasOfs = AliasOfs.create(env.getElementUtils(), defineComponentScopes); for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) { for (ClassName scope : componentDescriptor.scopes()) { diff --git a/java/dagger/hilt/processor/internal/root/RootProcessor.java b/java/dagger/hilt/processor/internal/root/RootProcessor.java index 8f8c94874..1ee4446d4 100644 --- a/java/dagger/hilt/processor/internal/root/RootProcessor.java +++ b/java/dagger/hilt/processor/internal/root/RootProcessor.java @@ -17,8 +17,11 @@ package dagger.hilt.processor.internal.root; import static com.google.common.base.Preconditions.checkState; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled; +import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; +import static java.util.Comparator.comparing; import static javax.lang.model.element.Modifier.PUBLIC; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING; @@ -28,16 +31,17 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.BaseProcessor; -import dagger.hilt.processor.internal.ComponentTree; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.ComponentDescriptor; +import dagger.hilt.processor.internal.ComponentNames; import dagger.hilt.processor.internal.ProcessorErrors; import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies; import dagger.hilt.processor.internal.definecomponent.DefineComponents; import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; @@ -50,9 +54,10 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; @IncrementalAnnotationProcessor(AGGREGATING) @AutoService(Processor.class) public final class RootProcessor extends BaseProcessor { - private final List<ClassName> rootNames = new ArrayList<>(); + private static final Comparator<TypeElement> QUALIFIED_NAME_COMPARATOR = + comparing(TypeElement::getQualifiedName, (n1, n2) -> n1.toString().compareTo(n2.toString())); + private final Set<ClassName> processed = new HashSet<>(); - private boolean isTestEnv; // TODO(bcorso): Consider using a Dagger component to create/scope these objects private final DefineComponents defineComponents = DefineComponents.create(); private GeneratesRootInputs generatesRootInputs; @@ -76,25 +81,13 @@ public final class RootProcessor extends BaseProcessor { @Override public void processEach(TypeElement annotation, Element element) throws Exception { TypeElement rootElement = MoreElements.asType(element); - boolean isTestRoot = RootType.of(getProcessingEnv(), rootElement).isTestRoot(); - checkState( - rootNames.isEmpty() || isTestEnv == isTestRoot, - "Cannot mix test roots with non-test roots:" - + "\n\tNon-Test Roots: %s" - + "\n\tTest Roots: %s", - isTestRoot ? rootNames : rootElement, - isTestRoot ? rootElement : rootNames); - isTestEnv = isTestRoot; - - rootNames.add(ClassName.get(rootElement)); - if (isTestEnv) { + RootType rootType = RootType.of(rootElement); + if (rootType.isTestRoot()) { new TestInjectorGenerator( - getProcessingEnv(), - TestRootMetadata.of(getProcessingEnv(), rootElement)).generate(); - } else { - ProcessorErrors.checkState( - rootNames.size() <= 1, element, "More than one root found: %s", rootNames); + getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement)) + .generate(); } + new AggregatedRootGenerator(rootElement, annotation, getProcessingEnv()).generate(); } @Override @@ -114,14 +107,22 @@ public final class RootProcessor extends BaseProcessor { return; } - ImmutableList<Root> rootsToProcess = - rootNames.stream() - .filter(rootName -> !processed.contains(rootName)) - // We create a new root element each round to avoid the jdk8 bug where - // TypeElement.equals does not work for elements across processing rounds. - .map(rootName -> getElementUtils().getTypeElement(rootName.toString())) + ImmutableSet<Root> allRoots = + AggregatedRootMetadata.from(getElementUtils()).stream() + .map(metadata -> Root.create(metadata.rootElement(), getProcessingEnv())) + .collect(toImmutableSet()); + + ImmutableSet<Root> processedRoots = + ProcessedRootSentinelMetadata.from(getElementUtils()).stream() + .flatMap(metadata -> metadata.rootElements().stream()) .map(rootElement -> Root.create(rootElement, getProcessingEnv())) - .collect(toImmutableList()); + .collect(toImmutableSet()); + + ImmutableSet<Root> rootsToProcess = + allRoots.stream() + .filter(root -> !processedRoots.contains(root)) + .filter(root -> !processed.contains(rootName(root))) + .collect(toImmutableSet()); if (rootsToProcess.isEmpty()) { // Skip further processing since there's no roots that need processing. @@ -132,40 +133,163 @@ public final class RootProcessor extends BaseProcessor { // all roots. We should consider if it's worth trying to continue processing for other // roots. At the moment, I think it's rare that if one root failed the others would not. try { - ComponentTree tree = defineComponents.getComponentTree(getElementUtils()); - ComponentDependencies deps = ComponentDependencies.from( - tree.getComponentDescriptors(), getElementUtils()); + validateRoots(allRoots, rootsToProcess); + + boolean isTestEnv = rootsToProcess.stream().anyMatch(Root::isTestRoot); + ComponentNames componentNames = + isTestEnv && isSharedTestComponentsEnabled(getProcessingEnv()) + ? ComponentNames.withRenamingIntoPackage( + ClassNames.DEFAULT_ROOT.packageName(), + rootsToProcess.stream().map(Root::element).collect(toImmutableList())) + : ComponentNames.withoutRenaming(); + + ImmutableSet<ComponentDescriptor> componentDescriptors = + defineComponents.getComponentDescriptors(getElementUtils()); + ComponentTree tree = ComponentTree.from(componentDescriptors); + ComponentDependencies deps = + ComponentDependencies.from(componentDescriptors, getElementUtils()); ImmutableList<RootMetadata> rootMetadatas = rootsToProcess.stream() .map(root -> RootMetadata.create(root, tree, deps, getProcessingEnv())) .collect(toImmutableList()); for (RootMetadata rootMetadata : rootMetadatas) { - setProcessingState(rootMetadata.root()); - generateComponents(rootMetadata); + if (!rootMetadata.canShareTestComponents()) { + generateComponents(rootMetadata, componentNames); + } } if (isTestEnv) { - generateTestComponentData(rootMetadatas); + ImmutableList<RootMetadata> rootsThatCanShareComponents = + rootMetadatas.stream() + .filter(RootMetadata::canShareTestComponents) + .collect(toImmutableList()); + generateTestComponentData(rootMetadatas, componentNames); + if (deps.hasEarlyEntryPoints() || !rootsThatCanShareComponents.isEmpty()) { + Root defaultRoot = Root.createDefaultRoot(getProcessingEnv()); + generateComponents( + RootMetadata.createForDefaultRoot( + defaultRoot, rootsThatCanShareComponents, tree, deps, getProcessingEnv()), + componentNames); + EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv()); + } } } catch (Exception e) { for (Root root : rootsToProcess) { - processed.add(root.classname()); + processed.add(rootName(root)); } throw e; + } finally { + rootsToProcess.forEach(this::setProcessingState); + // Calculate the roots processed in this round. We do this in the finally-block rather than in + // the try-block because the catch-block can change the processing state. + ImmutableSet<Root> rootsProcessedInRound = + rootsToProcess.stream() + // Only add a sentinel for processed roots. Skip preprocessed roots since those will + // will be processed in the next round. + .filter(root -> processed.contains(rootName(root))) + .collect(toImmutableSet()); + for (Root root : rootsProcessedInRound) { + new ProcessedRootSentinelGenerator(rootElement(root), getProcessingEnv()).generate(); + } + } + } + + private void validateRoots(ImmutableSet<Root> allRoots, ImmutableSet<Root> rootsToProcess) { + + ImmutableSet<TypeElement> rootElementsToProcess = + rootsToProcess.stream() + .map(Root::element) + .sorted(QUALIFIED_NAME_COMPARATOR) + .collect(toImmutableSet()); + + ImmutableSet<TypeElement> appRootElementsToProcess = + rootsToProcess.stream() + .filter(root -> !root.isTestRoot()) + .map(Root::element) + .sorted(QUALIFIED_NAME_COMPARATOR) + .collect(toImmutableSet()); + + // Perform validation between roots in this compilation unit. + if (!appRootElementsToProcess.isEmpty()) { + ImmutableSet<TypeElement> testRootElementsToProcess = + rootsToProcess.stream() + .filter(Root::isTestRoot) + .map(Root::element) + .sorted(QUALIFIED_NAME_COMPARATOR) + .collect(toImmutableSet()); + + ProcessorErrors.checkState( + testRootElementsToProcess.isEmpty(), + "Cannot process test roots and app roots in the same compilation unit:" + + "\n\tApp root in this compilation unit: %s" + + "\n\tTest roots in this compilation unit: %s", + appRootElementsToProcess, + testRootElementsToProcess); + + ProcessorErrors.checkState( + appRootElementsToProcess.size() == 1, + "Cannot process multiple app roots in the same compilation unit: %s", + appRootElementsToProcess); + } + + // Perform validation across roots previous compilation units. + if (!isCrossCompilationRootValidationDisabled(rootElementsToProcess, getProcessingEnv())) { + ImmutableSet<TypeElement> processedTestRootElements = + allRoots.stream() + .filter(Root::isTestRoot) + .filter(root -> !rootsToProcess.contains(root)) + .map(Root::element) + .sorted(QUALIFIED_NAME_COMPARATOR) + .collect(toImmutableSet()); + + // TODO(b/185742783): Add an explanation or link to docs to explain why we're forbidding this. + ProcessorErrors.checkState( + processedTestRootElements.isEmpty(), + "Cannot process new roots when there are test roots from a previous compilation unit:" + + "\n\tTest roots from previous compilation unit: %s" + + "\n\tAll roots from this compilation unit: %s", + processedTestRootElements, + rootElementsToProcess); + + ImmutableSet<TypeElement> processedAppRootElements = + allRoots.stream() + .filter(root -> !root.isTestRoot()) + .filter(root -> !rootsToProcess.contains(root)) + .map(Root::element) + .sorted(QUALIFIED_NAME_COMPARATOR) + .collect(toImmutableSet()); + + ProcessorErrors.checkState( + processedAppRootElements.isEmpty() || appRootElementsToProcess.isEmpty(), + "Cannot process app roots in this compilation unit since there are app roots in a " + + "previous compilation unit:" + + "\n\tApp roots in previous compilation unit: %s" + + "\n\tApp roots in this compilation unit: %s", + processedAppRootElements, + appRootElementsToProcess); } } private void setProcessingState(Root root) { - processed.add(root.classname()); + processed.add(rootName(root)); } - private void generateComponents(RootMetadata rootMetadata) throws IOException { - RootGenerator.generate(rootMetadata, getProcessingEnv()); + private ClassName rootName(Root root) { + return ClassName.get(rootElement(root)); } - private void generateTestComponentData(ImmutableList<RootMetadata> rootMetadatas) + private TypeElement rootElement(Root root) { + return root.element(); + } + + private void generateComponents(RootMetadata rootMetadata, ComponentNames componentNames) throws IOException { + RootGenerator.generate(rootMetadata, componentNames, getProcessingEnv()); + } + + private void generateTestComponentData( + ImmutableList<RootMetadata> rootMetadatas, ComponentNames componentNames) throws IOException { for (RootMetadata rootMetadata : rootMetadatas) { // TODO(bcorso): Consider moving this check earlier into processEach. TypeElement testElement = rootMetadata.testRootMetadata().testElement(); @@ -174,8 +298,7 @@ public final class RootProcessor extends BaseProcessor { testElement, "Hilt tests must be public, but found: %s", testElement); - new TestComponentDataGenerator(getProcessingEnv(), rootMetadata).generate(); + new TestComponentDataGenerator(getProcessingEnv(), rootMetadata, componentNames).generate(); } - new TestComponentDataSupplierGenerator(getProcessingEnv(), rootMetadatas).generate(); } } diff --git a/java/dagger/hilt/processor/internal/root/RootType.java b/java/dagger/hilt/processor/internal/root/RootType.java index 3545231c7..807f71d73 100644 --- a/java/dagger/hilt/processor/internal/root/RootType.java +++ b/java/dagger/hilt/processor/internal/root/RootType.java @@ -19,7 +19,6 @@ package dagger.hilt.processor.internal.root; import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; -import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; /** The valid root types for Hilt applications. */ @@ -47,7 +46,7 @@ import javax.lang.model.element.TypeElement; return annotation; } - public static RootType of(ProcessingEnvironment env, TypeElement element) { + public static RootType of(TypeElement element) { if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_APP)) { return ROOT; } else if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_TEST)) { diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java index 284f8cdee..101c124d9 100644 --- a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java +++ b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java @@ -20,6 +20,7 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static java.util.stream.Collectors.joining; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; +import static javax.lang.model.element.Modifier.PROTECTED; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.util.ElementFilter.constructorsIn; @@ -45,24 +46,28 @@ public final class TestComponentDataGenerator { private final ProcessingEnvironment processingEnv; private final RootMetadata rootMetadata; private final ClassName name; + private final ComponentNames componentNames; public TestComponentDataGenerator( ProcessingEnvironment processingEnv, - RootMetadata rootMetadata) { + RootMetadata rootMetadata, + ComponentNames componentNames) { this.processingEnv = processingEnv; this.rootMetadata = rootMetadata; + this.componentNames = componentNames; this.name = Processors.append( Processors.getEnclosedClassName(rootMetadata.testRootMetadata().testName()), - "_ComponentDataHolder"); + "_TestComponentDataSupplier"); } /** * * * <pre><code>{@code - * public final class FooTest_ComponentDataHolder { - * public static TestComponentData get() { + * public final class FooTest_TestComponentDataSupplier extends TestComponentDataSupplier { + * @Override + * protected TestComponentData get() { * return new TestComponentData( * false, // waitForBindValue * testInstance -> injectInternal(($1T) testInstance), @@ -83,15 +88,15 @@ public final class TestComponentDataGenerator { public void generate() throws IOException { TypeSpec.Builder generator = TypeSpec.classBuilder(name) + .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER) .addModifiers(PUBLIC, FINAL) - .addMethod(MethodSpec.constructorBuilder().addModifiers(PRIVATE).build()) .addMethod(getMethod()) .addMethod(getTestInjectInternalMethod()); Processors.addGeneratedAnnotation( generator, processingEnv, ClassNames.ROOT_PROCESSOR.toString()); - JavaFile.builder(rootMetadata.testRootMetadata().testName().packageName(), generator.build()) + JavaFile.builder(name.packageName(), generator.build()) .build() .writeTo(processingEnv.getFiler()); } @@ -99,8 +104,11 @@ public final class TestComponentDataGenerator { private MethodSpec getMethod() { TypeElement testElement = rootMetadata.testRootMetadata().testElement(); ClassName component = - ComponentNames.generatedComponent( - ClassName.get(testElement), ClassNames.SINGLETON_COMPONENT); + componentNames.generatedComponent( + rootMetadata.canShareTestComponents() + ? ClassNames.DEFAULT_ROOT + : ClassName.get(testElement), + ClassNames.SINGLETON_COMPONENT); ImmutableSet<TypeElement> daggerRequiredModules = rootMetadata.modulesThatDaggerCannotConstruct(ClassNames.SINGLETON_COMPONENT); ImmutableSet<TypeElement> hiltRequiredModules = @@ -109,7 +117,7 @@ public final class TestComponentDataGenerator { .collect(toImmutableSet()); return MethodSpec.methodBuilder("get") - .addModifiers(PUBLIC, STATIC) + .addModifiers(PROTECTED) .returns(ClassNames.TEST_COMPONENT_DATA) .addStatement( "return new $T($L, $L, $L, $L, $L)", @@ -202,14 +210,14 @@ public final class TestComponentDataGenerator { AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "unchecked") .build()) - .addStatement("$L.injectTest(testInstance)", getInjector(testElement)) + .addStatement(callInjectTest(testElement)) .build(); } - private static CodeBlock getInjector(TypeElement testElement) { + private CodeBlock callInjectTest(TypeElement testElement) { return CodeBlock.of( - "(($T) (($T) $T.getApplicationContext()).generatedComponent())", - ClassNames.TEST_INJECTOR, + "(($T) (($T) $T.getApplicationContext()).generatedComponent()).injectTest(testInstance)", + rootMetadata.testRootMetadata().testInjectorName(), ClassNames.GENERATED_COMPONENT_MANAGER, ClassNames.APPLICATION_PROVIDER); } diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java deleted file mode 100644 index e5a83b63c..000000000 --- a/java/dagger/hilt/processor/internal/root/TestComponentDataSupplierGenerator.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2020 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.hilt.processor.internal.root; - -import static javax.lang.model.element.Modifier.FINAL; -import static javax.lang.model.element.Modifier.PRIVATE; -import static javax.lang.model.element.Modifier.PROTECTED; -import static javax.lang.model.element.Modifier.PUBLIC; - -import com.google.common.collect.ImmutableList; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.WildcardTypeName; -import dagger.hilt.processor.internal.ClassNames; -import dagger.hilt.processor.internal.Processors; -import java.io.IOException; -import javax.annotation.processing.ProcessingEnvironment; - -/** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentDataSupplier} */ -public final class TestComponentDataSupplierGenerator { - private static final ClassName TEST_COMPONENT_DATA_SUPPLIER_IMPL = - ClassName.get("dagger.hilt.android.internal.testing", "TestComponentDataSupplierImpl"); - private static final ParameterizedTypeName CLASS_TYPE = - ParameterizedTypeName.get(ClassNames.CLASS, WildcardTypeName.subtypeOf(TypeName.OBJECT)); - private static final ParameterizedTypeName TEST_COMPONENT_DATA_MAP_TYPE = - ParameterizedTypeName.get(ClassNames.MAP, CLASS_TYPE, ClassNames.TEST_COMPONENT_DATA); - - private final ProcessingEnvironment processingEnv; - private final ImmutableList<RootMetadata> rootMetadatas; - - public TestComponentDataSupplierGenerator( - ProcessingEnvironment processingEnv, - ImmutableList<RootMetadata> rootMetadatas) { - this.processingEnv = processingEnv; - this.rootMetadatas = rootMetadatas; - } - - /** - * <pre><code>{@code - * public final class TestComponentDataSupplierImpl extends TestComponentDataSupplier { - * private final Map<Class<?>, TestComponentData> testComponentDataMap = new HashMap<>(); - * - * protected TestComponentDataSupplierImpl() { - * testComponentDataMap.put(FooTest.class, new FooTest_ComponentData()); - * testComponentDataMap.put(BarTest.class, new BarTest_ComponentData()); - * ... - * } - * - * @Override - * protected Map<Class<?>, TestComponentData> get() { - * return testComponentDataMap; - * } - * } - * }</code></pre> - */ - public void generate() throws IOException { - TypeSpec.Builder generator = - TypeSpec.classBuilder(TEST_COMPONENT_DATA_SUPPLIER_IMPL) - .addModifiers(PUBLIC, FINAL) - .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER) - .addField( - FieldSpec.builder( - TEST_COMPONENT_DATA_MAP_TYPE, "testComponentDataMap", PRIVATE, FINAL) - .initializer("new $T<>($L)", ClassNames.HASH_MAP, rootMetadatas.size()) - .build()) - .addMethod(constructor()) - .addMethod(getMethod()); - - Processors.addGeneratedAnnotation( - generator, processingEnv, ClassNames.ROOT_PROCESSOR.toString()); - - JavaFile.builder(TEST_COMPONENT_DATA_SUPPLIER_IMPL.packageName(), generator.build()) - .build() - .writeTo(processingEnv.getFiler()); - } - - - private MethodSpec constructor() { - MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); - for (RootMetadata rootMetadata : rootMetadatas) { - ClassName testName = rootMetadata.testRootMetadata().testName(); - ClassName testComponentDataHolderName = - Processors.append(Processors.getEnclosedClassName(testName), "_ComponentDataHolder"); - constructor.addStatement( - "testComponentDataMap.put($T.class, $T.get())", - testName, - testComponentDataHolderName); - } - return constructor.build(); - } - - private MethodSpec getMethod() { - return MethodSpec.methodBuilder("get") - .addAnnotation(Override.class) - .addModifiers(PROTECTED) - .returns(TEST_COMPONENT_DATA_MAP_TYPE) - .addStatement("return testComponentDataMap") - .build(); - } -} diff --git a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java index fc97feda6..b7200e7fb 100644 --- a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java +++ b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java @@ -20,7 +20,6 @@ import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.Processors; @@ -41,7 +40,9 @@ public final class TestInjectorGenerator { // @GeneratedEntryPoint // @InstallIn(SingletonComponent.class) - // public interface FooTest_GeneratedInjector extends TestInjector<FooTest> {} + // public interface FooTest_GeneratedInjector { + // void injectTest(FooTest fooTest); + // } public void generate() throws IOException { TypeSpec.Builder builder = TypeSpec.interfaceBuilder(metadata.testInjectorName()) @@ -53,11 +54,8 @@ public final class TestInjectorGenerator { .addMember("value", "$T.class", installInComponent(metadata.testElement())) .build()) .addModifiers(Modifier.PUBLIC) - .addSuperinterface( - ParameterizedTypeName.get(ClassNames.TEST_INJECTOR, metadata.testName())) .addMethod( MethodSpec.methodBuilder("injectTest") - .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addParameter( metadata.testName(), diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java new file mode 100644 index 000000000..654a690c4 --- /dev/null +++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.uninstallmodules; + +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.AnnotationSpec; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import java.io.IOException; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +/** + * Generates an {@link dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} + * annotation. + */ +final class AggregatedUninstallModulesGenerator { + + private final ProcessingEnvironment env; + private final TypeElement testElement; + private final ImmutableList<TypeElement> uninstallModuleElements; + + AggregatedUninstallModulesGenerator( + TypeElement testElement, + ImmutableList<TypeElement> uninstallModuleElements, + ProcessingEnvironment env) { + this.testElement = testElement; + this.uninstallModuleElements = uninstallModuleElements; + this.env = env; + } + + void generate() throws IOException { + Processors.generateAggregatingClass( + ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE, + aggregatedUninstallModulesAnnotation(), + testElement, + getClass(), + env); + } + + private AnnotationSpec aggregatedUninstallModulesAnnotation() { + AnnotationSpec.Builder builder = + AnnotationSpec.builder(ClassNames.AGGREGATED_UNINSTALL_MODULES); + builder.addMember("test", "$S", testElement.getQualifiedName()); + uninstallModuleElements.stream() + .map(TypeElement::getQualifiedName) + .forEach(uninstallModule -> builder.addMember("uninstallModules", "$S", uninstallModule)); + return builder.build(); + } +} diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java new file mode 100644 index 000000000..eee78490d --- /dev/null +++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.uninstallmodules; + +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.processor.internal.AggregatedElements; +import dagger.hilt.processor.internal.AnnotationValues; +import dagger.hilt.processor.internal.ClassNames; +import dagger.hilt.processor.internal.Processors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +/** + * A class that represents the values stored in an + * {@link dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules} annotation. + */ +@AutoValue +public abstract class AggregatedUninstallModulesMetadata { + + /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */ + public abstract TypeElement testElement(); + + /** + * Returns the list of uninstall modules in {@link dagger.hilt.android.testing.UninstallModules}. + */ + public abstract ImmutableList<TypeElement> uninstallModuleElements(); + + /** Returns all aggregated deps in the aggregating package mapped by the top-level element. */ + public static ImmutableSet<AggregatedUninstallModulesMetadata> from(Elements elements) { + return AggregatedElements.from( + ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE, + ClassNames.AGGREGATED_UNINSTALL_MODULES, + elements) + .stream() + .map(aggregatedElement -> create(aggregatedElement, elements)) + .collect(toImmutableSet()); + } + + private static AggregatedUninstallModulesMetadata create(TypeElement element, Elements elements) { + AnnotationMirror annotationMirror = + Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_UNINSTALL_MODULES); + + ImmutableMap<String, AnnotationValue> values = + Processors.getAnnotationValues(elements, annotationMirror); + + return new AutoValue_AggregatedUninstallModulesMetadata( + elements.getTypeElement(AnnotationValues.getString(values.get("test"))), + AnnotationValues.getAnnotationValues(values.get("uninstallModules")).stream() + .map(AnnotationValues::getString) + .map(elements::getTypeElement) + .collect(toImmutableList())); + } +} diff --git a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD index 73c46061e..4f944be48 100644 --- a/java/dagger/hilt/android/processor/internal/uninstallmodules/BUILD +++ b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD @@ -20,13 +20,14 @@ package(default_visibility = ["//:src"]) java_plugin( name = "processor", generates_api = 1, - processor_class = "dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor", + processor_class = "dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor", deps = [":processor_lib"], ) java_library( name = "processor_lib", srcs = [ + "AggregatedUninstallModulesGenerator.java", "UninstallModulesProcessor.java", ], deps = [ @@ -38,10 +39,26 @@ java_library( "//java/dagger/internal/guava:collect", "@google_bazel_common//third_party/java/auto:service", "@google_bazel_common//third_party/java/incap", + "@google_bazel_common//third_party/java/javapoet", "@maven//:com_google_auto_auto_common", ], ) +java_library( + name = "aggregated_uninstall_modules_metadata", + srcs = [ + "AggregatedUninstallModulesMetadata.java", + ], + deps = [ + "//java/dagger/hilt/processor/internal:aggregated_elements", + "//java/dagger/hilt/processor/internal:classnames", + "//java/dagger/hilt/processor/internal:processors", + "//java/dagger/internal/codegen/extension", + "//java/dagger/internal/guava:collect", + "@google_bazel_common//third_party/java/auto:value", + ], +) + filegroup( name = "srcs_filegroup", srcs = glob(["*"]), diff --git a/java/dagger/hilt/android/processor/internal/uninstallmodules/UninstallModulesProcessor.java b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java index e92f0f0a4..c7e528d9a 100644 --- a/java/dagger/hilt/android/processor/internal/uninstallmodules/UninstallModulesProcessor.java +++ b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package dagger.hilt.android.processor.internal.uninstallmodules; +package dagger.hilt.processor.internal.uninstallmodules; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING; @@ -23,6 +23,7 @@ import com.google.auto.common.MoreElements; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.squareup.javapoet.ClassName; import dagger.hilt.processor.internal.BaseProcessor; import dagger.hilt.processor.internal.ClassNames; import dagger.hilt.processor.internal.ProcessorErrors; @@ -40,7 +41,7 @@ public final class UninstallModulesProcessor extends BaseProcessor { @Override public Set<String> getSupportedAnnotationTypes() { - return ImmutableSet.of(ClassNames.IGNORE_MODULES.toString()); + return ImmutableSet.of(ClassNames.UNINSTALL_MODULES.toString()); } @Override @@ -56,12 +57,24 @@ public final class UninstallModulesProcessor extends BaseProcessor { ClassNames.HILT_ANDROID_TEST.simpleName(), element); - ImmutableList<TypeElement> invalidModules = + TypeElement testElement = MoreElements.asType(element); + ImmutableList<TypeElement> uninstallModules = Processors.getAnnotationClassValues( - getElementUtils(), - Processors.getAnnotationMirror(element, ClassNames.IGNORE_MODULES), - "value") - .stream() + getElementUtils(), + Processors.getAnnotationMirror(testElement, ClassNames.UNINSTALL_MODULES), + "value"); + + checkModulesHaveInstallIn(testElement, uninstallModules); + checkModulesDontOriginateFromTest(testElement, uninstallModules); + + new AggregatedUninstallModulesGenerator(testElement, uninstallModules, getProcessingEnv()) + .generate(); + } + + private void checkModulesHaveInstallIn( + TypeElement testElement, ImmutableList<TypeElement> uninstallModules) { + ImmutableList<TypeElement> invalidModules = + uninstallModules.stream() .filter( module -> !(Processors.hasAnnotation(module, ClassNames.MODULE) @@ -71,10 +84,27 @@ public final class UninstallModulesProcessor extends BaseProcessor { ProcessorErrors.checkState( invalidModules.isEmpty(), // TODO(b/152801981): Point to the annotation value rather than the annotated element. - element, - "@%s should only include modules annotated with both @Module and @InstallIn, but found: " - + "%s.", - annotation.getSimpleName(), + testElement, + "@UninstallModules should only include modules annotated with both @Module and @InstallIn, " + + "but found: %s.", + invalidModules); + } + + private void checkModulesDontOriginateFromTest( + TypeElement testElement, ImmutableList<TypeElement> uninstallModules) { + ImmutableList<ClassName> invalidModules = + uninstallModules.stream() + .filter( + module -> + Processors.getOriginatingTestElement(module, getElementUtils()).isPresent()) + .map(ClassName::get) + .collect(toImmutableList()); + + ProcessorErrors.checkState( + invalidModules.isEmpty(), + // TODO(b/152801981): Point to the annotation value rather than the annotated element. + testElement, + "@UninstallModules should not contain test modules, but found: %s", invalidModules); } } diff --git a/java/dagger/hilt/proguard-rules.pro b/java/dagger/hilt/proguard-rules.pro new file mode 100644 index 000000000..3b953dfa1 --- /dev/null +++ b/java/dagger/hilt/proguard-rules.pro @@ -0,0 +1,3 @@ +# Keep for the reflective cast done in EntryPoints. +# See b/183070411#comment4 for more info. +-keep,allowobfuscation,allowshrinking @dagger.hilt.EntryPoint class *
\ No newline at end of file diff --git a/java/dagger/internal/DaggerGenerated.java b/java/dagger/internal/DaggerGenerated.java new file mode 100644 index 000000000..6a872e0f3 --- /dev/null +++ b/java/dagger/internal/DaggerGenerated.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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 top-level class of each Dagger generated source file. */ +@Documented +@Retention(CLASS) +@Target(TYPE) +public @interface DaggerGenerated {} diff --git a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java index abc0436a0..dbd2b3365 100644 --- a/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedFactoryProcessingStep.java @@ -34,6 +34,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreElements; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; @@ -137,7 +138,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ } ImmutableSet<ExecutableElement> abstractFactoryMethods = - AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements, types); + AssistedInjectionAnnotations.assistedFactoryMethods(factory, elements); if (abstractFactoryMethods.isEmpty()) { report.addError( @@ -226,11 +227,6 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ } @Override - public ClassName nameGeneratedType(ProvisionBinding binding) { - return generatedClassNameForBinding(binding); - } - - @Override public Element originatingElement(ProvisionBinding binding) { return binding.bindingElement().get(); } @@ -268,10 +264,10 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ // } // } @Override - public Optional<TypeSpec.Builder> write(ProvisionBinding binding) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) { TypeElement factory = asType(binding.bindingElement().get()); - ClassName name = nameGeneratedType(binding); + ClassName name = generatedClassNameForBinding(binding); TypeSpec.Builder builder = TypeSpec.classBuilder(name) .addModifiers(PUBLIC, FINAL) @@ -333,7 +329,7 @@ final class AssistedFactoryProcessingStep extends TypeCheckingProcessingStep<Typ name, delegateFactoryParam) .build()); - return Optional.of(builder); + return ImmutableList.of(builder); } /** Returns the generated factory {@link TypeName type} for an @AssistedInject constructor. */ diff --git a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java index 4f2f5b7b7..167c4aebe 100644 --- a/java/dagger/internal/codegen/AssistedInjectProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedInjectProcessingStep.java @@ -76,7 +76,10 @@ final class AssistedInjectProcessingStep extends TypeCheckingProcessingStep<Exec for (AssistedParameter assistedParameter : assistedParameters) { if (!uniqueAssistedParameters.add(assistedParameter)) { report.addError( - "@AssistedInject constructor has duplicate @Assisted type: " + assistedParameter, + String.format("@AssistedInject constructor has duplicate @Assisted type: %s. " + + "Consider setting an identifier on the parameter by using " + + "@Assisted(\"identifier\") in both the factory and @AssistedInject constructor", + assistedParameter), assistedParameter.variableElement()); } } diff --git a/java/dagger/internal/codegen/AssistedProcessingStep.java b/java/dagger/internal/codegen/AssistedProcessingStep.java index 3173987e1..12a0d34b4 100644 --- a/java/dagger/internal/codegen/AssistedProcessingStep.java +++ b/java/dagger/internal/codegen/AssistedProcessingStep.java @@ -27,7 +27,6 @@ import dagger.internal.codegen.binding.AssistedInjectionAnnotations; import dagger.internal.codegen.binding.InjectionAnnotations; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.internal.codegen.validation.TypeCheckingProcessingStep; import dagger.internal.codegen.validation.ValidationReport; import java.lang.annotation.Annotation; @@ -47,7 +46,6 @@ final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableEl private final KotlinMetadataUtil kotlinMetadataUtil; private final InjectionAnnotations injectionAnnotations; private final DaggerElements elements; - private final DaggerTypes types; private final Messager messager; @Inject @@ -55,13 +53,11 @@ final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableEl KotlinMetadataUtil kotlinMetadataUtil, InjectionAnnotations injectionAnnotations, DaggerElements elements, - DaggerTypes types, Messager messager) { super(MoreElements::asVariable); this.kotlinMetadataUtil = kotlinMetadataUtil; this.injectionAnnotations = injectionAnnotations; this.elements = elements; - this.types = types; this.messager = messager; } @@ -113,7 +109,7 @@ final class AssistedProcessingStep extends TypeCheckingProcessingStep<VariableEl TypeElement enclosingElement = closestEnclosingTypeElement(element); return AssistedInjectionAnnotations.isAssistedFactoryType(enclosingElement) // This assumes we've already validated AssistedFactory and that a valid method exists. - && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements, types) + && AssistedInjectionAnnotations.assistedFactoryMethod(enclosingElement, elements) .equals(element); } return false; diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD index be970be12..e2f74e9e7 100644 --- a/java/dagger/internal/codegen/BUILD +++ b/java/dagger/internal/codegen/BUILD @@ -127,6 +127,8 @@ java_plugin( "genclass=${package}.Dagger${outerclasses}${classname}", "annotation=dagger.producers.ProductionComponent;" + "genclass=${package}.Dagger${outerclasses}${classname}", + "annotation=dagger.MapKey;" + + "genclass=${package}.${outerclasses}${classname}Creator", ], deps = [":processor"], ) diff --git a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java index 8a535fe13..120714b33 100644 --- a/java/dagger/internal/codegen/ProcessingEnvironmentModule.java +++ b/java/dagger/internal/codegen/ProcessingEnvironmentModule.java @@ -22,15 +22,18 @@ import dagger.Module; import dagger.Provides; import dagger.Reusable; import dagger.internal.codegen.SpiModule.ProcessorClassLoader; +import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions; import dagger.internal.codegen.compileroption.ProcessingOptions; import dagger.internal.codegen.langmodel.DaggerElements; +import dagger.multibindings.IntoSet; import dagger.spi.BindingGraphPlugin; import java.util.Map; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; +import javax.inject.Singleton; import javax.lang.model.SourceVersion; import javax.lang.model.util.Types; @@ -73,10 +76,15 @@ interface ProcessingEnvironmentModule { } @Provides + @Singleton static DaggerElements daggerElements(ProcessingEnvironment processingEnvironment) { return new DaggerElements(processingEnvironment); } + @Binds + @IntoSet + ClearableCache daggerElementAsClearableCache(DaggerElements elements); + @Provides @ProcessorClassLoader static ClassLoader processorClassloader(ProcessingEnvironment processingEnvironment) { diff --git a/java/dagger/internal/codegen/base/ComponentAnnotation.java b/java/dagger/internal/codegen/base/ComponentAnnotation.java index c9b3580c1..b70ef54bb 100644 --- a/java/dagger/internal/codegen/base/ComponentAnnotation.java +++ b/java/dagger/internal/codegen/base/ComponentAnnotation.java @@ -57,6 +57,19 @@ public abstract class ComponentAnnotation { private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_ANNOTATIONS = ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class); + // TODO(erichang): Move ComponentCreatorAnnotation into /base and use that here? + /** The component/subcomponent creator annotation types. */ + private static final ImmutableSet<Class<? extends Annotation>> CREATOR_ANNOTATIONS = + ImmutableSet.of( + Component.Builder.class, + Component.Factory.class, + ProductionComponent.Builder.class, + ProductionComponent.Factory.class, + Subcomponent.Builder.class, + Subcomponent.Factory.class, + ProductionSubcomponent.Builder.class, + ProductionSubcomponent.Factory.class); + /** All component annotation types. */ private static final ImmutableSet<Class<? extends Annotation>> ALL_COMPONENT_ANNOTATIONS = ImmutableSet.<Class<? extends Annotation>>builder() @@ -64,6 +77,13 @@ public abstract class ComponentAnnotation { .addAll(SUBCOMPONENT_ANNOTATIONS) .build(); + /** All component and creator annotation types. */ + private static final ImmutableSet<Class<? extends Annotation>> + ALL_COMPONENT_AND_CREATOR_ANNOTATIONS = ImmutableSet.<Class<? extends Annotation>>builder() + .addAll(ALL_COMPONENT_ANNOTATIONS) + .addAll(CREATOR_ANNOTATIONS) + .build(); + /** The annotation itself. */ public abstract AnnotationMirror annotation(); @@ -206,6 +226,11 @@ public abstract class ComponentAnnotation { return ALL_COMPONENT_ANNOTATIONS; } + /** All component and creator annotation types. */ + public static ImmutableSet<Class<? extends Annotation>> allComponentAndCreatorAnnotations() { + return ALL_COMPONENT_AND_CREATOR_ANNOTATIONS; + } + /** * An actual component annotation. * diff --git a/java/dagger/internal/codegen/base/Keys.java b/java/dagger/internal/codegen/base/Keys.java index a25f9963f..4d139266d 100644 --- a/java/dagger/internal/codegen/base/Keys.java +++ b/java/dagger/internal/codegen/base/Keys.java @@ -16,6 +16,10 @@ package dagger.internal.codegen.base; +import static com.google.auto.common.MoreTypes.asTypeElement; +import static dagger.internal.codegen.base.ComponentAnnotation.allComponentAndCreatorAnnotations; +import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent; + import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import dagger.internal.codegen.langmodel.DaggerTypes; @@ -88,4 +92,14 @@ public final class Keys { }, null); } + + /** + * Returns {@code true} if the given key is for a component/subcomponent or a creator of a + * component/subcomponent. + */ + public static boolean isComponentOrCreator(Key key) { + return !key.qualifier().isPresent() + && key.type().getKind() == TypeKind.DECLARED + && isAnyAnnotationPresent(asTypeElement(key.type()), allComponentAndCreatorAnnotations()); + } } diff --git a/java/dagger/internal/codegen/base/SourceFileGenerator.java b/java/dagger/internal/codegen/base/SourceFileGenerator.java index 02348a4f4..ada67d30d 100644 --- a/java/dagger/internal/codegen/base/SourceFileGenerator.java +++ b/java/dagger/internal/codegen/base/SourceFileGenerator.java @@ -16,17 +16,20 @@ package dagger.internal.codegen.base; + import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression.UNCHECKED; import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; +import dagger.internal.DaggerGenerated; import dagger.internal.codegen.javapoet.AnnotationSpecs; import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression; import dagger.internal.codegen.langmodel.DaggerElements; @@ -74,22 +77,21 @@ public abstract class SourceFileGenerator<T> { /** Generates a source file to be compiled for {@code T}. */ public void generate(T input) throws SourceFileGenerationException { - Optional<TypeSpec.Builder> type = write(input); - if (!type.isPresent()) { - return; - } - try { - buildJavaFile(input, type.get()).writeTo(filer); - } catch (Exception e) { - // if the code above threw a SFGE, use that - Throwables.propagateIfPossible(e, SourceFileGenerationException.class); - // otherwise, throw a new one - throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input)); + for (TypeSpec.Builder type : topLevelTypes(input)) { + try { + buildJavaFile(input, type).writeTo(filer); + } catch (Exception e) { + // if the code above threw a SFGE, use that + Throwables.propagateIfPossible(e, SourceFileGenerationException.class); + // otherwise, throw a new one + throw new SourceFileGenerationException(Optional.empty(), e, originatingElement(input)); + } } } private JavaFile buildJavaFile(T input, TypeSpec.Builder typeSpecBuilder) { typeSpecBuilder.addOriginatingElement(originatingElement(input)); + typeSpecBuilder.addAnnotation(DaggerGenerated.class); Optional<AnnotationSpec> generatedAnnotation = generatedAnnotation(elements, sourceVersion) .map( @@ -109,7 +111,9 @@ public abstract class SourceFileGenerator<T> { .build())); JavaFile.Builder javaFileBuilder = - JavaFile.builder(nameGeneratedType(input).packageName(), typeSpecBuilder.build()) + JavaFile.builder( + elements.getPackageOf(originatingElement(input)).getQualifiedName().toString(), + typeSpecBuilder.build()) .skipJavaLangImports(true); if (!generatedAnnotation.isPresent()) { javaFileBuilder.addFileComment("Generated by Dagger ($L).", GENERATED_COMMENTS); @@ -117,19 +121,16 @@ public abstract class SourceFileGenerator<T> { return javaFileBuilder.build(); } - /** Implementations should return the {@link ClassName} for the top-level type to be generated. */ - public abstract ClassName nameGeneratedType(T input); - /** Returns the originating element of the generating type. */ public abstract Element originatingElement(T input); /** - * Returns a {@link TypeSpec.Builder type} to be generated for {@code T}, or {@link - * Optional#empty()} if no file should be generated. + * Returns {@link TypeSpec.Builder types} be generated for {@code T}, or an empty list if no types + * should be generated. + * + * <p>Every type will be generated in its own file. */ - // TODO(ronshapiro): write() makes more sense in JavaWriter where all writers are mutable. - // consider renaming to something like typeBuilder() which conveys the mutability of the result - public abstract Optional<TypeSpec.Builder> write(T input); + public abstract ImmutableList<TypeSpec.Builder> topLevelTypes(T input); /** Returns {@link Suppression}s that are applied to files generated by this generator. */ // TODO(b/134590785): When suppressions are removed locally, remove this and inline the usages diff --git a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java index 8d6ee5df6..f9929aef6 100644 --- a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java +++ b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java @@ -59,14 +59,14 @@ import javax.lang.model.type.TypeMirror; public final class AssistedInjectionAnnotations { /** Returns the factory method for the given factory {@link TypeElement}. */ public static ExecutableElement assistedFactoryMethod( - TypeElement factory, DaggerElements elements, DaggerTypes types) { - return getOnlyElement(assistedFactoryMethods(factory, elements, types)); + TypeElement factory, DaggerElements elements) { + return getOnlyElement(assistedFactoryMethods(factory, elements)); } /** Returns the list of abstract factory methods for the given factory {@link TypeElement}. */ public static ImmutableSet<ExecutableElement> assistedFactoryMethods( - TypeElement factory, DaggerElements elements, DaggerTypes types) { - return MoreElements.getLocalAndInheritedMethods(factory, types, elements).stream() + TypeElement factory, DaggerElements elements) { + return elements.getLocalAndInheritedMethods(factory).stream() .filter(method -> method.getModifiers().contains(ABSTRACT)) .filter(method -> !method.isDefault()) .collect(toImmutableSet()); @@ -170,7 +170,7 @@ public final class AssistedInjectionAnnotations { TypeMirror factory, DaggerElements elements, DaggerTypes types) { DeclaredType factoryType = asDeclared(factory); TypeElement factoryElement = asTypeElement(factoryType); - ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements, types); + ExecutableElement factoryMethod = assistedFactoryMethod(factoryElement, elements); ExecutableType factoryMethodType = asExecutable(types.asMemberOf(factoryType, factoryMethod)); DeclaredType assistedInjectType = asDeclared(factoryMethodType.getReturnType()); return new AutoValue_AssistedInjectionAnnotations_AssistedFactoryMetadata( diff --git a/java/dagger/internal/codegen/binding/BindingFactory.java b/java/dagger/internal/codegen/binding/BindingFactory.java index 6f2fc805e..eb7c1322a 100644 --- a/java/dagger/internal/codegen/binding/BindingFactory.java +++ b/java/dagger/internal/codegen/binding/BindingFactory.java @@ -191,7 +191,7 @@ public final class BindingFactory { } ExecutableElement factoryMethod = - AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements, types); + AssistedInjectionAnnotations.assistedFactoryMethod(factory, elements); ExecutableType factoryMethodType = MoreTypes.asExecutable(types.asMemberOf(factoryType, factoryMethod)); return ProvisionBinding.builder() diff --git a/java/dagger/internal/codegen/binding/BindingGraph.java b/java/dagger/internal/codegen/binding/BindingGraph.java index 4936a0526..533d113f1 100644 --- a/java/dagger/internal/codegen/binding/BindingGraph.java +++ b/java/dagger/internal/codegen/binding/BindingGraph.java @@ -16,6 +16,7 @@ package dagger.internal.codegen.binding; +import static com.google.common.collect.Iterables.transform; import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; import static dagger.internal.codegen.extension.DaggerStreams.presentValues; import static dagger.internal.codegen.extension.DaggerStreams.stream; @@ -26,11 +27,12 @@ import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import com.google.auto.value.AutoValue; import com.google.auto.value.extension.memoized.Memoized; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.Sets; -import com.google.common.graph.Graphs; import com.google.common.graph.ImmutableNetwork; import com.google.common.graph.Traverser; import dagger.model.BindingGraph.ChildFactoryMethodEdge; @@ -39,7 +41,9 @@ import dagger.model.BindingGraph.Edge; import dagger.model.BindingGraph.Node; import dagger.model.ComponentPath; import dagger.model.Key; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import javax.lang.model.element.ExecutableElement; @@ -104,6 +108,15 @@ public abstract class BindingGraph { public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() { return super.nodesByClass(); } + + /** + * Returns an index of each {@link BindingNode} by its {@link ComponentPath}. Accessing this for + * a component and its parent components is faster than doing a graph traversal. + */ + @Memoized + ImmutableListMultimap<ComponentPath, BindingNode> bindingsByComponent() { + return Multimaps.index(transform(bindings(), BindingNode.class::cast), Node::componentPath); + } } static BindingGraph create( @@ -115,12 +128,12 @@ public abstract class BindingGraph { Optional<BindingGraph> parent, ComponentNode componentNode, TopLevelBindingGraph topLevelBindingGraph) { - ImmutableSet<BindingNode> reachableBindingNodes = - Graphs.reachableNodes(topLevelBindingGraph.network().asGraph(), componentNode).stream() - .filter(node -> isSubpath(componentNode.componentPath(), node.componentPath())) - .filter(node -> node instanceof BindingNode) - .map(node -> (BindingNode) node) - .collect(toImmutableSet()); + List<BindingNode> reachableBindingNodes = new ArrayList<>(); + for (ComponentPath path = componentNode.componentPath(); + !path.components().isEmpty(); + path = ComponentPath.create(path.components().subList(0, path.components().size() - 1))) { + reachableBindingNodes.addAll(topLevelBindingGraph.bindingsByComponent().get(path)); + } // Construct the maps of the ContributionBindings and MembersInjectionBindings. Map<Key, BindingNode> contributionBindings = new HashMap<>(); @@ -331,17 +344,4 @@ public abstract class BindingGraph { .addAll(membersInjectionBindings.values()) .build(); } - - // TODO(bcorso): Move this to ComponentPath - private static boolean isSubpath(ComponentPath path, ComponentPath subpath) { - if (path.components().size() < subpath.components().size()) { - return false; - } - for (int i = 0; i < subpath.components().size(); i++) { - if (!path.components().get(i).equals(subpath.components().get(i))) { - return false; - } - } - return true; - } } diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java index 2c15e2625..a94f6b01e 100644 --- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java +++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java @@ -47,6 +47,7 @@ import dagger.MembersInjector; import dagger.Reusable; import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.ContributionType; +import dagger.internal.codegen.base.Keys; import dagger.internal.codegen.base.MapType; import dagger.internal.codegen.base.OptionalType; import dagger.internal.codegen.compileroption.CompilerOptions; @@ -796,7 +797,7 @@ public final class BindingGraphFactory implements ClearableCache { * 2. If there are any explicit bindings in this component, they may conflict with those in * the ancestor component, so resolve them here so that conflicts can be caught. */ - if (getPreviouslyResolvedBindings(key).isPresent()) { + if (getPreviouslyResolvedBindings(key).isPresent() && !Keys.isComponentOrCreator(key)) { /* Resolve in the parent in case there are multibinding contributions or conflicts in some * component between this one and the previously-resolved one. */ parentResolver.get().resolve(key); diff --git a/java/dagger/internal/codegen/binding/BindsTypeChecker.java b/java/dagger/internal/codegen/binding/BindsTypeChecker.java index f3e0a1b81..d850fd373 100644 --- a/java/dagger/internal/codegen/binding/BindsTypeChecker.java +++ b/java/dagger/internal/codegen/binding/BindsTypeChecker.java @@ -18,7 +18,6 @@ package dagger.internal.codegen.binding; import static com.google.common.collect.Iterables.getOnlyElement; -import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.collect.ImmutableList; import dagger.internal.codegen.base.ContributionType; @@ -83,8 +82,8 @@ public final class BindsTypeChecker { // type.asElement().getEnclosedElements() is not used because some non-standard JDKs (e.g. // J2CL) don't redefine Set.add() (whose only purpose of being redefined in the standard JDK // is documentation, and J2CL's implementation doesn't declare docs for JDK types). - // MoreElements.getLocalAndInheritedMethods ensures that the method will always be present. - MoreElements.getLocalAndInheritedMethods(MoreTypes.asTypeElement(type), types, elements)) { + // getLocalAndInheritedMethods ensures that the method will always be present. + elements.getLocalAndInheritedMethods(MoreTypes.asTypeElement(type))) { if (method.getSimpleName().contentEquals(methodName)) { methodsForName.add(method); } diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptor.java b/java/dagger/internal/codegen/binding/ComponentDescriptor.java index a7e4cc4f3..f6ea62c77 100644 --- a/java/dagger/internal/codegen/binding/ComponentDescriptor.java +++ b/java/dagger/internal/codegen/binding/ComponentDescriptor.java @@ -45,6 +45,8 @@ import dagger.model.DependencyRequest; import dagger.model.Scope; import dagger.producers.CancellationPolicy; import dagger.producers.ProductionComponent; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Stream; @@ -240,19 +242,17 @@ public abstract class ComponentDescriptor { /** Returns the first component method associated with this binding request, if one exists. */ public Optional<ComponentMethodDescriptor> firstMatchingComponentMethod(BindingRequest request) { - return componentMethods().stream() - .filter(method -> doesComponentMethodMatch(method, request)) - .findFirst(); + return Optional.ofNullable(firstMatchingComponentMethods().get(request)); } - /** Returns true if the component method matches the binding request. */ - private static boolean doesComponentMethodMatch( - ComponentMethodDescriptor componentMethod, BindingRequest request) { - return componentMethod - .dependencyRequest() - .map(BindingRequest::bindingRequest) - .filter(request::equals) - .isPresent(); + @Memoized + ImmutableMap<BindingRequest, ComponentMethodDescriptor> + firstMatchingComponentMethods() { + Map<BindingRequest, ComponentMethodDescriptor> methods = new HashMap<>(); + for (ComponentMethodDescriptor method : entryPointMethods()) { + methods.putIfAbsent(BindingRequest.bindingRequest(method.dependencyRequest().get()), method); + } + return ImmutableMap.copyOf(methods); } /** The entry point methods on the component type. Each has a {@link DependencyRequest}. */ diff --git a/java/dagger/internal/codegen/binding/ComponentRequirement.java b/java/dagger/internal/codegen/binding/ComponentRequirement.java index fa24b56f3..9d54f4dc3 100644 --- a/java/dagger/internal/codegen/binding/ComponentRequirement.java +++ b/java/dagger/internal/codegen/binding/ComponentRequirement.java @@ -16,7 +16,6 @@ package dagger.internal.codegen.binding; -import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName; @@ -38,7 +37,6 @@ import dagger.BindsOptionalOf; import dagger.Provides; import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; -import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.model.BindingKind; import dagger.model.Key; import dagger.multibindings.Multibinds; @@ -115,14 +113,12 @@ public abstract class ComponentRequirement { * of the default behavior in {@link #nullPolicy}. * * <p>Some implementations' null policy can be determined upon construction (e.g., for binding - * instances), but others' require Elements and Types, which must wait until {@link #nullPolicy} - * is called. + * instances), but others' require Elements which must wait until {@link #nullPolicy} is called. */ abstract Optional<NullPolicy> overrideNullPolicy(); /** The requirement's null policy. */ - public NullPolicy nullPolicy( - DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) { + public NullPolicy nullPolicy(DaggerElements elements, KotlinMetadataUtil metadataUtil) { if (overrideNullPolicy().isPresent()) { return overrideNullPolicy().get(); } @@ -130,9 +126,7 @@ public abstract class ComponentRequirement { case MODULE: return componentCanMakeNewInstances(typeElement(), metadataUtil) ? NullPolicy.NEW - : requiresAPassedInstance(elements, types, metadataUtil) - ? NullPolicy.THROW - : NullPolicy.ALLOW; + : requiresAPassedInstance(elements, metadataUtil) ? NullPolicy.THROW : NullPolicy.ALLOW; case DEPENDENCY: case BOUND_INSTANCE: return NullPolicy.THROW; @@ -144,13 +138,12 @@ public abstract class ComponentRequirement { * Returns true if the passed {@link ComponentRequirement} requires a passed instance in order to * be used within a component. */ - public boolean requiresAPassedInstance( - DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) { + public boolean requiresAPassedInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) { if (!kind().isModule()) { // Bound instances and dependencies always require the user to provide an instance. return true; } - return requiresModuleInstance(elements, types, metadataUtil) + return requiresModuleInstance(elements, metadataUtil) && !componentCanMakeNewInstances(typeElement(), metadataUtil); } @@ -164,8 +157,7 @@ public abstract class ComponentRequirement { * <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered * {@code static}, requiring no module instance. */ - private boolean requiresModuleInstance( - DaggerElements elements, DaggerTypes types, KotlinMetadataUtil metadataUtil) { + private boolean requiresModuleInstance(DaggerElements elements, KotlinMetadataUtil metadataUtil) { boolean isKotlinObject = metadataUtil.isObjectClass(typeElement()) || metadataUtil.isCompanionObjectClass(typeElement()); @@ -173,8 +165,7 @@ public abstract class ComponentRequirement { return false; } - ImmutableSet<ExecutableElement> methods = - getLocalAndInheritedMethods(typeElement(), types, elements); + ImmutableSet<ExecutableElement> methods = elements.getLocalAndInheritedMethods(typeElement()); return methods.stream() .filter(this::isBindingMethod) .map(ExecutableElement::getModifiers) diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java index e1b35daf9..66270c697 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentCreatorImplementationFactory.java @@ -237,7 +237,7 @@ final class ComponentCreatorImplementationFactory { method.addStatement( "this.$N = $L", fields.get(requirement), - requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW) + requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW) ? CodeBlock.of("$N", parameter) : CodeBlock.of("$T.checkNotNull($N)", Preconditions.class, parameter)); return maybeReturnThis(method); @@ -310,7 +310,7 @@ final class ComponentCreatorImplementationFactory { private void addNullHandlingForField( ComponentRequirement requirement, FieldSpec field, MethodSpec.Builder factoryMethod) { - switch (requirement.nullPolicy(elements, types, metadataUtil)) { + switch (requirement.nullPolicy(elements, metadataUtil)) { case NEW: checkState(requirement.kind().isModule()); factoryMethod @@ -334,7 +334,7 @@ final class ComponentCreatorImplementationFactory { private void addNullHandlingForParameter( ComponentRequirement requirement, String parameter, MethodSpec.Builder factoryMethod) { - if (!requirement.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.ALLOW)) { + if (!requirement.nullPolicy(elements, metadataUtil).equals(NullPolicy.ALLOW)) { // Factory method parameters are always required unless they are a nullable // binds-instance (i.e. ALLOW) factoryMethod.addStatement("$T.checkNotNull($L)", Preconditions.class, parameter); diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java index e04ee14a5..098da81e6 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentGenerator.java @@ -19,6 +19,7 @@ package dagger.internal.codegen.componentgenerator; import static com.google.common.base.Verify.verify; import static dagger.internal.codegen.binding.SourceFiles.classFileName; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeSpec; import dagger.Component; @@ -26,7 +27,6 @@ import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.writing.ComponentImplementation; -import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; @@ -47,11 +47,6 @@ final class ComponentGenerator extends SourceFileGenerator<BindingGraph> { this.componentImplementationFactory = componentImplementationFactory; } - @Override - public ClassName nameGeneratedType(BindingGraph input) { - return componentName(input.componentTypeElement()); - } - static ClassName componentName(TypeElement componentDefinitionType) { ClassName componentName = ClassName.get(componentDefinitionType); return ClassName.get(componentName.packageName(), "Dagger" + classFileName(componentName)); @@ -63,10 +58,11 @@ final class ComponentGenerator extends SourceFileGenerator<BindingGraph> { } @Override - public Optional<TypeSpec.Builder> write(BindingGraph bindingGraph) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(BindingGraph bindingGraph) { ComponentImplementation componentImplementation = componentImplementationFactory.createComponentImplementation(bindingGraph); - verify(componentImplementation.name().equals(nameGeneratedType(bindingGraph))); - return Optional.of(componentImplementation.generate()); + verify( + componentImplementation.name().equals(componentName(bindingGraph.componentTypeElement()))); + return ImmutableList.of(componentImplementation.generate()); } } diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java index 84179d602..179c411e4 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentGeneratorModule.java @@ -18,11 +18,9 @@ package dagger.internal.codegen.componentgenerator; import dagger.Binds; import dagger.Module; -import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.ComponentDescriptor; -import dagger.multibindings.IntoSet; /** Provides bindings needed to generated the component. */ @Module(subcomponents = TopLevelImplementationComponent.class) @@ -41,8 +39,4 @@ public interface ComponentGeneratorModule { @Binds abstract SourceFileGenerator<ComponentDescriptor> componentHjarGenerator( ComponentHjarGenerator hjarGenerator); - - @Binds - @IntoSet - ClearableCache componentImplementationFactory(ComponentImplementationFactory cache); } diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java index b386a4f23..c8b8c97c3 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentHjarGenerator.java @@ -33,6 +33,7 @@ import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreTypes; import com.google.common.base.Ascii; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; @@ -48,7 +49,6 @@ import dagger.internal.codegen.kotlin.KotlinMetadataUtil; import dagger.internal.codegen.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.producers.internal.CancellationListener; -import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import javax.annotation.processing.Filer; @@ -91,18 +91,13 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript } @Override - public ClassName nameGeneratedType(ComponentDescriptor input) { - return componentName(input.typeElement()); - } - - @Override public Element originatingElement(ComponentDescriptor input) { return input.typeElement(); } @Override - public Optional<TypeSpec.Builder> write(ComponentDescriptor componentDescriptor) { - ClassName generatedTypeName = nameGeneratedType(componentDescriptor); + public ImmutableList<TypeSpec.Builder> topLevelTypes(ComponentDescriptor componentDescriptor) { + ClassName generatedTypeName = componentName(componentDescriptor.typeElement()); TypeSpec.Builder generatedComponent = TypeSpec.classBuilder(generatedTypeName) .addModifiers(FINAL) @@ -148,8 +143,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript && !hasBindsInstanceMethods(componentDescriptor) && componentRequirements(componentDescriptor) .noneMatch( - requirement -> - requirement.requiresAPassedInstance(elements, types, metadataUtil))) { + requirement -> requirement.requiresAPassedInstance(elements, metadataUtil))) { generatedComponent.addMethod(createMethod(componentDescriptor)); } @@ -174,7 +168,7 @@ final class ComponentHjarGenerator extends SourceFileGenerator<ComponentDescript .addMethod(onProducerFutureCancelledMethod()); } - return Optional.of(generatedComponent); + return ImmutableList.of(generatedComponent); } private MethodSpec emptyComponentMethod(TypeElement typeElement, ExecutableElement baseMethod) { diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java index 04cb80f94..2be7d3861 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationBuilder.java @@ -16,7 +16,6 @@ package dagger.internal.codegen.componentgenerator; -import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.base.Preconditions.checkState; import static com.squareup.javapoet.MethodSpec.constructorBuilder; @@ -146,7 +145,8 @@ public final class ComponentImplementationBuilder { .map(ComponentCreatorImplementation::spec) .ifPresent(this::addCreatorClass); - getLocalAndInheritedMethods(graph.componentTypeElement(), types, elements) + elements + .getLocalAndInheritedMethods(graph.componentTypeElement()) .forEach(method -> componentImplementation.claimMethodName(method.getSimpleName())); addFactoryMethods(); @@ -495,7 +495,7 @@ public final class ComponentImplementationBuilder { private boolean canInstantiateAllRequirements() { return !Iterables.any( graph.componentRequirements(), - dependency -> dependency.requiresAPassedInstance(elements, types, metadataUtil)); + dependency -> dependency.requiresAPassedInstance(elements, metadataUtil)); } private void createSubcomponentFactoryMethod(ExecutableElement factoryMethod) { diff --git a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java index fdfcc9dce..0d29b8693 100644 --- a/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java +++ b/java/dagger/internal/codegen/componentgenerator/ComponentImplementationFactory.java @@ -16,26 +16,20 @@ package dagger.internal.codegen.componentgenerator; -import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; import static dagger.internal.codegen.componentgenerator.ComponentGenerator.componentName; -import dagger.internal.codegen.base.ClearableCache; import dagger.internal.codegen.binding.BindingGraph; import dagger.internal.codegen.binding.KeyFactory; import dagger.internal.codegen.compileroption.CompilerOptions; import dagger.internal.codegen.writing.ComponentImplementation; import dagger.internal.codegen.writing.SubcomponentNames; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Singleton; -import javax.lang.model.element.TypeElement; /** Factory for {@link ComponentImplementation}s. */ @Singleton -final class ComponentImplementationFactory implements ClearableCache { - private final Map<TypeElement, ComponentImplementation> topLevelComponentCache = new HashMap<>(); +final class ComponentImplementationFactory { private final KeyFactory keyFactory; private final CompilerOptions compilerOptions; private final TopLevelImplementationComponent.Builder topLevelImplementationComponentBuilder; @@ -54,13 +48,6 @@ final class ComponentImplementationFactory implements ClearableCache { * Returns a top-level (non-nested) component implementation for a binding graph. */ ComponentImplementation createComponentImplementation(BindingGraph bindingGraph) { - return reentrantComputeIfAbsent( - topLevelComponentCache, - bindingGraph.componentTypeElement(), - component -> createComponentImplementationUncached(bindingGraph)); - } - - private ComponentImplementation createComponentImplementationUncached(BindingGraph bindingGraph) { ComponentImplementation componentImplementation = ComponentImplementation.topLevelComponentImplementation( bindingGraph, @@ -82,9 +69,4 @@ final class ComponentImplementationFactory implements ClearableCache { .componentImplementationBuilder() .build(); } - - @Override - public void clearCache() { - topLevelComponentCache.clear(); - } } diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java index 296da4465..5fb49f005 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java @@ -362,6 +362,7 @@ abstract class KotlinMetadata { Builder addConstructor(FunctionMetadata constructor) { constructorsBuilder().add(constructor); + functionsBySignatureBuilder().put(constructor.signature(), constructor); return this; } diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java index 76d28f0ce..980ff3438 100644 --- a/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java +++ b/java/dagger/internal/codegen/kotlin/KotlinMetadataUtil.java @@ -100,6 +100,11 @@ public final class KotlinMetadataUtil { && metadataFactory.create(typeElement).classMetadata().flags(IS_COMPANION_OBJECT); } + /** Returns {@code true} if this type element is a Kotlin object or companion object. */ + public boolean isObjectOrCompanionObjectClass(TypeElement typeElement) { + return isObjectClass(typeElement) || isCompanionObjectClass(typeElement); + } + /* Returns {@code true} if this type element has a Kotlin Companion Object. */ public boolean hasEnclosedCompanionObject(TypeElement typeElement) { return hasMetadata(typeElement) @@ -131,6 +136,15 @@ public final class KotlinMetadataUtil { } /** + * Returns {@code true} if the given type element was declared {@code internal} in its Kotlin + * source. + */ + public boolean isVisibilityInternal(TypeElement type) { + return hasMetadata(type) + && metadataFactory.create(type).classMetadata().flags(Flag.IS_INTERNAL); + } + + /** * Returns {@code true} if the given executable element was declared {@code internal} in its * Kotlin source. */ diff --git a/java/dagger/internal/codegen/langmodel/BUILD b/java/dagger/internal/codegen/langmodel/BUILD index 670f4aa99..25f1e6210 100644 --- a/java/dagger/internal/codegen/langmodel/BUILD +++ b/java/dagger/internal/codegen/langmodel/BUILD @@ -26,6 +26,7 @@ java_library( tags = ["maven:merged"], deps = [ "//java/dagger:core", + "//java/dagger/internal/codegen/base:shared", "//java/dagger/internal/guava:base", "//java/dagger/internal/guava:collect", "//java/dagger/internal/guava:concurrent", diff --git a/java/dagger/internal/codegen/langmodel/DaggerElements.java b/java/dagger/internal/codegen/langmodel/DaggerElements.java index 12cec31d5..51c2a605a 100644 --- a/java/dagger/internal/codegen/langmodel/DaggerElements.java +++ b/java/dagger/internal/codegen/langmodel/DaggerElements.java @@ -17,7 +17,6 @@ package dagger.internal.codegen.langmodel; import static com.google.auto.common.MoreElements.asExecutable; -import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreElements.hasModifiers; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Lists.asList; @@ -34,10 +33,12 @@ import com.google.common.collect.Iterables; import com.google.common.graph.Traverser; import com.squareup.javapoet.ClassName; import dagger.Reusable; +import dagger.internal.codegen.base.ClearableCache; import java.io.Writer; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -75,8 +76,9 @@ import javax.lang.model.util.Types; /** Extension of {@link Elements} that adds Dagger-specific methods. */ @Reusable -public final class DaggerElements implements Elements { - +public final class DaggerElements implements Elements, ClearableCache { + private final Map<TypeElement, ImmutableSet<ExecutableElement>> getLocalAndInheritedMethodsCache = + new HashMap<>(); private final Elements elements; private final Types types; @@ -100,8 +102,13 @@ public final class DaggerElements implements Elements { private static final Traverser<Element> GET_ENCLOSED_ELEMENTS = Traverser.forTree(Element::getEnclosedElements); + public ImmutableSet<ExecutableElement> getLocalAndInheritedMethods(TypeElement type) { + return getLocalAndInheritedMethodsCache.computeIfAbsent( + type, k -> MoreElements.getLocalAndInheritedMethods(type, types, elements)); + } + public ImmutableSet<ExecutableElement> getUnimplementedMethods(TypeElement type) { - return FluentIterable.from(getLocalAndInheritedMethods(type, types, elements)) + return FluentIterable.from(getLocalAndInheritedMethods(type)) .filter(hasModifiers(ABSTRACT)) .toSet(); } @@ -118,7 +125,7 @@ public final class DaggerElements implements Elements { /** Returns the type element for a class name. */ public TypeElement getTypeElement(ClassName className) { - return getTypeElement(className.withoutAnnotations().toString()); + return getTypeElement(className.canonicalName()); } /** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */ @@ -492,4 +499,9 @@ public final class DaggerElements implements Elements { public boolean isFunctionalInterface(TypeElement type) { return elements.isFunctionalInterface(type); } + + @Override + public void clearCache() { + getLocalAndInheritedMethodsCache.clear(); + } } diff --git a/java/dagger/internal/codegen/validation/BindingElementValidator.java b/java/dagger/internal/codegen/validation/BindingElementValidator.java index 4557745ee..b8f9912b4 100644 --- a/java/dagger/internal/codegen/validation/BindingElementValidator.java +++ b/java/dagger/internal/codegen/validation/BindingElementValidator.java @@ -17,7 +17,6 @@ package dagger.internal.codegen.validation; import static com.google.auto.common.MoreTypes.asTypeElement; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verifyNotNull; import static dagger.internal.codegen.base.Scopes.scopesOf; import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent; @@ -142,10 +141,12 @@ public abstract class BindingElementValidator<E extends Element> { protected abstract class ElementValidator { protected final E element; protected final ValidationReport.Builder<E> report; + private final ImmutableCollection<? extends AnnotationMirror> qualifiers; protected ElementValidator(E element) { this.element = element; this.report = ValidationReport.about(element); + qualifiers = injectionAnnotations.getQualifiers(element); } /** Checks the element for validity. */ @@ -185,10 +186,15 @@ public abstract class BindingElementValidator<E extends Element> { protected void checkType() { switch (ContributionType.fromBindingElement(element)) { case UNIQUE: - /* Validate that a unique binding is not attempting to bind a framework type. This - * validation is only appropriate for unique bindings because multibindings may collect - * framework types. E.g. Set<Provider<Foo>> is perfectly reasonable. */ + // Validate that a unique binding is not attempting to bind a framework type. This + // validation is only appropriate for unique bindings because multibindings may collect + // framework types. E.g. Set<Provider<Foo>> is perfectly reasonable. checkFrameworkType(); + + // Validate that a unique binding is not attempting to bind an unqualified assisted type. + // This validation is only appropriate for unique bindings because multibindings may + // collect assisted types. + checkAssistedType(); // fall through case SET: @@ -208,22 +214,26 @@ public abstract class BindingElementValidator<E extends Element> { TypeKind kind = keyType.getKind(); if (kind.equals(VOID)) { report.addError(bindingElements("must %s a value (not void)", bindingElementTypeVerb())); - } else if (kind == DECLARED) { - checkNotAssistedInject(keyType); - } else if (!(kind.isPrimitive() || kind.equals(ARRAY) || kind.equals(TYPEVAR))) { + } else if (!(kind.isPrimitive() + || kind.equals(DECLARED) + || kind.equals(ARRAY) + || kind.equals(TYPEVAR))) { report.addError(badTypeMessage()); } } - /** Adds errors for a method return type. */ - private void checkNotAssistedInject(TypeMirror keyType) { - checkState(keyType.getKind() == TypeKind.DECLARED); - TypeElement keyElement = asTypeElement(keyType); - if (isAssistedInjectionType(keyElement)) { - report.addError("Dagger does not support providing @AssistedInject types.", keyElement); - } - if (isAssistedFactoryType(keyElement)) { - report.addError("Dagger does not support providing @AssistedFactory types.", keyElement); + /** Adds errors for unqualified assisted types. */ + private void checkAssistedType() { + if (qualifiers.isEmpty() + && bindingElementType().isPresent() + && bindingElementType().get().getKind() == DECLARED) { + TypeElement keyElement = asTypeElement(bindingElementType().get()); + if (isAssistedInjectionType(keyElement)) { + report.addError("Dagger does not support providing @AssistedInject types.", keyElement); + } + if (isAssistedFactoryType(keyElement)) { + report.addError("Dagger does not support providing @AssistedFactory types.", keyElement); + } } } @@ -254,8 +264,6 @@ public abstract class BindingElementValidator<E extends Element> { * Adds an error if the element has more than one {@linkplain Qualifier qualifier} annotation. */ private void checkQualifiers() { - ImmutableCollection<? extends AnnotationMirror> qualifiers = - injectionAnnotations.getQualifiers(element); if (qualifiers.size() > 1) { for (AnnotationMirror qualifier : qualifiers) { report.addError( diff --git a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java index aa20232b9..28b1c2bc8 100644 --- a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java @@ -318,7 +318,7 @@ public final class ComponentDescriptorValidator { Set<ComponentRequirement> mustBePassed = Sets.filter( componentModuleAndDependencyRequirements, - input -> input.nullPolicy(elements, types, metadataUtil).equals(NullPolicy.THROW)); + input -> input.nullPolicy(elements, metadataUtil).equals(NullPolicy.THROW)); // Component requirements that the creator must be able to set, but can't Set<ComponentRequirement> missingRequirements = Sets.difference(mustBePassed, creatorModuleAndDependencyRequirements); diff --git a/java/dagger/internal/codegen/validation/ComponentValidator.java b/java/dagger/internal/codegen/validation/ComponentValidator.java index b3322e5b2..72a44f251 100644 --- a/java/dagger/internal/codegen/validation/ComponentValidator.java +++ b/java/dagger/internal/codegen/validation/ComponentValidator.java @@ -17,7 +17,6 @@ package dagger.internal.codegen.validation; import static com.google.auto.common.MoreElements.asType; -import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreElements.isAnnotationPresent; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.auto.common.MoreTypes.asExecutable; @@ -243,8 +242,7 @@ public final class ComponentValidator implements ClearableCache { } private void validateComponentMethods() { - getLocalAndInheritedMethods(component, types, elements).stream() - .filter(method -> method.getModifiers().contains(ABSTRACT)) + elements.getUnimplementedMethods(component).stream() .map(ComponentMethodValidator::new) .forEachOrdered(ComponentMethodValidator::validateMethod); } diff --git a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java index 68c960478..f28049753 100644 --- a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java +++ b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java @@ -77,11 +77,21 @@ final class DependencyRequestValidator { // Don't validate assisted parameters. These are not dependency requests. return; } - checkQualifiers(report, requestElement); - checkType(report, requestElement, requestType); + if (missingQualifierMetadata(requestElement)) { + report.addError( + "Unable to read annotations on an injected Kotlin property. The Dagger compiler must" + + " also be applied to any project containing @Inject properties.", + requestElement); + + // Skip any further validation if we don't have valid metadata for a type that needs it. + return; + } + + new Validator(report, requestElement, requestType).validate(); } - private void checkQualifiers(ValidationReport.Builder<?> report, Element requestElement) { + /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */ + private boolean missingQualifierMetadata(Element requestElement) { if (requestElement.getKind() == ElementKind.FIELD // static injected fields are not supported, no need to get qualifier from kotlin metadata && !requestElement.getModifiers().contains(STATIC) @@ -91,64 +101,80 @@ final class DependencyRequestValidator { Optional.ofNullable( elements.getTypeElement( membersInjectorNameForType(asType(requestElement.getEnclosingElement())))); - if (!membersInjector.isPresent()) { - report.addError( - "Unable to read annotations on an injected Kotlin property. The Dagger compiler must" - + " also be applied to any project containing @Inject properties.", - requestElement); - return; // finish checking qualifiers since current information is unreliable. - } + return !membersInjector.isPresent(); } + return false; + } - ImmutableCollection<? extends AnnotationMirror> qualifiers = - injectionAnnotations.getQualifiers(requestElement); - if (qualifiers.size() > 1) { - for (AnnotationMirror qualifier : qualifiers) { - report.addError( - "A single dependency request may not use more than one @Qualifier", - requestElement, - qualifier); + private final class Validator { + private final ValidationReport.Builder<?> report; + private final Element requestElement; + private final TypeMirror requestType; + private final TypeMirror keyType; + private final RequestKind requestKind; + private final ImmutableCollection<? extends AnnotationMirror> qualifiers; + + + Validator(ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) { + this.report = report; + this.requestElement = requestElement; + this.requestType = requestType; + this.keyType = extractKeyType(requestType); + this.requestKind = RequestKinds.getRequestKind(requestType); + this.qualifiers = injectionAnnotations.getQualifiers(requestElement); + } + + void validate() { + checkQualifiers(); + checkType(); + } + + private void checkQualifiers() { + if (qualifiers.size() > 1) { + for (AnnotationMirror qualifier : qualifiers) { + report.addError( + "A single dependency request may not use more than one @Qualifier", + requestElement, + qualifier); + } } } - } - private void checkType( - ValidationReport.Builder<?> report, Element requestElement, TypeMirror requestType) { - TypeMirror keyType = extractKeyType(requestType); - RequestKind requestKind = RequestKinds.getRequestKind(requestType); - if (keyType.getKind() == TypeKind.DECLARED) { - TypeElement typeElement = asTypeElement(keyType); - if (isAssistedInjectionType(typeElement)) { - report.addError( - "Dagger does not support injecting @AssistedInject type, " - + requestType - + ". Did you mean to inject its assisted factory type instead?", - requestElement); + private void checkType() { + if (qualifiers.isEmpty() && keyType.getKind() == TypeKind.DECLARED) { + TypeElement typeElement = asTypeElement(keyType); + if (isAssistedInjectionType(typeElement)) { + report.addError( + "Dagger does not support injecting @AssistedInject type, " + + requestType + + ". Did you mean to inject its assisted factory type instead?", + requestElement); + } + if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) { + report.addError( + "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " + + "or Produced<T> when T is an @AssistedFactory-annotated type such as " + + keyType, + requestElement); + } } - if (requestKind != RequestKind.INSTANCE && isAssistedFactoryType(typeElement)) { + if (keyType.getKind().equals(WILDCARD)) { + // TODO(ronshapiro): Explore creating this message using RequestKinds. report.addError( "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " - + "or Produced<T> when T is an @AssistedFactory-annotated type such as " + + "or Produced<T> when T is a wildcard type such as " + keyType, requestElement); } - } - if (keyType.getKind().equals(WILDCARD)) { - // TODO(ronshapiro): Explore creating this message using RequestKinds. - report.addError( - "Dagger does not support injecting Provider<T>, Lazy<T>, Producer<T>, " - + "or Produced<T> when T is a wildcard type such as " - + keyType, - requestElement); - } - if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) { - DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType); - if (membersInjectorType.getTypeArguments().isEmpty()) { - report.addError("Cannot inject a raw MembersInjector", requestElement); - } else { - report.addSubreport( - membersInjectionValidator.validateMembersInjectionRequest( - requestElement, membersInjectorType.getTypeArguments().get(0))); + if (MoreTypes.isType(keyType) && MoreTypes.isTypeOf(MembersInjector.class, keyType)) { + DeclaredType membersInjectorType = MoreTypes.asDeclared(keyType); + if (membersInjectorType.getTypeArguments().isEmpty()) { + report.addError("Cannot inject a raw MembersInjector", requestElement); + } else { + report.addSubreport( + membersInjectionValidator.validateMembersInjectionRequest( + requestElement, membersInjectorType.getTypeArguments().get(0))); + } } } } diff --git a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java index 06f68f68d..9787f4f37 100644 --- a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java +++ b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java @@ -24,6 +24,7 @@ import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey; import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.assistedInjectedConstructors; import static dagger.internal.codegen.binding.InjectionAnnotations.injectedConstructors; import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding; +import static dagger.internal.codegen.langmodel.DaggerTypes.unwrapType; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; @@ -112,7 +113,11 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { /** Caches the binding and generates it if it needs generation. */ void tryRegisterBinding(B binding, boolean warnIfNotAlreadyGenerated) { tryToCacheBinding(binding); - tryToGenerateBinding(binding, warnIfNotAlreadyGenerated); + + @SuppressWarnings("unchecked") + B maybeUnresolved = + binding.unresolved().isPresent() ? (B) binding.unresolved().get() : binding; + tryToGenerateBinding(maybeUnresolved, warnIfNotAlreadyGenerated); } /** @@ -199,9 +204,6 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { */ private void registerBinding(ProvisionBinding binding, boolean warnIfNotAlreadyGenerated) { provisionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); - if (binding.unresolved().isPresent()) { - provisionBindings.tryToGenerateBinding(binding.unresolved().get(), warnIfNotAlreadyGenerated); - } } /** @@ -229,10 +231,6 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { } membersInjectionBindings.tryRegisterBinding(binding, warnIfNotAlreadyGenerated); - if (binding.unresolved().isPresent()) { - membersInjectionBindings.tryToGenerateBinding( - binding.unresolved().get(), warnIfNotAlreadyGenerated); - } } @Override @@ -255,15 +253,16 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { ValidationReport<TypeElement> report = injectValidator.validateConstructor(constructorElement); report.printMessagesTo(messager); - if (report.isClean()) { - ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); - registerBinding(binding, warnIfNotAlreadyGenerated); - if (!binding.injectionSites().isEmpty()) { - tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated); - } - return Optional.of(binding); + if (!report.isClean()) { + return Optional.empty(); } - return Optional.empty(); + + ProvisionBinding binding = bindingFactory.injectionBinding(constructorElement, resolvedType); + registerBinding(binding, warnIfNotAlreadyGenerated); + if (!binding.injectionSites().isEmpty()) { + tryRegisterMembersInjectedType(typeElement, resolvedType, warnIfNotAlreadyGenerated); + } + return Optional.of(binding); } @Override @@ -286,17 +285,18 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { ValidationReport<TypeElement> report = injectValidator.validateMembersInjectionType(typeElement); report.printMessagesTo(messager); - if (report.isClean()) { - MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); - registerBinding(binding, warnIfNotAlreadyGenerated); - for (Optional<DeclaredType> supertype = types.nonObjectSuperclass(type); - supertype.isPresent(); - supertype = types.nonObjectSuperclass(supertype.get())) { - getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get())); - } - return Optional.of(binding); + if (!report.isClean()) { + return Optional.empty(); + } + + MembersInjectionBinding binding = bindingFactory.membersInjectionBinding(type, resolvedType); + registerBinding(binding, warnIfNotAlreadyGenerated); + for (Optional<DeclaredType> supertype = types.nonObjectSuperclass(type); + supertype.isPresent(); + supertype = types.nonObjectSuperclass(supertype.get())) { + getOrFindMembersInjectionBinding(keyFactory.forMembersInjectedType(supertype.get())); } - return Optional.empty(); + return Optional.of(binding); } @CanIgnoreReturnValue @@ -352,7 +352,7 @@ final class InjectBindingRegistryImpl implements InjectBindingRegistry { if (!isValidMembersInjectionKey(key)) { return Optional.empty(); } - Key membersInjectionKey = keyFactory.forMembersInjectedType(types.unwrapType(key.type())); + Key membersInjectionKey = keyFactory.forMembersInjectedType(unwrapType(key.type())); return getOrFindMembersInjectionBinding(membersInjectionKey) .map(binding -> bindingFactory.membersInjectorBinding(key, binding)); } diff --git a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java index 2c72e5f0d..1c2eb1c9a 100644 --- a/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java +++ b/java/dagger/internal/codegen/validation/MonitoringModuleGenerator.java @@ -26,6 +26,7 @@ import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; @@ -38,7 +39,6 @@ import dagger.multibindings.Multibinds; import dagger.producers.ProductionScope; import dagger.producers.monitoring.ProductionComponentMonitor; import dagger.producers.monitoring.internal.Monitors; -import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; @@ -54,19 +54,14 @@ final class MonitoringModuleGenerator extends SourceFileGenerator<TypeElement> { } @Override - public ClassName nameGeneratedType(TypeElement componentElement) { - return SourceFiles.generatedMonitoringModuleName(componentElement); - } - - @Override public Element originatingElement(TypeElement componentElement) { return componentElement; } @Override - public Optional<TypeSpec.Builder> write(TypeElement componentElement) { - return Optional.of( - classBuilder(nameGeneratedType(componentElement)) + public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement componentElement) { + return ImmutableList.of( + classBuilder(SourceFiles.generatedMonitoringModuleName(componentElement)) .addAnnotation(Module.class) .addModifiers(ABSTRACT) .addMethod(privateConstructor()) diff --git a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java index 96e6340af..fa3a16cac 100644 --- a/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java +++ b/java/dagger/internal/codegen/writing/AnnotationCreatorGenerator.java @@ -39,7 +39,6 @@ import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; import dagger.internal.codegen.langmodel.DaggerElements; import java.util.LinkedHashSet; -import java.util.Optional; import java.util.Set; import javax.annotation.processing.Filer; import javax.inject.Inject; @@ -88,18 +87,13 @@ public class AnnotationCreatorGenerator extends SourceFileGenerator<TypeElement> } @Override - public ClassName nameGeneratedType(TypeElement annotationType) { - return getAnnotationCreatorClassName(annotationType); - } - - @Override public Element originatingElement(TypeElement annotationType) { return annotationType; } @Override - public Optional<TypeSpec.Builder> write(TypeElement annotationType) { - ClassName generatedTypeName = nameGeneratedType(annotationType); + public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement annotationType) { + ClassName generatedTypeName = getAnnotationCreatorClassName(annotationType); TypeSpec.Builder annotationCreatorBuilder = classBuilder(generatedTypeName) .addModifiers(PUBLIC, FINAL) @@ -109,7 +103,7 @@ public class AnnotationCreatorGenerator extends SourceFileGenerator<TypeElement> annotationCreatorBuilder.addMethod(buildCreateMethod(generatedTypeName, annotationElement)); } - return Optional.of(annotationCreatorBuilder); + return ImmutableList.of(annotationCreatorBuilder); } private MethodSpec buildCreateMethod(ClassName generatedTypeName, TypeElement annotationElement) { diff --git a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java b/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java index d0c481c7a..d90ab71f1 100644 --- a/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java +++ b/java/dagger/internal/codegen/writing/AssistedFactoryBindingExpression.java @@ -83,7 +83,7 @@ final class AssistedFactoryBindingExpression extends SimpleInvocationBindingExpr private TypeSpec anonymousfactoryImpl(Expression assistedInjectionExpression) { TypeElement factory = asType(binding.bindingElement().get()); DeclaredType factoryType = asDeclared(binding.key().type()); - ExecutableElement factoryMethod = assistedFactoryMethod(factory, elements, types); + ExecutableElement factoryMethod = assistedFactoryMethod(factory, elements); // We can't use MethodSpec.overriding directly because we need to control the parameter names. MethodSpec factoryOverride = MethodSpec.overriding(factoryMethod, factoryType, types).build(); diff --git a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java b/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java index 5f1f0bdd8..dc15c998b 100644 --- a/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java +++ b/java/dagger/internal/codegen/writing/ComponentBindingExpressions.java @@ -212,19 +212,14 @@ public final class ComponentBindingExpressions { public MethodSpec getComponentMethod(ComponentMethodDescriptor componentMethod) { checkArgument(componentMethod.dependencyRequest().isPresent()); BindingRequest request = bindingRequest(componentMethod.dependencyRequest().get()); - MethodSpec.Builder method = - MethodSpec.overriding( + return MethodSpec.overriding( componentMethod.methodElement(), MoreTypes.asDeclared(graph.componentTypeElement().asType()), - types); - // Even though this is not used if the method is abstract, we need to invoke the binding - // expression in order for the side of effect of the method being added to the - // ComponentImplementation - CodeBlock methodBody = - getBindingExpression(request) - .getComponentMethodImplementation(componentMethod, componentImplementation); - - return method.addCode(methodBody).build(); + types) + .addCode( + getBindingExpression(request) + .getComponentMethodImplementation(componentMethod, componentImplementation)) + .build(); } /** Returns the {@link BindingExpression} for the given {@link BindingRequest}. */ @@ -460,8 +455,7 @@ public final class ComponentBindingExpressions { private BindingExpression providerBindingExpression(ContributionBinding binding) { if (binding.kind().equals(DELEGATE) && !needsCaching(binding)) { return new DelegateBindingExpression(binding, RequestKind.PROVIDER, this, types, elements); - } else if (compilerOptions.fastInit( - topLevelComponentImplementation.componentDescriptor().typeElement()) + } else if (isFastInit() && frameworkInstanceCreationExpression(binding).useInnerSwitchingProvider() && !(instanceBindingExpression(binding) instanceof DerivedFromFrameworkInstanceBindingExpression)) { @@ -493,24 +487,27 @@ public final class ComponentBindingExpressions { /** * Returns a binding expression for {@link RequestKind#INSTANCE} requests. - * - * <p>If there is a direct expression (not calling {@link Provider#get()}) we can use for an - * instance of this binding, return it, wrapped in a method if the binding {@linkplain - * #needsCaching(ContributionBinding) needs to be cached} or the expression has dependencies. - * - * <p>In fastInit mode, we can use direct expressions unless the binding needs to be cached. */ private BindingExpression instanceBindingExpression(ContributionBinding binding) { Optional<BindingExpression> maybeDirectInstanceExpression = unscopedDirectInstanceExpression(binding); - if (canUseDirectInstanceExpression(binding) && maybeDirectInstanceExpression.isPresent()) { - BindingExpression directInstanceExpression = maybeDirectInstanceExpression.get(); - return directInstanceExpression.requiresMethodEncapsulation() || needsCaching(binding) - ? wrapInMethod( - binding, - bindingRequest(binding.key(), RequestKind.INSTANCE), - directInstanceExpression) - : directInstanceExpression; + if (maybeDirectInstanceExpression.isPresent()) { + // If this is the case where we don't need to use Provider#get() because there's no caching + // and it isn't an assisted factory, or because we're in fastInit mode (since fastInit avoids + // using Providers), we can try to use the direct expression, possibly wrapped in a method + // if necessary (e.g. it has dependencies). + if ((!needsCaching(binding) && binding.kind() != BindingKind.ASSISTED_FACTORY) + || isFastInit()) { + BindingExpression directInstanceExpression = maybeDirectInstanceExpression.get(); + // While this can't require caching in default mode, if we're in fastInit mode and we need + // caching we also need to wrap it in a method. + return directInstanceExpression.requiresMethodEncapsulation() || needsCaching(binding) + ? wrapInMethod( + binding, + bindingRequest(binding.key(), RequestKind.INSTANCE), + directInstanceExpression) + : directInstanceExpression; + } } return new DerivedFromFrameworkInstanceBindingExpression( binding.key(), FrameworkType.PROVIDER, RequestKind.INSTANCE, this, types); @@ -610,27 +607,12 @@ public final class ComponentBindingExpressions { * MapFactory} or {@code SetFactory}. */ private boolean useStaticFactoryCreation(ContributionBinding binding) { - return !compilerOptions.fastInit( - topLevelComponentImplementation.componentDescriptor().typeElement()) + return !isFastInit() || binding.kind().equals(MULTIBOUND_MAP) || binding.kind().equals(MULTIBOUND_SET); } /** - * Returns {@code true} if we can use a direct (not {@code Provider.get()}) expression for this - * binding. If the binding doesn't {@linkplain #needsCaching(ContributionBinding) need to be - * cached} and the binding is not an {@link BindingKind.ASSISTED_FACTORY}, we can. - * - * <p>In fastInit mode, we can use a direct expression even if the binding {@linkplain - * #needsCaching(ContributionBinding) needs to be cached}. - */ - private boolean canUseDirectInstanceExpression(ContributionBinding binding) { - return (!needsCaching(binding) && binding.kind() != BindingKind.ASSISTED_FACTORY) - || compilerOptions.fastInit( - topLevelComponentImplementation.componentDescriptor().typeElement()); - } - - /** * Returns a binding expression that uses a given one as the body of a method that users call. If * a component provision method matches it, it will be the method implemented. If it does not * match a component provision method and the binding is modifiable, then a new public modifiable @@ -690,8 +672,7 @@ public final class ComponentBindingExpressions { private MethodImplementationStrategy methodImplementationStrategy( ContributionBinding binding, BindingRequest request) { - if (compilerOptions.fastInit( - topLevelComponentImplementation.componentDescriptor().typeElement())) { + if (isFastInit()) { if (request.isRequestKind(RequestKind.PROVIDER)) { return MethodImplementationStrategy.SINGLE_CHECK; } else if (request.isRequestKind(RequestKind.INSTANCE) && needsCaching(binding)) { @@ -718,4 +699,9 @@ public final class ComponentBindingExpressions { } return true; } + + private boolean isFastInit() { + return compilerOptions.fastInit( + topLevelComponentImplementation.componentDescriptor().typeElement()); + } } diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java index ef6912460..a09620e87 100644 --- a/java/dagger/internal/codegen/writing/ComponentImplementation.java +++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java @@ -276,7 +276,10 @@ public final class ComponentImplementation { "%s is not a child component of %s", childDescriptor.typeElement(), componentDescriptor().typeElement()); - return name.nestedClass(subcomponentNames.get(childDescriptor) + "Impl"); + // TODO(erichang): Hacky fix to shorten the suffix if we're too deeply + // nested to save on file name length. 2 chosen arbitrarily. + String suffix = name.simpleNames().size() > 2 ? "I" : "Impl"; + return name.nestedClass(subcomponentNames.get(childDescriptor) + suffix); } /** diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java index 5a40c0206..af5d45e66 100644 --- a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java +++ b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java @@ -96,7 +96,7 @@ final class DependencyMethodProviderCreationExpression COMPONENT_PROVISION_FACTORY, classBuilder(factoryClassName()) .addSuperinterface(providerOf(keyType)) - .addModifiers(PRIVATE, STATIC) + .addModifiers(PRIVATE, STATIC, FINAL) .addField(dependencyClassName, dependency().variableName(), PRIVATE, FINAL) .addMethod( constructorBuilder() diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java index 70edd1a95..03ad27cbb 100644 --- a/java/dagger/internal/codegen/writing/FactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java @@ -97,32 +97,27 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding } @Override - public ClassName nameGeneratedType(ProvisionBinding binding) { - return generatedClassNameForBinding(binding); - } - - @Override public Element originatingElement(ProvisionBinding binding) { // we only create factories for bindings that have a binding element return binding.bindingElement().get(); } @Override - public Optional<TypeSpec.Builder> write(ProvisionBinding binding) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(ProvisionBinding binding) { // We don't want to write out resolved bindings -- we want to write out the generic version. checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); if (binding.factoryCreationStrategy().equals(DELEGATE)) { - return Optional.empty(); + return ImmutableList.of(); } - return Optional.of(factoryBuilder(binding)); + return ImmutableList.of(factoryBuilder(binding)); } private TypeSpec.Builder factoryBuilder(ProvisionBinding binding) { TypeSpec.Builder factoryBuilder = - classBuilder(nameGeneratedType(binding)) + classBuilder(generatedClassNameForBinding(binding)) .addModifiers(PUBLIC, FINAL) .addTypeVariables(bindingTypeElementTypeVariableNames(binding)); @@ -194,8 +189,9 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding switch (binding.factoryCreationStrategy()) { case SINGLETON_INSTANCE: FieldSpec.Builder instanceFieldBuilder = - FieldSpec.builder(nameGeneratedType(binding), "INSTANCE", PRIVATE, STATIC, FINAL) - .initializer("new $T()", nameGeneratedType(binding)); + FieldSpec.builder( + generatedClassNameForBinding(binding), "INSTANCE", PRIVATE, STATIC, FINAL) + .initializer("new $T()", generatedClassNameForBinding(binding)); if (!bindingTypeElementTypeVariableNames(binding).isEmpty()) { // If the factory has type parameters, ignore them in the field declaration & initializer @@ -203,7 +199,8 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding createMethodBuilder.addAnnotation(suppressWarnings(UNCHECKED)); } - ClassName instanceHolderName = nameGeneratedType(binding).nestedClass("InstanceHolder"); + ClassName instanceHolderName = + generatedClassNameForBinding(binding).nestedClass("InstanceHolder"); createMethodBuilder.addStatement("return $T.INSTANCE", instanceHolderName); factoryBuilder.addType( TypeSpec.classBuilder(instanceHolderName) @@ -248,7 +245,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding request -> frameworkTypeUsageStatement( CodeBlock.of("$N", frameworkFields.get(request)), request.kind()), - nameGeneratedType(binding), + generatedClassNameForBinding(binding), moduleParameter(binding).map(module -> CodeBlock.of("$N", module)), compilerOptions, metadataUtil); @@ -265,7 +262,7 @@ public final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding .addCode( InjectionSiteMethod.invokeAll( binding.injectionSites(), - nameGeneratedType(binding), + generatedClassNameForBinding(binding), instance, binding.key().type(), frameworkFieldUsages(binding.dependencies(), frameworkFields)::get, diff --git a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java index 5a4b6f132..d4dbde794 100644 --- a/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java +++ b/java/dagger/internal/codegen/writing/HjarSourceFileGenerator.java @@ -19,14 +19,15 @@ package dagger.internal.codegen.writing; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; +import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; import static javax.lang.model.element.Modifier.PRIVATE; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import dagger.internal.codegen.base.SourceFileGenerator; -import java.util.Optional; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; @@ -47,18 +48,15 @@ public final class HjarSourceFileGenerator<T> extends SourceFileGenerator<T> { } @Override - public ClassName nameGeneratedType(T input) { - return delegate.nameGeneratedType(input); - } - - @Override public Element originatingElement(T input) { return delegate.originatingElement(input); } @Override - public Optional<TypeSpec.Builder> write(T input) { - return delegate.write(input).map(completeType -> skeletonType(completeType.build())); + public ImmutableList<TypeSpec.Builder> topLevelTypes(T input) { + return delegate.topLevelTypes(input).stream() + .map(completeType -> skeletonType(completeType.build())) + .collect(toImmutableList()); } private TypeSpec.Builder skeletonType(TypeSpec completeType) { diff --git a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java index d52735963..889f8985c 100644 --- a/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java +++ b/java/dagger/internal/codegen/writing/InaccessibleMapKeyProxyGenerator.java @@ -22,14 +22,13 @@ import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; -import com.squareup.javapoet.ClassName; +import com.google.common.collect.ImmutableList; 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.langmodel.DaggerElements; import dagger.internal.codegen.langmodel.DaggerTypes; -import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; @@ -53,24 +52,21 @@ public final class InaccessibleMapKeyProxyGenerator } @Override - public ClassName nameGeneratedType(ContributionBinding binding) { - return MapKeys.mapKeyProxyClassName(binding); - } - - @Override public Element originatingElement(ContributionBinding binding) { // a map key is only ever present on bindings that have a binding element return binding.bindingElement().get(); } @Override - public Optional<TypeSpec.Builder> write(ContributionBinding binding) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(ContributionBinding binding) { return MapKeys.mapKeyFactoryMethod(binding, types, elements) .map( method -> - classBuilder(nameGeneratedType(binding)) + classBuilder(MapKeys.mapKeyProxyClassName(binding)) .addModifiers(PUBLIC, FINAL) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) - .addMethod(method)); + .addMethod(method)) + .map(ImmutableList::of) + .orElse(ImmutableList.of()); } } diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java index df618b070..8630ce6ca 100644 --- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java +++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java @@ -61,7 +61,6 @@ import dagger.internal.codegen.langmodel.DaggerTypes; import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod; import dagger.model.DependencyRequest; import java.util.Map.Entry; -import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.SourceVersion; @@ -87,20 +86,15 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI } @Override - public ClassName nameGeneratedType(MembersInjectionBinding binding) { - return membersInjectorNameForType(binding.membersInjectedType()); - } - - @Override public Element originatingElement(MembersInjectionBinding binding) { return binding.membersInjectedType(); } @Override - public Optional<TypeSpec.Builder> write(MembersInjectionBinding binding) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(MembersInjectionBinding binding) { // Empty members injection bindings are special and don't need source files. if (binding.injectionSites().isEmpty()) { - return Optional.empty(); + return ImmutableList.of(); } // Members injectors for classes with no local injection sites and no @Inject @@ -108,7 +102,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI if (!binding.hasLocalInjectionSites() && injectedConstructors(binding.membersInjectedType()).isEmpty() && assistedInjectedConstructors(binding.membersInjectedType()).isEmpty()) { - return Optional.empty(); + return ImmutableList.of(); } @@ -118,7 +112,7 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI "tried to generate a MembersInjector for a binding of a resolved generic type: %s", binding); - ClassName generatedTypeName = nameGeneratedType(binding); + ClassName generatedTypeName = membersInjectorNameForType(binding.membersInjectedType()); ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); TypeSpec.Builder injectorTypeBuilder = classBuilder(generatedTypeName) @@ -222,6 +216,6 @@ public final class MembersInjectorGenerator extends SourceFileGenerator<MembersI gwtIncompatibleAnnotation(binding).ifPresent(injectorTypeBuilder::addAnnotation); - return Optional.of(injectorTypeBuilder); + return ImmutableList.of(injectorTypeBuilder); } } diff --git a/java/dagger/internal/codegen/writing/ModuleProxies.java b/java/dagger/internal/codegen/writing/ModuleProxies.java index fcd9b56e5..6d174f824 100644 --- a/java/dagger/internal/codegen/writing/ModuleProxies.java +++ b/java/dagger/internal/codegen/writing/ModuleProxies.java @@ -27,6 +27,7 @@ import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import static javax.lang.model.util.ElementFilter.constructorsIn; +import com.google.common.collect.ImmutableList; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeSpec; @@ -78,25 +79,20 @@ public final class ModuleProxies { } @Override - public ClassName nameGeneratedType(TypeElement moduleElement) { - return moduleProxies.constructorProxyTypeName(moduleElement); - } - - @Override public Element originatingElement(TypeElement moduleElement) { return moduleElement; } @Override - public Optional<TypeSpec.Builder> write(TypeElement moduleElement) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(TypeElement moduleElement) { ModuleKind.checkIsModule(moduleElement, metadataUtil); return moduleProxies.nonPublicNullaryConstructor(moduleElement).isPresent() - ? Optional.of(buildProxy(moduleElement)) - : Optional.empty(); + ? ImmutableList.of(buildProxy(moduleElement)) + : ImmutableList.of(); } private TypeSpec.Builder buildProxy(TypeElement moduleElement) { - return classBuilder(nameGeneratedType(moduleElement)) + return classBuilder(moduleProxies.constructorProxyTypeName(moduleElement)) .addModifiers(PUBLIC, FINAL) .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) .addMethod( diff --git a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java index 5e5292359..a013be940 100644 --- a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java +++ b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java @@ -103,10 +103,6 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti this.keyFactory = keyFactory; } - @Override - public ClassName nameGeneratedType(ProductionBinding binding) { - return generatedClassNameForBinding(binding); - } @Override public Element originatingElement(ProductionBinding binding) { @@ -115,7 +111,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti } @Override - public Optional<TypeSpec.Builder> write(ProductionBinding binding) { + public ImmutableList<TypeSpec.Builder> topLevelTypes(ProductionBinding binding) { // We don't want to write out resolved bindings -- we want to write out the generic version. checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); @@ -123,7 +119,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti TypeName providedTypeName = TypeName.get(binding.contributedType()); TypeName futureTypeName = listenableFutureOf(providedTypeName); - ClassName generatedTypeName = nameGeneratedType(binding); + ClassName generatedTypeName = generatedClassNameForBinding(binding); TypeSpec.Builder factoryBuilder = classBuilder(generatedTypeName) .addModifiers(PUBLIC, FINAL) @@ -233,7 +229,7 @@ public final class ProducerFactoryGenerator extends SourceFileGenerator<Producti gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation); // TODO(gak): write a sensible toString - return Optional.of(factoryBuilder); + return ImmutableList.of(factoryBuilder); } private MethodSpec staticFactoryMethod(ProductionBinding binding, MethodSpec constructor) { diff --git a/javatests/artifacts/dagger-android/simple/build.gradle b/javatests/artifacts/dagger-android/simple/build.gradle index 8c179e503..d29aa0c7a 100644 --- a/javatests/artifacts/dagger-android/simple/build.gradle +++ b/javatests/artifacts/dagger-android/simple/build.gradle @@ -34,5 +34,20 @@ allprojects { mavenCentral() mavenLocal() } + // TODO(bcorso): Consider organizing all projects under a single project + // that share a build.gradle configuration to reduce the copy-paste. + // Local tests: Adds logs for individual local tests + tasks.withType(Test) { + testLogging { + exceptionFormat "full" + showCauses true + showExceptions true + showStackTraces true + showStandardStreams true + events = ["passed", "skipped", "failed", "standardOut", "standardError"] + } + } } +// Instrumentation tests: Combines test reports for all modules +apply plugin: 'android-reporting' diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle index 43ccd1d55..4f21d0a38 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle +++ b/javatests/artifacts/hilt-android/gradleConfigCache/app/build.gradle @@ -38,6 +38,9 @@ android { kotlinOptions { jvmTarget = '1.8' } + lintOptions { + checkReleaseBuilds = false + } testOptions { unitTests.includeAndroidResources = true } @@ -45,6 +48,7 @@ android { hilt { enableTransformForLocalTests = true + enableExperimentalClasspathAggregation = true } dependencies { diff --git a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle b/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle index 3b0cddeb4..de0566c5d 100644 --- a/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle +++ b/javatests/artifacts/hilt-android/gradleConfigCache/build.gradle @@ -38,4 +38,20 @@ allprojects { mavenCentral() mavenLocal() } + // TODO(bcorso): Consider organizing all projects under a single project + // that share a build.gradle configuration to reduce the copy-paste. + // Local tests: Adds logs for individual local tests + tasks.withType(Test) { + testLogging { + exceptionFormat "full" + showCauses true + showExceptions true + showStackTraces true + showStandardStreams true + events = ["passed", "skipped", "failed", "standardOut", "standardError"] + } + } } + +// Instrumentation tests: Combines test reports for all modules +apply plugin: 'android-reporting'
\ No newline at end of file diff --git a/javatests/artifacts/hilt-android/simple/app/build.gradle b/javatests/artifacts/hilt-android/simple/app/build.gradle index 98b79e188..1d76ec26c 100644 --- a/javatests/artifacts/hilt-android/simple/app/build.gradle +++ b/javatests/artifacts/hilt-android/simple/app/build.gradle @@ -97,7 +97,6 @@ dependencies { // To help us catch version skew related issues in hilt extensions. // TODO(bcorso): Add examples testing the actual API. - implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01' implementation 'androidx.hilt:hilt-work:1.0.0-alpha01' annotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01' testAnnotationProcessor 'androidx.hilt:hilt-compiler:1.0.0-alpha01' diff --git a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java new file mode 100644 index 000000000..de2e37aef --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/CustomTestApplicationTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.simple.BaseTestApplication.Foo; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +public final class CustomTestApplicationTest { + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testApplicationBaseClass() throws Exception { + assertThat((Context) getApplicationContext()).isInstanceOf(BaseTestApplication.class); + } + + @Test + public void testEarlyEntryPoint() throws Exception { + BaseTestApplication app = (BaseTestApplication) getApplicationContext(); + + // Assert that all scoped Foo instances from EarlyEntryPoint are equal + Foo earlyFoo = app.earlyFoo(); + Foo lazyEarlyFoo1 = app.lazyEarlyFoo(); + Foo lazyEarlyFoo2 = app.lazyEarlyFoo(); + assertThat(earlyFoo).isNotNull(); + assertThat(lazyEarlyFoo1).isNotNull(); + assertThat(lazyEarlyFoo2).isNotNull(); + assertThat(earlyFoo).isEqualTo(lazyEarlyFoo1); + assertThat(earlyFoo).isEqualTo(lazyEarlyFoo2); + + // Assert that all scoped Foo instances from EntryPoint are equal + Foo lazyFoo1 = app.lazyFoo(); + Foo lazyFoo2 = app.lazyFoo(); + assertThat(lazyFoo1).isNotNull(); + assertThat(lazyFoo2).isNotNull(); + assertThat(lazyFoo1).isEqualTo(lazyFoo2); + + // Assert that scoped Foo instances from EarlyEntryPoint and EntryPoint are not equal + assertThat(earlyFoo).isNotEqualTo(lazyFoo1); + } +} diff --git a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java index 6ee721c84..8d4913b35 100644 --- a/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java +++ b/javatests/artifacts/hilt-android/simple/app/src/androidTest/java/dagger/hilt/android/simple/SimpleEmulatorTestRunner.java @@ -19,14 +19,15 @@ package dagger.hilt.android.simple; import android.app.Application; import android.content.Context; import androidx.test.runner.AndroidJUnitRunner; -import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.android.testing.CustomTestApplication; /** A custom runner to setup the emulator application class for tests. */ +@CustomTestApplication(BaseTestApplication.class) public final class SimpleEmulatorTestRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException { - return super.newApplication(cl, HiltTestApplication.class.getName(), context); + return super.newApplication(cl, SimpleEmulatorTestRunner_Application.class.getName(), context); } } diff --git a/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java new file mode 100644 index 000000000..7af0cb39e --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app/src/sharedTest/java/dagger/hilt/android/simple/BaseTestApplication.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import android.app.Application; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPoint; +import dagger.hilt.android.EarlyEntryPoints; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** A custom application used to test @EarlyEntryPoint */ +public class BaseTestApplication extends Application { + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EarlyFooEntryPoint { + Foo earlyFoo(); + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo lazyFoo(); + } + + @Singleton + public static final class Foo { + @Inject + Foo() {} + } + + private Foo earlyFoo; + + @Override + public void onCreate() { + super.onCreate(); + earlyFoo = EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).earlyFoo(); + } + + public Foo earlyFoo() { + return earlyFoo; + } + + public Foo lazyEarlyFoo() { + return EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).earlyFoo(); + } + + public Foo lazyFoo() { + return EntryPoints.get(this, FooEntryPoint.class).lazyFoo(); + } +} diff --git a/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java b/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java new file mode 100644 index 000000000..8c7f73f27 --- /dev/null +++ b/javatests/artifacts/hilt-android/simple/app/src/test/java/dagger/hilt/android/simple/CustomTestApplicationTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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.hilt.android.simple; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.simple.BaseTestApplication.Foo; +import dagger.hilt.android.testing.CustomTestApplication; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */ +@CustomTestApplication(BaseTestApplication.class) +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = CustomTestApplicationTest_Application.class) +public final class CustomTestApplicationTest { + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testApplicationBaseClass() throws Exception { + assertThat((Context) getApplicationContext()).isInstanceOf(BaseTestApplication.class); + } + + @Test + public void testEarlyEntryPoint() throws Exception { + BaseTestApplication app = (BaseTestApplication) getApplicationContext(); + + // Assert that all scoped Foo instances from EarlyEntryPoint are equal + Foo earlyFoo = app.earlyFoo(); + Foo lazyEarlyFoo1 = app.lazyEarlyFoo(); + Foo lazyEarlyFoo2 = app.lazyEarlyFoo(); + assertThat(earlyFoo).isNotNull(); + assertThat(lazyEarlyFoo1).isNotNull(); + assertThat(lazyEarlyFoo2).isNotNull(); + assertThat(earlyFoo).isEqualTo(lazyEarlyFoo1); + assertThat(earlyFoo).isEqualTo(lazyEarlyFoo2); + + // Assert that all scoped Foo instances from EntryPoint are equal + Foo lazyFoo1 = app.lazyFoo(); + Foo lazyFoo2 = app.lazyFoo(); + assertThat(lazyFoo1).isNotNull(); + assertThat(lazyFoo2).isNotNull(); + assertThat(lazyFoo1).isEqualTo(lazyFoo2); + + // Assert that scoped Foo instances from EarlyEntryPoint and EntryPoint are not equal + assertThat(earlyFoo).isNotEqualTo(lazyFoo1); + } +} diff --git a/javatests/artifacts/hilt-android/simple/build.gradle b/javatests/artifacts/hilt-android/simple/build.gradle index 770fabd31..59336bcbf 100644 --- a/javatests/artifacts/hilt-android/simple/build.gradle +++ b/javatests/artifacts/hilt-android/simple/build.gradle @@ -39,4 +39,20 @@ allprojects { mavenCentral() mavenLocal() } + // TODO(bcorso): Consider organizing all projects under a single project + // that share a build.gradle configuration to reduce the copy-paste. + // Local tests: Adds logs for individual local tests + tasks.withType(Test) { + testLogging { + exceptionFormat "full" + showCauses true + showExceptions true + showStackTraces true + showStandardStreams true + events = ["passed", "skipped", "failed", "standardOut", "standardError"] + } + } } + +// Instrumentation tests: Combines test reports for all modules +apply plugin: 'android-reporting' diff --git a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle index d59447608..e7eb9b252 100644 --- a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle +++ b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle @@ -38,4 +38,17 @@ allprojects { mavenCentral() mavenLocal() } + // TODO(bcorso): Consider organizing all projects under a single project + // that share a build.gradle configuration to reduce the copy-paste. + // Local tests: Adds logs for individual local tests + tasks.withType(Test) { + testLogging { + exceptionFormat "full" + showCauses true + showExceptions true + showStackTraces true + showStandardStreams true + events = ["passed", "skipped", "failed", "standardOut", "standardError"] + } + } } diff --git a/javatests/dagger/android/AndroidInjectionTest.java b/javatests/dagger/android/AndroidInjectionTest.java index 7e60ebd52..499e9817d 100644 --- a/javatests/dagger/android/AndroidInjectionTest.java +++ b/javatests/dagger/android/AndroidInjectionTest.java @@ -23,16 +23,19 @@ import static org.robolectric.annotation.LooperMode.Mode.LEGACY; import android.app.Activity; import android.app.Application; import android.app.Fragment; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; import org.robolectric.util.FragmentTestUtil; @LooperMode(LEGACY) -@RunWith(RobolectricTestRunner.class) +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) public final class AndroidInjectionTest { // Most positive tests are performed in javatests/dagger/android/support/functional, but diff --git a/javatests/dagger/android/AndroidProguardTest.java b/javatests/dagger/android/AndroidProguardTest.java index 0f51e4979..899d1f5f7 100644 --- a/javatests/dagger/android/AndroidProguardTest.java +++ b/javatests/dagger/android/AndroidProguardTest.java @@ -18,13 +18,17 @@ package dagger.android; import static com.google.common.truth.Truth.assertThat; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.android.internal.AndroidInjectionKeys; import java.net.URL; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -@RunWith(RobolectricTestRunner.class) +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) public class AndroidProguardTest { @Test diff --git a/javatests/dagger/android/DispatchingAndroidInjectorTest.java b/javatests/dagger/android/DispatchingAndroidInjectorTest.java index 37d3d616d..4ae1b0706 100644 --- a/javatests/dagger/android/DispatchingAndroidInjectorTest.java +++ b/javatests/dagger/android/DispatchingAndroidInjectorTest.java @@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import android.app.Activity; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableMap; import dagger.android.AndroidInjector.Factory; import dagger.android.DispatchingAndroidInjector.InvalidInjectorBindingException; @@ -28,9 +30,11 @@ import javax.inject.Provider; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; -@RunWith(RobolectricTestRunner.class) +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) public final class DispatchingAndroidInjectorTest { @Test public void withClassKeys() { @@ -108,10 +112,7 @@ public final class DispatchingAndroidInjectorTest { Map<String, Provider<AndroidInjector.Factory<?>>> injectorFactoriesWithStringKeys) { return new DispatchingAndroidInjector<>( - injectorFactoriesWithClassKeys, - injectorFactoriesWithStringKeys , - ImmutableMap.of(), - ImmutableMap.of()); + injectorFactoriesWithClassKeys, injectorFactoriesWithStringKeys); } static class FooActivity extends Activity {} diff --git a/javatests/dagger/android/processor/BUILD b/javatests/dagger/android/processor/BUILD index d7b210c5e..c639a4191 100644 --- a/javatests/dagger/android/processor/BUILD +++ b/javatests/dagger/android/processor/BUILD @@ -36,6 +36,10 @@ GenJavaTests( "@google_bazel_common//third_party/java/compile_testing", "@google_bazel_common//third_party/java/junit", "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/javatests/dagger/android/support/AndroidSupportInjectionTest.java b/javatests/dagger/android/support/AndroidSupportInjectionTest.java index 25c5e9460..f7d300992 100644 --- a/javatests/dagger/android/support/AndroidSupportInjectionTest.java +++ b/javatests/dagger/android/support/AndroidSupportInjectionTest.java @@ -20,21 +20,25 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import android.app.Application; +import android.os.Build; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.ext.junit.runners.AndroidJUnit4; import dagger.android.AndroidInjector; import dagger.android.HasAndroidInjector; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; +import org.robolectric.Robolectric; import org.robolectric.annotation.Config; -import org.robolectric.shadows.support.v4.SupportFragmentTestUtil; -@RunWith(RobolectricTestRunner.class) +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) public final class AndroidSupportInjectionTest { @Test public void injectFragment_simpleApplication() { Fragment fragment = new Fragment(); - SupportFragmentTestUtil.startFragment(fragment); + startFragment(fragment); try { AndroidSupportInjection.inject(fragment); @@ -56,7 +60,7 @@ public final class AndroidSupportInjectionTest { @Config(application = ApplicationReturnsNull.class) public void fragmentInjector_returnsNull() { Fragment fragment = new Fragment(); - SupportFragmentTestUtil.startFragment(fragment); + startFragment(fragment); try { AndroidSupportInjection.inject(fragment); @@ -75,4 +79,12 @@ public final class AndroidSupportInjectionTest { assertThat(e).hasMessageThat().contains("fragment"); } } + + void startFragment(Fragment fragment) { + Robolectric.setupActivity(FragmentActivity.class) + .getSupportFragmentManager() + .beginTransaction() + .add(fragment, "") + .commitNow(); + } } diff --git a/javatests/dagger/android/support/BUILD b/javatests/dagger/android/support/BUILD index 6bbfaa184..2ef1ece6e 100644 --- a/javatests/dagger/android/support/BUILD +++ b/javatests/dagger/android/support/BUILD @@ -25,6 +25,9 @@ GenRobolectricTests( srcs = glob(["*.java"]), functional = False, javacopts = DOCLINT_HTML_AND_SYNTAX, + manifest_values = { + "minSdkVersion": "14", + }, deps = [ "//:dagger_with_compiler", "//java/dagger/android", @@ -34,7 +37,11 @@ GenRobolectricTests( "//java/dagger/internal/guava:concurrent", "@google_bazel_common//third_party/java/junit", "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", "@maven//:androidx_appcompat_appcompat", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", ], ) diff --git a/javatests/dagger/android/support/functional/AndroidManifest.xml b/javatests/dagger/android/support/functional/AndroidManifest.xml index 0b5f4cd0b..2b40e1077 100644 --- a/javatests/dagger/android/support/functional/AndroidManifest.xml +++ b/javatests/dagger/android/support/functional/AndroidManifest.xml @@ -17,7 +17,7 @@ package="dagger.android.support.functional"> <!-- Bump targetSdk to 29 when we update to Java 11 --> - <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="28" /> + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" /> <application android:theme="@style/Theme.AppCompat" android:name=".UsesGeneratedModulesApplication"> diff --git a/javatests/dagger/android/support/functional/BUILD b/javatests/dagger/android/support/functional/BUILD index 1ae110858..459d55fee 100644 --- a/javatests/dagger/android/support/functional/BUILD +++ b/javatests/dagger/android/support/functional/BUILD @@ -29,10 +29,13 @@ android_library( manifest = "AndroidManifest.xml", resource_files = glob(["res/**"]), deps = [ + "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", "@maven//:androidx_appcompat_appcompat", "@maven//:androidx_annotation_annotation", - "//java/dagger/internal/guava:collect-android", "//:dagger_with_compiler", "//:android", "//:android-support", @@ -44,13 +47,22 @@ android_library( GenRobolectricTests( name = "functional_tests", srcs = glob(["*Test.java"]), + manifest_values = { + "minSdkVersion": "14", + }, deps = [ ":functional", "//:android", "//:android-support", "//:dagger_with_compiler", "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:androidx_test_core", + "@maven//:androidx_test_ext_junit", "@maven//:junit_junit", "@maven//:org_robolectric_robolectric", ], diff --git a/javatests/dagger/android/support/functional/InjectorsTest.java b/javatests/dagger/android/support/functional/InjectorsTest.java index 73bb98a02..eef84297c 100644 --- a/javatests/dagger/android/support/functional/InjectorsTest.java +++ b/javatests/dagger/android/support/functional/InjectorsTest.java @@ -20,16 +20,19 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Intent; import android.content.res.Configuration; +import android.os.Build; import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; -@RunWith(RobolectricTestRunner.class) +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) public class InjectorsTest { private ActivityController<TestActivity> activityController; private TestActivity activity; diff --git a/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java b/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java new file mode 100644 index 000000000..494372a0e --- /dev/null +++ b/javatests/dagger/functional/assisted/AssistedFactoryAsQualifiedBindingTest.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2021 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.functional.assisted; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.BindsOptionalOf; +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; +import dagger.multibindings.IntoSet; +import dagger.multibindings.Multibinds; +import java.lang.annotation.Retention; +import java.util.Optional; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Qualifier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests that qualified assisted types can be provided and injected as normal types. */ +@RunWith(JUnit4.class) +public final class AssistedFactoryAsQualifiedBindingTest { + + @Qualifier + @Retention(RUNTIME) + @interface AsComponentDependency {} + + @Qualifier + @Retention(RUNTIME) + @interface AsProvides {} + + @Qualifier + @Retention(RUNTIME) + @interface AsBinds {} + + @Qualifier + @Retention(RUNTIME) + @interface AsOptional {} + + @Qualifier + @Retention(RUNTIME) + @interface AsMultibinding {} + + @Component(modules = BarFactoryModule.class) + interface TestComponent { + Foo foo(); + + @Component.Factory + interface Factory { + TestComponent create( + @BindsInstance @AsComponentDependency Bar bar, + @BindsInstance @AsComponentDependency BarFactory barFactory); + } + } + + @Module + interface BarFactoryModule { + + @Provides + @AsProvides + static Bar providesBar(@AsComponentDependency Bar bar) { + return bar; + } + + @Provides + @AsProvides + static BarFactory providesBarFactory(@AsComponentDependency BarFactory barFactory) { + return barFactory; + } + + @Binds + @AsBinds + Bar bindsBar(@AsComponentDependency Bar bar); + + @Binds + @AsBinds + BarFactory bindsBarFactory(@AsComponentDependency BarFactory barFactory); + + @BindsOptionalOf + @AsOptional + Bar optionalBar(); + + @BindsOptionalOf + @AsOptional + BarFactory optionalBarFactory(); + + @Provides + @AsOptional + static Bar providesOptionalBar(@AsComponentDependency Bar bar) { + return bar; + } + + @Provides + @AsOptional + static BarFactory providesOptionalBarFactory(@AsComponentDependency BarFactory barFactory) { + return barFactory; + } + + @Multibinds + @AsMultibinding + Set<Bar> barSet(); + + @Multibinds + @AsMultibinding + Set<BarFactory> barFactorySet(); + + @Provides + @IntoSet + @AsMultibinding + static Bar providesMultibindingBar(@AsComponentDependency Bar bar) { + return bar; + } + + @Provides + @IntoSet + @AsMultibinding + static BarFactory providesMultibindingBarFactory(@AsComponentDependency BarFactory barFactory) { + return barFactory; + } + + @Multibinds + Set<Bar> unqualifiedBarSet(); + + @Multibinds + Set<BarFactory> unqualifiedBarFactorySet(); + + @Provides + @IntoSet + static Bar providesUnqualifiedMultibindingBar(@AsComponentDependency Bar bar) { + return bar; + } + + @Provides + @IntoSet + static BarFactory providesUnqualifiedMultibindingBarFactory( + @AsComponentDependency BarFactory barFactory) { + return barFactory; + } + } + + static class Foo { + private final BarFactory barFactory; + private final Bar barAsComponentDependency; + private final BarFactory barFactoryAsComponentDependency; + private final Bar barAsProvides; + private final BarFactory barFactoryAsProvides; + private final Bar barAsBinds; + private final BarFactory barFactoryAsBinds; + private final Optional<Bar> optionalBar; + private final Optional<BarFactory> optionalBarFactory; + private final Set<Bar> barSet; + private final Set<BarFactory> barFactorySet; + private final Set<Bar> unqualifiedBarSet; + private final Set<BarFactory> unqualifiedBarFactorySet; + + @Inject + Foo( + BarFactory barFactory, + @AsComponentDependency Bar barAsComponentDependency, + @AsComponentDependency BarFactory barFactoryAsComponentDependency, + @AsProvides Bar barAsProvides, + @AsProvides BarFactory barFactoryAsProvides, + @AsBinds Bar barAsBinds, + @AsBinds BarFactory barFactoryAsBinds, + @AsOptional Optional<Bar> optionalBar, + @AsOptional Optional<BarFactory> optionalBarFactory, + @AsMultibinding Set<Bar> barSet, + @AsMultibinding Set<BarFactory> barFactorySet, + Set<Bar> unqualifiedBarSet, + Set<BarFactory> unqualifiedBarFactorySet) { + this.barFactory = barFactory; + this.barAsComponentDependency = barAsComponentDependency; + this.barFactoryAsComponentDependency = barFactoryAsComponentDependency; + this.barAsProvides = barAsProvides; + this.barFactoryAsProvides = barFactoryAsProvides; + this.barAsBinds = barAsBinds; + this.barFactoryAsBinds = barFactoryAsBinds; + this.optionalBar = optionalBar; + this.optionalBarFactory = optionalBarFactory; + this.barSet = barSet; + this.barFactorySet = barFactorySet; + this.unqualifiedBarSet = unqualifiedBarSet; + this.unqualifiedBarFactorySet = unqualifiedBarFactorySet; + } + } + + static class Bar { + @AssistedInject + Bar() {} + } + + @AssistedFactory + interface BarFactory { + Bar create(); + } + + @Test + public void testFoo() { + Bar bar = new Bar(); + BarFactory barFactory = () -> bar; + Foo foo = + DaggerAssistedFactoryAsQualifiedBindingTest_TestComponent.factory() + .create(bar, barFactory) + .foo(); + + // Test we can inject the "real" BarFactory implemented by Dagger + assertThat(foo.barFactory).isNotNull(); + assertThat(foo.barFactory).isNotEqualTo(barFactory); + assertThat(foo.barFactory.create()).isNotEqualTo(bar); + + // Test injection of a qualified Bar/BarFactory with custom @BindsInstance implementation + assertThat(foo.barAsComponentDependency).isEqualTo(bar); + assertThat(foo.barFactoryAsComponentDependency).isEqualTo(barFactory); + + // Test injection of a qualified Bar/BarFactory with custom @Provides implementation + assertThat(foo.barAsProvides).isEqualTo(bar); + assertThat(foo.barFactoryAsProvides).isEqualTo(barFactory); + + // Test injection of a qualified Bar/BarFactory with custom @Binds implementation + assertThat(foo.barAsBinds).isEqualTo(bar); + assertThat(foo.barFactoryAsBinds).isEqualTo(barFactory); + + // Test injection of a qualified Bar/BarFactory with custom @BindsOptionalOf implementation + assertThat(foo.optionalBar).isPresent(); + assertThat(foo.optionalBar).hasValue(bar); + assertThat(foo.optionalBarFactory).isPresent(); + assertThat(foo.optionalBarFactory).hasValue(barFactory); + + // Test injection of a qualified Bar/BarFactory as multibinding + assertThat(foo.barSet).containsExactly(bar); + assertThat(foo.barFactorySet).containsExactly(barFactory); + + // Test injection of a unqualified Bar/BarFactory as multibinding + assertThat(foo.unqualifiedBarSet).containsExactly(bar); + assertThat(foo.unqualifiedBarFactorySet).containsExactly(barFactory); + } +} diff --git a/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java b/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java new file mode 100644 index 000000000..7065acc20 --- /dev/null +++ b/javatests/dagger/functional/subcomponent/SubcomponentBuilderMultibindingsTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2015 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.functional.subcomponent; + +import static com.google.common.truth.Truth.assertThat; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; +import dagger.multibindings.IntoSet; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Qualifier; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Regression tests for an issue where subcomponent builder bindings were incorrectly reused from + * a parent even if the subcomponent were redeclared on the child component. This manifested via + * multibindings, especially since subcomponent builder bindings are special in that we cannot + * traverse them to see if they depend on local multibinding contributions. + */ +@RunWith(JUnit4.class) +public final class SubcomponentBuilderMultibindingsTest { + + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + public @interface ParentFoo{} + + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + public @interface ChildFoo{} + + public static final class Foo { + final Set<String> multi; + @Inject Foo(Set<String> multi) { + this.multi = multi; + } + } + + // This tests the case where a subcomponent is installed in both the parent and child component. + // In this case, we expect two subcomponents to be generated with the child one including the + // child multibinding contribution. + public static final class ChildInstallsFloating { + @Component(modules = ParentModule.class) + public interface Parent { + @ParentFoo Foo getParentFoo(); + + Child getChild(); + } + + @Subcomponent(modules = ChildModule.class) + public interface Child { + @ChildFoo Foo getChildFoo(); + } + + @Subcomponent + public interface FloatingSub { + Foo getFoo(); + + @Subcomponent.Builder + public interface Builder { + FloatingSub build(); + } + } + + @Module(subcomponents = FloatingSub.class) + public interface ParentModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "parent"; + } + + @Provides + @ParentFoo + static Foo provideParentFoo(FloatingSub.Builder builder) { + return builder.build().getFoo(); + } + } + + // The subcomponent installation of FloatingSub here is the key difference + @Module(subcomponents = FloatingSub.class) + public interface ChildModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "child"; + } + + @Provides + @ChildFoo + static Foo provideChildFoo(FloatingSub.Builder builder) { + return builder.build().getFoo(); + } + } + + private ChildInstallsFloating() {} + } + + // This is the same as the above, except this time the child does not install the subcomponent + // builder. Here, we expect the child to reuse the parent subcomponent binding (we want to avoid + // any mistakes that might implicitly create a new subcomponent relationship) and so therefore + // we expect only one subcomponent to be generated in the parent resulting in the child not seeing + // the child multibinding contribution. + public static final class ChildDoesNotInstallFloating { + @Component(modules = ParentModule.class) + public interface Parent { + @ParentFoo Foo getParentFoo(); + + Child getChild(); + } + + @Subcomponent(modules = ChildModule.class) + public interface Child { + @ChildFoo Foo getChildFoo(); + } + + @Subcomponent + public interface FloatingSub { + Foo getFoo(); + + @Subcomponent.Builder + public interface Builder { + FloatingSub build(); + } + } + + @Module(subcomponents = FloatingSub.class) + public interface ParentModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "parent"; + } + + @Provides + @ParentFoo + static Foo provideParentFoo(FloatingSub.Builder builder) { + return builder.build().getFoo(); + } + } + + // The lack of a subcomponent installation of FloatingSub here is the key difference + @Module + public interface ChildModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "child"; + } + + @Provides + @ChildFoo + static Foo provideChildFoo(FloatingSub.Builder builder) { + return builder.build().getFoo(); + } + } + + private ChildDoesNotInstallFloating() {} + } + + // This is similar to the first, except this time the components installs the subcomponent via + // factory methods. Here, we expect the child to get a new subcomponent and so should see its + // multibinding contribution. + public static final class ChildInstallsFloatingFactoryMethod { + @Component(modules = ParentModule.class) + public interface Parent { + @ParentFoo Foo getParentFoo(); + + Child getChild(); + + FloatingSub getFloatingSub(); + } + + @Subcomponent(modules = ChildModule.class) + public interface Child { + @ChildFoo Foo getChildFoo(); + + FloatingSub getFloatingSub(); + } + + @Subcomponent + public interface FloatingSub { + Foo getFoo(); + } + + @Module + public interface ParentModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "parent"; + } + + @Provides + @ParentFoo + static Foo provideParentFoo(Parent componentSelf) { + return componentSelf.getFloatingSub().getFoo(); + } + } + + @Module + public interface ChildModule { + @Provides + @IntoSet + static String provideStringMulti() { + return "child"; + } + + @Provides + @ChildFoo + static Foo provideChildFoo(Child componentSelf) { + return componentSelf.getFloatingSub().getFoo(); + } + } + + private ChildInstallsFloatingFactoryMethod() {} + } + + @Test + public void testChildInstallsFloating() { + ChildInstallsFloating.Parent parentComponent = + DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloating_Parent.create(); + assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); + assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child"); + } + + @Test + public void testChildDoesNotInstallFloating() { + ChildDoesNotInstallFloating.Parent parentComponent = + DaggerSubcomponentBuilderMultibindingsTest_ChildDoesNotInstallFloating_Parent.create(); + assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); + // Don't expect the child contribution because the child didn't redeclare the subcomponent + // dependency, meaning it intends to just use the subcomponent relationship from the parent + // component. + assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent"); + } + + @Test + public void testChildInstallsFloatingFactoryMethod() { + ChildInstallsFloatingFactoryMethod.Parent parentComponent = + DaggerSubcomponentBuilderMultibindingsTest_ChildInstallsFloatingFactoryMethod_Parent.create(); + assertThat(parentComponent.getParentFoo().multi).containsExactly("parent"); + assertThat(parentComponent.getChild().getChildFoo().multi).containsExactly("parent", "child"); + } +} diff --git a/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java new file mode 100644 index 000000000..03aa3248d --- /dev/null +++ b/javatests/dagger/hilt/android/ActivityInjectedSavedStateViewModelTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class ActivityInjectedSavedStateViewModelTest { + + private static final String DATA_KEY = "TEST_KEY"; + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void memberInjectedViewModelWithSavedState() { + Intent intent = new Intent(getApplicationContext(), TestActivity.class); + intent.putExtra(DATA_KEY, "test data"); + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent)) { + scenario.onActivity( + activity -> { + String data = activity.myViewModel.handle.get(DATA_KEY); + assertThat(data).isEqualTo("test data"); + }); + } + } + + // Note that assertion of object not being yet injected is in the SuperActivity, while the + // assertion in the scenario is confirming injection eventually does occur. + @Test + public void notYetMemberInjectedSuperActivity() { + try (ActivityScenario<TestActivityWithSuperActivity> scenario = + ActivityScenario.launch(TestActivityWithSuperActivity.class)) { + scenario.onActivity(activity -> assertThat(activity.someObject).isNotNull()); + } + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity + extends Hilt_ActivityInjectedSavedStateViewModelTest_TestActivity { + @Inject MyViewModel myViewModel; + } + + @AndroidEntryPoint(SuperActivity.class) + public static final class TestActivityWithSuperActivity + extends Hilt_ActivityInjectedSavedStateViewModelTest_TestActivityWithSuperActivity {} + + public static class SuperActivity extends FragmentActivity { + @Inject Object someObject; + + @Override + protected void onCreate(Bundle savedInstanceState) { + assertThat(someObject).isNull(); // not yet injected + super.onCreate(savedInstanceState); + } + } + + @Module + @InstallIn(ActivityComponent.class) + static final class MyViewModelModel { + @Provides + static MyViewModel provideModel(FragmentActivity activity) { + return new ViewModelProvider(activity).get(MyViewModel.class); + } + + @Provides + static Object provideObject() { + return new Object(); + } + } + + public static final class MyViewModel extends ViewModel { + final SavedStateHandle handle; + + public MyViewModel(SavedStateHandle handle) { + this.handle = handle; + } + } +} diff --git a/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java new file mode 100644 index 000000000..0e75741c4 --- /dev/null +++ b/javatests/dagger/hilt/android/ActivityInjectedViewModelTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import android.content.Intent; +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Tests that the Hilt view model factory doesn't cause errors with non-Hilt view models. This was + * a problem at one point because if the non-Hilt view model is accessed before saved state + * restoration happens, even if the non-Hilt view model doesn't use saved state, it can cause an + * error because the default Hilt view model factory does use saved state. This test ensures we + * still allow non-Hilt view models without saved state to be used before saved state is restored. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class ActivityInjectedViewModelTest { + + private static final String DATA_KEY = "TEST_KEY"; + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void memberInjectedViewModelWithSavedState() { + final Intent intent = new Intent(getApplicationContext(), TestActivity.class); + intent.putExtra(DATA_KEY, "test data"); + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(intent)) { + scenario.onActivity(activity -> assertThat(activity.myViewModel).isNotNull()); + } + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_ActivityInjectedViewModelTest_TestActivity { + @Inject MyViewModel myViewModel; + } + + @Module + @InstallIn(ActivityComponent.class) + static final class MyViewModelModel { + @Provides + static MyViewModel provideModel(FragmentActivity activity) { + return new ViewModelProvider(activity).get(MyViewModel.class); + } + } + + public static final class MyViewModel extends ViewModel {} +} diff --git a/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java new file mode 100644 index 000000000..286998c19 --- /dev/null +++ b/javatests/dagger/hilt/android/ActivityRetainedClearedListenerTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import androidx.lifecycle.Lifecycle.State; +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.ActivityRetainedLifecycle.OnClearedListener; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import java.util.concurrent.atomic.AtomicReference; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class ActivityRetainedClearedListenerTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void onClearedInvoked() { + final TestClearedListener callback = new TestClearedListener(); + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> activity.activityRetainedLifecycle.addOnClearedListener(callback)); + assertThat(callback.onClearedInvoked).isEqualTo(0); + + // Recreate should not cause ViewModel to be cleared + scenario.recreate(); + assertThat(callback.onClearedInvoked).isEqualTo(0); + + // Destroying activity (not due to recreate) should cause ViewModel to be cleared + scenario.moveToState(State.DESTROYED); + assertThat(callback.onClearedInvoked).isEqualTo(1); + } + } + + @Test + public void addOnClearedListener_tooLate() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + // Grab the activity (leak it a bit) from the scenario and destroy it. + AtomicReference<TestActivity> testActivity = new AtomicReference<>(); + scenario.onActivity(testActivity::set); + scenario.moveToState(State.DESTROYED); + + try { + TestClearedListener callback = new TestClearedListener(); + testActivity.get().activityRetainedLifecycle.addOnClearedListener(callback); + fail("An exception should have been thrown."); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains( + "There was a race between the call to add/remove an OnClearedListener and " + + "onCleared(). This can happen when posting to the Main thread from a " + + "background thread, which is not supported."); + } + } + } + + @Test + public void removeOnClearedListener_tooLate() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + // Grab the activity (leak it a bit) from the scenario and destroy it. + AtomicReference<TestActivity> testActivity = new AtomicReference<>(); + scenario.onActivity(testActivity::set); + scenario.moveToState(State.DESTROYED); + + try { + TestClearedListener callback = new TestClearedListener(); + testActivity.get().activityRetainedLifecycle.removeOnClearedListener(callback); + fail("An exception should have been thrown."); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains( + "There was a race between the call to add/remove an OnClearedListener and " + + "onCleared(). This can happen when posting to the Main thread from a " + + "background thread, which is not supported."); + } + } + } + + static class TestClearedListener implements OnClearedListener { + int onClearedInvoked = 0; + + @Override + public void onCleared() { + onClearedInvoked++; + } + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity + extends Hilt_ActivityRetainedClearedListenerTest_TestActivity { + @Inject ActivityRetainedLifecycle activityRetainedLifecycle; + } +} diff --git a/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java b/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java new file mode 100644 index 000000000..c7162250f --- /dev/null +++ b/javatests/dagger/hilt/android/ActivityScenarioRuleTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.lifecycle.Lifecycle.State.RESUMED; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.activity.ComponentActivity; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests that {@link ActivityScenarioRule} works with Hilt tests. */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class ActivityScenarioRuleTest { + private static final String STR_VALUE = "STR_VALUE"; + + /** An activity to test injection. */ + @AndroidEntryPoint(ComponentActivity.class) + public static final class TestActivity extends Hilt_ActivityScenarioRuleTest_TestActivity { + @Inject String str; + } + + public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + public ActivityScenarioRule<TestActivity> scenarioRule = + new ActivityScenarioRule<>(TestActivity.class); + + @Rule public RuleChain chain = RuleChain.outerRule(hiltRule).around(scenarioRule); + + @BindValue String str = STR_VALUE; + + @Test + public void testState() { + assertThat(scenarioRule.getScenario().getState()).isEqualTo(RESUMED); + } + + @Test + public void testInjection() { + scenarioRule + .getScenario() + .onActivity(activity -> assertThat(activity.str).isEqualTo(STR_VALUE)); + } +} diff --git a/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java new file mode 100644 index 000000000..048e0f76e --- /dev/null +++ b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassOtherPkg.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.hilt.android.other.pkg; + +import androidx.activity.ComponentActivity; +import dagger.hilt.android.AndroidEntryPoint; + +/** Used in {@link AndroidEntryPointBaseClassTest}. */ +public final class AndroidEntryPointBaseClassOtherPkg { + + @AndroidEntryPoint(ComponentActivity.class) + public static class LBaseActivity extends Hilt_AndroidEntryPointBaseClassOtherPkg_LBaseActivity {} + + @AndroidEntryPoint + public static class SBaseActivity extends ComponentActivity {} + + private AndroidEntryPointBaseClassOtherPkg() {} +} diff --git a/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java new file mode 100644 index 000000000..1078e7dbf --- /dev/null +++ b/javatests/dagger/hilt/android/AndroidEntryPointBaseClassTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.activity.ComponentActivity; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.other.pkg.AndroidEntryPointBaseClassOtherPkg; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Regression test for https://github.com/google/dagger/issues/1910 + * + * <p>There are 8 different tests to cover 3 levels of inheritance where each level uses either the + * long-form (L) or short-form (S) of @AndroidEntryPoint: + * + * <ol> + * <li> L -> L -> L + * <li> L -> L -> S + * <li> L -> S -> L + * <li> L -> S -> S + * <li> S -> L -> L + * <li> S -> L -> S + * <li> S -> S -> L + * <li> S -> S -> S + * </ol> + * + * Note: We don't actually test injection in this class because Bazel doesn't do bytecode injection. + * We're only testing that the classes build, and verifying their inheritance matches what we + * expect. + */ +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P) +public final class AndroidEntryPointBaseClassTest { + + @AndroidEntryPoint + public static final class SSActivity extends AndroidEntryPointBaseClassOtherPkg.SBaseActivity {} + + @AndroidEntryPoint + public static final class SLActivity extends AndroidEntryPointBaseClassOtherPkg.LBaseActivity {} + + @AndroidEntryPoint(AndroidEntryPointBaseClassOtherPkg.SBaseActivity.class) + public static final class LSActivity extends Hilt_AndroidEntryPointBaseClassTest_LSActivity {} + + @AndroidEntryPoint(AndroidEntryPointBaseClassOtherPkg.LBaseActivity.class) + public static final class LLActivity extends Hilt_AndroidEntryPointBaseClassTest_LLActivity {} + + @AndroidEntryPoint(LL.class) + public static final class LLL extends Hilt_AndroidEntryPointBaseClassTest_LLL {} + + @AndroidEntryPoint(LS.class) + public static final class LLS extends Hilt_AndroidEntryPointBaseClassTest_LLS {} + + @AndroidEntryPoint(SL.class) + public static final class LSL extends Hilt_AndroidEntryPointBaseClassTest_LSL {} + + @AndroidEntryPoint(SS.class) + public static final class LSS extends Hilt_AndroidEntryPointBaseClassTest_LSS {} + + @AndroidEntryPoint + public static final class SLL extends LL {} + + @AndroidEntryPoint + public static final class SLS extends LS {} + + @AndroidEntryPoint + public static final class SSL extends SL {} + + @AndroidEntryPoint + public static final class SSS extends SS {} + + @AndroidEntryPoint(L.class) + public static class LL extends Hilt_AndroidEntryPointBaseClassTest_LL {} + + @AndroidEntryPoint(S.class) + public static class LS extends Hilt_AndroidEntryPointBaseClassTest_LS {} + + @AndroidEntryPoint + public static class SL extends L {} + + @AndroidEntryPoint + public static class SS extends S {} + + @AndroidEntryPoint(ComponentActivity.class) + public static class L extends Hilt_AndroidEntryPointBaseClassTest_L {} + + @AndroidEntryPoint + public static class S extends ComponentActivity {} + + @Test + public void checkGeneratedClassHierarchy_shortForm() throws Exception { + // When using the short form notation, the generated top level class is not actually assignable + // to the generated base classes at compile time + assertIsNotAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_SSS.class, + Hilt_AndroidEntryPointBaseClassTest_S.class); + assertIsNotAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_SS.class, + Hilt_AndroidEntryPointBaseClassTest_S.class); + } + + @Test + public void checkGeneratedClassHierarchy_longForm() throws Exception { + // When using the long form notation, they are assignable at compile time + assertIsAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_LLL.class, + Hilt_AndroidEntryPointBaseClassTest_LL.class); + assertIsAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_LL.class, + Hilt_AndroidEntryPointBaseClassTest_L.class); + } + + @Test + public void checkGeneratedClassHierarchy_shortFormRoot() throws Exception { + // If the root is short-form, then the child class cannot be assigned to it. + assertIsNotAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_LLS.class, + Hilt_AndroidEntryPointBaseClassTest_S.class); + assertIsNotAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_LS.class, + Hilt_AndroidEntryPointBaseClassTest_S.class); + } + + @Test + public void checkGeneratedClassHierarchy_longFormRoot() throws Exception { + // If the root is long-form, then the child class can be assigned to it. + assertIsAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_SSL.class, + Hilt_AndroidEntryPointBaseClassTest_L.class); + assertIsAssignableTo( + Hilt_AndroidEntryPointBaseClassTest_SL.class, + Hilt_AndroidEntryPointBaseClassTest_L.class); + } + + /** Asserts that the {@code class1} is not assignable to the {@code class2}. */ + private static void assertIsNotAssignableTo(Class<?> class1, Class<?> class2) { + assertThat(class2.isAssignableFrom(class1)).isFalse(); + } + + /** Asserts that the {@code class1} is assignable to the {@code class2}. */ + private static void assertIsAssignableTo(Class<?> class1, Class<?> class2) { + assertThat(class1).isAssignableTo(class2); + } +} diff --git a/javatests/dagger/hilt/android/AndroidManifest.xml b/javatests/dagger/hilt/android/AndroidManifest.xml new file mode 100644 index 000000000..7f6ede633 --- /dev/null +++ b/javatests/dagger/hilt/android/AndroidManifest.xml @@ -0,0 +1,47 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="dagger.hilt.android"> + + <uses-sdk android:minSdkVersion="14" /> + + <application> + <activity + android:name=".ActivityInjectedSavedStateViewModelTest$TestActivity" + android:exported="false"/> + <activity + android:name=".ActivityInjectedSavedStateViewModelTest$TestActivityWithSuperActivity" + android:exported="false"/> + <activity + android:name=".ActivityInjectedViewModelTest$TestActivity" + android:exported="false"/> + <activity + android:name=".ActivityRetainedClearedListenerTest$TestActivity" + android:exported="false"/> + <activity + android:name=".ActivityScenarioRuleTest$TestActivity" + android:exported="false"/> + <activity + android:name=".DefaultViewModelFactoryTest$TestActivity" + android:exported="false"/> + <activity + android:name=".QualifierInKotlinFieldsTest$TestActivity" + android:exported="false"/> + <activity + android:name=".UsesSharedComponent1Test$TestActivity" + android:exported="false"/> + <activity + android:name=".UsesSharedComponent2Test$TestActivity" + android:exported="false"/> + <activity + android:name=".UsesSharedComponentEnclosedTest$EnclosedTest$TestActivity" + android:exported="false"/> + <activity + android:name=".ViewModelScopedTest$TestActivity" + android:exported="false"/> + <activity + android:name=".ViewModelWithBaseTest$TestActivity" + android:exported="false"/> + <activity + android:name="dagger.hilt.android.testsubpackage.UsesSharedComponent1Test$TestActivity" + android:exported="false"/> + </application> +</manifest> diff --git a/javatests/dagger/hilt/android/BUILD b/javatests/dagger/hilt/android/BUILD new file mode 100644 index 000000000..1f9bd0809 --- /dev/null +++ b/javatests/dagger/hilt/android/BUILD @@ -0,0 +1,649 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") + +package(default_visibility = ["//:src"]) + +# Checks that multiple test roots can be compiled together. This library +# only compiles the sources. they are tested in the android_local_tests. +android_library( + name = "multi_test_root_tests", + srcs = [ + "ActivityScenarioRuleTest.java", + "CustomTestApplicationTest.java", + "MultiTestRoot1Test.java", + "MultiTestRoot2Test.java", + "MultiTestRootExternalModules.java", + ], + exports_manifest = 1, + manifest = "AndroidManifest.xml", + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:custom_test_application", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/android/testing:uninstall_modules", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_test_core", + "@maven//:androidx_test_ext_junit", + "@maven//:junit_junit", + "@maven//:org_robolectric_robolectric", + ], +) + +android_local_test( + name = "ActivityScenarioRuleTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":multi_test_root_tests", + ], +) + +android_local_test( + name = "CustomTestApplicationTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":multi_test_root_tests", + ], +) + +android_local_test( + name = "MultiTestRoot1Test", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":multi_test_root_tests", + ], +) + +android_local_test( + name = "MultiTestRoot2Test", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":multi_test_root_tests", + ], +) + +android_local_test( + name = "EarlyEntryPointHiltAndroidAppRuntimeTest", + size = "small", + srcs = ["EarlyEntryPointHiltAndroidAppRuntimeTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":EarlyEntryPointHiltAndroidAppRuntimeClasses", + "//:android_local_test_exports", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android:package_info", + "@google_bazel_common//third_party/java/truth", + "@maven//:junit_junit", + ], +) + +android_library( + name = "EarlyEntryPointHiltAndroidAppRuntimeClasses", + srcs = ["EarlyEntryPointHiltAndroidAppRuntimeClasses.java"], + deps = [ + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:package_info", + ], +) + +android_local_test( + name = "EarlyEntryPointHiltAndroidTestRuntimeTest", + size = "small", + srcs = [ + "EarlyEntryPointHiltAndroidTestRuntimeClasses.java", + "EarlyEntryPointHiltAndroidTestRuntimeTest.java", + ], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:define_component", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "EarlyEntryPointCustomApplicationTest", + size = "small", + srcs = [ + "EarlyEntryPointCustomApplicationClasses.java", + "EarlyEntryPointCustomApplicationTest.java", + ], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:custom_test_application", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "EarlyEntryPointNoEntryPointsDefinedTest", + size = "small", + srcs = ["EarlyEntryPointNoEntryPointsDefinedTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "AndroidEntryPointBaseClassTest", + size = "small", + srcs = [ + "AndroidEntryPointBaseClassOtherPkg.java", + "AndroidEntryPointBaseClassTest.java", + ], + javacopts = [ + # Note: Hilt's bytecode injection doesn't work in Blaze but we disable + # superclass validation in this test just to verify everything builds. + "-Adagger.hilt.android.internal.disableAndroidSuperclassValidation=true", + # Note: Used to test base classes across java packages. + "-Xep:PackageLocation:OFF", + ], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "ModuleTest", + srcs = ["ModuleTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "InjectionTest", + size = "small", + srcs = ["InjectionTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "InstallInObjectModuleTest", + srcs = ["InstallInObjectModuleTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":InstallInObjectModuleClasses", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +kt_android_library( + name = "InstallInObjectModuleClasses", + testonly = True, + srcs = ["InstallInObjectModule.kt"], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/components", + ], +) + +android_local_test( + name = "InternalKtModuleTest", + size = "small", + srcs = ["InternalKtModuleTest.java"], + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//javatests/dagger/hilt/testmodules", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_local_test( + name = "DefaultViewModelFactoryTest", + srcs = ["DefaultViewModelFactoryTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/lifecycle", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +android_local_test( + name = "QualifierInKotlinFieldsTest", + srcs = ["QualifierInKotlinFieldsTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":QualifierInFieldsClass", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +kt_android_library( + name = "QualifierInFieldsClass", + testonly = True, + srcs = ["QualifierInFieldsClass.kt"], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt/android/qualifiers", + ], +) + +android_local_test( + name = "ActivityRetainedClearedListenerTest", + srcs = ["ActivityRetainedClearedListenerTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:activity_retained_lifecycle", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +android_local_test( + name = "ActivityInjectedViewModelTest", + srcs = ["ActivityInjectedViewModelTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +android_local_test( + name = "ViewModelScopedTest", + srcs = ["ViewModelScopedTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/lifecycle", + "//java/dagger/hilt/android/scopes", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +android_local_test( + name = "ViewModelWithBaseTest", + srcs = ["ViewModelWithBaseTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/lifecycle", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_activity_activity", + "@maven//:androidx_fragment_fragment", + "@maven//:androidx_lifecycle_lifecycle_common", + "@maven//:androidx_lifecycle_lifecycle_viewmodel", + "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate", + "@maven//:junit_junit", + ], +) + +android_local_test( + name = "ActivityInjectedSavedStateViewModelTest", + srcs = ["ActivityInjectedSavedStateViewModelTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/internal/guava:base-android", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:junit_junit", + ], +) + +android_library( + name = "uses_component_common", + srcs = [ + "UsesComponentHelper.java", + "UsesComponentTestClasses.java", + ], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:define_component", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android/components", + "@google_bazel_common//third_party/java/jsr330_inject", + ], +) + +# This has to be split into a separate target in order to avoid +# being compiled as "test bindings" in the compilation unit of +# the test class itself. +android_library( + name = "uses_component_test_module", + srcs = ["UsesComponentTestModule.java"], + deps = [ + ":uses_component_common", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android/components", + ], +) + +android_library( + name = "shared_component_test_classes", + srcs = [ + "UsesLocalComponentTestBindingsTest.java", + "UsesLocalComponentUninstallModuleTest.java", + "UsesSharedComponent1Test.java", + "UsesSharedComponent2Test.java", + "UsesSharedComponentEnclosedTest.java", + "//javatests/dagger/hilt/android/testsubpackage:UsesLocalComponentTestBindingsTest.java", + "//javatests/dagger/hilt/android/testsubpackage:UsesSharedComponent1Test.java", + ], + exports_manifest = 1, + javacopts = ["-Adagger.hilt.shareTestComponents=true"], + manifest = "AndroidManifest.xml", + deps = [ + ":uses_component_common", + ":uses_component_test_module", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:custom_test_application", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/android/testing:uninstall_modules", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_test_core", + "@maven//:androidx_test_ext_junit", + "@maven//:junit_junit", + "@maven//:org_robolectric_annotations", + "@maven//:org_robolectric_robolectric", + ], +) + +# Separate target that uses @TestInstallIn to replace the global binding +android_library( + name = "test_install_in_test_classes", + srcs = [ + "TestInstallInModules.java", + "UsesSharedComponentTestInstallInTest.java", + ], + javacopts = ["-Adagger.hilt.shareTestComponents=true"], + deps = [ + ":uses_component_common", + ":uses_component_test_module", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:package_info", + "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/testing:test_install_in", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "@maven//:androidx_test_core", + "@maven//:androidx_test_ext_junit", + "@maven//:junit_junit", + "@maven//:org_robolectric_annotations", + "@maven//:org_robolectric_robolectric", + ], +) + +android_local_test( + name = "UsesLocalComponentTestBindingsTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesLocalComponentUninstallModuleTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesSharedComponent1Test", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesSharedComponent2Test", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesSharedComponentEnclosedTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesSharedComponentTestInstallInTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + ":test_install_in_test_classes", + ], +) diff --git a/javatests/dagger/hilt/android/CustomTestApplicationTest.java b/javatests/dagger/hilt/android/CustomTestApplicationTest.java new file mode 100644 index 000000000..26de8d46d --- /dev/null +++ b/javatests/dagger/hilt/android/CustomTestApplicationTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.app.Application; +import android.content.Context; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.CustomTestApplication; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests {@link dagger.hilt.android.testing.CustomTestApplication}. */ +@CustomTestApplication(CustomTestApplicationTest.BaseApplication.class) +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = CustomTestApplicationTest_Application.class) +public final class CustomTestApplicationTest { + static class BaseApplication extends Application {} + + static class OtherBaseApplication extends Application {} + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testApplicationBaseClass() throws Exception { + assertThat((Context) getApplicationContext()).isInstanceOf(BaseApplication.class); + } + + @CustomTestApplication(OtherBaseApplication.class) + public static class Other {} + + @Test + @Config(application = CustomTestApplicationTest_Other_Application.class) + public void testOtherApplicationBaseClass() throws Exception { + assertThat((Context) getApplicationContext()).isInstanceOf(OtherBaseApplication.class); + } +} diff --git a/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java new file mode 100644 index 000000000..78cd5688a --- /dev/null +++ b/javatests/dagger/hilt/android/DefaultViewModelFactoryTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import android.os.Build; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.lifecycle.HiltViewModel; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class DefaultViewModelFactoryTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @BindValue String hiltStringValue = "hilt"; + + @Test + public void activityFactoryFallsBackToBase() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(new ViewModelProvider(activity).get(TestHiltViewModel.class).value) + .isEqualTo("hilt"); + assertThat(new ViewModelProvider(activity).get(TestViewModel.class).value) + .isEqualTo("non-hilt"); + }); + } + } + + @Test + public void fragmentFactoryFallbsBackToBase() { + // TODO(danysantiago): Use FragmentScenario when it becomes available. + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + TestFragment fragment = new TestFragment(); + activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow(); + assertThat(new ViewModelProvider(fragment).get(TestHiltViewModel.class).value) + .isEqualTo("hilt"); + assertThat(new ViewModelProvider(fragment).get(TestViewModel.class).value) + .isEqualTo("non-hilt"); + }); + } + } + + @HiltViewModel + public static final class TestHiltViewModel extends ViewModel { + final String value; + + @Inject + TestHiltViewModel(String value) { + this.value = value; + } + } + + public static final class TestViewModel extends ViewModel { + final String value; + // Take in a string so it cannot be constructed by the default view model factory + public TestViewModel(String value) { + this.value = value; + } + } + + @AndroidEntryPoint(BaseActivity.class) + public static final class TestActivity extends Hilt_DefaultViewModelFactoryTest_TestActivity {} + + public static class BaseActivity extends FragmentActivity { + @SuppressWarnings("unchecked") + @Override public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { + return new ViewModelProvider.Factory() { + @Override public <T extends ViewModel> T create(Class<T> clazz) { + assertThat(clazz).isEqualTo(TestViewModel.class); + return (T) new TestViewModel("non-hilt"); + } + }; + } + } + + @AndroidEntryPoint(BaseFragment.class) + public static final class TestFragment extends Hilt_DefaultViewModelFactoryTest_TestFragment {} + + public static class BaseFragment extends Fragment { + @SuppressWarnings("unchecked") + @Override public ViewModelProvider.Factory getDefaultViewModelProviderFactory() { + return new ViewModelProvider.Factory() { + @Override public <T extends ViewModel> T create(Class<T> clazz) { + assertThat(clazz).isEqualTo(TestViewModel.class); + return (T) new TestViewModel("non-hilt"); + } + }; + } + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java new file mode 100644 index 000000000..3e48ee26c --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationClasses.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Classes for {@link EarlyEntryPointCustomApplicationTest}. */ +public final class EarlyEntryPointCustomApplicationClasses { + private EarlyEntryPointCustomApplicationClasses() {} + + // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class. + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EarlyFooEntryPoint { + Foo foo(); + } + + @Singleton + public static class Foo { + @Inject + Foo() {} + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java new file mode 100644 index 000000000..3d8819099 --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointCustomApplicationTest.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.app.Application; +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPointCustomApplicationClasses.EarlyFooEntryPoint; +import dagger.hilt.android.EarlyEntryPointCustomApplicationClasses.Foo; +import dagger.hilt.android.testing.CustomTestApplication; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.components.SingletonComponent; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@CustomTestApplication(EarlyEntryPointCustomApplicationTest.BaseApplication.class) +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config( + sdk = Build.VERSION_CODES.P, + application = EarlyEntryPointCustomApplicationTest_Application.class) +public final class EarlyEntryPointCustomApplicationTest { + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo foo(); + } + + public static class BaseApplication extends Application { + Foo earlyFoo = null; + IllegalStateException entryPointsException = null; + + @Override + public void onCreate() { + super.onCreate(); + + // Test that calling EarlyEntryPoints works before the test instance is created. + earlyFoo = EarlyEntryPoints.get(this, EarlyFooEntryPoint.class).foo(); + + // Test that calling EntryPoints fails if called before the test instance is created. + try { + EntryPoints.get(this, FooEntryPoint.class); + } catch (IllegalStateException e) { + entryPointsException = e; + } + } + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testEarlyEntryPointsPasses() throws Exception { + BaseApplication baseApplication = (BaseApplication) getApplicationContext(); + assertThat(baseApplication.earlyFoo).isNotNull(); + } + + @Test + public void testEntryPointsFails() throws Exception { + BaseApplication baseApplication = (BaseApplication) getApplicationContext(); + assertThat(baseApplication.entryPointsException).isNotNull(); + assertThat(baseApplication.entryPointsException) + .hasMessageThat() + .contains("The component was not created."); + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java new file mode 100644 index 000000000..80defd154 --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeClasses.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import android.app.Application; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** Classes for {@link EarlyEntryPointHiltAndroidAppRuntimeTest}. */ +public final class EarlyEntryPointHiltAndroidAppRuntimeClasses { + private EarlyEntryPointHiltAndroidAppRuntimeClasses() {} + + // @HiltAndroidApp cannot be nested in tests because @Config.application won't accept it. + @HiltAndroidApp(Application.class) + public static class TestApplication + extends Hilt_EarlyEntryPointHiltAndroidAppRuntimeClasses_TestApplication {} + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo foo(); + } + + // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class. + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EarlyFooEntryPoint { + Foo foo(); + } + + @Singleton + static class Foo { + @Inject + Foo() {} + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java new file mode 100644 index 000000000..fe75ae3a6 --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidAppRuntimeTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.EarlyFooEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.Foo; +import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.FooEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidAppRuntimeClasses.TestApplication; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = TestApplication.class) +public final class EarlyEntryPointHiltAndroidAppRuntimeTest { + // Tests that when using @HiltAndroidApp + // 1) calling with the wrong parameters doesn't throw + // 2) EarlyEntryPoints returns the same thing as EntryPoints. + @Test + public void testEntryPoints() throws Exception { + Object generatedComponent = ((TestApplication) getApplicationContext()).generatedComponent(); + + Foo foo1 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo(); + Foo foo3 = EntryPoints.get(generatedComponent, FooEntryPoint.class).foo(); + Foo foo4 = EntryPoints.get(generatedComponent, EarlyFooEntryPoint.class).foo(); + Foo foo5 = EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo(); + Foo foo6 = EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo(); + + assertThat(foo1).isEqualTo(foo2); + assertThat(foo1).isEqualTo(foo3); + assertThat(foo1).isEqualTo(foo4); + assertThat(foo1).isEqualTo(foo5); + assertThat(foo1).isEqualTo(foo6); + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java new file mode 100644 index 000000000..85d7c7c20 --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeClasses.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import dagger.BindsInstance; +import dagger.hilt.DefineComponent; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentScoped; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeTest.Bar; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeTest.Foo; +import dagger.hilt.components.SingletonComponent; +import java.lang.annotation.Retention; +import javax.inject.Scope; + +/** Classes for {@link EarlyEntryPointHiltAndroidTestRuntimeTest}. */ +public final class EarlyEntryPointHiltAndroidTestRuntimeClasses { + private EarlyEntryPointHiltAndroidTestRuntimeClasses() {} + + // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class. + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EarlyFooEntryPoint { + Foo foo(); + } + + // @EarlyEntryPoint cannot be nested in tests, so we've separated it out into this class. + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EarlyMySubcomponentBuilderEntryPoint { + MySubcomponent.Builder mySubcomponentBuilder(); + } + + @Scope + @Retention(RUNTIME) + public @interface MySubcomponentScoped {} + + @MySubcomponentScoped + @DefineComponent(parent = SingletonComponent.class) + public interface MySubcomponent { + @DefineComponent.Builder + interface Builder { + @BindsInstance + Builder id(int id); + + MySubcomponent build(); + } + } + + // This needs to be defined outside the test so that it gets installed in the early component. + @EntryPoint + @InstallIn(SingletonComponent.class) + interface MySubcomponentBuilderEntryPoint { + MySubcomponent.Builder mySubcomponentBuilder(); + } + + // This needs to be defined outside the test so that it gets installed in the early component. + @EntryPoint + @InstallIn(MySubcomponent.class) + interface BarEntryPoint { + Bar bar(); + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java new file mode 100644 index 000000000..2050d671c --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointHiltAndroidTestRuntimeTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.BarEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyFooEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.EarlyMySubcomponentBuilderEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponent; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentBuilderEntryPoint; +import dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses.MySubcomponentScoped; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class EarlyEntryPointHiltAndroidTestRuntimeTest { + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo foo(); + } + + @Singleton + public static class Foo { + @Inject + Foo() {} + } + + @MySubcomponentScoped + public static class Bar { + final Foo foo; + final int id; + + @Inject + Bar(Foo foo, int id) { + this.foo = foo; + this.id = id; + } + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testEarlyEntryPointsWrongEntryPointFails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class)); + + assertThat(exception) + .hasMessageThat() + .contains( + "FooEntryPoint should be called with EntryPoints.get() rather than " + + "EarlyEntryPoints.get()"); + } + + @Test + public void testEntryPointsWrongEntryPointFails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> EntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class)); + + assertThat(exception) + .hasMessageThat() + .contains( + "Interface, dagger.hilt.android.EarlyEntryPointHiltAndroidTestRuntimeClasses." + + "EarlyFooEntryPoint, annotated with @EarlyEntryPoint should be called with " + + "EarlyEntryPoints.get() rather than EntryPoints.get()"); + } + + @Test + public void testComponentInstances() { + Object componentManager = ((HiltTestApplication) getApplicationContext()).componentManager(); + Object component1 = ((TestApplicationComponentManager) componentManager).generatedComponent(); + Object component2 = ((TestApplicationComponentManager) componentManager).generatedComponent(); + assertThat(component1).isNotNull(); + assertThat(component2).isNotNull(); + assertThat(component1).isEqualTo(component2); + + Object earlyComponent1 = + ((TestApplicationComponentManager) componentManager).earlySingletonComponent(); + Object earlyComponent2 = + ((TestApplicationComponentManager) componentManager).earlySingletonComponent(); + assertThat(earlyComponent1).isNotNull(); + assertThat(earlyComponent2).isNotNull(); + assertThat(earlyComponent1).isEqualTo(earlyComponent2); + assertThat(earlyComponent1).isNotEqualTo(component1); + } + + // Test that the early entry point returns a different @Singleton binding instance. + @Test + public void testScopedEntryPointValues() { + Foo foo1 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo(); + Foo earlyFoo1 = + EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo(); + Foo earlyFoo2 = + EarlyEntryPoints.get(getApplicationContext(), EarlyFooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(earlyFoo1).isNotNull(); + assertThat(earlyFoo2).isNotNull(); + + assertThat(foo1).isEqualTo(foo2); + assertThat(earlyFoo1).isEqualTo(earlyFoo2); + assertThat(earlyFoo1).isNotEqualTo(foo1); + } + + // Test that the a subcomponent of the early component does not need to use EarlyEntryPoints. + @Test + public void testSubcomponentEntryPoints() { + MySubcomponent subcomponent = + EntryPoints.get(getApplicationContext(), MySubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(5) + .build(); + + MySubcomponent earlySubcomponent = + EarlyEntryPoints.get( + getApplicationContext(), EarlyMySubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(11) + .build(); + + assertThat(subcomponent).isNotNull(); + assertThat(earlySubcomponent).isNotNull(); + assertThat(subcomponent).isNotEqualTo(earlySubcomponent); + + // Test that subcomponents can use EntryPoints + Bar bar1 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar(); + Bar bar2 = EntryPoints.get(subcomponent, BarEntryPoint.class).bar(); + + // Test that early subcomponents can use EntryPoints or EarlyEntryPoints + Bar earlyBar1 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar(); + Bar earlyBar2 = EntryPoints.get(earlySubcomponent, BarEntryPoint.class).bar(); + + assertThat(bar1).isNotNull(); + assertThat(bar2).isNotNull(); + assertThat(earlyBar1).isNotNull(); + assertThat(earlyBar2).isNotNull(); + assertThat(bar1).isEqualTo(bar2); + assertThat(earlyBar1).isEqualTo(earlyBar2); + assertThat(bar1).isNotEqualTo(earlyBar1); + } +} diff --git a/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java b/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java new file mode 100644 index 000000000..a912615a9 --- /dev/null +++ b/javatests/dagger/hilt/android/EarlyEntryPointNoEntryPointsDefinedTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +// The main purpose of this test is to check the error messages if EarlyEntryPoints is called +// without the EarlyComponent being generated (i.e. if no @EarlyEntryPoints are defined). +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class EarlyEntryPointNoEntryPointsDefinedTest { + @EntryPoint + @InstallIn(SingletonComponent.class) + interface FooEntryPoint { + Foo foo(); + } + + @Singleton + public static class Foo { + @Inject + Foo() {} + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testEarlyComponentDoesNotExist() throws Exception { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + TestApplicationComponentManager componentManager = + (TestApplicationComponentManager) app.componentManager(); + + RuntimeException exception = + assertThrows(RuntimeException.class, () -> componentManager.earlySingletonComponent()); + + assertThat(exception) + .hasMessageThat() + .contains( + "The EarlyComponent was requested but does not exist. Check that you have " + + "annotated your test class with @HiltAndroidTest and that the processor is " + + "running over your test"); + } + + @Test + public void testEarlyEntryPointsWrongEntryPointFails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> EarlyEntryPoints.get(getApplicationContext(), FooEntryPoint.class)); + + assertThat(exception) + .hasMessageThat() + .contains( + "FooEntryPoint should be called with EntryPoints.get() rather than " + + "EarlyEntryPoints.get()"); + } +} diff --git a/javatests/dagger/hilt/android/InjectionTest.java b/javatests/dagger/hilt/android/InjectionTest.java new file mode 100644 index 000000000..7c3427919 --- /dev/null +++ b/javatests/dagger/hilt/android/InjectionTest.java @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.fail; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.IntentService; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.IBinder; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.components.FragmentComponent; +import dagger.hilt.android.components.ServiceComponent; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicLong; +import javax.inject.Inject; +import javax.inject.Qualifier; +import javax.inject.Singleton; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class InjectionTest { + private static final String APP_BINDING = "APP_BINDING"; + private static final String ACTIVITY_BINDING = "ACTIVIY_BINDING"; + private static final String FRAGMENT_BINDING = "FRAGMENT_BINDING"; + private static final String SERVICE_BINDING = "SERVICE_BINDING"; + + @Retention(RUNTIME) + @Qualifier + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @interface ApplicationLevel {} + + @Retention(RUNTIME) + @Qualifier + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @interface ActivityLevel {} + + @Retention(RUNTIME) + @Qualifier + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @interface FragmentLevel {} + + @Retention(RUNTIME) + @Qualifier + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + @interface ServiceLevel {} + + /** Application level bindings */ + @Module + @InstallIn(SingletonComponent.class) + static final class AppModule { + @Provides + @ApplicationLevel + static String providesAppBinding() { + return APP_BINDING; + } + + @Provides + @Singleton + static AtomicLong provideCounter() { + return new AtomicLong(); + } + + @Provides + static Long provideCount(AtomicLong counter) { + return counter.incrementAndGet(); + } + } + + /** Activity level bindings */ + @Module + @InstallIn(ActivityComponent.class) + static final class ActivityModule { + @Provides + @ActivityLevel + static String providesActivityBinding() { + return ACTIVITY_BINDING; + } + } + + /** Fragment level bindings */ + @Module + @InstallIn(FragmentComponent.class) + static final class FragmentModule { + @Provides + @FragmentLevel + static String providesFragmentBinding() { + return FRAGMENT_BINDING; + } + } + + /** Service level bindings */ + @Module + @InstallIn(ServiceComponent.class) + static final class ServiceModule { + @Provides + @ServiceLevel + static String providesServiceBinding() { + return SERVICE_BINDING; + } + } + + /** Hilt Activity */ + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_InjectionTest_TestActivity { + @Inject @ApplicationLevel String appBinding; + @Inject @ActivityLevel String activityBinding; + boolean onCreateCalled; + + @Override + public void onCreate(Bundle onSavedInstanceState) { + assertThat(appBinding).isNull(); + assertThat(activityBinding).isNull(); + + super.onCreate(onSavedInstanceState); + + assertThat(appBinding).isEqualTo(APP_BINDING); + assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING); + + onCreateCalled = true; + } + } + + /** Non-Hilt Activity */ + public static final class NonHiltActivity extends FragmentActivity {} + + /** Hilt Fragment */ + @AndroidEntryPoint(Fragment.class) + public static final class TestFragment extends Hilt_InjectionTest_TestFragment { + @Inject @ApplicationLevel String appBinding; + @Inject @ActivityLevel String activityBinding; + @Inject @FragmentLevel String fragmentBinding; + boolean onAttachContextCalled; + boolean onAttachActivityCalled; + + @Override + public void onAttach(Context context) { + preInjectionAssert(); + super.onAttach(context); + postInjectionAssert(); + onAttachContextCalled = true; + } + + @Override + public void onAttach(Activity activity) { + preInjectionAssert(); + super.onAttach(activity); + postInjectionAssert(); + onAttachActivityCalled = true; + } + + private void preInjectionAssert() { + assertThat(appBinding).isNull(); + assertThat(activityBinding).isNull(); + assertThat(fragmentBinding).isNull(); + } + + private void postInjectionAssert() { + assertThat(appBinding).isEqualTo(APP_BINDING); + assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(fragmentBinding).isEqualTo(FRAGMENT_BINDING); + } + } + + /** Non-Hilt Fragment */ + public static final class NonHiltFragment extends Fragment {} + + /** Hilt extends parameterized fragment. */ + @AndroidEntryPoint(ParameterizedFragment.class) + public static final class TestParameterizedFragment + extends Hilt_InjectionTest_TestParameterizedFragment<Integer> { + @Inject @ApplicationLevel String appBinding; + @Inject @ActivityLevel String activityBinding; + @Inject @FragmentLevel String fragmentBinding; + boolean onAttachContextCalled; + boolean onAttachActivityCalled; + + @Override + public void onAttach(Context context) { + preInjectionAssert(); + super.onAttach(context); + postInjectionAssert(); + onAttachContextCalled = true; + } + + @Override + public void onAttach(Activity activity) { + preInjectionAssert(); + super.onAttach(activity); + postInjectionAssert(); + onAttachActivityCalled = true; + } + + private void preInjectionAssert() { + assertThat(appBinding).isNull(); + assertThat(activityBinding).isNull(); + assertThat(fragmentBinding).isNull(); + } + + private void postInjectionAssert() { + assertThat(appBinding).isEqualTo(APP_BINDING); + assertThat(activityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(fragmentBinding).isEqualTo(FRAGMENT_BINDING); + } + } + + /** Non-Hilt parameterized fragment */ + public static class ParameterizedFragment<T> extends Fragment {} + + /** Hilt View */ + @AndroidEntryPoint(LinearLayout.class) + public static final class TestView extends Hilt_InjectionTest_TestView { + @Inject @ApplicationLevel String appBinding; + @Inject @ActivityLevel String activityBinding; + + TestView(Context context) { + super(context); + } + + TestView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + TestView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(21) + TestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + } + + /** Hilt View (With Fragment bindings) */ + @WithFragmentBindings + @AndroidEntryPoint(LinearLayout.class) + public static final class TestViewWithFragmentBindings + extends Hilt_InjectionTest_TestViewWithFragmentBindings { + @Inject @ApplicationLevel String appBinding; + @Inject @ActivityLevel String activityBinding; + @Inject @FragmentLevel String fragmentBinding; + + TestViewWithFragmentBindings(Context context) { + super(context); + } + } + + @AndroidEntryPoint(Service.class) + public static final class TestService extends Hilt_InjectionTest_TestService { + @Inject @ApplicationLevel String appBinding; + @Inject @ServiceLevel String serviceBinding; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + } + + @AndroidEntryPoint(IntentService.class) + public static final class TestIntentService extends Hilt_InjectionTest_TestIntentService { + private static final String NAME = "TestIntentServiceName"; + @Inject @ApplicationLevel String appBinding; + @Inject @ServiceLevel String serviceBinding; + + TestIntentService() { + super(NAME); + } + + @Override + public void onHandleIntent(Intent intent) {} + } + + @AndroidEntryPoint(BroadcastReceiver.class) + public static final class TestBroadcastReceiver extends Hilt_InjectionTest_TestBroadcastReceiver { + @Inject @ApplicationLevel String appBinding; + Intent lastIntent = null; + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + lastIntent = intent; + } + } + + @AndroidEntryPoint(BaseBroadcastReceiver.class) + public static final class TestBroadcastReceiverWithBaseImplementingOnReceive + extends Hilt_InjectionTest_TestBroadcastReceiverWithBaseImplementingOnReceive { + @Inject @ApplicationLevel String appBinding; + Intent baseLastIntent = null; + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + baseLastIntent = intent; + } + } + + abstract static class BaseBroadcastReceiver extends BroadcastReceiver { + Intent lastIntent = null; + + @Override + public void onReceive(Context context, Intent intent) { + lastIntent = intent; + } + } + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @ApplicationLevel String appBinding; + + @Before + public void setup() { + rule.inject(); + } + + @Test + public void testAppInjection() throws Exception { + assertThat(appBinding).isEqualTo(APP_BINDING); + } + + @Test + public void testActivityInjection() throws Exception { + ActivityController<TestActivity> controller = Robolectric.buildActivity(TestActivity.class); + + assertThat(controller.get().onCreateCalled).isFalse(); + controller.create(); + assertThat(controller.get().onCreateCalled).isTrue(); + } + + @Test + public void testFragmentInjection() throws Exception { + TestFragment fragment = new TestFragment(); + assertThat(fragment.onAttachContextCalled).isFalse(); + assertThat(fragment.onAttachActivityCalled).isFalse(); + setupFragment(TestActivity.class, fragment); + assertThat(fragment.onAttachContextCalled).isTrue(); + assertThat(fragment.onAttachActivityCalled).isTrue(); + } + + @Test + public void testParameterizedFragmentInjection() throws Exception { + TestParameterizedFragment fragment = new TestParameterizedFragment(); + assertThat(fragment.onAttachContextCalled).isFalse(); + assertThat(fragment.onAttachActivityCalled).isFalse(); + setupFragment(TestActivity.class, fragment); + assertThat(fragment.onAttachContextCalled).isTrue(); + assertThat(fragment.onAttachActivityCalled).isTrue(); + } + + @Test + public void testViewNoFragmentBindingsWithActivity() throws Exception { + TestActivity activity = Robolectric.setupActivity(TestActivity.class); + TestView view = new TestView(activity); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + } + + @Test + public void testViewNoFragmentBindingsWithFragment() throws Exception { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + TestView view = new TestView(fragment.getContext()); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + } + + @Test + public void testViewNoFragmentBindingsWithFragment_secondConstructor() throws Exception { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + TestView view = new TestView(fragment.getContext(), /* attrs= */ null); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + } + + @Test + public void testViewNoFragmentBindingsWithFragment_thirdConstructor() throws Exception { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + TestView view = new TestView(fragment.getContext(), /* attrs= */ null, /* defStyleAttr= */ 0); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + } + + @Test + @Config(sdk = 21) + public void testViewNoFragmentBindingsWithFragment_fourthConstructor_presentOnTwentyOne() + throws Exception { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + TestView view = + new TestView( + fragment.getContext(), /* attrs= */ null, /* defStyleAttr= */ 0, /* defStyleRes= */ 0); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + } + + @Test + @Config(sdk = 19) + public void testViewNoFragmentBindingsWithFragment_fourthConstructor_notPresentOnTwenty() { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + + assertThrows( + NoSuchMethodError.class, + () -> + new TestView( + fragment.getContext(), + /* attrs= */ null, + /* defStyleAttr= */ 0, + /* defStyleRes= */ 0)); + } + + @Test + public void testServiceInjection() throws Exception { + TestService testService = Robolectric.setupService(TestService.class); + assertThat(testService.appBinding).isEqualTo(APP_BINDING); + assertThat(testService.serviceBinding).isEqualTo(SERVICE_BINDING); + } + + @Test + public void testIntentServiceInjection() throws Exception { + TestIntentService testIntentService = Robolectric.setupService(TestIntentService.class); + assertThat(testIntentService.appBinding).isEqualTo(APP_BINDING); + assertThat(testIntentService.serviceBinding).isEqualTo(SERVICE_BINDING); + } + + @Test + public void testBroadcastReceiverInjection() throws Exception { + TestBroadcastReceiver testBroadcastReceiver = new TestBroadcastReceiver(); + Intent intent = new Intent(); + testBroadcastReceiver.onReceive(getApplicationContext(), intent); + assertThat(testBroadcastReceiver.appBinding).isEqualTo(APP_BINDING); + assertThat(testBroadcastReceiver.lastIntent).isSameInstanceAs(intent); + } + + @Test + public void testBroadcastReceiverWithBaseImplementingOnReceiveInjection() throws Exception { + TestBroadcastReceiverWithBaseImplementingOnReceive testBroadcastReceiver = + new TestBroadcastReceiverWithBaseImplementingOnReceive(); + Intent intent = new Intent(); + testBroadcastReceiver.onReceive(getApplicationContext(), intent); + assertThat(testBroadcastReceiver.appBinding).isEqualTo(APP_BINDING); + assertThat(testBroadcastReceiver.lastIntent).isSameInstanceAs(intent); + assertThat(testBroadcastReceiver.baseLastIntent).isSameInstanceAs(intent); + } + + @Test + public void testViewWithFragmentBindingsWithFragment() throws Exception { + TestFragment fragment = setupFragment(TestActivity.class, new TestFragment()); + + Context fragmentContext = fragment.getContext(); + TestViewWithFragmentBindings view = new TestViewWithFragmentBindings(fragmentContext); + assertThat(view.appBinding).isEqualTo(APP_BINDING); + assertThat(view.activityBinding).isEqualTo(ACTIVITY_BINDING); + assertThat(view.fragmentBinding).isEqualTo(FRAGMENT_BINDING); + } + + @Test + public void testViewWithFragmentBindingsFailsWithActivity() throws Exception { + TestActivity activity = Robolectric.setupActivity(TestActivity.class); + try { + new TestViewWithFragmentBindings(activity); + fail("Expected test to fail but it passes!"); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains( + "@WithFragmentBindings Hilt view must be attached to an @AndroidEntryPoint Fragment"); + } + } + + @Test + public void testFragmentAttachedToNonHiltActivityFails() throws Exception { + NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class); + try { + activity + .getSupportFragmentManager() + .beginTransaction() + .add(new TestFragment(), null) + .commitNow(); + fail("Expected test to fail but it passes!"); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains("Hilt Fragments must be attached to an @AndroidEntryPoint Activity"); + } + } + + @Test + public void testViewAttachedToNonHiltActivityFails() throws Exception { + NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class); + try { + new TestView(activity); + fail("Expected test to fail but it passes!"); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains("Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity"); + } + } + + @Test + public void testViewAttachedToNonHiltFragmentFails() throws Exception { + NonHiltActivity activity = Robolectric.setupActivity(NonHiltActivity.class); + NonHiltFragment fragment = new NonHiltFragment(); + activity.getSupportFragmentManager().beginTransaction().add(fragment, null).commitNow(); + Context nonHiltContext = fragment.getContext(); + try { + new TestView(nonHiltContext); + fail("Expected test to fail but it passes!"); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains("Hilt view must be attached to an @AndroidEntryPoint Fragment or Activity"); + } + } + + @Test + public void testViewAttachedToApplicationContextFails() throws Exception { + try { + new TestView(getApplicationContext()); + fail("Expected test to fail but it passes!"); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessageThat() + .contains( + "Hilt view cannot be created using the application context. " + + "Use a Hilt Fragment or Activity context"); + } + } + + /** Hilt Activity that manually calls inject(). */ + @AndroidEntryPoint(FragmentActivity.class) + public static final class DoubleInjectActivity extends Hilt_InjectionTest_DoubleInjectActivity { + @Inject Long counter; + + @Override + public void onCreate(Bundle onSavedInstanceState) { + inject(); + super.onCreate(onSavedInstanceState); + } + } + + @Test + public void testActivityDoesNotInjectTwice() throws Exception { + ActivityController<DoubleInjectActivity> controller = + Robolectric.buildActivity(DoubleInjectActivity.class); + controller.create(); + assertThat(controller.get().counter).isEqualTo(1L); + } + + /** Hilt Fragment that manually calls inject(). */ + @AndroidEntryPoint(Fragment.class) + public static final class DoubleInjectFragment extends Hilt_InjectionTest_DoubleInjectFragment { + @Inject Long counter; + + @Override + public void onAttach(Context context) { + inject(); + super.onAttach(context); + } + + @Override + public void onAttach(Activity activity) { + inject(); + super.onAttach(activity); + } + } + + @Test + public void testFragmentDoesNotInjectTwice() throws Exception { + DoubleInjectFragment fragment = setupFragment(TestActivity.class, new DoubleInjectFragment()); + assertThat(fragment.counter).isEqualTo(1L); + } + + /** Hilt View that manually calls inject(). */ + @AndroidEntryPoint(LinearLayout.class) + public static final class DoubleInjectView extends Hilt_InjectionTest_DoubleInjectView { + @Inject Long counter; + + DoubleInjectView(Context context) { + super(context); + inject(); + } + + DoubleInjectView(Context context, AttributeSet attrs) { + super(context, attrs); + inject(); + } + + DoubleInjectView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + inject(); + } + + @TargetApi(21) + DoubleInjectView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + inject(); + } + } + + @Test + public void testViewDoesNotInjectTwice() throws Exception { + TestActivity activity = Robolectric.setupActivity(TestActivity.class); + DoubleInjectView view = new DoubleInjectView(activity); + assertThat(view.counter).isEqualTo(1L); + } + + /** Hilt Service that manually calls inject(). */ + @AndroidEntryPoint(Service.class) + public static final class DoubleInjectService extends Hilt_InjectionTest_DoubleInjectService { + @Inject Long counter; + + @Override public void onCreate() { + inject(); + super.onCreate(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + } + + @Test + public void testServiceDoesNotInjectTwice() throws Exception { + DoubleInjectService testService = Robolectric.setupService(DoubleInjectService.class); + assertThat(testService.counter).isEqualTo(1L); + } + + /** Hilt BroadcastReceiver that manually calls inject(). */ + @AndroidEntryPoint(BroadcastReceiver.class) + public static final class DoubleInjectBroadcastReceiver + extends Hilt_InjectionTest_DoubleInjectBroadcastReceiver { + @Inject Long counter; + + @Override + public void onReceive(Context context, Intent intent) { + inject(context); + super.onReceive(context, intent); + } + } + + @Test + public void testBroadcastReceiverDoesNotInjectTwice() throws Exception { + DoubleInjectBroadcastReceiver testBroadcastReceiver = new DoubleInjectBroadcastReceiver(); + Intent intent = new Intent(); + testBroadcastReceiver.onReceive(getApplicationContext(), intent); + assertThat(testBroadcastReceiver.counter).isEqualTo(1L); + } + + private static <T extends Fragment> T setupFragment( + Class<? extends FragmentActivity> activityClass, T fragment) { + FragmentActivity activity = Robolectric.setupActivity(activityClass); + attachFragment(activity, fragment); + return fragment; + } + + private static void attachFragment(FragmentActivity activity, Fragment fragment) { + activity.getSupportFragmentManager().beginTransaction().add(fragment, "").commitNow(); + } +} diff --git a/javatests/dagger/hilt/android/InstallInObjectModule.kt b/javatests/dagger/hilt/android/InstallInObjectModule.kt new file mode 100644 index 000000000..dba857128 --- /dev/null +++ b/javatests/dagger/hilt/android/InstallInObjectModule.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 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.hilt.android + +import dagger.Module +import dagger.Provides +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface TestEntryPoint { + fun getInteger(): Int + fun getDouble(): Double + fun getBoolean(): Boolean +} + +@Module +@InstallIn(SingletonComponent::class) +object InstallInObjectModule { + @Provides + fun provideBoolean() = true +} + +@Module +@InstallIn(SingletonComponent::class) +class InstallInCompanionObjectModule { + @Provides + fun provideDouble() = 2.0 + + companion object { + @Provides + fun provideInt() = 1 + } +} diff --git a/javatests/dagger/hilt/android/InstallInObjectModuleTest.java b/javatests/dagger/hilt/android/InstallInObjectModuleTest.java new file mode 100644 index 000000000..760d18104 --- /dev/null +++ b/javatests/dagger/hilt/android/InstallInObjectModuleTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class InstallInObjectModuleTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void verifyObjectModule() { + TestEntryPoint entryPoint = EntryPoints.get(getApplicationContext(), TestEntryPoint.class); + assertThat(entryPoint.getInteger()).isEqualTo(1); + assertThat(entryPoint.getDouble()).isEqualTo(2.0); + assertThat(entryPoint.getBoolean()).isTrue(); + } +} diff --git a/javatests/dagger/hilt/android/InternalKtModuleTest.java b/javatests/dagger/hilt/android/InternalKtModuleTest.java new file mode 100644 index 000000000..0b489896e --- /dev/null +++ b/javatests/dagger/hilt/android/InternalKtModuleTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class InternalKtModuleTest { + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject String string; + @Inject Integer intValue; + + @Before + public void setUp() { + rule.inject(); + } + + @Test + public void testBindingFromInternalKtModule() { + assertThat(string).isEqualTo("expected_string_value"); + assertThat(intValue).isEqualTo(9); + } +} diff --git a/javatests/dagger/hilt/android/ModuleTest.java b/javatests/dagger/hilt/android/ModuleTest.java new file mode 100644 index 000000000..0dadfe0ec --- /dev/null +++ b/javatests/dagger/hilt/android/ModuleTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Named; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class ModuleTest { + public static class Dep1 {} + public static class Dep2 {} + public static class Dep3 {} + public static class Dep4 { @Inject Dep4() {}} + public static class Dep5 { @Inject Dep5() {}} + public static class Dep6 {} + public static class Dep7 {} + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + // Test that modules with only static methods can have private constructors. + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule1 { + private TestModule1() {} // This is fine because Dagger doesn't need an instance. + + @Provides + static Dep1 provide() { + return new Dep1(); + } + } + + // Test that modules with only static methods can have constructors with parameters. + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule2 { + TestModule2(String str) {} // This is fine because Dagger doesn't need an instance. + + @Provides + static Dep2 provide() { + return new Dep2(); + } + } + + // Test that modules with non-static methods can have constructors with parameters if no-arg + // constructor exists. + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule3 { + TestModule3() { + this(""); + } + + TestModule3(String str) {} // This is fine because Dagger can use the other constructor + + @Provides + Dep3 provide() { + return new Dep3(); + } + } + + // Test that modules with only abstract methods can have private constructors. + @Module + @InstallIn(SingletonComponent.class) + @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here + abstract class TestModule4 { + private TestModule4() {} // This is fine because Dagger doesn't need an instance. + + @Binds @Named("Dep4") abstract Object bind(Dep4 impl); + } + + // Test that modules with only abstract methods can have constructors with parameters. + @Module + @InstallIn(SingletonComponent.class) + @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here + abstract class TestModule5 { + TestModule5(String str) {} // This is fine because Dagger doesn't need an instance. + + @Binds @Named("Dep5") abstract Object bind(Dep5 impl); + } + + // Test that static modules with no methods can have private constructors. + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule6 { + private TestModule6() {} // This is fine because Dagger doesn't need an instance. + } + + // Test that static modules with no methods can have constructors with parameters. + @Module + @InstallIn(SingletonComponent.class) + static final class TestModule7 { + TestModule7(String str) {} // This is fine because Dagger doesn't need an instance. + } + + // Test that abstract modules with no methods can have private constructors. + @Module + @InstallIn(SingletonComponent.class) + @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here + abstract class TestModule8 { + private TestModule8() {} // This is fine because Dagger doesn't need an instance. + } + + // Test that abstract modules with no methods can have constructors with parameters. + @Module + @InstallIn(SingletonComponent.class) + @SuppressWarnings("ClassCanBeStatic") // purposely testing non-static class here + abstract class TestModule9 { + TestModule9(String str) {} // This is fine because Dagger doesn't need an instance. + } + + @Inject Dep1 dep1; + @Inject Dep2 dep2; + @Inject Dep5 dep3; + @Inject @Named("Dep4") Object dep4; + @Inject @Named("Dep5") Object dep5; + + @Before + public void setup() { + rule.inject(); + } + + @Test + public void testDep1() throws Exception { + assertThat(dep1).isNotNull(); + } + + @Test + public void testDep2() throws Exception { + assertThat(dep2).isNotNull(); + } + + @Test + public void testDep3() throws Exception { + assertThat(dep3).isNotNull(); + } + + @Test + public void testDep4() throws Exception { + assertThat(dep4).isNotNull(); + assertThat(dep4).isInstanceOf(Dep4.class); + } + + @Test + public void testDep5() throws Exception { + assertThat(dep5).isNotNull(); + assertThat(dep5).isInstanceOf(Dep5.class); + } +} diff --git a/javatests/dagger/hilt/android/MultiTestRoot1Test.java b/javatests/dagger/hilt/android/MultiTestRoot1Test.java new file mode 100644 index 000000000..49c03bc75 --- /dev/null +++ b/javatests/dagger/hilt/android/MultiTestRoot1Test.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.activity.ComponentActivity; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.android.testing.UninstallModules; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Named; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; + +// TODO(bcorso): Support transitively ignoring the @Module.includes of ignored modules? +// TODO(bcorso): Support including non-test @UninstallModules using @UninstallModules.includes? +@UninstallModules({ + MultiTestRootExternalModules.PkgPrivateAppModule.class, + MultiTestRootExternalModules.PkgPrivateActivityModule.class +}) +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class MultiTestRoot1Test { + private static final int INT_VALUE = 9; + private static final String STR_VALUE = "MultiTestRoot1TestValue"; + private static final long LONG_VALUE = 11L; + private static final String REPLACE_EXTERNAL_STR_VALUE = "REPLACED_EXTERNAL_STR_VALUE"; + private static final long REPLACE_EXTERNAL_LONG_VALUE = 17L; + private static final String BIND_VALUE_STRING = "BIND_VALUE_STRING"; + private static final String TEST_QUALIFIER = "TEST_QUALIFIER"; + + @AndroidEntryPoint(ComponentActivity.class) + public static class TestActivity extends Hilt_MultiTestRoot1Test_TestActivity { + @Inject Baz baz; + @Inject @MultiTestRootExternalModules.External Long externalLongValue; + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindValueEntryPoint { + @Named(TEST_QUALIFIER) + String bindValueString(); + } + + @Module + @InstallIn(SingletonComponent.class) + public interface ReplaceExternalAppModule { + @Provides + @MultiTestRootExternalModules.External + static String provideString() { + return REPLACE_EXTERNAL_STR_VALUE; + } + } + + @Module + @InstallIn(ActivityComponent.class) + public interface ReplaceExternalActivityModule { + @Provides + @MultiTestRootExternalModules.External + static Long provideString() { + return REPLACE_EXTERNAL_LONG_VALUE; + } + } + + @Module + @InstallIn(ActivityComponent.class) + public interface TestActivityModule { + @Provides + static Baz provideBaz() { + return new Baz(LONG_VALUE); + } + } + + @Module + @InstallIn(SingletonComponent.class) + interface PkgPrivateTestModule { + @Provides + static Qux provideQux() { + return new Qux(); + } + } + + @Module + @InstallIn(SingletonComponent.class) + public interface TestModule { + @Provides + static int provideInt() { + return INT_VALUE; + } + + @Provides + static String provideString() { + return STR_VALUE; + } + } + + public static final class Outer { + @Module + @InstallIn(SingletonComponent.class) + public interface NestedTestModule { + @Provides + static long provideLong() { + return LONG_VALUE; + } + } + + private Outer() {} + } + + static class Foo { + final int value; + + @Inject + Foo(int value) { + this.value = value; + } + } + + static class Bar { + final String value; + + Bar(String value) { + this.value = value; + } + } + + static class Baz { + final long value; + + Baz(long value) { + this.value = value; + } + } + + static class Qux {} + + @Module + @InstallIn(SingletonComponent.class) + public interface BarModule { + @Provides + static Bar provideBar(String value) { + return new Bar(value); + } + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BarEntryPoint { + Bar getBar(); + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface PkgPrivateQuxEntryPoint { + Qux getQux(); + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject Foo foo; + @Inject Qux qux; + @Inject Long longValue; + @Inject @MultiTestRootExternalModules.External String externalStrValue; + + @BindValue + @Named(TEST_QUALIFIER) + String bindValueString = BIND_VALUE_STRING; + + @Test + public void testInjectFromTestModule() throws Exception { + assertThat(foo).isNull(); + setupComponent(); + assertThat(foo).isNotNull(); + assertThat(foo.value).isEqualTo(INT_VALUE); + } + + @Test + public void testInjectFromNestedTestModule() throws Exception { + assertThat(longValue).isNull(); + setupComponent(); + assertThat(longValue).isNotNull(); + assertThat(longValue).isEqualTo(LONG_VALUE); + } + + @Test + public void testInjectFromExternalAppModule() throws Exception { + assertThat(externalStrValue).isNull(); + setupComponent(); + assertThat(externalStrValue).isNotNull(); + assertThat(externalStrValue).isEqualTo(REPLACE_EXTERNAL_STR_VALUE); + assertThat(externalStrValue).isNotEqualTo(MultiTestRootExternalModules.EXTERNAL_STR_VALUE); + } + + @Test + public void testInjectFromExternalActivityModule() throws Exception { + setupComponent(); + ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class); + assertThat(ac.get().externalLongValue).isNull(); + ac.create(); + assertThat(ac.get().externalLongValue).isNotNull(); + assertThat(ac.get().externalLongValue).isEqualTo(REPLACE_EXTERNAL_LONG_VALUE); + assertThat(ac.get().externalLongValue) + .isNotEqualTo(MultiTestRootExternalModules.EXTERNAL_LONG_VALUE); + } + + @Test + public void testInjectFromPkgPrivateTestModule() throws Exception { + assertThat(qux).isNull(); + setupComponent(); + assertThat(qux).isNotNull(); + } + + @Test + public void testLocalEntryPoint() throws Exception { + setupComponent(); + Bar bar = EntryPoints.get(getApplicationContext(), BarEntryPoint.class).getBar(); + assertThat(bar).isNotNull(); + assertThat(bar.value).isEqualTo(STR_VALUE); + } + + @Test + public void testLocalPkgPrivateEntryPoint() throws Exception { + setupComponent(); + Qux qux = EntryPoints.get(getApplicationContext(), PkgPrivateQuxEntryPoint.class).getQux(); + assertThat(qux).isNotNull(); + } + + @Test + public void testAndroidEntryPoint() throws Exception { + setupComponent(); + ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class); + assertThat(ac.get().baz).isNull(); + ac.create(); + assertThat(ac.get().baz).isNotNull(); + assertThat(ac.get().baz.value).isEqualTo(LONG_VALUE); + } + + @Test + public void testMissingMultiTestRoot2EntryPoint() throws Exception { + setupComponent(); + ClassCastException exception = + assertThrows( + ClassCastException.class, + () -> EntryPoints.get(getApplicationContext(), MultiTestRoot2Test.BarEntryPoint.class)); + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Cannot cast dagger.hilt.android.DaggerMultiTestRoot1Test_HiltComponents_SingletonC" + + " to dagger.hilt.android.MultiTestRoot2Test$BarEntryPoint"); + } + + @Test + public void testBindValueFieldIsProvided() throws Exception { + setupComponent(); + assertThat(bindValueString).isEqualTo(BIND_VALUE_STRING); + assertThat(getBinding()).isEqualTo(BIND_VALUE_STRING); + } + + @Test + public void testBindValueIsMutable() throws Exception { + setupComponent(); + bindValueString = "newValue"; + assertThat(getBinding()).isEqualTo("newValue"); + } + + void setupComponent() { + rule.inject(); + } + + private static String getBinding() { + return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString(); + } +} diff --git a/javatests/dagger/hilt/android/MultiTestRoot2Test.java b/javatests/dagger/hilt/android/MultiTestRoot2Test.java new file mode 100644 index 000000000..39aecfa2e --- /dev/null +++ b/javatests/dagger/hilt/android/MultiTestRoot2Test.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.activity.ComponentActivity; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Named; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.android.controller.ActivityController; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class MultiTestRoot2Test { + private static final int INT_VALUE = 13; + private static final String STR_VALUE = "MultiTestRoot2TestValue"; + private static final long LONG_VALUE = 17L; + private static final String BIND_VALUE_STRING = "BIND_VALUE_STRING"; + private static final String TEST_QUALIFIER = "TEST_QUALIFIER"; + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindValueEntryPoint { + @Named(TEST_QUALIFIER) + String bindValueString(); + } + + @AndroidEntryPoint(ComponentActivity.class) + public static class TestActivity extends Hilt_MultiTestRoot2Test_TestActivity { + @Inject Baz baz; + @Inject @MultiTestRootExternalModules.External Long externalLongValue; + } + + @Module + @InstallIn(ActivityComponent.class) + public interface TestActivityModule { + @Provides + static Baz provideBaz() { + return new Baz(LONG_VALUE); + } + } + + @Module + @InstallIn(SingletonComponent.class) + interface PkgPrivateTestModule { + @Provides + static Qux provideQux() { + return new Qux(); + } + } + + @Module + @InstallIn(SingletonComponent.class) + public interface TestModule { + @Provides + static int provideInt() { + return INT_VALUE; + } + + @Provides + static String provideString() { + return STR_VALUE; + } + } + + public static final class Outer { + @Module + @InstallIn(SingletonComponent.class) + public interface NestedTestModule { + @Provides + static long provideLong() { + return LONG_VALUE; + } + } + + private Outer() {} + } + + static class Foo { + final int value; + + @Inject + Foo(int value) { + this.value = value; + } + } + + static class Bar { + final String value; + + Bar(String value) { + this.value = value; + } + } + + static class Baz { + final long value; + + Baz(long value) { + this.value = value; + } + } + + static class Qux {} + + @Module + @InstallIn(SingletonComponent.class) + public interface BarModule { + @Provides + static Bar provideBar(String value) { + return new Bar(value); + } + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BarEntryPoint { + Bar getBar(); + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + interface PkgPrivateQuxEntryPoint { + Qux getQux(); + } + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject Foo foo; + @Inject Qux qux; + @Inject String str; + @Inject Long longValue; + @Inject @MultiTestRootExternalModules.External String externalStrValue; + + @BindValue + @Named(TEST_QUALIFIER) + String bindValueString = BIND_VALUE_STRING; + + @Test + public void testInjectFromTestModule() throws Exception { + assertThat(foo).isNull(); + rule.inject(); + assertThat(foo).isNotNull(); + assertThat(foo.value).isEqualTo(INT_VALUE); + } + + @Test + public void testInjectFromTestModuleWithArgs() throws Exception { + assertThat(str).isNull(); + rule.inject(); + assertThat(str).isNotNull(); + assertThat(str).isEqualTo(STR_VALUE); + } + + @Test + public void testInjectFromNestedTestModule() throws Exception { + assertThat(longValue).isNull(); + rule.inject(); + assertThat(longValue).isNotNull(); + assertThat(longValue).isEqualTo(LONG_VALUE); + } + + @Test + public void testInjectFromPkgPrivateTestModule() throws Exception { + assertThat(qux).isNull(); + rule.inject(); + assertThat(qux).isNotNull(); + } + + @Test + public void testInjectFromExternalAppModule() throws Exception { + assertThat(externalStrValue).isNull(); + rule.inject(); + assertThat(externalStrValue).isNotNull(); + assertThat(externalStrValue).isEqualTo(MultiTestRootExternalModules.EXTERNAL_STR_VALUE); + } + + @Test + public void testInjectFromExternalActivityModule() throws Exception { + rule.inject(); + ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class); + assertThat(ac.get().externalLongValue).isNull(); + ac.create(); + assertThat(ac.get().externalLongValue).isNotNull(); + assertThat(ac.get().externalLongValue) + .isEqualTo(MultiTestRootExternalModules.EXTERNAL_LONG_VALUE); + } + + @Test + public void testLocalEntryPoint() throws Exception { + rule.inject(); + Bar bar = EntryPoints.get(getApplicationContext(), BarEntryPoint.class).getBar(); + assertThat(bar).isNotNull(); + assertThat(bar.value).isEqualTo(STR_VALUE); + } + + @Test + public void testLocalPkgPrivateEntryPoint() throws Exception { + rule.inject(); + Qux qux = EntryPoints.get(getApplicationContext(), PkgPrivateQuxEntryPoint.class).getQux(); + assertThat(qux).isNotNull(); + } + + @Test + public void testAndroidEntryPoint() throws Exception { + rule.inject(); + ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class); + assertThat(ac.get().baz).isNull(); + ac.create(); + assertThat(ac.get().baz).isNotNull(); + assertThat(ac.get().baz.value).isEqualTo(LONG_VALUE); + } + + @Test + public void testMissingMultiTestRoot1EntryPoint() throws Exception { + rule.inject(); + ClassCastException exception = + assertThrows( + ClassCastException.class, + () -> EntryPoints.get(getApplicationContext(), MultiTestRoot1Test.BarEntryPoint.class)); + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Cannot cast dagger.hilt.android.DaggerMultiTestRoot2Test_HiltComponents_SingletonC" + + " to dagger.hilt.android.MultiTestRoot1Test$BarEntryPoint"); + } + + @Test + public void testBindValueFieldIsProvided() throws Exception { + rule.inject(); + assertThat(bindValueString).isEqualTo(BIND_VALUE_STRING); + assertThat(getBinding()).isEqualTo(BIND_VALUE_STRING); + } + + @Test + public void testBindValueIsMutable() throws Exception { + rule.inject(); + bindValueString = "newValue"; + assertThat(getBinding()).isEqualTo("newValue"); + } + + private static String getBinding() { + return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString(); + } +} diff --git a/javatests/dagger/hilt/android/MultiTestRootExternalModules.java b/javatests/dagger/hilt/android/MultiTestRootExternalModules.java new file mode 100644 index 000000000..5d2c7f89a --- /dev/null +++ b/javatests/dagger/hilt/android/MultiTestRootExternalModules.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.components.ActivityComponent; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Qualifier; + +public final class MultiTestRootExternalModules { + static final long EXTERNAL_LONG_VALUE = 43L; + static final String EXTERNAL_STR_VALUE = "EXTERNAL_STRING_VALUE"; + + @Qualifier + @interface External {} + + @Module + @InstallIn(SingletonComponent.class) + interface PkgPrivateAppModule { + @Provides + @External + static String provideStringValue() { + return EXTERNAL_STR_VALUE; + } + } + + @Module + @InstallIn(ActivityComponent.class) + interface PkgPrivateActivityModule { + @Provides + @External + static Long provideLongValue() { + return EXTERNAL_LONG_VALUE; + } + } + + private MultiTestRootExternalModules() {} +} diff --git a/javatests/dagger/hilt/android/QualifierInFieldsClass.kt b/javatests/dagger/hilt/android/QualifierInFieldsClass.kt new file mode 100644 index 000000000..ec3f6ff73 --- /dev/null +++ b/javatests/dagger/hilt/android/QualifierInFieldsClass.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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.hilt.android + +import android.content.Context +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +class QualifierInFieldsClass @Inject constructor() { + @Inject + @ApplicationContext + lateinit var appContext: Context + + @Inject + @ActivityContext + lateinit var activityContext: Context +} diff --git a/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java b/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java new file mode 100644 index 000000000..8e62b99d2 --- /dev/null +++ b/javatests/dagger/hilt/android/QualifierInKotlinFieldsTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class QualifierInKotlinFieldsTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void activityFactory() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.object.appContext).isNotNull(); + assertThat(activity.object.activityContext).isNotNull(); + }); + } + } + + // This test activity injects a class that is defined in Kotlin because we want to test the + // qualifiers in Kotlin fields / properties (generated getters and setter with backing field). + // Ideally we would write this test in Kotlin, but there is no open fule for writting android + // local tests in Kotlin. + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_QualifierInKotlinFieldsTest_TestActivity { + @Inject QualifierInFieldsClass object; + } +} diff --git a/javatests/dagger/hilt/android/TestInstallInModules.java b/javatests/dagger/hilt/android/TestInstallInModules.java new file mode 100644 index 000000000..0affa7344 --- /dev/null +++ b/javatests/dagger/hilt/android/TestInstallInModules.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.components.SingletonComponent; +import dagger.hilt.testing.TestInstallIn; + +/** Replaces a production binding in tests. */ +final class TestInstallInModules { + private TestInstallInModules() {} + + @Module + @TestInstallIn(components = SingletonComponent.class, replaces = UsesComponentTestModule.class) + interface TestInstallInModule { + @Provides + @UsesComponentQualifier + static String provideLocalString() { + return "test_install_in_string"; + } + } +} diff --git a/javatests/dagger/hilt/android/UsesComponentHelper.java b/javatests/dagger/hilt/android/UsesComponentHelper.java new file mode 100644 index 000000000..179bf710c --- /dev/null +++ b/javatests/dagger/hilt/android/UsesComponentHelper.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +/** + * Utility methods for tests that verify which generated component is used to inject the test class. + */ +public abstract class UsesComponentHelper { + + public static String defaultComponentName() { + return "dagger.hilt.android.internal.testing.root.DaggerDefault_HiltComponents_SingletonC"; + } + + /** + * Returns the name of a component that cannot use the default component. Does not handle deduping + * if test class names clash. + */ + public static String perTestComponentName(Object testInstance) { + return "dagger.hilt.android.internal.testing.root.Dagger" + + testInstance.getClass().getSimpleName() + + "_HiltComponents_SingletonC"; + } + + /** + * Returns the name of a component that cannot use the default component, including the expected + * prefix applied by Hilt to dedupe clashing class names. + */ + public static String perTestComponentNameWithDedupePrefix( + String expectedPrefix, Object testInstance) { + return "dagger.hilt.android.internal.testing.root.Dagger" + + expectedPrefix + + testInstance.getClass().getSimpleName() + + "_HiltComponents_SingletonC"; + } + + private UsesComponentHelper() {} +} diff --git a/javatests/dagger/hilt/android/UsesComponentTestClasses.java b/javatests/dagger/hilt/android/UsesComponentTestClasses.java new file mode 100644 index 000000000..9445f2ae1 --- /dev/null +++ b/javatests/dagger/hilt/android/UsesComponentTestClasses.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import dagger.BindsInstance; +import dagger.hilt.DefineComponent; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.lang.annotation.Retention; +import javax.inject.Inject; +import javax.inject.Qualifier; +import javax.inject.Scope; + +/** + * Subcomponent used to verify that subcomponents are correctly installed in shared test components. + */ +public abstract class UsesComponentTestClasses { + /** Qualifier for test bindings. */ + @Qualifier + public @interface UsesComponentQualifier {} + + @UsesComponentTestSubcomponentScoped + public static class Foo { + final int id; + + @Inject + Foo(int id) { + this.id = id; + } + } + + @Scope + @Retention(RUNTIME) + public @interface UsesComponentTestSubcomponentScoped {} + + @UsesComponentTestSubcomponentScoped + @DefineComponent(parent = SingletonComponent.class) + public interface UsesComponentTestSubcomponent { + @DefineComponent.Builder + interface Builder { + @BindsInstance + Builder id(int id); + + UsesComponentTestSubcomponent build(); + } + } + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface UsesComponentTestSubcomponentBuilderEntryPoint { + UsesComponentTestSubcomponent.Builder mySubcomponentBuilder(); + } + + @EntryPoint + @InstallIn(UsesComponentTestSubcomponent.class) + public interface FooEntryPoint { + Foo foo(); + } + + private UsesComponentTestClasses() {} +} diff --git a/javatests/dagger/hilt/android/UsesComponentTestModule.java b/javatests/dagger/hilt/android/UsesComponentTestModule.java new file mode 100644 index 000000000..2b16a1c82 --- /dev/null +++ b/javatests/dagger/hilt/android/UsesComponentTestModule.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.components.SingletonComponent; + +/** Module for shared test component tests. */ +@Module +@InstallIn(SingletonComponent.class) +final class UsesComponentTestModule { + + @Provides + @UsesComponentQualifier + static String provideString() { + return "shared_string"; + } + + private UsesComponentTestModule() {} +} diff --git a/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java b/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java new file mode 100644 index 000000000..6c878e745 --- /dev/null +++ b/javatests/dagger/hilt/android/UsesLocalComponentTestBindingsTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Qualifier; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that provides its own test bindings, and therefore cannot use the shared components. + * + * <p>Note that this test class exactly matches the simple name of {@link + * dagger.hilt.android.testsubpackage.UsesLocalComponentTestBindingsTest}. This is intentional and + * used to verify generated code class names do not clash. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesLocalComponentTestBindingsTest { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Qualifier + @interface TestQualifier {} + + @Inject @UsesComponentQualifier String injectedString; + @Inject @TestQualifier String localString; + + @Module + @InstallIn(SingletonComponent.class) + public static final class TestModule { + @Provides + @TestQualifier + static String provideString() { + return "local_string"; + } + + private TestModule() {} + } + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + assertThat(localString).isEqualTo("local_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testUsesLocalComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.perTestComponentNameWithDedupePrefix("dha_", this)); + } +} diff --git a/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java b/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java new file mode 100644 index 000000000..624ba8319 --- /dev/null +++ b/javatests/dagger/hilt/android/UsesLocalComponentUninstallModuleTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.android.testing.UninstallModules; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +@UninstallModules(UsesComponentTestModule.class) +public final class UsesLocalComponentUninstallModuleTest { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Module + @InstallIn(SingletonComponent.class) + public interface LocalTestModule { + + @Provides + @UsesComponentQualifier + static String provideString() { + return "local_string"; + } + } + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("local_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testUsesLocalComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.perTestComponentName(this)); + } +} diff --git a/javatests/dagger/hilt/android/UsesSharedComponent1Test.java b/javatests/dagger/hilt/android/UsesSharedComponent1Test.java new file mode 100644 index 000000000..9b8f287ac --- /dev/null +++ b/javatests/dagger/hilt/android/UsesSharedComponent1Test.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that provides none of its own test bindings, and therefore uses the shared components. + * + * <p>Note that this test class exactly matches the simple name of {@link + * dagger.hilt.android.testsubpackage.UsesSharedComponent1Test}. This is intentional and used to + * verify generated code class names do not clash. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesSharedComponent1Test { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testActivity() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.activityString).isEqualTo("shared_string"); + }); + } + } + + @Test + public void testUsesSharedComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.defaultComponentName()); + } + + @Test + public void testLocalComponentNotGenerated() { + assertThrows( + ClassNotFoundException.class, + () -> Class.forName(UsesComponentHelper.perTestComponentName(this))); + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_UsesSharedComponent1Test_TestActivity { + @Inject @UsesComponentQualifier String activityString; + } +} diff --git a/javatests/dagger/hilt/android/UsesSharedComponent2Test.java b/javatests/dagger/hilt/android/UsesSharedComponent2Test.java new file mode 100644 index 000000000..d3b247d1a --- /dev/null +++ b/javatests/dagger/hilt/android/UsesSharedComponent2Test.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that provides none of its own test bindings, and therefore uses the shared components. A + * duplicate of {@link SharedComponent1Test} to test that multiple test roots with shared components + * can be compiled in the same target. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesSharedComponent2Test { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testActivity() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.activityString).isEqualTo("shared_string"); + }); + } + } + + @Test + public void testUsesSharedComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.defaultComponentName()); + } + + @Test + public void testLocalComponentNotGenerated() { + assertThrows( + ClassNotFoundException.class, + () -> Class.forName(UsesComponentHelper.perTestComponentName(this))); + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_UsesSharedComponent2Test_TestActivity { + @Inject @UsesComponentQualifier String activityString; + } +} diff --git a/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java b/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java new file mode 100644 index 000000000..60ef2fc7a --- /dev/null +++ b/javatests/dagger/hilt/android/UsesSharedComponentEnclosedTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that uses the {@link Enclosed} test runner and provides none of its own test bindings, and + * therefore uses the shared components. + */ +@RunWith(Enclosed.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesSharedComponentEnclosedTest { + + @HiltAndroidTest + @RunWith(AndroidJUnit4.class) + // Robolectric requires Java9 to run API 29 and above, so use API 28 instead + @Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) + public static final class EnclosedTest { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testActivity() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.activityString).isEqualTo("shared_string"); + }); + } + } + + @Test + public void testUsesSharedComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.defaultComponentName()); + } + + @Test + public void testLocalComponentNotGenerated() { + assertThrows( + ClassNotFoundException.class, + () -> Class.forName(UsesComponentHelper.perTestComponentName(this))); + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity + extends Hilt_UsesSharedComponentEnclosedTest_EnclosedTest_TestActivity { + @Inject @UsesComponentQualifier String activityString; + } + } +} diff --git a/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java b/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java new file mode 100644 index 000000000..8de562b2a --- /dev/null +++ b/javatests/dagger/hilt/android/UsesSharedComponentTestInstallInTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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.hilt.android; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesSharedComponentTestInstallInTest { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("test_install_in_string"); + } + + @Test + public void testUsesLocalComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.defaultComponentName()); + } + + @Test + public void testLocalComponentNotGenerated() { + assertThrows( + ClassNotFoundException.class, + () -> Class.forName(UsesComponentHelper.perTestComponentName(this))); + } +} diff --git a/javatests/dagger/hilt/android/ViewModelScopedTest.java b/javatests/dagger/hilt/android/ViewModelScopedTest.java new file mode 100644 index 000000000..e37e42ac7 --- /dev/null +++ b/javatests/dagger/hilt/android/ViewModelScopedTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import android.os.Build; +import android.os.Bundle; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.lifecycle.HiltViewModel; +import dagger.hilt.android.scopes.ViewModelScoped; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class ViewModelScopedTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void testViewModelScopeInFragment() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + TestFragment fragment = + (TestFragment) activity.getSupportFragmentManager().findFragmentByTag("tag"); + assertThat(fragment.vm.one.bar).isEqualTo(fragment.vm.two.bar); + }); + } + } + + @AndroidEntryPoint(FragmentActivity.class) + public static class TestActivity extends Hilt_ViewModelScopedTest_TestActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState == null) { + Fragment f = + getSupportFragmentManager() + .getFragmentFactory() + .instantiate(TestFragment.class.getClassLoader(), TestFragment.class.getName()); + getSupportFragmentManager().beginTransaction().add(0, f, "tag").commitNow(); + } + } + } + + @AndroidEntryPoint(Fragment.class) + public static class TestFragment extends Hilt_ViewModelScopedTest_TestFragment { + MyViewModel vm; + + @Override + public void onCreate(@Nullable Bundle bundle) { + super.onCreate(bundle); + vm = new ViewModelProvider(this).get(MyViewModel.class); + } + } + + @HiltViewModel + static class MyViewModel extends ViewModel { + + final DependsOnBarOne one; + final DependsOnBarTwo two; + + @Inject + MyViewModel(DependsOnBarOne one, DependsOnBarTwo two) { + this.one = one; + this.two = two; + } + } + + @ViewModelScoped + static class Bar { + @Inject + Bar() {} + } + + static class DependsOnBarOne { + final Bar bar; + + @Inject + DependsOnBarOne(Bar bar) { + this.bar = bar; + } + } + + static class DependsOnBarTwo { + final Bar bar; + + @Inject + DependsOnBarTwo(Bar bar) { + this.bar = bar; + } + } +} diff --git a/javatests/dagger/hilt/android/ViewModelWithBaseTest.java b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java new file mode 100644 index 000000000..4e4a47c0b --- /dev/null +++ b/javatests/dagger/hilt/android/ViewModelWithBaseTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 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.hilt.android; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.lifecycle.SavedStateHandle; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import android.os.Build; +import android.os.Bundle; +import androidx.fragment.app.FragmentActivity; +import androidx.annotation.Nullable; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.lifecycle.HiltViewModel; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public class ViewModelWithBaseTest { + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Test + public void verifyBaseInjection() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.myViewModel.foo).isNotNull(); + assertThat(activity.myViewModel.bar).isNotNull(); + }); + } + } + + @AndroidEntryPoint(FragmentActivity.class) + public static class TestActivity extends Hilt_ViewModelWithBaseTest_TestActivity { + + MyViewModel myViewModel; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + myViewModel = new ViewModelProvider(this).get(MyViewModel.class); + } + } + + @HiltViewModel + static class MyViewModel extends BaseViewModel { + + final Foo foo; + + @Inject + MyViewModel(SavedStateHandle handle, Foo foo) { + this.foo = foo; + } + } + + abstract static class BaseViewModel extends ViewModel { + @Inject Bar bar; + } + + static class Foo { + @Inject + Foo() {} + } + + static class Bar { + @Inject + Bar() {} + } +} diff --git a/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml b/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml new file mode 100644 index 000000000..cc5128efd --- /dev/null +++ b/javatests/dagger/hilt/android/internal/managers/AndroidManifest.xml @@ -0,0 +1,11 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="dagger.hilt.android.internal.managers"> + + <uses-sdk android:minSdkVersion="14" /> + + <application> + <activity + android:name=".FragmentContextWrapperLeakTest$TestActivity" + android:exported="false"/> + </application> +</manifest> diff --git a/javatests/dagger/hilt/android/internal/managers/BUILD b/javatests/dagger/hilt/android/internal/managers/BUILD new file mode 100644 index 000000000..ff4f432f7 --- /dev/null +++ b/javatests/dagger/hilt/android/internal/managers/BUILD @@ -0,0 +1,39 @@ +# Copyright (C) 2021 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +package(default_visibility = ["//:src"]) + +android_local_test( + name = "FragmentContextWrapperLeakTest", + size = "small", + srcs = ["FragmentContextWrapperLeakTest.java"], + manifest = "AndroidManifest.xml", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "@maven//:junit_junit", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/lifecycle", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) diff --git a/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java new file mode 100644 index 000000000..38086c6aa --- /dev/null +++ b/javatests/dagger/hilt/android/internal/managers/FragmentContextWrapperLeakTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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.hilt.android.internal.managers; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.lifecycle.Lifecycle; +import android.os.Build; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.internal.managers.ViewComponentManager.FragmentContextWrapper; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class FragmentContextWrapperLeakTest { + /** An activity to test injection. */ + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_FragmentContextWrapperLeakTest_TestActivity {} + + /** A fragment to test injection. */ + @AndroidEntryPoint(Fragment.class) + public static final class TestFragment extends Hilt_FragmentContextWrapperLeakTest_TestFragment {} + + @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Test + public void testFragmentContextWrapperDoesNotLeakFragment() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + TestFragment fragment = new TestFragment(); + scenario.onActivity( + activity -> + activity + .getSupportFragmentManager() + .beginTransaction() + .add(fragment, "TestFragment") + .commitNow()); + + FragmentContextWrapper fragmentContextWrapper = + (FragmentContextWrapper) fragment.getContext(); + assertThat(fragmentContextWrapper.getFragment()).isEqualTo(fragment); + scenario.moveToState(Lifecycle.State.DESTROYED); + NullPointerException exception = + assertThrows(NullPointerException.class, fragmentContextWrapper::getFragment); + assertThat(exception).hasMessageThat().contains("The fragment has already been destroyed"); + } + } +} diff --git a/javatests/dagger/hilt/android/processor/AndroidCompilers.java b/javatests/dagger/hilt/android/processor/AndroidCompilers.java index 5ce0a2ad8..1fb19ae3e 100644 --- a/javatests/dagger/hilt/android/processor/AndroidCompilers.java +++ b/javatests/dagger/hilt/android/processor/AndroidCompilers.java @@ -22,12 +22,13 @@ import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compiler; import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointProcessor; import dagger.hilt.android.processor.internal.customtestapplication.CustomTestApplicationProcessor; -import dagger.hilt.android.processor.internal.uninstallmodules.UninstallModulesProcessor; import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsProcessor; import dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor; +import dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor; import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputProcessor; import dagger.hilt.processor.internal.originatingelement.OriginatingElementProcessor; import dagger.hilt.processor.internal.root.RootProcessor; +import dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor; import dagger.internal.codegen.ComponentProcessor; import dagger.testing.compile.CompilerTests; import java.util.Arrays; @@ -68,6 +69,7 @@ public final class AndroidCompilers { new AndroidEntryPointProcessor(), new ComponentProcessor(), new DefineComponentProcessor(), + new EarlyEntryPointProcessor(), new GeneratesRootInputProcessor(), new OriginatingElementProcessor(), new CustomTestApplicationProcessor(), diff --git a/javatests/dagger/hilt/android/processor/BUILD b/javatests/dagger/hilt/android/processor/BUILD index 7fb23201b..4ec4d56a6 100644 --- a/javatests/dagger/hilt/android/processor/BUILD +++ b/javatests/dagger/hilt/android/processor/BUILD @@ -22,13 +22,13 @@ java_library( deps = [ "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib", "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib", - "//java/dagger/hilt/android/processor/internal/uninstallmodules:processor_lib", - "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib", "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib", "//java/dagger/hilt/processor/internal/definecomponent:processor_lib", + "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib", "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib", "//java/dagger/hilt/processor/internal/originatingelement:processor_lib", "//java/dagger/hilt/processor/internal/root:processor_lib", + "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib", "//java/dagger/internal/codegen:processor", "//java/dagger/internal/guava:collect", "//java/dagger/testing/compile", diff --git a/javatests/dagger/hilt/android/processor/internal/BUILD b/javatests/dagger/hilt/android/processor/internal/BUILD new file mode 100644 index 000000000..9a0a8f2a0 --- /dev/null +++ b/javatests/dagger/hilt/android/processor/internal/BUILD @@ -0,0 +1,36 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +compiler_test( + name = "GeneratorsTest", + srcs = ["GeneratorsTest.java"], + compiler_deps = [ + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android:android_entry_point", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + ], + deps = [ + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + "//javatests/dagger/hilt/android/processor:android_compilers", + ], +) diff --git a/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java new file mode 100644 index 000000000..ecfd1f6aa --- /dev/null +++ b/javatests/dagger/hilt/android/processor/internal/GeneratorsTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2020 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.hilt.android.processor.internal; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class GeneratorsTest { + + @Test + public void copyConstructorParametersCopiesExternalNullables() { + JavaFileObject baseActivity = + JavaFileObjects.forSourceLines( + "test.BaseActivity", + "package test;", + "", + "import androidx.fragment.app.FragmentActivity;", + "", + "public abstract class BaseActivity extends FragmentActivity {", + " protected BaseActivity(", + " @androidx.annotation.Nullable String supportNullable,", + " @androidx.annotation.Nullable String androidxNullable,", + " @javax.annotation.Nullable String javaxNullable) { }", + "}"); + JavaFileObject myActivity = + JavaFileObjects.forSourceLines( + "test.MyActivity", + "package test;", + "", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@AndroidEntryPoint(BaseActivity.class)", + "public class MyActivity extends Hilt_MyActivity {", + " public MyActivity(", + " String supportNullable,", + " String androidxNullable,", + " String javaxNullable) {", + " super(supportNullable, androidxNullable, javaxNullable);", + " }", + "}"); + Compilation compilation = compiler().compile(baseActivity, myActivity); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyActivity") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyActivity", + "package test;", + "", + "import androidx.annotation.Nullable;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator\")", + "abstract class Hilt_MyActivity extends BaseActivity implements", + " GeneratedComponentManagerHolder {", + " Hilt_MyActivity(", + " @Nullable String supportNullable,", + " @Nullable String androidxNullable,", + " @javax.annotation.Nullable String javaxNullable) {", + " super(supportNullable, androidxNullable, javaxNullable);", + " _initHiltInternal();", + " }", + "}")); + } + + @Test + public void copyConstructorParametersConvertsAndroidInternalNullableToExternal() { + // Relies on View(Context, AttributeSet), which has android-internal + // @android.annotation.Nullable on AttributeSet. + JavaFileObject myView = + JavaFileObjects.forSourceLines( + "test.MyView", + "package test;", + "", + "import android.content.Context;", + "import android.util.AttributeSet;", + "import android.view.View;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@AndroidEntryPoint(View.class)", + "public class MyView extends Hilt_MyView {", + " public MyView(Context context, AttributeSet attrs) {", + " super(context, attrs);", + " }", + "}"); + Compilation compilation = compiler().compile(myView); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyView") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyView", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ViewGenerator\")", + "abstract class Hilt_MyView extends View implements", + "GeneratedComponentManagerHolder {", + // The generated parameter names are copied from the base class. Since we only have + // the jar and not the source for these base classes the parameter names are missing + " Hilt_MyView(Context arg0, @Nullable AttributeSet arg1) {", + " super(arg0, arg1);", + " inject();", + " }", + "}")); + } + + @Test + public void copyTargetApiAnnotationActivity() { + JavaFileObject myActivity = + JavaFileObjects.forSourceLines( + "test.MyActivity", + "package test;", + "", + "import android.annotation.TargetApi;", + "import androidx.fragment.app.FragmentActivity;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@TargetApi(24)", + "@AndroidEntryPoint(FragmentActivity.class)", + "public class MyActivity extends Hilt_MyActivity {}"); + Compilation compilation = compiler().compile(myActivity); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyActivity") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyActivity", + " package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ActivityGenerator\")", + "@TargetApi(24)", + "abstract class Hilt_MyActivity extends FragmentActivity ", + "implements GeneratedComponentManagerHolder {", + "}")); + } + + @Test + public void copyTargetApiAnnotationOverView() { + JavaFileObject myView = + JavaFileObjects.forSourceLines( + "test.MyView", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.widget.LinearLayout;", + "import android.content.Context;", + "import android.util.AttributeSet;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@TargetApi(24)", + "@AndroidEntryPoint(LinearLayout.class)", + "public class MyView extends Hilt_MyView {", + " public MyView(Context context, AttributeSet attributeSet){", + " super(context, attributeSet);", + " }", + "", + "}"); + Compilation compilation = compiler().compile(myView); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyView") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyView", + "", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ViewGenerator\")", + "@TargetApi(24)", + "abstract class Hilt_MyView extends LinearLayout implements" + + " GeneratedComponentManagerHolder {", + "}")); + } + + @Test + public void copyTargetApiAnnotationApplication() { + JavaFileObject myApplication = + JavaFileObjects.forSourceLines( + "test.MyApplication", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@TargetApi(24)", + "@HiltAndroidApp(Application.class)", + "public class MyApplication extends Hilt_MyApplication {}"); + Compilation compilation = compiler().compile(myApplication); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyApplication") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyApplication", + " package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator\")", + "@TargetApi(24)", + "abstract class Hilt_MyApplication extends Application implements" + + " GeneratedComponentManagerHolder {}")); + } + + @Test + public void copyTargetApiAnnotationFragment() { + JavaFileObject myApplication = + JavaFileObjects.forSourceLines( + "test.MyFragment", + "package test;", + "", + "import android.annotation.TargetApi;", + "import androidx.fragment.app.Fragment;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@TargetApi(24)", + "@AndroidEntryPoint(Fragment.class)", + "public class MyFragment extends Hilt_MyFragment {}"); + Compilation compilation = compiler().compile(myApplication); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyFragment") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyFragment", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.FragmentGenerator\")", + "@TargetApi(24)", + "@SuppressWarnings(\"deprecation\")", + "abstract class Hilt_MyFragment extends Fragment implements" + + " GeneratedComponentManagerHolder {}")); + } + + @Test + public void copyTargetApiBroadcastRecieverGenerator() { + JavaFileObject myBroadcastReceiver = + JavaFileObjects.forSourceLines( + "test.MyBroadcastReceiver", + "package test;", + "", + "import android.content.BroadcastReceiver;", + "import android.annotation.TargetApi;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@TargetApi(24)", + "@AndroidEntryPoint(BroadcastReceiver.class)", + "public class MyBroadcastReceiver extends Hilt_MyBroadcastReceiver {}"); + Compilation compilation = compiler().compile(myBroadcastReceiver); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyBroadcastReceiver") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyBroadcastReceiver", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.BroadcastReceiverGenerator\")", + "@TargetApi(24)", + "abstract class Hilt_MyBroadcastReceiver extends BroadcastReceiver {}")); + } + + @Test + public void copyTargetApiServiceGenerator() { + JavaFileObject myService = + JavaFileObjects.forSourceLines( + "test.MyService", + "package test;", + "", + "import android.annotation.TargetApi;", + "import android.content.Intent;", + "import android.app.Service;", + "import android.os.IBinder;", + "import dagger.hilt.android.AndroidEntryPoint;", + "", + "@TargetApi(24)", + "@AndroidEntryPoint(Service.class)", + "public class MyService extends Hilt_MyService {", + " @Override", + " public IBinder onBind(Intent intent){", + " return null;", + " }", + "}"); + Compilation compilation = compiler().compile(myService); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/Hilt_MyService") + .containsElementsIn( + JavaFileObjects.forSourceLines( + "test.Hilt_MyService", + "package test;", + "", + "@Generated(\"dagger.hilt.android.processor.internal.androidentrypoint.ServiceGenerator\")", + "@TargetApi(24)", + "abstract class Hilt_MyService extends Service implements" + + " GeneratedComponentManagerHolder{}")); + } +} diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD index bf2ed4c8a..654b573a0 100644 --- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD +++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD @@ -40,6 +40,25 @@ compiler_test( ], ) +compiler_test( + name = "EarlyEntryPointProcessorTest", + srcs = ["EarlyEntryPointProcessorTest.java"], + compiler_deps = [ + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:early_entry_point", + "//java/dagger/hilt/android/components", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@androidsdk//:platforms/android-30/android.jar", + ], + deps = [ + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + "//javatests/dagger/hilt/android/processor:android_compilers", + ], +) + java_library( name = "InstallInModule", srcs = ["InstallInModule.java"], diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java new file mode 100644 index 000000000..3bf3a31de --- /dev/null +++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 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.hilt.android.processor.internal.aggregateddeps; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EarlyEntryPointProcessorTest { + + @Test + public void testUsedWithEntryPoint_fails() { + JavaFileObject entryPoint = + JavaFileObjects.forSourceLines( + "test.UsedWithEntryPoint", + "package test;", + "", + "import dagger.hilt.android.EarlyEntryPoint;", + "import dagger.hilt.EntryPoint;", + "import dagger.hilt.InstallIn;", + "import dagger.hilt.components.SingletonComponent;", + "", + "@EarlyEntryPoint", + "@EntryPoint", + "@InstallIn(SingletonComponent.class)", + "public interface UsedWithEntryPoint {}"); + Compilation compilation = compiler().compile(entryPoint); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "Only one of the following annotations can be used on test.UsedWithEntryPoint: " + + "[dagger.hilt.EntryPoint, dagger.hilt.android.EarlyEntryPoint]") + .inFile(entryPoint) + .onLine(11); + } + + @Test + public void testNotSingletonComponent_fails() { + JavaFileObject entryPoint = + JavaFileObjects.forSourceLines( + "test.NotSingletonComponent", + "package test;", + "", + "import dagger.hilt.android.EarlyEntryPoint;", + "import dagger.hilt.android.components.ActivityComponent;", + "import dagger.hilt.EntryPoint;", + "import dagger.hilt.InstallIn;", + "", + "@EarlyEntryPoint", + "@InstallIn(ActivityComponent.class)", + "public interface NotSingletonComponent {}"); + + Compilation compilation = compiler().compile(entryPoint); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@EarlyEntryPoint can only be installed into the SingletonComponent. " + + "Found: [dagger.hilt.android.components.ActivityComponent]") + .inFile(entryPoint) + .onLine(10); + } + + @Test + public void testThatTestInstallInCannotOriginateFromTest() { + JavaFileObject test = + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.hilt.EntryPoint;", + "import dagger.hilt.InstallIn;", + "import dagger.hilt.android.EarlyEntryPoint;", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "import dagger.hilt.components.SingletonComponent;", + "", + "@HiltAndroidTest", + "public class MyTest {", + " @EarlyEntryPoint", + " @InstallIn(SingletonComponent.class)", + " interface NestedEarlyEntryPoint {}", + "}"); + Compilation compilation = compiler().compile(test); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "@EarlyEntryPoint-annotated entry point, test.MyTest.NestedEarlyEntryPoint, cannot " + + "be nested in (or originate from) a @HiltAndroidTest-annotated class, " + + "test.MyTest.") + .inFile(test) + .onLine(13); + } +} diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD new file mode 100644 index 000000000..6fc39e435 --- /dev/null +++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD @@ -0,0 +1,36 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +compiler_test( + name = "CustomTestApplicationProcessorTest", + srcs = ["CustomTestApplicationProcessorTest.java"], + compiler_deps = [ + "//java/dagger/hilt/android/testing:custom_test_application", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/android:hilt_android_app", + "@androidsdk//:platforms/android-30/android.jar", + ], + deps = [ + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + "//javatests/dagger/hilt/android/processor:android_compilers", + ], +) diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java new file mode 100644 index 000000000..f0372ab5b --- /dev/null +++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2020 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.hilt.android.processor.internal.customtestapplication; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CustomTestApplicationProcessorTest { + + @Test + public void validBaseClass_succeeds() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.testing.CustomTestApplication;", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@CustomTestApplication(Application.class)", + "@HiltAndroidTest", + "public class HiltTest {}")); + + assertThat(compilation).succeeded(); + } + + @Test + public void incorrectBaseType_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "public class Foo {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(Foo.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication value should be an instance of android.app.Application. " + + "Found: test.Foo"); + } + + @Test + public void baseWithHiltAndroidApp_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class BaseApplication extends Hilt_BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(BaseApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. " + + "Found: test.BaseApplication"); + } + + @Test + public void superclassWithHiltAndroidApp_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class BaseApplication extends Hilt_BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.ParentApplication", + "package test;", + "", + "public class ParentApplication extends BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(ParentApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. " + + "Found: test.BaseApplication"); + } + + @Test + public void withInjectField_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject String str;", + "}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(BaseApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject fields. Found test.BaseApplication with @Inject fields [str]"); + } + + @Test + public void withSuperclassInjectField_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject String str;", + "}"), + JavaFileObjects.forSourceLines( + "test.ParentApplication", + "package test;", + "", + "public class ParentApplication extends BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(ParentApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject fields. Found test.BaseApplication with @Inject fields [str]"); + } + + @Test + public void withInjectMethod_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject String str() { return null; }", + "}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(BaseApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]"); + } + + @Test + public void withSuperclassInjectMethod_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject String str() { return null; }", + "}"), + JavaFileObjects.forSourceLines( + "test.ParentApplication", + "package test;", + "", + "public class ParentApplication extends BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(ParentApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]"); + } + + @Test + public void withInjectConstructor_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject BaseApplication() {}", + "}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(BaseApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject constructors. Found test.BaseApplication with @Inject constructors " + + "[BaseApplication()]"); + } + + @Test + public void withSuperclassInjectConstructor_fails() { + Compilation compilation = + compiler().compile( + JavaFileObjects.forSourceLines( + "test.BaseApplication", + "package test;", + "", + "import android.app.Application;", + "import javax.inject.Inject;", + "", + "public class BaseApplication extends Application {", + " @Inject BaseApplication() {}", + "}"), + JavaFileObjects.forSourceLines( + "test.ParentApplication", + "package test;", + "", + "public class ParentApplication extends BaseApplication {}"), + JavaFileObjects.forSourceLines( + "test.HiltTest", + "package test;", + "", + "import dagger.hilt.android.testing.CustomTestApplication;", + "", + "@CustomTestApplication(ParentApplication.class)", + "public class HiltTest {}")); + + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@CustomTestApplication does not support application classes (or super classes) with " + + "@Inject constructors. Found test.BaseApplication with @Inject constructors " + + "[BaseApplication()]"); + } +} diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt index 7c3e45fd7..df020ffa9 100644 --- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt +++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelGeneratorTest.kt @@ -45,7 +45,7 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -60,7 +60,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -68,23 +68,25 @@ class ViewModelGeneratorTest { public final class MyViewModel_HiltModules { private MyViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.MyViewModel") @HiltViewModelMap public abstract ViewModel binds(MyViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet @@ -123,7 +125,7 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -138,7 +140,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -146,23 +148,25 @@ class ViewModelGeneratorTest { public final class MyViewModel_HiltModules { private MyViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.MyViewModel") @HiltViewModelMap public abstract ViewModel binds(MyViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet @@ -208,7 +212,7 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -223,7 +227,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -231,23 +235,25 @@ class ViewModelGeneratorTest { public final class MyViewModel_HiltModules { private MyViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.MyViewModel") @HiltViewModelMap public abstract ViewModel binds(MyViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet @@ -294,7 +300,7 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -309,7 +315,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE; - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -317,23 +323,25 @@ class ViewModelGeneratorTest { public final class MyViewModel_HiltModules { private MyViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.MyViewModel") @HiltViewModelMap public abstract ViewModel binds(MyViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet @@ -387,7 +395,7 @@ class ViewModelGeneratorTest { val expected = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -402,7 +410,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE; - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = MyViewModel.class @@ -410,23 +418,25 @@ class ViewModelGeneratorTest { public final class MyViewModel_HiltModules { private MyViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.MyViewModel") @HiltViewModelMap public abstract ViewModel binds(MyViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet @@ -466,7 +476,7 @@ class ViewModelGeneratorTest { val expectedModule = """ package dagger.hilt.android.test; - + import androidx.lifecycle.ViewModel; import dagger.Binds; import dagger.Module; @@ -481,7 +491,7 @@ class ViewModelGeneratorTest { import dagger.multibindings.StringKey; import java.lang.String; import $GENERATED_TYPE - + $GENERATED_ANNOTATION @OriginatingElement( topLevelClass = Outer.class @@ -489,23 +499,25 @@ class ViewModelGeneratorTest { public final class Outer_InnerViewModel_HiltModules { private Outer_InnerViewModel_HiltModules() { } - + @Module @InstallIn(ViewModelComponent.class) public static abstract class BindsModule { + private BindsModule() {} + @Binds @IntoMap @StringKey("dagger.hilt.android.test.Outer${'$'}InnerViewModel") @HiltViewModelMap public abstract ViewModel binds(Outer.InnerViewModel vm); } - + @Module @InstallIn(ActivityRetainedComponent.class) public static final class KeyModule { private KeyModule() { } - + @Provides @IntoSet @HiltViewModelMap.KeySet diff --git a/javatests/dagger/hilt/android/testing/BUILD b/javatests/dagger/hilt/android/testing/BUILD new file mode 100644 index 000000000..e3367778d --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BUILD @@ -0,0 +1,162 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +package(default_visibility = ["//:src"]) + +android_local_test( + name = "BindValueTest", + srcs = ["BindValueTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "BindValueIntoMapTest", + size = "small", + srcs = ["BindValueIntoMapTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "@google_bazel_common//third_party/java/auto:value", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "BindValueIntoSetTest", + size = "small", + srcs = ["BindValueIntoSetTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "BindElementsIntoSetTest", + size = "small", + srcs = ["BindElementsIntoSetTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "//java/dagger/internal/guava:collect", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "TestRootModulesTest", + size = "small", + srcs = ["TestRootModulesTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "HiltAndroidRuleTest", + size = "small", + srcs = ["HiltAndroidRuleTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + ":HiltAndroidRuleTestApp", + "//:android_local_test_exports", + "//java/dagger/internal/guava:collect", + "//:dagger_with_compiler", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/qualifiers", + "//java/dagger/hilt/android/testing:hilt_android_rule", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_library( + name = "HiltAndroidRuleTestApp", + srcs = ["HiltAndroidRuleTestApp.java"], + deps = [ + "//java/dagger/hilt/android:hilt_android_app", + ], +) + +android_local_test( + name = "DelayComponentReadyTest", + srcs = ["DelayComponentReadyTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + "//:android_local_test_exports", + "@google_bazel_common//third_party/java/jsr330_inject", + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt:entry_point", + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:bind_value", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) diff --git a/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java b/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java new file mode 100644 index 000000000..e92565fb9 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BindElementsIntoSetTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.ImmutableSet; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Provider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class BindElementsIntoSetTest { + private static final String SET_STRING_1 = "SetString1"; + private static final String SET_STRING_2 = "SetString2"; + private static final String SET_STRING_3 = "SetString3"; + + @BindElementsIntoSet Set<String> bindElementsSet1 = ImmutableSet.of(SET_STRING_1); + + @BindElementsIntoSet Set<String> bindElementsSet2 = ImmutableSet.of(SET_STRING_2); + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindElementsIntoSetEntryPoint { + Set<String> getStringSet(); + } + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject Set<String> stringSet; + + @Inject Provider<Set<String>> providedStringSet; + + @Test + public void testMutated() throws Exception { + rule.inject(); + // basic check that initial/default values are properly injected + assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2); + // Test empty sets (something that cannot be done with @BindValueIntoSet) + bindElementsSet1 = ImmutableSet.of(); + bindElementsSet2 = ImmutableSet.of(); + assertThat(providedStringSet.get()).isEmpty(); + // Test multiple elements in set. + bindElementsSet1 = ImmutableSet.of(SET_STRING_1, SET_STRING_2, SET_STRING_3); + assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2, SET_STRING_3); + } + +} diff --git a/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt new file mode 100644 index 000000000..e0f6079ea --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt @@ -0,0 +1,59 @@ +package dagger.hilt.android.testing + +import androidx.test.core.app.ApplicationProvider.getApplicationContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.hilt.EntryPoint +import dagger.hilt.EntryPoints +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Named +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@HiltAndroidTest +@RunWith(AndroidJUnit4::class) +@Config(application = HiltTestApplication::class) +class BindValueInKotlinValTest { + + @EntryPoint + @InstallIn(SingletonComponent::class) + interface BindValueEntryPoint { + fun bindValueString1(): String + + @Named(TEST_QUALIFIER) + fun bindValueString2(): String + } + + @get:Rule + val rule = HiltAndroidRule(this) + + @BindValue + val bindValueString1 = BIND_VALUE_STRING1 + + @BindValue + @Named(TEST_QUALIFIER) + val bindValueString2 = BIND_VALUE_STRING2 + + @Test + fun testBindValueFieldIsProvided() { + assertThat(bindValueString1).isEqualTo(BIND_VALUE_STRING1) + assertThat(getBinding1()).isEqualTo(BIND_VALUE_STRING1) + assertThat(bindValueString2).isEqualTo(BIND_VALUE_STRING2) + assertThat(getBinding2()).isEqualTo(BIND_VALUE_STRING2) + } + + companion object { + private const val BIND_VALUE_STRING1 = "BIND_VALUE_STRING1" + private const val BIND_VALUE_STRING2 = "BIND_VALUE_STRING2" + private const val TEST_QUALIFIER = "TEST_QUALIFIER" + + private fun getBinding1() = + EntryPoints.get(getApplicationContext(), BindValueEntryPoint::class.java).bindValueString1() + + private fun getBinding2() = + EntryPoints.get(getApplicationContext(), BindValueEntryPoint::class.java).bindValueString2() + } +} diff --git a/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java b/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java new file mode 100644 index 000000000..588edcbb6 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BindValueIntoMapTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.MapKey; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Provider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class BindValueIntoMapTest { + private static final String KEY1 = "SOME_KEY"; + private static final String KEY2 = "SOME_OTHER_KEY"; + private static final String VALUE1 = "SOME_VALUE"; + private static final String VALUE2 = "SOME_OTHER_VALUE"; + private static final String VALUE3 = "A_THIRD_VALUE"; + + @BindValueIntoMap + @MyMapKey(KEY1) + String boundValue1 = VALUE1; + + @BindValueIntoMap + @MyMapKey(KEY2) + String boundValue2 = VALUE2; + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindValuesIntoMapEntryPoint { + Map<String, String> getStringStringMap(); + } + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject Provider<Map<String, String>> mapProvider; + + @Test + public void testInjectedAndModified() throws Exception { + rule.inject(); + Map<String, String> oldMap = mapProvider.get(); + assertThat(oldMap).containsExactly(KEY1, VALUE1, KEY2, VALUE2); + boundValue1 = VALUE3; + Map<String, String> newMap = mapProvider.get(); + assertThat(oldMap).containsExactly(KEY1, VALUE1, KEY2, VALUE2); + assertThat(newMap).containsExactly(KEY1, VALUE3, KEY2, VALUE2); + } + + @MapKey + @interface MyMapKey { + String value(); + } +} diff --git a/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java b/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java new file mode 100644 index 000000000..276a3d8af --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BindValueIntoSetTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Provider; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class BindValueIntoSetTest { + private static final String SET_STRING_1 = "SetString1"; + private static final String SET_STRING_2 = "SetString2"; + private static final String SET_STRING_3 = "SetString3"; + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindValueIntoSetEntryPoint { + Set<String> getStringSet(); + } + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @BindValueIntoSet String bindValueSetString1 = SET_STRING_1; + @BindValueIntoSet String bindValueSetString2 = SET_STRING_2; + + @Inject Set<String> stringSet; + @Inject Provider<Set<String>> providedStringSet; + + @Test + public void testMutated() throws Exception { + rule.inject(); + // basic check that initial/default values are properly injected + assertThat(providedStringSet.get()).containsExactly(SET_STRING_1, SET_STRING_2); + bindValueSetString1 = SET_STRING_3; + // change the value for bindValueSetString1 from 1 to 3 + assertThat(providedStringSet.get()).containsExactly(SET_STRING_2, SET_STRING_3); + } + + +} diff --git a/javatests/dagger/hilt/android/testing/BindValueTest.java b/javatests/dagger/hilt/android/testing/BindValueTest.java new file mode 100644 index 000000000..2e85f6b3a --- /dev/null +++ b/javatests/dagger/hilt/android/testing/BindValueTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Named; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class BindValueTest { + private static final String BIND_VALUE_STRING1 = "BIND_VALUE_STRING1"; + private static final String BIND_VALUE_STRING2 = "BIND_VALUE_STRING2"; + private static final String TEST_QUALIFIER1 = "TEST_QUALIFIER1"; + private static final String TEST_QUALIFIER2 = "TEST_QUALIFIER2"; + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface BindValueEntryPoint { + @Named(TEST_QUALIFIER1) + String bindValueString1(); + + @Named(TEST_QUALIFIER2) + String bindValueString2(); + } + + @Rule public final HiltAndroidRule rule = new HiltAndroidRule(this); + + @BindValue + @Named(TEST_QUALIFIER1) + String bindValueString1 = BIND_VALUE_STRING1; + + @BindValue + @Named(TEST_QUALIFIER2) + String bindValueString2 = BIND_VALUE_STRING2; + + @Test + public void testBindValueFieldIsProvided() throws Exception { + assertThat(bindValueString1).isEqualTo(BIND_VALUE_STRING1); + assertThat(getBinding1()).isEqualTo(BIND_VALUE_STRING1); + + assertThat(bindValueString2).isEqualTo(BIND_VALUE_STRING2); + assertThat(getBinding2()).isEqualTo(BIND_VALUE_STRING2); + } + + @Test + public void testBindValueIsMutable() throws Exception { + bindValueString1 = "newValue"; + assertThat(getBinding1()).isEqualTo("newValue"); + } + + @Test + public void testCallingComponentReadyWithoutDelayComponentReady_fails() throws Exception { + IllegalStateException expected = + assertThrows(IllegalStateException.class, rule::componentReady); + assertThat(expected) + .hasMessageThat() + .isEqualTo("Called componentReady(), even though delayComponentReady() was not used."); + } + + private static String getBinding1() { + return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString1(); + } + + private static String getBinding2() { + return EntryPoints.get(getApplicationContext(), BindValueEntryPoint.class).bindValueString2(); + } +} diff --git a/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java b/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java new file mode 100644 index 000000000..760e01803 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/DelayComponentReadyTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoint; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.junit.runners.model.Statement; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class DelayComponentReadyTest { + private static final String EXPECTED_VALUE = "expected"; + + @EntryPoint + @InstallIn(SingletonComponent.class) + public interface FooEntryPoint { + String foo(); + } + + // If true, verifies that HiltAndroidRule threw an IllegalStateException + private boolean verifyTestRuleThrew = false; + + // A test rule that wraps HiltAndroidRule to verify it throws + private final TestRule exceptionVerifyingRule = + (base, description) -> { + return new Statement() { + @Override + public void evaluate() throws Throwable { + AtomicReference<IllegalStateException> caught = new AtomicReference<>(); + try { + base.evaluate(); + } catch (IllegalStateException e) { + caught.set(e); + if (!verifyTestRuleThrew) { + throw e; + } + } + if (verifyTestRuleThrew) { + IllegalStateException expected = caught.get(); + if (expected == null) { + throw new AssertionError("Did not throw expected expection"); + } + assertThat(expected) + .hasMessageThat() + .isEqualTo("Used delayComponentReady(), but never called componentReady()"); + } + } + }; + }; + + private final HiltAndroidRule rule = new HiltAndroidRule(this).delayComponentReady(); + + @Rule public final RuleChain ruleChain = RuleChain.outerRule(exceptionVerifyingRule).around(rule); + + @BindValue String foo; + + @Test + public void testLateBindValue() throws Exception { + AtomicReference<String> fooRef = new AtomicReference<>(); + OnComponentReadyRunner.addListener( + ApplicationProvider.getApplicationContext(), + FooEntryPoint.class, + entryPoint -> fooRef.set(entryPoint.foo())); + + // Test that setting the listener before the component is ready doesn't run the listener. + assertThat(fooRef.get()).isNull(); + + foo = EXPECTED_VALUE; + rule.componentReady().inject(); + assertThat(EntryPoints.get(getApplicationContext(), FooEntryPoint.class).foo()) + .isEqualTo(EXPECTED_VALUE); + } + + @Test + public void testUnitializedBindValue_fails() throws Exception { + OnComponentReadyRunner.addListener( + ApplicationProvider.getApplicationContext(), FooEntryPoint.class, FooEntryPoint::foo); + + // foo not set + NullPointerException expected = assertThrows(NullPointerException.class, rule::componentReady); + // This is not the best error message, but it is equivalent to the message from a regular + // (non-delayed) @BindValue returning null; + assertThat(expected) + .hasMessageThat() + .isEqualTo("Cannot return null from a non-@Nullable @Provides method"); + } + + @Test + public void testDoubleComponentReady_fails() throws Exception { + foo = EXPECTED_VALUE; + rule.componentReady(); + IllegalStateException expected = + assertThrows(IllegalStateException.class, rule::componentReady); + assertThat(expected).hasMessageThat().isEqualTo("Called componentReady() multiple times"); + } + + @Test + public void testMissingComponentReady_fails() throws Exception { + // componentReady not called + foo = EXPECTED_VALUE; + IllegalStateException expected = assertThrows(IllegalStateException.class, rule::inject); + assertThat(expected) + .hasMessageThat() + .isEqualTo("Called inject() before calling componentReady()"); + } + + @Test + public void testDelayComponentReadyAfterStart_fails() throws Exception { + IllegalStateException expected = + assertThrows(IllegalStateException.class, rule::delayComponentReady); + assertThat(expected) + .hasMessageThat() + .isEqualTo("Called delayComponentReady after test execution started"); + // Prevents failure due to never calling componentReady() + foo = EXPECTED_VALUE; + rule.componentReady(); + } + + @Test + public void neverCallsComponentReady_fails() throws Exception { + // Does not call componentReady() + verifyTestRuleThrew = true; + } +} diff --git a/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java new file mode 100644 index 000000000..2f5fcd259 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.app.Application; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +public final class HiltAndroidRuleTest { + public static final class NonHiltTest {} + + @Test + @Config(application = HiltTestApplication.class) + public void testMissingHiltAndroidTest_fails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> new HiltAndroidRule(new NonHiltTest())); + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Expected dagger.hilt.android.testing.HiltAndroidRuleTest$NonHiltTest to be " + + "annotated with @HiltAndroidTest."); + + } + + @Test + @Config(application = Application.class) + public void testNonHiltTestApplication_fails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> new HiltAndroidRule(HiltAndroidRuleTest.this)); + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Hilt test, dagger.hilt.android.testing.HiltAndroidRuleTest, must use a Hilt test " + + "application but found android.app.Application. To fix, configure the test to " + + "use HiltTestApplication or a custom Hilt test application generated with " + + "@CustomTestApplication."); + + } + + @Test + @Config(application = HiltAndroidRuleTestApp.class) + public void testHiltAndroidApplication_fails() throws Exception { + IllegalStateException exception = + assertThrows( + IllegalStateException.class, + () -> new HiltAndroidRule(HiltAndroidRuleTest.this)); + assertThat(exception) + .hasMessageThat() + .isEqualTo( + "Hilt test, dagger.hilt.android.testing.HiltAndroidRuleTest, cannot use a " + + "@HiltAndroidApp application but found " + + "dagger.hilt.android.testing.HiltAndroidRuleTestApp. To fix, configure the " + + "test to use HiltTestApplication or a custom Hilt test application generated " + + "with @CustomTestApplication."); + + } +} diff --git a/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java new file mode 100644 index 000000000..9ec7d0869 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/HiltAndroidRuleTestApp.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import android.app.Application; +import dagger.hilt.android.HiltAndroidApp; + +/** A Hilt application used to test errors in {@link HiltAndroidRuleTest}. */ +@HiltAndroidApp(Application.class) +final class HiltAndroidRuleTestApp extends Hilt_HiltAndroidRuleTestApp {} diff --git a/javatests/dagger/hilt/android/testing/TestRootModulesTest.java b/javatests/dagger/hilt/android/testing/TestRootModulesTest.java new file mode 100644 index 000000000..97753ccee --- /dev/null +++ b/javatests/dagger/hilt/android/testing/TestRootModulesTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.inject.Inject; +import javax.inject.Qualifier; +import javax.inject.Singleton; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class TestRootModulesTest { + @Rule public final HiltAndroidRule rules = new HiltAndroidRule(this); + + @Retention(RUNTIME) + @Qualifier + @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) + public @interface TestQualifier { + int value(); + } + + @Inject + @TestQualifier(0) + String testString0; + + @Inject + @TestQualifier(1) + String testString1; + + @Inject + @TestQualifier(2) + String testString2; + + @Inject + @TestQualifier(3) + String testString3; + + @Inject + @TestQualifier(4) + String testString4; + + @Inject FooImpl fooImpl; + @Inject Foo foo; + + @Module + @InstallIn(SingletonComponent.class) + public final class NonStaticModuleNonStaticProvidesDefaultConstructor { + @Provides + @TestQualifier(0) + String provideString() { + return "0"; + } + + NonStaticModuleNonStaticProvidesDefaultConstructor() {} + } + + @Module + @InstallIn(SingletonComponent.class) + public final class NonStaticModuleNonStaticProvides { + @Provides + @TestQualifier(1) + String provideString() { + return "1"; + } + } + + @Module + @InstallIn(SingletonComponent.class) + public static final class StaticModuleStaticProvides { + @Provides + @TestQualifier(2) + static String provideString() { + return "2"; + } + + private StaticModuleStaticProvides() {} + } + + @Module + @InstallIn(SingletonComponent.class) + public static final class StaticModuleNonStaticProvidesDefaultConstructor { + @Provides + @TestQualifier(3) + String provideString() { + return "3"; + } + } + + @Module + @InstallIn(SingletonComponent.class) + public abstract static class AbstractModuleStaticProvides { + @Provides + @TestQualifier(4) + static String provideString() { + return "4"; + } + + private AbstractModuleStaticProvides() {} + } + + @Module + @InstallIn(SingletonComponent.class) + public abstract static class AbstractModuleBindsMethod { + @Binds + abstract Foo foo(FooImpl fooImpl); + } + + interface Foo {} + + @Singleton + static final class FooImpl implements Foo { + @Inject + FooImpl() {} + } + + @Test + public void testInjection() throws Exception { + rules.inject(); + assertEquals("0", testString0); + assertEquals("1", testString1); + assertEquals("2", testString2); + assertEquals("3", testString3); + assertEquals("4", testString4); + assertEquals(fooImpl, foo); + } +} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/BUILD b/javatests/dagger/hilt/android/testing/testinstallin/BUILD new file mode 100644 index 000000000..63d0bca95 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/BUILD @@ -0,0 +1,107 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +package(default_visibility = ["//:src"]) + +android_local_test( + name = "TestInstallInFooTest", + srcs = ["TestInstallInFooTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + ":TestInstallInModules", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "@maven//:junit_junit", + + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +android_local_test( + name = "TestInstallInBarTest", + srcs = ["TestInstallInBarTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + ":TestInstallInModules", + "//:android_local_test_exports", + "//:dagger_with_compiler", + "@maven//:junit_junit", + + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + + "@google_bazel_common//third_party/java/truth", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/android/testing:uninstall_modules", + "//java/dagger/hilt/testing:test_install_in", + ], +) + +android_local_test( + name = "TestInstallInAppTest", + srcs = ["TestInstallInAppTest.java"], + manifest_values = { + "minSdkVersion": "15", + "targetSdkVersion": "27", + }, + deps = [ + ":TestInstallInApp", + ":TestInstallInModules", + "//:android_local_test_exports", + "@maven//:junit_junit", + + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + + "@google_bazel_common//third_party/java/truth", + ], +) + +android_library( + name = "TestInstallInApp", + testonly = True, + srcs = ["TestInstallInApp.java"], + deps = [ + ":TestInstallInModules", + "//:dagger_with_compiler", + "//java/dagger/hilt/android:hilt_android_app", + ], +) + +android_library( + name = "TestInstallInModules", + testonly = True, + srcs = ["TestInstallInModules.java"], + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/android/components", + "//java/dagger/hilt/testing:test_install_in", + ], +) diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java new file mode 100644 index 000000000..0ad35df5e --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInApp.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing.testinstallin; + +import android.app.Application; +import dagger.hilt.android.HiltAndroidApp; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo; +import javax.inject.Inject; + +/** + * An application to test {@link dagger.hilt.testing.TestInstallIn} are ignored when using {@link + * HiltAndroidApp}. + * + * <p>This class is used by {@link TestInstallInAppTest}. + */ +@HiltAndroidApp(Application.class) +public class TestInstallInApp extends Hilt_TestInstallInApp { + @Inject Foo foo; + @Inject Bar bar; +} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java new file mode 100644 index 000000000..beb198c89 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInAppTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing.testinstallin; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooModule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +// Test that Foo and Bar use the global @InstallIn module +@RunWith(AndroidJUnit4.class) +@Config(application = TestInstallInApp.class) +public final class TestInstallInAppTest { + + @Test + public void testFoo() { + assertThat(getMyApplication().foo.moduleClass).isEqualTo(GlobalFooModule.class); + } + + @Test + public void testBar() { + assertThat(getMyApplication().bar.moduleClass).isEqualTo(GlobalBarModule.class); + } + + private static TestInstallInApp getMyApplication() { + return (TestInstallInApp) getApplicationContext(); + } +} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java new file mode 100644 index 000000000..8cf9de7aa --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInBarTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing.testinstallin; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.android.testing.UninstallModules; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooTestModule; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Tests that Foo uses the global {@linkplain TestInstallIn} module and that Bar uses the local + * {@linkplain InstallIn} module due to {@linkplain UninstallModules}. + */ +@UninstallModules(GlobalBarModule.class) +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class TestInstallInBarTest { + + @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Module + @InstallIn(SingletonComponent.class) + public interface LocalBarTestModule { + @Provides + static Bar provideBar() { + return new Bar(LocalBarTestModule.class); + } + } + + @Inject Foo foo; + @Inject Bar bar; + + @Test + public void testFoo() { + hiltRule.inject(); + assertThat(foo.moduleClass).isEqualTo(GlobalFooTestModule.class); + } + + @Test + public void testBar() { + hiltRule.inject(); + assertThat(bar.moduleClass).isEqualTo(LocalBarTestModule.class); + } +} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java new file mode 100644 index 000000000..8a7815700 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInFooTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing.testinstallin; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Bar; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.Foo; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalBarModule; +import dagger.hilt.android.testing.testinstallin.TestInstallInModules.GlobalFooTestModule; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +// Test that Foo uses the global @TestInstallIn module and Bar uses the global @InstallIn module. +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +@Config(application = HiltTestApplication.class) +public final class TestInstallInFooTest { + + @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Inject Foo foo; + @Inject Bar bar; + + @Test + public void testFoo() { + hiltRule.inject(); + assertThat(foo.moduleClass).isEqualTo(GlobalFooTestModule.class); + } + + @Test + public void testBar() { + hiltRule.inject(); + assertThat(bar.moduleClass).isEqualTo(GlobalBarModule.class); + } +} diff --git a/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java new file mode 100644 index 000000000..ab15d98d6 --- /dev/null +++ b/javatests/dagger/hilt/android/testing/testinstallin/TestInstallInModules.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 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.hilt.android.testing.testinstallin; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; +import dagger.hilt.testing.TestInstallIn; + +/** Modules and classes used in TestInstallInFooTest and TestInstallInBarTest. */ +final class TestInstallInModules { + private TestInstallInModules() {} + + static class Foo { + Class<?> moduleClass; + + Foo(Class<?> moduleClass) { + this.moduleClass = moduleClass; + } + } + + static class Bar { + Class<?> moduleClass; + + Bar(Class<?> moduleClass) { + this.moduleClass = moduleClass; + } + } + + @Module + @InstallIn(SingletonComponent.class) + interface GlobalFooModule { + @Provides + static Foo provideFoo() { + return new Foo(GlobalFooModule.class); + } + } + + @Module + @InstallIn(SingletonComponent.class) + interface GlobalBarModule { + @Provides + static Bar provideFoo() { + return new Bar(GlobalBarModule.class); + } + } + + @Module + @TestInstallIn(components = SingletonComponent.class, replaces = GlobalFooModule.class) + interface GlobalFooTestModule { + @Provides + static Foo provideFoo() { + return new Foo(GlobalFooTestModule.class); + } + } +} diff --git a/javatests/dagger/hilt/android/testsubpackage/BUILD b/javatests/dagger/hilt/android/testsubpackage/BUILD new file mode 100644 index 000000000..114c861a6 --- /dev/null +++ b/javatests/dagger/hilt/android/testsubpackage/BUILD @@ -0,0 +1,40 @@ +# Copyright (C) 2021 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(default_visibility = ["//:src"]) + +android_local_test( + name = "UsesLocalComponentTestBindingsTest", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//javatests/dagger/hilt/android:shared_component_test_classes", + ], +) + +android_local_test( + name = "UsesSharedComponent1Test", + manifest_values = { + "minSdkVersion": "14", + }, + deps = [ + "//javatests/dagger/hilt/android:shared_component_test_classes", + ], +) + +exports_files(srcs = [ + "UsesLocalComponentTestBindingsTest.java", + "UsesSharedComponent1Test.java", +]) diff --git a/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java b/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java new file mode 100644 index 000000000..5f1ff8fb3 --- /dev/null +++ b/javatests/dagger/hilt/android/testsubpackage/UsesLocalComponentTestBindingsTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2021 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.hilt.android.testsubpackage; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Build; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.Module; +import dagger.Provides; +import dagger.hilt.EntryPoints; +import dagger.hilt.InstallIn; +import dagger.hilt.android.UsesComponentHelper; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import dagger.hilt.components.SingletonComponent; +import javax.inject.Inject; +import javax.inject.Qualifier; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that provides its own test bindings, and therefore cannot use the shared components. + * + * <p>Note that this test class exactly matches the simple name of {@link + * dagger.hilt.android.UsesLocalComponentTestBindingsTest}. This is intentional and used to verify + * generated code class names do not clash. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesLocalComponentTestBindingsTest { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Qualifier + @interface TestQualifier {} + + @Inject @UsesComponentQualifier String injectedString; + @Inject @TestQualifier String localString; + + @Module + @InstallIn(SingletonComponent.class) + public static final class TestModule { + @Provides + @TestQualifier + static String provideString() { + return "local_string"; + } + + private TestModule() {} + } + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + assertThat(localString).isEqualTo("local_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testUsesLocalComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.perTestComponentNameWithDedupePrefix("dhat_", this)); + } +} diff --git a/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java b/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java new file mode 100644 index 000000000..e99bbe2f5 --- /dev/null +++ b/javatests/dagger/hilt/android/testsubpackage/UsesSharedComponent1Test.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 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.hilt.android.testsubpackage; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import android.os.Build; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import dagger.hilt.EntryPoints; +import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.UsesComponentHelper; +import dagger.hilt.android.UsesComponentTestClasses.Foo; +import dagger.hilt.android.UsesComponentTestClasses.FooEntryPoint; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentQualifier; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponent; +import dagger.hilt.android.UsesComponentTestClasses.UsesComponentTestSubcomponentBuilderEntryPoint; +import dagger.hilt.android.internal.testing.TestApplicationComponentManager; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.HiltTestApplication; +import javax.inject.Inject; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * A test that provides none of its own test bindings, and therefore uses the shared components. + * + * <p>Note that this test class exactly matches the simple name of {@link + * dagger.hilt.android.UsesSharedComponent1Test}. This is intentional and used to verify generated + * code class names do not clash. + */ +@HiltAndroidTest +@RunWith(AndroidJUnit4.class) +// Robolectric requires Java9 to run API 29 and above, so use API 28 instead +@Config(sdk = Build.VERSION_CODES.P, application = HiltTestApplication.class) +public final class UsesSharedComponent1Test { + + @Rule public HiltAndroidRule rule = new HiltAndroidRule(this); + + @Inject @UsesComponentQualifier String injectedString; + + @Test + public void testInject() { + rule.inject(); + assertThat(injectedString).isEqualTo("shared_string"); + } + + @Test + public void testSubcomponent() { + UsesComponentTestSubcomponent subcomponent = + EntryPoints.get( + getApplicationContext(), UsesComponentTestSubcomponentBuilderEntryPoint.class) + .mySubcomponentBuilder() + .id(123) + .build(); + + Foo foo1 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + Foo foo2 = EntryPoints.get(subcomponent, FooEntryPoint.class).foo(); + + assertThat(foo1).isNotNull(); + assertThat(foo2).isNotNull(); + assertThat(foo1).isSameInstanceAs(foo2); + } + + @Test + public void testActivity() { + try (ActivityScenario<TestActivity> scenario = ActivityScenario.launch(TestActivity.class)) { + scenario.onActivity( + activity -> { + assertThat(activity.activityString).isEqualTo("shared_string"); + }); + } + } + + @Test + public void testUsesSharedComponent() { + HiltTestApplication app = (HiltTestApplication) getApplicationContext(); + Object generatedComponent = + ((TestApplicationComponentManager) app.componentManager()).generatedComponent(); + assertThat(generatedComponent.getClass().getName()) + .isEqualTo(UsesComponentHelper.defaultComponentName()); + } + + @Test + public void testLocalComponentNotGenerated() { + assertThrows( + ClassNotFoundException.class, + () -> Class.forName(UsesComponentHelper.perTestComponentName(this))); + } + + @AndroidEntryPoint(FragmentActivity.class) + public static final class TestActivity extends Hilt_UsesSharedComponent1Test_TestActivity { + @Inject @UsesComponentQualifier String activityString; + } +} diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java index 901c7a3b0..a8c5fbe9c 100644 --- a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java +++ b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java @@ -64,7 +64,7 @@ public final class DefineComponentProcessorTest { JavaFileObject componentOutput = JavaFileObjects.forSourceLines( - "dagger.hilt.processor.internal.definecomponent.codegen.test_FooComponent", + "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponent", "package dagger.hilt.processor.internal.definecomponent.codegen;", "", "import dagger.hilt.internal.definecomponent.DefineComponentClasses;", @@ -72,11 +72,11 @@ public final class DefineComponentProcessorTest { "", "@DefineComponentClasses(component = \"test.FooComponent\")", "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", - "interface test_FooComponent {}"); + "public class _test_FooComponent {}"); JavaFileObject builderOutput = JavaFileObjects.forSourceLines( - "dagger.hilt.processor.internal.definecomponent.codegen.test_FooComponentBuilder", + "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponentBuilder", "package dagger.hilt.processor.internal.definecomponent.codegen;", "", "import dagger.hilt.internal.definecomponent.DefineComponentClasses;", @@ -84,7 +84,7 @@ public final class DefineComponentProcessorTest { "", "@DefineComponentClasses(builder = \"test.FooComponentBuilder\")", "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")", - "interface test_FooComponentBuilder {}"); + "public class _test_FooComponentBuilder {}"); Compilation compilation = compiler().compile(component, builder); assertThat(compilation).succeeded(); diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD new file mode 100644 index 000000000..3135159ce --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD @@ -0,0 +1,41 @@ +# Copyright (C) 2020 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. + +# Description: +# Tests for internal code for implementing Hilt processors. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +compiler_test( + name = "OriginatingElementProcessorTest", + srcs = ["OriginatingElementProcessorTest.java"], + compiler_deps = [ + "//java/dagger/hilt/codegen:originating_element", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + ], + deps = [ + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["**/*"]), +) diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java new file mode 100644 index 000000000..444fb1d67 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 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.hilt.processor.internal.originatingelement; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class OriginatingElementProcessorTest { + + @Test + public void originatingElementOnInnerClass_fails() { + JavaFileObject outer1 = + JavaFileObjects.forSourceLines( + "test.Outer1", + "package test;", + "", + "class Outer1 {}"); + JavaFileObject outer2 = + JavaFileObjects.forSourceLines( + "test.Outer2", + "package test;", + "", + "import dagger.hilt.codegen.OriginatingElement;", + "", + "class Outer2 {", + " @OriginatingElement(topLevelClass = Outer1.class)", + " static class Inner {}", + "}"); + Compilation compilation = compiler().compile(outer1, outer2); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "@OriginatingElement should only be used to annotate top-level types, but found: " + + "test.Outer2.Inner"); + } + + @Test + public void originatingElementValueWithInnerClass_fails() { + JavaFileObject outer1 = + JavaFileObjects.forSourceLines( + "test.Outer1", + "package test;", + "", + "class Outer1 {", + " static class Inner {}", + "}"); + JavaFileObject outer2 = + JavaFileObjects.forSourceLines( + "test.Outer2", + "package test;", + "", + "import dagger.hilt.codegen.OriginatingElement;", + "", + "@OriginatingElement(topLevelClass = Outer1.Inner.class)", + "class Outer2 {}"); + Compilation compilation = compiler().compile(outer1, outer2); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + "OriginatingElement.topLevelClass value should be a top-level class, but found: " + + "test.Outer1.Inner"); + } +} diff --git a/javatests/dagger/hilt/processor/internal/root/BUILD b/javatests/dagger/hilt/processor/internal/root/BUILD new file mode 100644 index 000000000..0dba875c7 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/BUILD @@ -0,0 +1,127 @@ +# Copyright (C) 2020 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. +# Description: +# Tests for internal code for implementing Hilt processors. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +android_library( + name = "MyAppPreviousCompilation", + srcs = ["MyAppPreviousCompilation.java"], + deps = [ + "//java/dagger/hilt/android:hilt_android_app", + ], +) + +compiler_test( + name = "MyAppPreviousCompilationTest", + srcs = ["MyAppPreviousCompilationTest.java"], + compiler_deps = [ + ":MyAppPreviousCompilation", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + ], + deps = [ + "//java/dagger/internal/guava:collect", + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +android_library( + name = "MyTestPreviousCompilation", + srcs = ["MyTestPreviousCompilation.java"], + deps = [ + "//:android_local_test_exports", + "//java/dagger/hilt/android/testing:hilt_android_test", + ], +) + +compiler_test( + name = "MyTestPreviousCompilationTest", + srcs = ["MyTestPreviousCompilationTest.java"], + compiler_deps = [ + ":MyTestPreviousCompilation", + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + ], + deps = [ + "//java/dagger/internal/guava:collect", + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +compiler_test( + name = "RootProcessorErrorsTest", + srcs = ["RootProcessorErrorsTest.java"], + compiler_deps = [ + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + ], + deps = [ + "//java/dagger/internal/guava:collect", + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +compiler_test( + name = "RootFileFormatterTest", + srcs = ["RootFileFormatterTest.java"], + compiler_deps = [ + "//java/dagger/hilt/android:hilt_android_app", + "//java/dagger/hilt/android/testing:hilt_android_test", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + "@maven//:org_robolectric_robolectric", + "@maven//:androidx_test_ext_junit", + "@maven//:androidx_test_core", + ], + deps = [ + "//java/dagger/internal/guava:base", + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java new file mode 100644 index 000000000..5830803b4 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilation.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import android.app.Application; +import dagger.hilt.android.HiltAndroidApp; + +/** Defines a {@link HiltAndroidApp} for {@link MyAppPreviousCompilationTest}. */ +public final class MyAppPreviousCompilation { + + @HiltAndroidApp(Application.class) + public static final class MyApp extends Hilt_MyAppPreviousCompilation_MyApp {} + + private MyAppPreviousCompilation() {} +} diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java new file mode 100644 index 000000000..adf6d9b2d --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static com.google.testing.compile.CompilationSubject.assertThat; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import dagger.hilt.android.processor.AndroidCompilers; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class MyAppPreviousCompilationTest { + + @Parameters(name = "{0}") + public static ImmutableCollection<Object[]> parameters() { + return ImmutableList.copyOf(new Object[][] {{true}, {false}}); + } + + private final boolean disableCrossCompilationRootValidation; + + public MyAppPreviousCompilationTest(boolean disableCrossCompilationRootValidation) { + this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation; + } + + private Compiler compiler() { + return AndroidCompilers.compiler() + .withOptions( + String.format( + "-Adagger.hilt.disableCrossCompilationRootValidation=%s", + disableCrossCompilationRootValidation)); + } + + @Test + public void testRootTest() { + JavaFileObject testRoot = + JavaFileObjects.forSourceLines( + "test.TestRoot", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@HiltAndroidTest", + "public class TestRoot {}"); + + // This test case should succeed independent of disableCrossCompilationRootValidation. + Compilation compilation = compiler().compile(testRoot); + assertThat(compilation).succeeded(); + } + + @Test + public void appRootTest() { + JavaFileObject appRoot = + JavaFileObjects.forSourceLines( + "test.AppRoot", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class AppRoot extends Hilt_AppRoot {}"); + + Compilation compilation = compiler().compile(appRoot); + if (disableCrossCompilationRootValidation) { + assertThat(compilation).succeeded(); + } else { + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "Cannot process app roots in this compilation unit since there are app roots in a " + + "previous compilation unit:" + + "\n \tApp roots in previous compilation unit: [" + + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp]" + + "\n \tApp roots in this compilation unit: [test.AppRoot]"); + } + } +} diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java new file mode 100644 index 000000000..f704df5f5 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilation.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import dagger.hilt.android.testing.HiltAndroidTest; + +/** Defines a {@link HiltAndroidTest} for {@link MyTestPreviousCompilationTest}. */ +public final class MyTestPreviousCompilation { + + @HiltAndroidTest + public static final class MyTest {} + + private MyTestPreviousCompilation() {} +} diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java new file mode 100644 index 000000000..9e9fae57e --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static com.google.testing.compile.CompilationSubject.assertThat; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import dagger.hilt.android.processor.AndroidCompilers; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class MyTestPreviousCompilationTest { + + @Parameters(name = "{0}") + public static ImmutableCollection<Object[]> parameters() { + return ImmutableList.copyOf(new Object[][] {{true}, {false}}); + } + + private final boolean disableCrossCompilationRootValidation; + + public MyTestPreviousCompilationTest(boolean disableCrossCompilationRootValidation) { + this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation; + } + + private Compiler compiler() { + return AndroidCompilers.compiler() + .withOptions( + String.format( + "-Adagger.hilt.disableCrossCompilationRootValidation=%s", + disableCrossCompilationRootValidation)); + } + + @Test + public void testRootTest() { + JavaFileObject testRoot = + JavaFileObjects.forSourceLines( + "test.TestRoot", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@HiltAndroidTest", + "public class TestRoot {}"); + + Compilation compilation = compiler().compile(testRoot); + if (disableCrossCompilationRootValidation) { + assertThat(compilation).succeeded(); + } else { + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "Cannot process new roots when there are test roots from a previous compilation unit:" + + "\n \tTest roots from previous compilation unit: " + + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]" + + "\n \tAll roots from this compilation unit: [test.TestRoot]"); + } + } + + @Test + public void appRootTest() { + JavaFileObject appRoot = + JavaFileObjects.forSourceLines( + "test.AppRoot", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class AppRoot extends Hilt_AppRoot {}"); + + Compilation compilation = compiler().compile(appRoot); + if (disableCrossCompilationRootValidation) { + assertThat(compilation).succeeded(); + } else { + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "Cannot process new roots when there are test roots from a previous compilation unit:" + + "\n \tTest roots from previous compilation unit: " + + "[dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest]" + + "\n \tAll roots from this compilation unit: [test.AppRoot]"); + } + } +} diff --git a/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java new file mode 100644 index 000000000..6d598c83f --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/RootFileFormatterTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 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.hilt.processor.internal.root; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.common.base.Joiner; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// This test makes sure we don't regress the formatting in the components file. +@RunWith(JUnit4.class) +public final class RootFileFormatterTest { + private static final Joiner JOINER = Joiner.on("\n"); + + @Test + public void testProdComponents() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.TestApplication", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class TestApplication extends Hilt_TestApplication {}"), + entryPoint("SingletonComponent", "EntryPoint1"), + entryPoint("SingletonComponent", "EntryPoint2"), + entryPoint("ActivityComponent", "EntryPoint3"), + entryPoint("ActivityComponent", "EntryPoint4")); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/TestApplication_HiltComponents") + .contentsAsUtf8String() + .contains( + JOINER.join( + " public abstract static class SingletonC implements" + + " HiltWrapper_ActivityRetainedComponentManager" + + "_ActivityRetainedComponentBuilderEntryPoint,", + " ServiceComponentManager.ServiceComponentBuilderEntryPoint,", + " SingletonComponent,", + " GeneratedComponent,", + " EntryPoint1,", + " EntryPoint2,", + " TestApplication_GeneratedInjector {")); + + assertThat(compilation) + .generatedSourceFile("test/TestApplication_HiltComponents") + .contentsAsUtf8String() + .contains( + JOINER.join( + " public abstract static class ActivityC implements ActivityComponent,", + " FragmentComponentManager.FragmentComponentBuilderEntryPoint,", + " ViewComponentManager.ViewComponentBuilderEntryPoint,", + " GeneratedComponent,", + " EntryPoint3,", + " EntryPoint4 {")); + } + + @Test + public void testTestComponents() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@HiltAndroidTest", + "public class MyTest {}"), + entryPoint("SingletonComponent", "EntryPoint1"), + entryPoint("SingletonComponent", "EntryPoint2"), + entryPoint("ActivityComponent", "EntryPoint3"), + entryPoint("ActivityComponent", "EntryPoint4")); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("test/MyTest_HiltComponents") + .contentsAsUtf8String() + .contains( + JOINER.join( + " public abstract static class SingletonC implements" + + " HiltWrapper_ActivityRetainedComponentManager" + + "_ActivityRetainedComponentBuilderEntryPoint,", + " ServiceComponentManager.ServiceComponentBuilderEntryPoint,", + " SingletonComponent,", + " TestSingletonComponent,", + " EntryPoint1,", + " EntryPoint2,", + " MyTest_GeneratedInjector {")); + + assertThat(compilation) + .generatedSourceFile("test/MyTest_HiltComponents") + .contentsAsUtf8String() + .contains( + JOINER.join( + " public abstract static class ActivityC implements ActivityComponent,", + " FragmentComponentManager.FragmentComponentBuilderEntryPoint,", + " ViewComponentManager.ViewComponentBuilderEntryPoint,", + " GeneratedComponent,", + " EntryPoint3,", + " EntryPoint4 {")); + } + + @Test + public void testSharedTestComponents() { + Compilation compilation = + compiler() + .withOptions("-Adagger.hilt.shareTestComponents=true") + .compile( + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@HiltAndroidTest", + "public class MyTest {}"), + entryPoint("SingletonComponent", "EntryPoint1")); + assertThat(compilation).succeeded(); + assertThat(compilation) + .generatedSourceFile("dagger/hilt/android/internal/testing/root/Default_HiltComponents") + .contentsAsUtf8String() + .contains( + JOINER.join( + " public abstract static class SingletonC implements" + + " HiltWrapper_ActivityRetainedComponentManager" + + "_ActivityRetainedComponentBuilderEntryPoint,", + " ServiceComponentManager.ServiceComponentBuilderEntryPoint,", + " SingletonComponent,", + " TestSingletonComponent,", + " EntryPoint1,", + " MyTest_GeneratedInjector {")); + } + + private static JavaFileObject entryPoint(String component, String name) { + return JavaFileObjects.forSourceLines( + "test." + name, + "package test;", + "", + "import dagger.hilt.EntryPoint;", + "import dagger.hilt.InstallIn;", + component.equals("SingletonComponent") ? "import dagger.hilt.components.SingletonComponent;" + : "import dagger.hilt.android.components." + component + ";", + "", + "@EntryPoint", + "@InstallIn(" + component + ".class)", + "public interface " + name + " {}"); + } +} diff --git a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java new file mode 100644 index 000000000..e9ed8c418 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 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.hilt.processor.internal.root; + +import static com.google.testing.compile.CompilationSubject.assertThat; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; +import com.google.testing.compile.JavaFileObjects; +import dagger.hilt.android.processor.AndroidCompilers; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class RootProcessorErrorsTest { + + @Parameters(name = "{0}") + public static ImmutableCollection<Object[]> parameters() { + return ImmutableList.copyOf(new Object[][] {{true}, {false}}); + } + + private final boolean disableCrossCompilationRootValidation; + + public RootProcessorErrorsTest(boolean disableCrossCompilationRootValidation) { + this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation; + } + + private Compiler compiler() { + return AndroidCompilers.compiler() + .withOptions( + String.format( + "-Adagger.hilt.disableCrossCompilationRootValidation=%s", + disableCrossCompilationRootValidation)); + } + + @Test + public void multipleAppRootsTest() { + JavaFileObject appRoot1 = + JavaFileObjects.forSourceLines( + "test.AppRoot1", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class AppRoot1 extends Hilt_AppRoot1 {}"); + + JavaFileObject appRoot2 = + JavaFileObjects.forSourceLines( + "test.AppRoot2", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class AppRoot2 extends Hilt_AppRoot2 {}"); + + // This test case should fail independent of disableCrossCompilationRootValidation. + Compilation compilation = compiler().compile(appRoot1, appRoot2); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "Cannot process multiple app roots in the same compilation unit: " + + "[test.AppRoot1, test.AppRoot2]"); + } + + @Test + public void appRootWithTestRootTest() { + JavaFileObject appRoot = + JavaFileObjects.forSourceLines( + "test.AppRoot", + "package test;", + "", + "import android.app.Application;", + "import dagger.hilt.android.HiltAndroidApp;", + "", + "@HiltAndroidApp(Application.class)", + "public class AppRoot extends Hilt_AppRoot {}"); + + JavaFileObject testRoot = + JavaFileObjects.forSourceLines( + "test.TestRoot", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "", + "@HiltAndroidTest", + "public class TestRoot {}"); + + // This test case should fail independent of disableCrossCompilationRootValidation. + Compilation compilation = compiler().compile(appRoot, testRoot); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "Cannot process test roots and app roots in the same compilation unit:" + + "\n \tApp root in this compilation unit: [test.AppRoot]" + + "\n \tTest roots in this compilation unit: [test.TestRoot]"); + } +} diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD new file mode 100644 index 000000000..caceb7ce5 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD @@ -0,0 +1,44 @@ +# Copyright (C) 2020 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. + +# Description: +# Tests for internal code for implementing Hilt processors. + +load("//java/dagger/testing/compile:macros.bzl", "compiler_test") + +package(default_visibility = ["//:src"]) + +compiler_test( + name = "UninstallModulesProcessorTest", + srcs = ["UninstallModulesProcessorTest.java"], + compiler_deps = [ + "//java/dagger/hilt/android:android_entry_point", + "//java/dagger/hilt/android/testing:hilt_android_test", + "//java/dagger/hilt/android/testing:uninstall_modules", + "//java/dagger/hilt/migration:disable_install_in_check", + "@androidsdk//:platforms/android-30/android.jar", + "@maven//:androidx_annotation_annotation", + ], + deps = [ + "//javatests/dagger/hilt/android/processor:android_compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java new file mode 100644 index 000000000..2be9ab670 --- /dev/null +++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 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.hilt.processor.internal.uninstallmodules; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.hilt.android.processor.AndroidCompilers.compiler; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UninstallModulesProcessorTest { + + @Test + public void testInvalidModuleNoInstallIn_fails() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "import dagger.hilt.android.testing.UninstallModules;", + "", + "@UninstallModules(InvalidModule.class)", + "@HiltAndroidTest", + "public class MyTest {}"), + JavaFileObjects.forSourceLines( + "test.InvalidModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.hilt.migration.DisableInstallInCheck;", + "", + "@DisableInstallInCheck", + "@Module", + "public class InvalidModule {}")); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "@UninstallModules should only include modules annotated with both @Module and " + + "@InstallIn, but found: [test.InvalidModule]."); + } + + @Test + public void testInvalidModuleNoModule_fails() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "import dagger.hilt.android.testing.UninstallModules;", + "", + "@UninstallModules(InvalidModule.class)", + "@HiltAndroidTest", + "public class MyTest {}"), + JavaFileObjects.forSourceLines( + "test.InvalidModule", + "package test;", + "", + "public class InvalidModule {", + "}")); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "@UninstallModules should only include modules annotated with both @Module and " + + "@InstallIn, but found: [test.InvalidModule]."); + } + + @Test + public void testInvalidTest_fails() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.InvalidTest", + "package test;", + "", + "import dagger.hilt.android.testing.UninstallModules;", + "", + "@UninstallModules(ValidModule.class)", + "public class InvalidTest {}"), + JavaFileObjects.forSourceLines( + "test.ValidModule", + "package test;", + "", + "import dagger.Module;", + "import dagger.hilt.InstallIn;", + "import dagger.hilt.components.SingletonComponent;", + "", + "@Module", + "@InstallIn(SingletonComponent.class)", + "public class ValidModule {}")); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + assertThat(compilation) + .hadErrorContaining( + "@UninstallModules should only be used on test classes annotated with @HiltAndroidTest," + + " but found: test.InvalidTest"); + } + + @Test + public void testInvalidTestModule_fails() { + Compilation compilation = + compiler() + .compile( + JavaFileObjects.forSourceLines( + "test.MyTest", + "package test;", + "", + "import dagger.Module;", + "import dagger.hilt.InstallIn;", + "import dagger.hilt.components.SingletonComponent;", + "import dagger.hilt.android.testing.HiltAndroidTest;", + "import dagger.hilt.android.testing.UninstallModules;", + "", + "@UninstallModules({", + " MyTest.PkgPrivateInvalidModule.class,", + " MyTest.PublicInvalidModule.class,", + "})", + "@HiltAndroidTest", + "public class MyTest {", + " @Module", + " @InstallIn(SingletonComponent.class)", + " interface PkgPrivateInvalidModule {}", + "", + " @Module", + " @InstallIn(SingletonComponent.class)", + " public interface PublicInvalidModule {}", + "}")); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(1); + // TODO(bcorso): Consider unwrapping pkg-private modules before reporting the error. + assertThat(compilation) + .hadErrorContaining( + "@UninstallModules should not contain test modules, but found: " + + "[test.MyTest.PkgPrivateInvalidModule, test.MyTest.PublicInvalidModule]"); + } +} diff --git a/javatests/dagger/hilt/testmodules/BUILD b/javatests/dagger/hilt/testmodules/BUILD new file mode 100644 index 000000000..7acd3d1ae --- /dev/null +++ b/javatests/dagger/hilt/testmodules/BUILD @@ -0,0 +1,37 @@ +# Copyright (C) 2021 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. +# +# Description: +# Builds and run tests related to AggregatedDepsProcessor. + +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") + +package(default_visibility = ["//:src"]) + +kt_android_library( + name = "testmodules", + testonly = True, + srcs = glob(["*.kt"]), + deps = [ + "//:dagger_with_compiler", + "//java/dagger/hilt:install_in", + "//java/dagger/hilt/components", + "@google_bazel_common//third_party/java/jsr330_inject", + ], +) + +filegroup( + name = "srcs_filegroup", + srcs = glob(["*"]), +) diff --git a/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt b/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt new file mode 100644 index 000000000..0a71e445b --- /dev/null +++ b/javatests/dagger/hilt/testmodules/InternalKtTestModule.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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.hilt.testmodules + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** Module for [dagger.hilt.android.InternalKtModuleTest]. */ +@Module +@InstallIn(SingletonComponent::class) +internal abstract class InternalKtTestModule private constructor() { + companion object { + @Provides + @Singleton + fun provideString(): String = "expected_string_value" + } +} + +@Module +@InstallIn(SingletonComponent::class) +internal object InternalKtObjectModule { + @Provides + fun provideString(): Int = 9 +} diff --git a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java index 2bcfedcc8..42b8f8a99 100644 --- a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java +++ b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java @@ -570,6 +570,56 @@ public class AssistedFactoryErrorsTest { } @Test + public void testProvidesAssistedBindingsAsOptional() { + JavaFileObject foo = + JavaFileObjects.forSourceLines( + "test.Foo", + "package test;", + "", + "import dagger.assisted.Assisted;", + "import dagger.assisted.AssistedInject;", + "import dagger.assisted.AssistedFactory;", + "", + "class Foo {", + " @AssistedInject Foo() {}", + "", + " @AssistedFactory", + " interface Factory {", + " Foo create();", + " }", + "}"); + + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.FooModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "interface FooModule {", + " @BindsOptionalOf Foo optionalFoo();", + "", + " @BindsOptionalOf Foo.Factory optionalFooFactory();", + "}"); + + Compilation compilation = compilerWithOptions(compilerMode.javacopts()).compile(foo, module); + assertThat(compilation).failed(); + assertThat(compilation).hadErrorCount(2); + assertThat(compilation) + .hadErrorContaining("[test.Foo] Dagger does not support providing @AssistedInject types.") + .inFile(module) + .onLine(9); + assertThat(compilation) + .hadErrorContaining( + "[test.Foo.Factory] Dagger does not support providing @AssistedFactory types.") + .inFile(module) + .onLine(11); + } + + @Test public void testInjectsProviderOfAssistedFactory() { JavaFileObject foo = JavaFileObjects.forSourceLines( diff --git a/javatests/dagger/internal/codegen/AssistedFactoryTest.java b/javatests/dagger/internal/codegen/AssistedFactoryTest.java index d75232197..cffd42eba 100644 --- a/javatests/dagger/internal/codegen/AssistedFactoryTest.java +++ b/javatests/dagger/internal/codegen/AssistedFactoryTest.java @@ -20,7 +20,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.common.collect.ImmutableCollection; import com.google.testing.compile.Compilation; @@ -96,7 +95,7 @@ public class AssistedFactoryTest { JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") - .addLines("package test;", "", GENERATED_CODE_ANNOTATIONS) + .addLines("package test;", "", GeneratedLines.generatedAnnotations()) .addLinesIn( FAST_INIT_MODE, "final class DaggerTestComponent implements TestComponent {", @@ -192,7 +191,7 @@ public class AssistedFactoryTest { JavaFileObject generatedComponent = compilerMode .javaFileBuilder("test.DaggerTestComponent") - .addLines("package test;", "", GENERATED_CODE_ANNOTATIONS) + .addLines("package test;", "", GeneratedLines.generatedAnnotations()) .addLinesIn( FAST_INIT_MODE, "final class DaggerTestComponent implements TestComponent {", diff --git a/javatests/dagger/internal/codegen/ComponentBuilderTest.java b/javatests/dagger/internal/codegen/ComponentBuilderTest.java index acf543721..c4e0e07f1 100644 --- a/javatests/dagger/internal/codegen/ComponentBuilderTest.java +++ b/javatests/dagger/internal/codegen/ComponentBuilderTest.java @@ -18,7 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; @@ -88,7 +87,7 @@ public class ComponentBuilderTest { "", "import dagger.internal.Preconditions;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private static final class Builder implements TestComponent.Builder {", " private TestModule testModule;", diff --git a/javatests/dagger/internal/codegen/ComponentCreatorTest.java b/javatests/dagger/internal/codegen/ComponentCreatorTest.java index 0a7e40eb1..3cf05ac9c 100644 --- a/javatests/dagger/internal/codegen/ComponentCreatorTest.java +++ b/javatests/dagger/internal/codegen/ComponentCreatorTest.java @@ -23,8 +23,6 @@ import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.ComponentCreatorTest.CompilerType.JAVAC; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_BUILDER; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY; import static dagger.internal.codegen.binding.ComponentCreatorKind.BUILDER; @@ -106,7 +104,7 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { "test.DaggerSimpleComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private static final class Builder implements SimpleComponent.Builder {", " @Override", @@ -159,9 +157,9 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { "test.DaggerTestComponent", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", "", @@ -357,10 +355,9 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { .addLines( "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private final Object object;", "", @@ -459,10 +456,9 @@ public class ComponentCreatorTest extends ComponentCreatorTestHelper { .addLines( "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private final Integer i;", "", diff --git a/javatests/dagger/internal/codegen/ComponentFactoryTest.java b/javatests/dagger/internal/codegen/ComponentFactoryTest.java index 0f155b982..35cbb6a67 100644 --- a/javatests/dagger/internal/codegen/ComponentFactoryTest.java +++ b/javatests/dagger/internal/codegen/ComponentFactoryTest.java @@ -18,7 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.COMPONENT_FACTORY; import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor; @@ -87,7 +86,7 @@ public class ComponentFactoryTest { "", "import dagger.internal.Preconditions;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private static final class Factory implements TestComponent.Factory {", " @Override", diff --git a/javatests/dagger/internal/codegen/ComponentProcessorTest.java b/javatests/dagger/internal/codegen/ComponentProcessorTest.java index eee6a0c3f..3e514c829 100644 --- a/javatests/dagger/internal/codegen/ComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/ComponentProcessorTest.java @@ -22,8 +22,6 @@ import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.auto.common.MoreElements; import com.google.common.base.Predicate; @@ -177,12 +175,12 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - "import dagger.Lazy;", - "import dagger.internal.DoubleCheck;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.Lazy;", + "import dagger.internal.DoubleCheck;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {") .addLinesIn( FAST_INIT_MODE, @@ -298,7 +296,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {") .addLinesIn( FAST_INIT_MODE, @@ -417,7 +415,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerOuterType_SimpleComponent", " implements OuterType.SimpleComponent {", " private DaggerOuterType_SimpleComponent() {}", @@ -498,10 +496,9 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", "", @@ -604,7 +601,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private B b() {", " return TestModule_BFactory.b(new C());", @@ -698,10 +695,9 @@ public class ComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " static final class Builder {", "", @@ -882,10 +878,9 @@ public class ComponentProcessorTest { "test.DaggerParent", "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", "", " private DaggerParent() {}", @@ -997,7 +992,7 @@ public class ComponentProcessorTest { "", "import com.google.errorprone.annotations.CanIgnoreReturnValue;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public void inject(SomeInjectedType instance) {", @@ -1053,7 +1048,7 @@ public class ComponentProcessorTest { "test.DaggerSimpleComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private Provider<SimpleComponent> simpleComponentProvider;", "", @@ -1117,7 +1112,7 @@ public class ComponentProcessorTest { "", "import com.google.errorprone.annotations.CanIgnoreReturnValue;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public SomeInjectedType createAndInject() {", @@ -1185,7 +1180,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerBComponent implements BComponent {") .addLinesIn(DEFAULT_MODE, " private Provider<A> aProvider;") .addLinesIn( @@ -1232,7 +1227,7 @@ public class ComponentProcessorTest { " }") .addLinesIn( DEFAULT_MODE, - " private static class test_AComponent_a implements Provider<A> {", + " private static final class test_AComponent_a implements Provider<A> {", " private final AComponent aComponent;", " ", " test_AComponent_a(AComponent aComponent) {", @@ -1318,7 +1313,7 @@ public class ComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final TestModule testModule;", " private final other.test.TestModule testModule2;", @@ -1440,10 +1435,9 @@ public class ComponentProcessorTest { "test.DaggerBComponent", "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerBComponent implements BComponent {", " private final AComponent aComponent;", "", @@ -1525,9 +1519,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - IMPORT_GENERATED_ANNOTATION, - "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private B b() {", " return new B(new C());", @@ -1605,9 +1597,9 @@ public class ComponentProcessorTest { "test.DaggerSimpleComponent", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private DaggerSimpleComponent() {}", "", @@ -1676,7 +1668,7 @@ public class ComponentProcessorTest { "test.DaggerSimpleComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " @Override", " public SomeInjectableType someInjectableType() {", @@ -2046,10 +2038,9 @@ public class ComponentProcessorTest { "test.DaggerParent", "package test;", "", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private DaggerParent() {", " }", @@ -2190,7 +2181,7 @@ public class ComponentProcessorTest { "test.TestModule_NonNullableStringFactory", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_NonNullableStringFactory", " implements Factory<String> {", " @Override", @@ -2210,7 +2201,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public String nonNullableString() {", @@ -2280,7 +2271,7 @@ public class ComponentProcessorTest { "test.TestModule_PrimitiveIntegerFactory", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_PrimitiveIntegerFactory", " implements Factory<Integer> {", "", @@ -2300,7 +2291,7 @@ public class ComponentProcessorTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Integer nonNullableInteger() {", @@ -2376,7 +2367,7 @@ public class ComponentProcessorTest { JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private String string() {", " return TestModule_StringFactory.string(numberProvider.get());", @@ -2446,7 +2437,7 @@ public class ComponentProcessorTest { JavaFileObjects.forSourceLines( "test.DaggerParent", "package test;", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private final class ChildImpl implements Child {", " @Override", @@ -2513,7 +2504,7 @@ public class ComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Injected injected() {", @@ -2586,7 +2577,7 @@ public class ComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public String unqualified() {", @@ -2638,9 +2629,9 @@ public class ComponentProcessorTest { "test.DaggerPublicComponent", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class DaggerPublicComponent implements PublicComponent {", " private DaggerPublicComponent() {}", "", diff --git a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java index 049ff85d7..05164a6a3 100644 --- a/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java +++ b/javatests/dagger/internal/codegen/ComponentRequirementFieldTest.java @@ -18,7 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -75,7 +74,7 @@ public class ComponentRequirementFieldTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final Integer i;", " private final List<String> list;", @@ -172,7 +171,7 @@ public class ComponentRequirementFieldTest { "import other.OtherPackageModule;", "import other.OtherPackageModule_LFactory;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final ParentModule parentModule;", " private final OtherPackageModule otherPackageModule;", @@ -244,7 +243,7 @@ public class ComponentRequirementFieldTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final Dep dep;", "", @@ -357,7 +356,7 @@ public class ComponentRequirementFieldTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final ParentModule parentModule;", "", @@ -385,7 +384,7 @@ public class ComponentRequirementFieldTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final ParentModule parentModule;", "", diff --git a/javatests/dagger/internal/codegen/ComponentShardTest.java b/javatests/dagger/internal/codegen/ComponentShardTest.java index fc59c926b..ec7f4990c 100644 --- a/javatests/dagger/internal/codegen/ComponentShardTest.java +++ b/javatests/dagger/internal/codegen/ComponentShardTest.java @@ -18,7 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import static java.util.stream.Collectors.joining; import com.google.common.collect.ImmutableList; @@ -89,7 +88,7 @@ public class ComponentShardTest { JavaFileObjects.forSourceLines( "dagger.internal.codegen.DaggerTestComponent", "package dagger.internal.codegen;", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final Shard1 shard1 = new Shard1();", "", diff --git a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java b/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java index bc729fc07..dc8b7588d 100644 --- a/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java +++ b/javatests/dagger/internal/codegen/DelegateBindingExpressionTest.java @@ -20,7 +20,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.CompilationSubject; @@ -144,7 +143,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -223,7 +222,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -299,7 +298,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -401,7 +400,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( DEFAULT_MODE, @@ -509,7 +508,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( DEFAULT_MODE, @@ -599,7 +598,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( DEFAULT_MODE, @@ -700,7 +699,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( DEFAULT_MODE, @@ -805,7 +804,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerRequestsSubtypeAsProvider", " implements RequestsSubtypeAsProvider {") .addLinesIn( @@ -897,7 +896,7 @@ public class DelegateBindingExpressionTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( DEFAULT_MODE, diff --git a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java index 6857039af..0c557c0a8 100644 --- a/javatests/dagger/internal/codegen/ElidedFactoriesTest.java +++ b/javatests/dagger/internal/codegen/ElidedFactoriesTest.java @@ -18,8 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -82,9 +80,9 @@ public class ElidedFactoriesTest { "test.DaggerSimpleComponent", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private DaggerSimpleComponent() {}", "", @@ -179,12 +177,12 @@ public class ElidedFactoriesTest { "test.DaggerSimpleComponent", "package test;", "", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.MemoizedSentinel;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.DoubleCheck;", + "import dagger.internal.MemoizedSentinel;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private volatile Object scopedType = new MemoizedSentinel();", " private volatile Provider<DependsOnScoped> dependsOnScopedProvider;", @@ -262,11 +260,11 @@ public class ElidedFactoriesTest { "test.DaggerSimpleComponent", "package test;", "", - "import dagger.internal.DoubleCheck;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.DoubleCheck;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private Provider<ScopedType> scopedTypeProvider;", " private Provider<DependsOnScoped> dependsOnScopedProvider;", @@ -373,11 +371,11 @@ public class ElidedFactoriesTest { "test.DaggerSimpleComponent", "package test;", "", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.MemoizedSentinel;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.DoubleCheck;", + "import dagger.internal.MemoizedSentinel;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private volatile Object scopedType = new MemoizedSentinel();", "", @@ -434,11 +432,11 @@ public class ElidedFactoriesTest { "test.DaggerSimpleComponent", "package test;", "", - "import dagger.internal.DoubleCheck;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.DoubleCheck;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerSimpleComponent implements SimpleComponent {", " private Provider<ScopedType> scopedTypeProvider;", "", diff --git a/javatests/dagger/internal/codegen/GeneratedLines.java b/javatests/dagger/internal/codegen/GeneratedLines.java index f9a1b7004..09b618855 100644 --- a/javatests/dagger/internal/codegen/GeneratedLines.java +++ b/javatests/dagger/internal/codegen/GeneratedLines.java @@ -17,12 +17,16 @@ package dagger.internal.codegen; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import java.util.stream.Collectors; /** * Common lines outputted during code generation. */ public final class GeneratedLines { - public static final String GENERATED_ANNOTATION = + private static final String DAGGER_GENERATED_ANNOTATION = "@DaggerGenerated"; + + private static final String GENERATED_ANNOTATION = "@Generated(" + "value = \"dagger.internal.codegen.ComponentProcessor\", " + "comments = \"https://dagger.dev\")"; @@ -30,14 +34,36 @@ public final class GeneratedLines { private static final String SUPPRESS_WARNINGS_ANNOTATION = "@SuppressWarnings({\"unchecked\", \"rawtypes\"})"; - public static final String GENERATED_CODE_ANNOTATIONS = - Joiner.on('\n').join(GENERATED_ANNOTATION, SUPPRESS_WARNINGS_ANNOTATION); + private static final String IMPORT_DAGGER_GENERATED = "import dagger.internal.DaggerGenerated;"; - public static final String IMPORT_GENERATED_ANNOTATION = + private static final String IMPORT_GENERATED_ANNOTATION = isBeforeJava9() ? "import javax.annotation.Generated;" : "import javax.annotation.processing.Generated;"; + /** Returns a {@code String} of sorted imports. Includes generated imports automatically. */ + public static String generatedImports(String... extraImports) { + return ImmutableSet.<String>builder() + .add(IMPORT_DAGGER_GENERATED) + .add(IMPORT_GENERATED_ANNOTATION) + .add(extraImports) + .build() + .stream() + .sorted() + .collect(Collectors.joining("\n")); + } + + /** Returns the annotations for a generated class. */ + public static String generatedAnnotations() { + return Joiner.on('\n') + .join(DAGGER_GENERATED_ANNOTATION, GENERATED_ANNOTATION, SUPPRESS_WARNINGS_ANNOTATION); + } + + /** Returns the annotations for a generated class without {@code SuppressWarnings}. */ + public static String generatedAnnotationsWithoutSuppressWarnings() { + return Joiner.on('\n').join(DAGGER_GENERATED_ANNOTATION, GENERATED_ANNOTATION); + } + static final String GENERATION_OPTIONS_ANNOTATION = "@GenerationOptions(fastInit = false)"; private static boolean isBeforeJava9() { diff --git a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java index 579e87a4a..3e78d6b5d 100644 --- a/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java +++ b/javatests/dagger/internal/codegen/InjectConstructorFactoryGeneratorTest.java @@ -22,8 +22,6 @@ import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; @@ -134,11 +132,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {", " private final Provider<T> tProvider;", "", @@ -183,11 +181,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B> implements", " Factory<GenericClass<A, B>> {", " private final Provider<A> aProvider;", @@ -236,10 +234,9 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<T> implements Factory<GenericClass<T>> {", " @Override", " public GenericClass<T> get() {", @@ -280,11 +277,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B>", " implements Factory<GenericClass<A, B>> {", " private final Provider<A> aProvider;", @@ -332,12 +329,12 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.internal.Factory;", - "import java.util.List;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import java.util.List;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A extends Number & Comparable<A>,", " B extends List<? extends String>,", " C extends List<? super String>>", @@ -399,13 +396,13 @@ public final class InjectConstructorFactoryGeneratorTest { "test.GenericClass_Factory", "package test;", "", - "import dagger.Lazy;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.Lazy;", + "import dagger.internal.DoubleCheck;", + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_Factory<A, B>", " implements Factory<GenericClass<A, B>> {", " private final Provider<A> aProvider;", @@ -1068,11 +1065,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.InjectConstructor_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", "", @@ -1115,11 +1112,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.AllInjections_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class AllInjections_Factory implements Factory<AllInjections> {", " private final Provider<String> sProvider;", " private final Provider<String> sProvider2;", @@ -1175,12 +1172,12 @@ public final class InjectConstructorFactoryGeneratorTest { "test.InjectConstructor_Factory", "package test;", "", - "import dagger.internal.Factory;", - "import java.util.List;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import java.util.List;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", "", @@ -1228,11 +1225,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.InjectConstructor_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", "", @@ -1284,12 +1281,12 @@ public final class InjectConstructorFactoryGeneratorTest { "test.InjectConstructor_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "import other.pkg.Outer;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;", + "import other.pkg.Outer;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", "", @@ -1343,11 +1340,11 @@ public final class InjectConstructorFactoryGeneratorTest { "test.InjectConstructor_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectConstructor_Factory ", " implements Factory<InjectConstructor> {", "", @@ -1398,10 +1395,9 @@ public final class InjectConstructorFactoryGeneratorTest { "test.SimpleType_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class SimpleType_Factory implements Factory<SimpleType> {", " @Override public SimpleType get() {", " return newInstance();", @@ -1446,10 +1442,9 @@ public final class InjectConstructorFactoryGeneratorTest { "test.OuterType_A_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class OuterType_A_Factory implements Factory<OuterType.A> {", " @Override public OuterType.A get() {", " return newInstance();", diff --git a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java index ed9dd658a..aa2a0ff6b 100644 --- a/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/MapBindingComponentProcessorTest.java @@ -19,7 +19,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -129,7 +128,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final MapModuleOne mapModuleOne;", " private final MapModuleTwo mapModuleTwo;", @@ -206,7 +205,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<Handler> provideAdminHandlerProvider;", " private Provider<Handler> provideLoginHandlerProvider;", @@ -357,7 +356,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<Map<Class<?>, Integer>> mapOfClassOfAndIntegerProvider;", "", @@ -443,7 +442,7 @@ public class MapBindingComponentProcessorTest { "mapkeys.MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey", "package mapkeys;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class MapModule_ComplexKeyWithInaccessibleAnnotationValueMapKey {", " public static MapKeys.ComplexKey create() {", " return MapKeys_ComplexKeyCreator.createComplexKey(", @@ -459,7 +458,7 @@ public class MapBindingComponentProcessorTest { "mapkeys.MapModule_ClassKeyMapKey", "package mapkeys;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class MapModule_ClassKeyMapKey {", " public static Class<?> create() {", " return MapKeys.Inaccessible.class;", @@ -536,7 +535,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final MapModuleOne mapModuleOne;", " private final MapModuleTwo mapModuleTwo;", @@ -613,7 +612,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<Handler> provideAdminHandlerProvider;", " private Provider<Handler> provideLoginHandlerProvider;", @@ -736,7 +735,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final MapModuleOne mapModuleOne;", " private final MapModuleTwo mapModuleTwo;", @@ -822,7 +821,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<Handler> provideAdminHandlerProvider;", " private Provider<Handler> provideLoginHandlerProvider;", @@ -949,7 +948,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final MapModuleOne mapModuleOne;", " private final MapModuleTwo mapModuleTwo;", @@ -1000,7 +999,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<Handler> provideAdminHandlerProvider;", " private Provider<Handler> provideLoginHandlerProvider;", @@ -1077,7 +1076,7 @@ public class MapBindingComponentProcessorTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final MapModule mapModule;", "", diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java b/javatests/dagger/internal/codegen/MapBindingExpressionTest.java index 3f33bd569..9acc20d5a 100644 --- a/javatests/dagger/internal/codegen/MapBindingExpressionTest.java +++ b/javatests/dagger/internal/codegen/MapBindingExpressionTest.java @@ -21,7 +21,6 @@ import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; @@ -92,7 +91,7 @@ public class MapBindingExpressionTest { "", "import dagger.internal.MapBuilder;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -274,7 +273,7 @@ public class MapBindingExpressionTest { "import other.UsesInaccessible;", "import other.UsesInaccessible_Factory;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public UsesInaccessible usesInaccessible() {", @@ -337,7 +336,7 @@ public class MapBindingExpressionTest { "test.DaggerParent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private final ParentModule parentModule;", "", diff --git a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java b/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java index 69ab5a799..30b05190a 100644 --- a/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java +++ b/javatests/dagger/internal/codegen/MapBindingExpressionWithGuavaTest.java @@ -20,7 +20,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -127,7 +126,7 @@ public class MapBindingExpressionWithGuavaTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -401,7 +400,7 @@ public class MapBindingExpressionWithGuavaTest { "import other.UsesInaccessible;", "import other.UsesInaccessible_Factory;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public UsesInaccessible usesInaccessible() {", @@ -463,7 +462,7 @@ public class MapBindingExpressionWithGuavaTest { "test.DaggerParent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private final ParentModule parentModule;", "", @@ -519,7 +518,7 @@ public class MapBindingExpressionWithGuavaTest { "", "import dagger.producers.internal.CancellationListener;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent, " + "CancellationListener {", " @Override", diff --git a/javatests/dagger/internal/codegen/MapKeyProcessorTest.java b/javatests/dagger/internal/codegen/MapKeyProcessorTest.java index ffdb2bc18..85f5a5c64 100644 --- a/javatests/dagger/internal/codegen/MapKeyProcessorTest.java +++ b/javatests/dagger/internal/codegen/MapKeyProcessorTest.java @@ -18,8 +18,6 @@ package dagger.internal.codegen; import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.auto.value.processor.AutoAnnotationProcessor; import com.google.common.collect.ImmutableList; @@ -70,10 +68,9 @@ public class MapKeyProcessorTest { "test.PathKeyCreator", "package test;", "", - "import com.google.auto.value.AutoAnnotation;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import com.google.auto.value.AutoAnnotation;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class PathKeyCreator {", " private PathKeyCreator() {}", "", @@ -119,10 +116,9 @@ public class MapKeyProcessorTest { "test.Container_PathKeyCreator", "package test;", "", - "import com.google.auto.value.AutoAnnotation;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import com.google.auto.value.AutoAnnotation;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class Container_PathKeyCreator {", " private Container_PathKeyCreator() {}", "", diff --git a/javatests/dagger/internal/codegen/MembersInjectionTest.java b/javatests/dagger/internal/codegen/MembersInjectionTest.java index ef69c712d..c44277922 100644 --- a/javatests/dagger/internal/codegen/MembersInjectionTest.java +++ b/javatests/dagger/internal/codegen/MembersInjectionTest.java @@ -23,8 +23,6 @@ import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import static javax.tools.StandardLocation.CLASS_OUTPUT; import com.google.common.base.Joiner; @@ -87,7 +85,7 @@ public class MembersInjectionTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Child child() {", @@ -144,9 +142,10 @@ public class MembersInjectionTest { "test.DaggerTestComponent", "package test;", "", - "import com.google.errorprone.annotations.CanIgnoreReturnValue;", + GeneratedLines.generatedImports( + "import com.google.errorprone.annotations.CanIgnoreReturnValue;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Child child() {", @@ -187,12 +186,12 @@ public class MembersInjectionTest { "test.GenericClass_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class GenericClass_MembersInjector<A, B>", " implements MembersInjector<GenericClass<A, B>> {", " private final Provider<A> aProvider;", @@ -277,12 +276,12 @@ public class MembersInjectionTest { "test.Child_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class Child_MembersInjector<T>", " implements MembersInjector<Child<T>> {", " private final Provider<T> xProvider;", @@ -360,14 +359,14 @@ public class MembersInjectionTest { "test.FieldInjection_MembersInjector", "package test;", "", - "import dagger.Lazy;", - "import dagger.MembersInjector;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.Lazy;", + "import dagger.MembersInjector;", + "import dagger.internal.DoubleCheck;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class FieldInjection_MembersInjector", " implements MembersInjector<FieldInjection> {", " private final Provider<String> stringProvider;", @@ -442,13 +441,13 @@ public class MembersInjectionTest { "test.FieldInjectionWithQualifier_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Named;", - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Named;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class FieldInjectionWithQualifier_MembersInjector", " implements MembersInjector<FieldInjectionWithQualifier> {", " private final Provider<String> aProvider;", @@ -511,13 +510,13 @@ public class MembersInjectionTest { "test.MethodInjection_MembersInjector", "package test;", "", - "import dagger.Lazy;", - "import dagger.MembersInjector;", - "import dagger.internal.DoubleCheck;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.Lazy;", + "import dagger.MembersInjector;", + "import dagger.internal.DoubleCheck;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class MethodInjection_MembersInjector", " implements MembersInjector<MethodInjection> {", " private final Provider<String> stringProvider;", @@ -601,12 +600,12 @@ public class MembersInjectionTest { "test.MixedMemberInjection_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class MixedMemberInjection_MembersInjector", " implements MembersInjector<MixedMemberInjection> {", " private final Provider<String> stringProvider;", @@ -684,12 +683,12 @@ public class MembersInjectionTest { "test.AllInjections_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class AllInjections_MembersInjector ", " implements MembersInjector<AllInjections> {", " private final Provider<String> sProvider;", @@ -751,12 +750,12 @@ public class MembersInjectionTest { "test.AllInjections_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class B_MembersInjector implements MembersInjector<B> {", " private final Provider<String> sProvider;", "", @@ -813,12 +812,12 @@ public class MembersInjectionTest { "test.OuterType_B_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class OuterType_B_MembersInjector", " implements MembersInjector<OuterType.B> {", " private final Provider<OuterType.A> aProvider;", @@ -879,12 +878,12 @@ public class MembersInjectionTest { "test.OuterType_B_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class OuterType_B_MembersInjector", " implements MembersInjector<OuterType.B> {", " private final Provider<OuterType.A> aProvider;", @@ -1055,12 +1054,12 @@ public class MembersInjectionTest { "test.Child_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class Child_MembersInjector implements MembersInjector<Child> {", " private final Provider<Foo> objectProvider;", " private final Provider<Bar> objectProvider2;", @@ -1253,12 +1252,12 @@ public class MembersInjectionTest { "test.InjectedType_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectedType_MembersInjector ", " implements MembersInjector<InjectedType> {", " private final Provider<Integer> primitiveIntProvider;", @@ -1295,11 +1294,11 @@ public class MembersInjectionTest { "test.InjectedType_Factory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class InjectedType_Factory implements Factory<InjectedType> {", " private final Provider<Integer> primitiveIntProvider;", "", @@ -1398,12 +1397,12 @@ public class MembersInjectionTest { "other.Inaccessible_MembersInjector", "package other;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class Inaccessible_MembersInjector", " implements MembersInjector<Inaccessible> {", " private final Provider<Foo> fooProvider;", @@ -1439,15 +1438,15 @@ public class MembersInjectionTest { "test.DaggerTestComponent", "package test;", "", - "import com.google.errorprone.annotations.CanIgnoreReturnValue;", - IMPORT_GENERATED_ANNOTATION, - "import other.Foo_Factory;", - "import other.Inaccessible_Factory;", - "import other.Inaccessible_MembersInjector;", - "import other.UsesInaccessible;", - "import other.UsesInaccessible_Factory;", + GeneratedLines.generatedImports( + "import com.google.errorprone.annotations.CanIgnoreReturnValue;", + "import other.Foo_Factory;", + "import other.Inaccessible_Factory;", + "import other.Inaccessible_MembersInjector;", + "import other.UsesInaccessible;", + "import other.UsesInaccessible_Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Object inaccessible() {", " return injectInaccessible(Inaccessible_Factory.newInstance());", @@ -1538,14 +1537,15 @@ public class MembersInjectionTest { .addLines( "package test;", "", - "import com.google.errorprone.annotations.CanIgnoreReturnValue;", - "import other.InaccessiblesModule;", - "import other.InaccessiblesModule_InaccessiblesFactory;", - "import other.UsesInaccessibles;", - "import other.UsesInaccessibles_Factory;", - "import other.UsesInaccessibles_MembersInjector;", + GeneratedLines.generatedImports( + "import com.google.errorprone.annotations.CanIgnoreReturnValue;", + "import other.InaccessiblesModule;", + "import other.InaccessiblesModule_InaccessiblesFactory;", + "import other.UsesInaccessibles;", + "import other.UsesInaccessibles_Factory;", + "import other.UsesInaccessibles_MembersInjector;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -1666,15 +1666,16 @@ public class MembersInjectionTest { "test.DaggerTestComponent", "package test;", "", - "import com.google.errorprone.annotations.CanIgnoreReturnValue;", - "import other.Foo_Factory;", - "import other.InjectsSubtype;", - "import other.InjectsSubtype_Factory;", - "import other.Subtype_Factory;", - "import other.Supertype;", - "import other.Supertype_MembersInjector;", - "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedImports( + "import com.google.errorprone.annotations.CanIgnoreReturnValue;", + "import other.Foo_Factory;", + "import other.InjectsSubtype;", + "import other.InjectsSubtype_Factory;", + "import other.Subtype_Factory;", + "import other.Supertype;", + "import other.Supertype_MembersInjector;"), + "", + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Object subtype() {", " return injectSubtype(Subtype_Factory.newInstance());", @@ -1734,12 +1735,12 @@ public class MembersInjectionTest { "test.A_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class A_MembersInjector implements MembersInjector<A> {", " private final Provider<String> valueCProvider;", " private final Provider<String> valueAProvider;", @@ -1772,12 +1773,12 @@ public class MembersInjectionTest { "test.C_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class C_MembersInjector implements MembersInjector<C> {", " private final Provider<String> valueCProvider;", "", @@ -1852,11 +1853,11 @@ public class MembersInjectionTest { "test.A_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class A_MembersInjector implements MembersInjector<A> {", " private final Provider<String> valueBProvider;", "", @@ -1879,12 +1880,12 @@ public class MembersInjectionTest { "test.B_MembersInjector", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.InjectedFieldSignature;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.InjectedFieldSignature;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class B_MembersInjector implements MembersInjector<B> {", " private final Provider<String> valueBProvider;", "", diff --git a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java index cbda1b8a5..84c7be4b8 100644 --- a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java +++ b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java @@ -24,8 +24,6 @@ import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatModuleMethod; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; @@ -224,11 +222,11 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", "", @@ -272,10 +270,9 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", "", @@ -318,10 +315,9 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - "import dagger.internal.Factory;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports("import dagger.internal.Factory;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", "", @@ -388,14 +384,14 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideObjectsFactory", "package test;", "", - "import dagger.MembersInjector;", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - "import java.util.List;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.MembersInjector;", + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import java.util.List;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideObjectsFactory", " implements Factory<List<Object>> {", " private final TestModule module;", @@ -461,11 +457,11 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringFactory implements Factory<String> {", " private final TestModule module;", "", @@ -513,12 +509,12 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideWildcardListFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - "import java.util.List;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import java.util.List;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideWildcardListFactory implements " + "Factory<List<List<?>>> {", " private final TestModule module;", @@ -566,12 +562,12 @@ public class ModuleFactoryGeneratorTest { "TestModule_ProvideStringsFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - "import java.util.Set;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import java.util.Set;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_ProvideStringsFactory implements Factory<Set<String>> {", " private final TestModule module;", "", @@ -877,13 +873,13 @@ public class ModuleFactoryGeneratorTest { "test.ParentModule_ProvideListBFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - "import java.util.List;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import java.util.List;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideListBFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<List<B>> {", " private final ParentModule<A, B, C> module;", @@ -916,12 +912,12 @@ public class ModuleFactoryGeneratorTest { "test.ParentModule_ProvideBElementFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideBElementFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<B> {", " private final ParentModule<A, B, C> module;", @@ -955,12 +951,12 @@ public class ModuleFactoryGeneratorTest { "test.ParentModule_ProvideBEntryFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParentModule_ProvideBEntryFactory<A extends CharSequence,", " B, C extends Number & Comparable<C>> implements Factory<B>> {", " private final ParentModule<A, B, C> module;", @@ -994,11 +990,11 @@ public class ModuleFactoryGeneratorTest { "test.ChildNumberModule_ProvideNumberFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ChildNumberModule_ProvideNumberFactory", " implements Factory<Number> {", " private final ChildNumberModule module;", @@ -1026,11 +1022,11 @@ public class ModuleFactoryGeneratorTest { "test.ChildIntegerModule_ProvideIntegerFactory", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ChildIntegerModule_ProvideIntegerFactory", " implements Factory<Integer> {", " private final ChildIntegerModule module;", @@ -1099,12 +1095,12 @@ public class ModuleFactoryGeneratorTest { "test.ParameterizedModule_ProvideMapStringNumberFactory;", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - "import java.util.Map;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import java.util.Map;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideMapStringNumberFactory", " implements Factory<Map<String, Number>> {", " @Override", @@ -1132,11 +1128,11 @@ public class ModuleFactoryGeneratorTest { "test.ParameterizedModule_ProvideNonGenericTypeFactory;", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideNonGenericTypeFactory", " implements Factory<Object> {", " @Override", @@ -1164,12 +1160,12 @@ public class ModuleFactoryGeneratorTest { "test.ParameterizedModule_ProvideNonGenericTypeWithDepsFactory;", "package test;", "", - "import dagger.internal.Factory;", - "import dagger.internal.Preconditions;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import dagger.internal.Factory;", + "import dagger.internal.Preconditions;", + "import javax.inject.Provider;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class ParameterizedModule_ProvideNonGenericTypeWithDepsFactory", " implements Factory<String> {", " private final Provider<Object> oProvider;", @@ -1406,7 +1402,7 @@ public class ModuleFactoryGeneratorTest { "test.TestModule_GetFactory", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_GetFactory implements Factory<Integer> {", " @Override", " public Integer get() {", @@ -1429,7 +1425,7 @@ public class ModuleFactoryGeneratorTest { "test.TestModule_CreateFactory", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "public final class TestModule_CreateFactory implements Factory<Boolean> {", " @Override", " public Boolean get() {", diff --git a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java index 60b6898c1..9b4353042 100644 --- a/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/OptionalBindingRequestFulfillmentTest.java @@ -20,7 +20,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -110,7 +109,7 @@ public class OptionalBindingRequestFulfillmentTest { "", "import com.google.common.base.Optional;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {") .addLinesIn( FAST_INIT_MODE, @@ -250,7 +249,7 @@ public class OptionalBindingRequestFulfillmentTest { "import com.google.common.base.Optional;", "import dagger.producers.internal.CancellationListener;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent, CancellationListener {", " @Override", " public ListenableFuture<Optional<Maybe>> maybe() {", diff --git a/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java index d41bc2d17..8e9bb8a47 100644 --- a/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java +++ b/javatests/dagger/internal/codegen/ProducerModuleFactoryGeneratorTest.java @@ -23,8 +23,6 @@ import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static dagger.internal.codegen.Compilers.daggerCompiler; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatMethodInUnannotatedClass; import static dagger.internal.codegen.DaggerModuleMethodSubject.Factory.assertThatProductionModuleMethod; -import static dagger.internal.codegen.GeneratedLines.GENERATED_ANNOTATION; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.google.common.util.concurrent.ListenableFuture; @@ -361,16 +359,16 @@ public class ProducerModuleFactoryGeneratorTest { "TestModule_ProduceStringFactory", "package test;", "", - "import com.google.common.util.concurrent.Futures;", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.producers.internal.AbstractProducesMethodProducer;", - "import dagger.producers.monitoring.ProducerToken;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import com.google.common.util.concurrent.Futures;", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.producers.internal.AbstractProducesMethodProducer;", + "import dagger.producers.monitoring.ProducerToken;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "import java.util.concurrent.Executor;", + "import javax.inject.Provider;"), "", - GENERATED_ANNOTATION, + GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(), "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})", "public final class TestModule_ProduceStringFactory", " extends AbstractProducesMethodProducer<Void, String> {", @@ -434,16 +432,16 @@ public class ProducerModuleFactoryGeneratorTest { "TestModule_ProduceStringFactory", "package test;", "", - "import com.google.common.util.concurrent.Futures;", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.producers.internal.AbstractProducesMethodProducer;", - "import dagger.producers.monitoring.ProducerToken;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", + GeneratedLines.generatedImports( + "import com.google.common.util.concurrent.Futures;", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.producers.internal.AbstractProducesMethodProducer;", + "import dagger.producers.monitoring.ProducerToken;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "import java.util.concurrent.Executor;", + "import javax.inject.Provider;"), "", - GENERATED_ANNOTATION, + GeneratedLines.generatedAnnotationsWithoutSuppressWarnings(), "@SuppressWarnings({\"FutureReturnValueIgnored\", \"unchecked\", \"rawtypes\"})", "public final class TestModule_ProduceStringFactory", " extends AbstractProducesMethodProducer<Void, String> {", diff --git a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java index 797fe2ecb..4043e768f 100644 --- a/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java +++ b/javatests/dagger/internal/codegen/ProductionComponentProcessorTest.java @@ -21,8 +21,6 @@ import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -234,21 +232,21 @@ public class ProductionComponentProcessorTest { "test.DaggerTestClass_SimpleComponent", "package test;", "", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.InstanceFactory;", - "import dagger.internal.MemoizedSentinel;", - "import dagger.internal.Preconditions;", - "import dagger.internal.SetFactory;", - "import dagger.producers.Producer;", - "import dagger.producers.internal.CancellationListener;", - "import dagger.producers.internal.Producers;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedImports( + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.internal.DoubleCheck;", + "import dagger.internal.InstanceFactory;", + "import dagger.internal.MemoizedSentinel;", + "import dagger.internal.Preconditions;", + "import dagger.internal.SetFactory;", + "import dagger.producers.Producer;", + "import dagger.producers.internal.CancellationListener;", + "import dagger.producers.internal.Producers;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "import java.util.concurrent.Executor;", + "import javax.inject.Provider;"), + "", + GeneratedLines.generatedAnnotations(), "final class DaggerTestClass_SimpleComponent", " implements TestClass.SimpleComponent, CancellationListener {", " private final TestClass.BModule bModule;", @@ -430,20 +428,20 @@ public class ProductionComponentProcessorTest { "test.DaggerTestClass_SimpleComponent", "package test;", "", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.internal.DoubleCheck;", - "import dagger.internal.InstanceFactory;", - "import dagger.internal.Preconditions;", - "import dagger.internal.SetFactory;", - "import dagger.producers.Producer;", - "import dagger.producers.internal.CancellationListener;", - "import dagger.producers.internal.Producers;", - "import dagger.producers.monitoring.ProductionComponentMonitor;", - "import java.util.concurrent.Executor;", - IMPORT_GENERATED_ANNOTATION, - "import javax.inject.Provider;", - "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedImports( + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.internal.DoubleCheck;", + "import dagger.internal.InstanceFactory;", + "import dagger.internal.Preconditions;", + "import dagger.internal.SetFactory;", + "import dagger.producers.Producer;", + "import dagger.producers.internal.CancellationListener;", + "import dagger.producers.internal.Producers;", + "import dagger.producers.monitoring.ProductionComponentMonitor;", + "import java.util.concurrent.Executor;", + "import javax.inject.Provider;"), + "", + GeneratedLines.generatedAnnotations(), "final class DaggerTestClass_SimpleComponent", " implements TestClass.SimpleComponent, CancellationListener {", " private Producer<TestClass.A> aEntryPoint;", @@ -650,7 +648,7 @@ public class ProductionComponentProcessorTest { new JavaFileBuilder(compilerMode, "test.DaggerRoot") .addLines( "package test;", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent, CancellationListener {", " private final class ChildImpl implements Child, CancellationListener {", " @Override", diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java index e143370dc..a343a601d 100644 --- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentTest.java @@ -19,8 +19,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.CLASS_PATH_WITHOUT_GUAVA_OPTION; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.testing.compile.Compilation; import com.google.testing.compile.Compiler; @@ -96,7 +94,7 @@ public class SetBindingRequestFulfillmentTest { "", "import dagger.internal.SetBuilder;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Set<String> strings() {", @@ -191,7 +189,7 @@ public class SetBindingRequestFulfillmentTest { "import other.UsesInaccessible;", "import other.UsesInaccessible_Factory;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Set setOfInaccessible2() {", " return SetBuilder.newSetBuilder(1)", @@ -261,12 +259,12 @@ public class SetBindingRequestFulfillmentTest { "test.DaggerParent", "package test;", "", - "import dagger.internal.Preconditions;", - "import java.util.Collections;", - "import java.util.Set;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import dagger.internal.Preconditions;", + "import java.util.Collections;", + "import java.util.Set;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private DaggerParent() {}", "", diff --git a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java index 2e3686fd5..a08113023 100644 --- a/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java +++ b/javatests/dagger/internal/codegen/SetBindingRequestFulfillmentWithGuavaTest.java @@ -18,8 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.Compilers.compilerWithOptions; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -99,7 +97,7 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "", "import com.google.common.collect.ImmutableSet;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Set<String> strings() {", @@ -202,7 +200,7 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "import other.UsesInaccessible;", "import other.UsesInaccessible_Factory;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Set setOfInaccessible2() {", " return ImmutableSet.copyOf(TestModule_EmptySetFactory.emptySet());", @@ -272,7 +270,7 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "", "import com.google.common.collect.ImmutableSet;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParent implements Parent {", " private final class ChildImpl implements Child {", " @Override", @@ -324,14 +322,14 @@ public class SetBindingRequestFulfillmentWithGuavaTest { "test.DaggerTestComponent", "package test;", "", - "import com.google.common.collect.ImmutableSet;", - "import com.google.common.util.concurrent.Futures;", - "import com.google.common.util.concurrent.ListenableFuture;", - "import dagger.producers.internal.CancellationListener;", - "import java.util.Set;", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports( + "import com.google.common.collect.ImmutableSet;", + "import com.google.common.util.concurrent.Futures;", + "import com.google.common.util.concurrent.ListenableFuture;", + "import dagger.producers.internal.CancellationListener;", + "import java.util.Set;"), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent, " + "CancellationListener {", " private DaggerTestComponent() {}", diff --git a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java index ea0b4cc19..32ae245dd 100644 --- a/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentCreatorRequestFulfillmentTest.java @@ -21,8 +21,6 @@ import static com.google.common.collect.Sets.immutableEnumSet; import static com.google.testing.compile.CompilationSubject.assertThat; import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_BUILDER; import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.SUBCOMPONENT_FACTORY; @@ -99,9 +97,9 @@ public class SubcomponentCreatorRequestFulfillmentTest extends ComponentCreatorT "test.DaggerC", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerC implements C {", " @Override", " public Sub.Builder sBuilder() {", diff --git a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java index 19a27ff85..c63522e88 100644 --- a/javatests/dagger/internal/codegen/SubcomponentValidationTest.java +++ b/javatests/dagger/internal/codegen/SubcomponentValidationTest.java @@ -21,8 +21,6 @@ import static dagger.internal.codegen.CompilerMode.DEFAULT_MODE; import static dagger.internal.codegen.CompilerMode.FAST_INIT_MODE; import static dagger.internal.codegen.Compilers.compilerWithOptions; import static dagger.internal.codegen.Compilers.daggerCompiler; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; -import static dagger.internal.codegen.GeneratedLines.IMPORT_GENERATED_ANNOTATION; import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; @@ -457,7 +455,7 @@ public class SubcomponentValidationTest { .addLines( "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParentComponent implements ParentComponent {") .addLinesIn( DEFAULT_MODE, @@ -656,7 +654,7 @@ public class SubcomponentValidationTest { "", "import test.subpackage.Sub;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Foo.Sub newInstanceSubcomponent() {", @@ -683,10 +681,10 @@ public class SubcomponentValidationTest { " private final class B_SubImpl implements Bar.Sub {", " @Override", " public Sub newSubcomponentInSubpackage() {", - " return new ts_SubImpl();", + " return new ts_SubI();", " }", "", - " private final class ts_SubImpl implements Sub {}", + " private final class ts_SubI implements Sub {}", " }", " }", "", @@ -738,7 +736,7 @@ public class SubcomponentValidationTest { "test.DaggerParentComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Sub newSubcomponent() {", @@ -803,7 +801,7 @@ public class SubcomponentValidationTest { JavaFileObjects.forSourceLines( "DaggerParentComponent", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParentComponent implements ParentComponent {", " @Override", " public Sub newSubcomponent() {", @@ -880,7 +878,7 @@ public class SubcomponentValidationTest { "", "import top1.a.b.c.d.E;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerParentComponent implements ParentComponent {", " @Override", " public E.F.Sub top1() {", @@ -937,7 +935,7 @@ public class SubcomponentValidationTest { "test.DaggerC", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerC implements C {", " @Override", " public Foo.C newInstanceC() {", @@ -996,9 +994,9 @@ public class SubcomponentValidationTest { "test.DaggerC", "package test;", "", - IMPORT_GENERATED_ANNOTATION, + GeneratedLines.generatedImports(), "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerC implements C {", " @Override", " public C.Foo.Sub.Builder fooBuilder() {", diff --git a/javatests/dagger/internal/codegen/SwitchingProviderTest.java b/javatests/dagger/internal/codegen/SwitchingProviderTest.java index 593ad497f..615b09dbf 100644 --- a/javatests/dagger/internal/codegen/SwitchingProviderTest.java +++ b/javatests/dagger/internal/codegen/SwitchingProviderTest.java @@ -18,7 +18,6 @@ package dagger.internal.codegen; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; -import static dagger.internal.codegen.GeneratedLines.GENERATED_CODE_ANNOTATIONS; import com.google.common.collect.ImmutableList; import com.google.testing.compile.Compilation; @@ -68,7 +67,7 @@ public class SwitchingProviderTest { JavaFileObjects.forSourceLines( "test.DaggerTestComponent", "package test;", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private final class SwitchingProvider<T> implements Provider<T> {", " @SuppressWarnings(\"unchecked\")", @@ -248,7 +247,7 @@ public class SwitchingProviderTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private volatile Provider<String> sProvider;", "", @@ -333,7 +332,7 @@ public class SwitchingProviderTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private volatile Object charSequence = new MemoizedSentinel();", " private volatile Provider<CharSequence> cProvider;", @@ -424,7 +423,7 @@ public class SwitchingProviderTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @Override", " public Provider<Set<String>> setProvider() {", @@ -469,7 +468,7 @@ public class SwitchingProviderTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " private Provider<MembersInjector<Foo>> fooMembersInjectorProvider;", "", @@ -540,7 +539,7 @@ public class SwitchingProviderTest { "test.DaggerTestComponent", "package test;", "", - GENERATED_CODE_ANNOTATIONS, + GeneratedLines.generatedAnnotations(), "final class DaggerTestComponent implements TestComponent {", " @SuppressWarnings(\"rawtypes\")", " private static final Provider ABSENT_JDK_OPTIONAL_PROVIDER =", diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD new file mode 100644 index 000000000..551e09139 --- /dev/null +++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD @@ -0,0 +1,37 @@ +# Copyright (C) 2020 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. + +# Description: +# Tests for dagger.internal.codegen.bindinggraphvalidation + +load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +GenJavaTests( + name = "bindinggraphvalidation_tests", + srcs = glob(["*.java"]), + functional = False, + javacopts = DOCLINT_HTML_AND_SYNTAX, + deps = [ + "//java/dagger/internal/codegen/bindinggraphvalidation", + "//javatests/dagger/internal/codegen:compilers", + "@google_bazel_common//third_party/java/compile_testing", + "@google_bazel_common//third_party/java/javapoet", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + "@maven//:com_google_auto_auto_common", + ], +) diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java new file mode 100644 index 000000000..50a65acd7 --- /dev/null +++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationTest.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2018 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.bindinggraphvalidation; + +import static com.google.testing.compile.CompilationSubject.assertThat; +import static dagger.internal.codegen.Compilers.compilerWithOptions; +import static dagger.internal.codegen.Compilers.daggerCompiler; +import static dagger.internal.codegen.bindinggraphvalidation.NullableBindingValidator.nullableToNonNullable; + +import com.google.testing.compile.Compilation; +import com.google.testing.compile.JavaFileObjects; +import javax.tools.JavaFileObject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class NullableBindingValidationTest { + private static final JavaFileObject NULLABLE = + JavaFileObjects.forSourceLines( + "test.Nullable", // force one-string-per-line format + "package test;", + "", + "public @interface Nullable {}"); + + @Test public void nullCheckForConstructorParameters() { + JavaFileObject a = JavaFileObjects.forSourceLines("test.A", + "package test;", + "", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A(String string) {}", + "}"); + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "final class TestModule {", + " @Nullable @Provides String provideString() { return null; }", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Nullable @Provides String TestModule.provideString()")); + + // but if we disable the validation, then it compiles fine. + Compilation compilation2 = + compilerWithOptions("-Adagger.nullableValidation=WARNING") + .compile(NULLABLE, a, module, component); + assertThat(compilation2).succeeded(); + } + + @Test public void nullCheckForMembersInjectParam() { + JavaFileObject a = JavaFileObjects.forSourceLines("test.A", + "package test;", + "", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A() {}", + " @Inject void register(String string) {}", + "}"); + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "final class TestModule {", + " @Nullable @Provides String provideString() { return null; }", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Nullable @Provides String TestModule.provideString()")); + + // but if we disable the validation, then it compiles fine. + Compilation compilation2 = + compilerWithOptions("-Adagger.nullableValidation=WARNING") + .compile(NULLABLE, a, module, component); + assertThat(compilation2).succeeded(); + } + + @Test public void nullCheckForVariable() { + JavaFileObject a = JavaFileObjects.forSourceLines("test.A", + "package test;", + "", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject String string;", + " @Inject A() {}", + "}"); + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "final class TestModule {", + " @Nullable @Provides String provideString() { return null; }", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Nullable @Provides String TestModule.provideString()")); + + // but if we disable the validation, then it compiles fine. + Compilation compilation2 = + compilerWithOptions("-Adagger.nullableValidation=WARNING") + .compile(NULLABLE, a, module, component); + assertThat(compilation2).succeeded(); + } + + @Test public void nullCheckForComponentReturn() { + JavaFileObject module = JavaFileObjects.forSourceLines("test.TestModule", + "package test;", + "", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "final class TestModule {", + " @Nullable @Provides String provideString() { return null; }", + "}"); + JavaFileObject component = JavaFileObjects.forSourceLines("test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " String string();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Nullable @Provides String TestModule.provideString()")); + + // but if we disable the validation, then it compiles fine. + Compilation compilation2 = + compilerWithOptions("-Adagger.nullableValidation=WARNING") + .compile(NULLABLE, module, component); + assertThat(compilation2).succeeded(); + } + + @Test + public void nullCheckForOptionalInstance() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A(Optional<String> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Nullable @Provides String TestModule.provideString()")); + } + + @Test + public void nullCheckForOptionalProvider() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import javax.inject.Inject;", + "import javax.inject.Provider;", + "", + "final class A {", + " @Inject A(Optional<Provider<String>> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).succeeded(); + } + + @Test + public void nullCheckForOptionalLazy() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import dagger.Lazy;", + "import javax.inject.Inject;", + "", + "final class A {", + " @Inject A(Optional<Lazy<String>> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).succeeded(); + } + + @Test + public void nullCheckForOptionalProviderOfLazy() { + JavaFileObject a = + JavaFileObjects.forSourceLines( + "test.A", + "package test;", + "", + "import com.google.common.base.Optional;", + "import dagger.Lazy;", + "import javax.inject.Inject;", + "import javax.inject.Provider;", + "", + "final class A {", + " @Inject A(Optional<Provider<Lazy<String>>> optional) {}", + "}"); + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.BindsOptionalOf;", + "import dagger.Provides;", + "import javax.inject.Inject;", + "", + "@dagger.Module", + "abstract class TestModule {", + " @Nullable @Provides static String provideString() { return null; }", + " @BindsOptionalOf abstract String optionalString();", + "}"); + JavaFileObject component = + JavaFileObjects.forSourceLines( + "test.TestComponent", + "package test;", + "", + "import dagger.Component;", + "", + "@Component(modules = TestModule.class)", + "interface TestComponent {", + " A a();", + "}"); + Compilation compilation = daggerCompiler().compile(NULLABLE, a, module, component); + assertThat(compilation).succeeded(); + } + + @Test + public void moduleValidation() { + JavaFileObject module = + JavaFileObjects.forSourceLines( + "test.TestModule", + "package test;", + "", + "import dagger.Binds;", + "import dagger.Module;", + "import dagger.Provides;", + "", + "@Module", + "abstract class TestModule {", + " @Provides @Nullable static String nullableString() { return null; }", + " @Binds abstract Object object(String string);", + "}"); + + Compilation compilation = + compilerWithOptions("-Adagger.fullBindingGraphValidation=ERROR") + .compile(module, NULLABLE); + assertThat(compilation).failed(); + assertThat(compilation) + .hadErrorContaining( + nullableToNonNullable( + "String", + "@Provides @Nullable String TestModule.nullableString()")); + } +} diff --git a/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java b/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java new file mode 100644 index 000000000..e9b81f365 --- /dev/null +++ b/javatests/dagger/internal/codegen/langmodel/AccessibilityTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 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.langmodel; + +import static com.google.common.truth.Truth.assertThat; +import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; + +import com.google.testing.compile.CompilationRule; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@SuppressWarnings("unused") // contains a variety things used by the compilation rule for testing +public class AccessibilityTest { + /* test data */ + public AccessibilityTest() {} + protected AccessibilityTest(Object o) {} + AccessibilityTest(Object o1, Object o2) {} + private AccessibilityTest(Object o1, Object o2, Object o3) {} + + public String publicField; + protected String protectedField; + String packagePrivateField; + private String privateField; + + public void publicMethod() {} + protected void protectedMethod() {} + void packagePrivateMethod() {} + private void privateMethod() {} + + public static final class PublicNestedClass {} + protected static final class ProtectedNestedClass {} + static final class PackagePrivateNestedClass {} + private static final class PrivateNestedClass {} + + @Rule + public final CompilationRule compilationRule = new CompilationRule(); + + private TypeElement testElement; + + @Before + public void setUp() { + Elements elements = compilationRule.getElements(); + testElement = elements.getTypeElement(AccessibilityTest.class.getCanonicalName()); + } + + @Test + public void isElementAccessibleFrom_publicType() { + assertThat(isElementAccessibleFrom(testElement, "literally.anything")).isTrue(); + } + + @Test + public void isElementAccessibleFrom_publicMethod() { + Element member = getMemberNamed("publicMethod"); + assertThat(isElementAccessibleFrom(member, "literally.anything")).isTrue(); + } + + @Test + public void isElementAccessibleFrom_protectedMethod() { + Element member = getMemberNamed("protectedMethod"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + @Test + public void isElementAccessibleFrom_packagePrivateMethod() { + Element member = getMemberNamed("packagePrivateMethod"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + @Test + public void isElementAccessibleFrom_privateMethod() { + Element member = getMemberNamed( "privateMethod"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isFalse(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + @Test + public void isElementAccessibleFrom_publicField() { + Element member = getMemberNamed("publicField"); + assertThat(isElementAccessibleFrom(member, "literally.anything")).isTrue(); + } + + @Test + public void isElementAccessibleFrom_protectedField() { + Element member = getMemberNamed("protectedField"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + @Test + public void isElementAccessibleFrom_packagePrivateField() { + Element member = getMemberNamed("packagePrivateField"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isTrue(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + @Test + public void isElementAccessibleFrom_privateField() { + Element member = getMemberNamed("privateField"); + assertThat(isElementAccessibleFrom(member, "dagger.internal.codegen")).isFalse(); + assertThat(isElementAccessibleFrom(member, "not.dagger.internal.codegen")).isFalse(); + } + + private Element getMemberNamed(String memberName) { + for (Element enclosedElement : testElement.getEnclosedElements()) { + if (enclosedElement.getSimpleName().contentEquals(memberName)) { + return enclosedElement; + } + } + throw new IllegalArgumentException(); + } +} + diff --git a/javatests/dagger/internal/codegen/validation/BUILD b/javatests/dagger/internal/codegen/validation/BUILD new file mode 100644 index 000000000..03fd684d1 --- /dev/null +++ b/javatests/dagger/internal/codegen/validation/BUILD @@ -0,0 +1,33 @@ +# Copyright (C) 2020 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. + +# Description: +# Tests for dagger.internal.codegen.validation + +load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX") +load("//:test_defs.bzl", "GenJavaTests") + +package(default_visibility = ["//:src"]) + +GenJavaTests( + name = "validation_tests", + srcs = glob(["*.java"]), + functional = False, + javacopts = DOCLINT_HTML_AND_SYNTAX, + deps = [ + "//java/dagger/internal/codegen/validation", + "@google_bazel_common//third_party/java/junit", + "@google_bazel_common//third_party/java/truth", + ], +) diff --git a/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java b/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java new file mode 100644 index 000000000..f6a1b79aa --- /dev/null +++ b/javatests/dagger/internal/codegen/validation/PackageNameCompressorTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 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.validation; + +import static com.google.common.truth.Truth.assertThat; +import static dagger.internal.codegen.validation.PackageNameCompressor.LEGEND_FOOTER; +import static dagger.internal.codegen.validation.PackageNameCompressor.LEGEND_HEADER; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link PackageNameCompressor}. */ +@RunWith(JUnit4.class) +public class PackageNameCompressorTest { + @Test + public void testSimple() { + String input = "Something is wrong with foo.bar.baz.Foo class!"; + String expectedOutput = "Something is wrong with Foo class!" + + LEGEND_HEADER + + "Foo: foo.bar.baz.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + @Test + public void testSameSimpleNames() { + String input = "Something is wrong with foo.bar.baz.Foo and foo.bar.qux.Foo class!"; + String expectedOutput = "Something is wrong with baz.Foo and qux.Foo class!" + + LEGEND_HEADER + + "baz.Foo: foo.bar.baz.Foo\n" + + "qux.Foo: foo.bar.qux.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + @Test + public void testMethodNames() { + String input = "Something is wrong with foo.bar.baz.Foo.provideFoo()!"; + String expectedOutput = "Something is wrong with Foo.provideFoo()!" + + LEGEND_HEADER + + "Foo: foo.bar.baz.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + @Test + public void testMultipleLevelsOfConflicts() { + String input = "Something is wrong with z.a.b.c.Foo, z.b.b.c.Foo, z.a.b.d.Foo class!"; + String expectedOutput = "Something is wrong with a.b.c.Foo, b.b.c.Foo, d.Foo class!" + + LEGEND_HEADER + + "a.b.c.Foo: z.a.b.c.Foo\n" + + "b.b.c.Foo: z.b.b.c.Foo\n" + + "d.Foo: z.a.b.d.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + // In some sense, we're really just compressing the outer class since the legend is going to + // only refer to the outer class. + @Test + public void testInnerClassesKeepOuterClassNameToo() { + String input = "Something is wrong with foo.bar.baz.Foo.Bar.Baz class!"; + String expectedOutput = "Something is wrong with Foo.Bar.Baz class!" + + LEGEND_HEADER + + "Foo: foo.bar.baz.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + // If relying on conflicts by inserting into the map, an extra conflict on c.Foo may result in + // uneven renaming because when the first two conflict on c.Foo they may make room for the next + // conflict to just take over what had previously been a conflict. Make sure that this unevenness + // doesn't happen. + @Test + public void testThreeMultiLevelConflicts() { + String input = "Something is wrong with z.a.c.Foo, z.b.c.Foo, and z.c.c.Foo class!"; + String expectedOutput = "Something is wrong with a.c.Foo, b.c.Foo, and c.c.Foo class!" + + LEGEND_HEADER + + "a.c.Foo: z.a.c.Foo\n" + + "b.c.Foo: z.b.c.Foo\n" + + "c.c.Foo: z.c.c.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + @Test + public void testDoesNotCompressSubstringsOfClasses() { + // This shouldn't try to compress the "ar.Foo" in "Bar.Foo" + String input = "Something is wrong with Bar.Foo class!"; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input); + } + + @Test + public void testNoClassNamesDoNotPutInLegend() { + String input = "Something is wrong with something!"; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input); + } + + @Test + public void testFullConflictsDoNotPutInLegend() { + String input = "Something is wrong with foo.Foo and bar.Foo class!"; + // No shortening can be done without loss of clarity so do not modify this and add no legend. + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(input); + } + + @Test + public void testLegendDoesNotIncludeJavaLang() { + String input = "Something is wrong with java.lang.Set, java.lang.a.Foo," + + " and java.lang.b.Foo class!"; + String expectedOutput = "Something is wrong with Set, a.Foo, and b.Foo class!" + + LEGEND_HEADER + + "a.Foo: java.lang.a.Foo\n" + + "b.Foo: java.lang.b.Foo\n" + + LEGEND_FOOTER; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } + + @Test + public void testOnlyExcludedPrefixesDoesNotPutInLegend() { + String input = "Something is wrong with java.lang.Set class!"; + String expectedOutput = "Something is wrong with Set class!"; + assertThat(PackageNameCompressor.compressPackagesInMessage(input)).isEqualTo(expectedOutput); + } +} diff --git a/test_defs.bzl b/test_defs.bzl index d16ad3660..6f2308877 100644 --- a/test_defs.bzl +++ b/test_defs.bzl @@ -59,11 +59,21 @@ def GenRobolectricTests( test_javacopts = None, functional = True, manifest_values = None): - # TODO(ronshapiro): enable these with these instructions: - # https://docs.bazel.build/versions/master/be/android.html#android_local_test_examples - # We probably want to import all of Robolectric's dependencies into bazel-common because there - # are some differences (i.e. we both provide Guava). - pass + deps = (deps or []) + ["//:android_local_test_exports"] + _GenTests( + native.android_library, + native.android_local_test, + name, + srcs, + deps, + test_only_deps, + plugins, + javacopts, + lib_javacopts, + test_javacopts, + functional, + test_kwargs = {"manifest_values": manifest_values}, + ) def _GenTests( library_rule_type, diff --git a/util/deploy-to-maven-central.sh b/util/deploy-to-maven-central.sh index de2b49c48..1a71f5fed 100755 --- a/util/deploy-to-maven-central.sh +++ b/util/deploy-to-maven-central.sh @@ -22,21 +22,6 @@ fi bash $(dirname $0)/run-local-tests.sh -# Note: we detach from head before making any sed changes to avoid commiting -# a particular version to master. This sed change used to be done at the very -# end of the script, but with the introduction of "-alpha" to the Hilt -# artifacts, we need to do the sed replacement before deploying the artifacts to -# maven. Note, that this sed replacement is only done for versioned releases. -# HEAD-SNAPSHOT and LOCAL_SNAPSHOT versions of Hilt artifacts do not contain -# "-alpha". -git checkout --detach - -# Set the version string that is used as a tag in all of our libraries. If -# another repo depends on a versioned tag of Dagger, their java_library.tags -# should match the versioned release. -sed -i s/'#ALPHA_POSTFIX'/'+ "-alpha"'/g build_defs.bzl -sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl - bash $(dirname $0)/deploy-dagger.sh \ "gpg:sign-and-deploy-file" \ "$VERSION_NAME" \ @@ -46,11 +31,20 @@ bash $(dirname $0)/deploy-dagger.sh \ bash $(dirname $0)/deploy-hilt.sh \ "gpg:sign-and-deploy-file" \ - "${VERSION_NAME}-alpha" \ + "$VERSION_NAME" \ "-DrepositoryId=sonatype-nexus-staging" \ "-Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/" \ "-Dgpg.keyname=${KEY}" +# Note: we detach from head before making any sed changes to avoid commiting +# a particular version to master. +git checkout --detach + +# Set the version string that is used as a tag in all of our libraries. If +# another repo depends on a versioned tag of Dagger, their java_library.tags +# should match the versioned release. +sed -i s/'${project.version}'/"${VERSION_NAME}"/g build_defs.bzl + # Note: We avoid commiting until after deploying in case deploying fails and # we need to run the script again. git commit -m "${VERSION_NAME} release" build_defs.bzl diff --git a/util/run-local-emulator-tests.sh b/util/run-local-emulator-tests.sh index 7fc77620f..347a632af 100755 --- a/util/run-local-emulator-tests.sh +++ b/util/run-local-emulator-tests.sh @@ -2,17 +2,23 @@ set -ex +# Instrumentation tests log results to logcat, so enable it during test runs. +adb logcat *:S TestRunner:V & LOGCAT_PID=$! + readonly GRADLE_PROJECTS=( "javatests/artifacts/hilt-android/simple" "javatests/artifacts/hilt-android/simpleKotlin" ) for project in "${GRADLE_PROJECTS[@]}"; do echo "Running gradle Android emulator tests for $project" - ./$project/gradlew -p $project connectedAndroidTest --no-daemon --stacktrace + ./$project/gradlew -p $project connectedAndroidTest --continue --no-daemon --stacktrace done # Run emulator tests in a project with configuration cache enabled # TODO(danysantiago): Once AGP 4.2.0 is stable, remove these project and enable # config cache in the other test projects. readonly CONFIG_CACHE_PROJECT="javatests/artifacts/hilt-android/gradleConfigCache" -./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT connectedAndroidTest --no-daemon --stacktrace --configuration-cache +./$CONFIG_CACHE_PROJECT/gradlew -p $CONFIG_CACHE_PROJECT connectedAndroidTest --continue --no-daemon --stacktrace --configuration-cache + +# Close logcat +if [ -n "$LOGCAT_PID" ] ; then kill $LOGCAT_PID; fi diff --git a/util/run-local-gradle-android-tests.sh b/util/run-local-gradle-android-tests.sh index ba51078e7..a9418a589 100755 --- a/util/run-local-gradle-android-tests.sh +++ b/util/run-local-gradle-android-tests.sh @@ -12,7 +12,7 @@ readonly ANDROID_GRADLE_PROJECTS=( for project in "${ANDROID_GRADLE_PROJECTS[@]}"; do echo "Running gradle tests for $project with AGP $AGP_VERSION_INPUT" AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project buildDebug --no-daemon --stacktrace - AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --no-daemon --stacktrace + AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --continue --no-daemon --stacktrace done # Run gradle tests in a project with configuration cache enabled diff --git a/util/run-local-gradle-tests.sh b/util/run-local-gradle-tests.sh index b88ccabc7..479158d8f 100755 --- a/util/run-local-gradle-tests.sh +++ b/util/run-local-gradle-tests.sh @@ -10,5 +10,5 @@ readonly GRADLE_PROJECTS=( for project in "${GRADLE_PROJECTS[@]}"; do echo "Running gradle tests for $project" ./$project/gradlew -p $project build --no-daemon --stacktrace - ./$project/gradlew -p $project test --no-daemon --stacktrace + ./$project/gradlew -p $project test --continue --no-daemon --stacktrace done diff --git a/workspace_defs.bzl b/workspace_defs.bzl index c017b76f9..2a63e433f 100644 --- a/workspace_defs.bzl +++ b/workspace_defs.bzl @@ -14,10 +14,10 @@ """A macro to configure Dagger deps within a workspace""" -load("//:build_defs.bzl", "POM_VERSION", "POM_VERSION_ALPHA") +load("//:build_defs.bzl", "POM_VERSION") _DAGGER_VERSION = POM_VERSION -_HILT_VERSION = POM_VERSION_ALPHA +_HILT_VERSION = POM_VERSION DAGGER_ARTIFACTS = [ "com.google.dagger:dagger:" + _DAGGER_VERSION, |