Skip to content

Commit 98a0275

Browse files
bcorsoDagger Team
authored and
Dagger Team
committed
Fix LazyClassKey proguard file issues.
* Fixes the name of the proguard file to contain an underscore between the package and simple names (e.g. `test.FooModule` was `testFooModule_LazyClassKeys.pro` and is now `test_FooModule_LazyClassKeys.pro`). * Fixes incremental processing by adding the originating element to the `writeResource` call (#4549). * Fixes bug where `StringBuilder` was declared outside module for-loop, which could lead to duplicate entries across proguard rules for different modules. * Fixed `ClassName#toString()` usage to `ClassName#canonicalName()`, since `toString()` is ambiguous and will silently break things when we migrate to `XClassName`. * Added test coverage for the proguard file name and contents. Fixes #4549 RELNOTES=Fixes #4549: Fixes incremental processing for LazyClassKey proguard files by adding the originating element to the `writeResource` call. PiperOrigin-RevId: 710126913
1 parent caa7e17 commit 98a0275

File tree

2 files changed

+258
-24
lines changed

2 files changed

+258
-24
lines changed

java/dagger/internal/codegen/processingstep/LazyClassKeyProcessingStep.java

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static androidx.room.compiler.processing.XElementKt.isTypeElement;
2020
import static java.nio.charset.StandardCharsets.UTF_8;
21+
import static java.util.stream.Collectors.joining;
2122

2223
import androidx.room.compiler.processing.XElement;
2324
import androidx.room.compiler.processing.XFiler;
@@ -38,15 +39,17 @@
3839
import java.io.OutputStream;
3940
import java.io.OutputStreamWriter;
4041
import java.nio.file.Path;
41-
import java.util.Collection;
4242
import java.util.Map;
4343
import java.util.Set;
4444
import javax.inject.Inject;
4545

4646
/** Generate keep rules for LazyClassKey referenced classes to prevent class merging. */
4747
final class LazyClassKeyProcessingStep extends TypeCheckingProcessingStep<XElement> {
4848
private static final String PROGUARD_KEEP_RULE = "-keep,allowobfuscation,allowshrinking class ";
49-
private final SetMultimap<ClassName, ClassName> processedElements = LinkedHashMultimap.create();
49+
50+
// Note: We aggregate @LazyClassKey usages across processing rounds, so we use ClassName instead
51+
// of XElement as the map key to avoid storing XElement instances across processing rounds.
52+
private final SetMultimap<ClassName, ClassName> lazyMapKeysByModule = LinkedHashMultimap.create();
5053
private final LazyMapKeyProxyGenerator lazyMapKeyProxyGenerator;
5154

5255
@Inject
@@ -73,7 +76,7 @@ protected void process(XElement element, ImmutableSet<ClassName> annotations) {
7376
return;
7477
}
7578
XTypeElement moduleElement = XElements.asTypeElement(element.getEnclosingElement());
76-
processedElements.put(moduleElement.getClassName(), lazyClassKey);
79+
lazyMapKeysByModule.put(moduleElement.getClassName(), lazyClassKey);
7780
XMethodElement method = XElements.asMethod(element);
7881
lazyMapKeyProxyGenerator.generate(method);
7982
}
@@ -91,41 +94,57 @@ private static boolean isModuleOrProducerModule(XElement element) {
9194
|| element.hasAnnotation(TypeNames.PRODUCER_MODULE));
9295
}
9396

97+
// TODO(b/386393062): Avoid generating proguard files in processOver.
9498
@Override
9599
public void processOver(
96100
XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) {
97101
super.processOver(env, elementsByAnnotation);
98-
StringBuilder proguardRules = new StringBuilder();
99-
for (Map.Entry<ClassName, Collection<ClassName>> moduleToLazyClassKeys :
100-
processedElements.asMap().entrySet()) {
101-
String bindingGraphProguardName =
102-
getFullyQualifiedEnclosedClassName(moduleToLazyClassKeys.getKey()) + "_LazyClassKeys.pro";
103-
for (ClassName lazyClassKey : moduleToLazyClassKeys.getValue()) {
104-
proguardRules.append(PROGUARD_KEEP_RULE).append(lazyClassKey).append("\n");
105-
}
106-
writeProguardFile(bindingGraphProguardName, proguardRules.toString(), env.getFiler());
107-
}
102+
lazyMapKeysByModule
103+
.asMap()
104+
.forEach(
105+
(moduleClassName, lazyClassKeys) -> {
106+
// Note: we could probably get better incremental performance by using the method
107+
// element instead of the module element as the originating element. However, that
108+
// would require appending the method name to each proguard file, which would probably
109+
// cause issues with the filename length limit (256 characters) given it already must
110+
// include the module's fully qualified name.
111+
XTypeElement originatingElement =
112+
env.requireTypeElement(moduleClassName.canonicalName());
113+
114+
Path proguardFile =
115+
Path.of(
116+
"META-INF/proguard",
117+
getFullyQualifiedEnclosedClassName(moduleClassName) + "_LazyClassKeys.pro");
118+
119+
String proguardFileContents =
120+
lazyClassKeys.stream()
121+
.map(lazyClassKey -> PROGUARD_KEEP_RULE + lazyClassKey.canonicalName())
122+
.collect(joining("\n"));
123+
124+
writeResource(env.getFiler(), originatingElement, proguardFile, proguardFileContents);
125+
});
126+
// Processing is over so this shouldn't matter, but clear the map just incase.
127+
lazyMapKeysByModule.clear();
108128
}
109129

110-
private void writeProguardFile(String proguardFileName, String proguardRules, XFiler filer) {
130+
private void writeResource(
131+
XFiler filer, XElement originatingElement, Path path, String contents) {
111132
try (OutputStream outputStream =
112-
filer.writeResource(
113-
Path.of("META-INF/proguard/" + proguardFileName),
114-
ImmutableList.<XElement>of(),
115-
XFiler.Mode.Isolating);
133+
filer.writeResource(path, ImmutableList.of(originatingElement), XFiler.Mode.Isolating);
116134
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) {
117-
writer.write(proguardRules);
135+
writer.write(contents);
118136
} catch (IOException e) {
119137
throw new IllegalStateException(e);
120138
}
121139
}
122140

123141
/** Returns the fully qualified class name, with _ instead of . */
124142
private static String getFullyQualifiedEnclosedClassName(ClassName className) {
125-
return className.packageName().replace('.', '_') + getEnclosedName(className);
126-
}
127-
128-
public static String getEnclosedName(ClassName name) {
129-
return Joiner.on('_').join(name.simpleNames());
143+
return Joiner.on('_')
144+
.join(
145+
ImmutableList.<String>builder()
146+
.add(className.packageName().replace('.', '_'))
147+
.addAll(className.simpleNames())
148+
.build());
130149
}
131150
}

javatests/dagger/internal/codegen/LazyClassKeyMapBindingComponentProcessorTest.java

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,19 @@
1616

1717
package dagger.internal.codegen;
1818

19+
import static com.google.common.truth.Truth.assertThat;
1920
import static com.google.testing.compile.CompilationSubject.assertThat;
21+
import static java.nio.charset.StandardCharsets.UTF_8;
2022

2123
import androidx.room.compiler.processing.util.Source;
24+
import com.google.common.truth.PrimitiveByteArraySubject;
25+
import com.google.common.truth.StringSubject;
26+
import com.google.common.truth.Subject;
2227
import com.google.testing.compile.Compilation;
2328
import com.google.testing.compile.JavaFileObjects;
2429
import dagger.testing.compile.CompilerTests;
2530
import dagger.testing.golden.GoldenFileRule;
31+
import java.lang.reflect.Method;
2632
import java.util.Collection;
2733
import javax.tools.JavaFileObject;
2834
import org.junit.Rule;
@@ -276,4 +282,213 @@ public void scopedLazyClassKeyProvider_compilesSuccessfully() throws Exception {
276282
.withProcessingOptions(compilerMode.processorOptions())
277283
.compile(subject -> subject.hasErrorCount(0));
278284
}
285+
286+
@Test
287+
public void testProguardFile() throws Exception {
288+
Source fooKey =
289+
CompilerTests.javaSource(
290+
"test.FooKey",
291+
"package test;",
292+
"",
293+
"interface FooKey {}");
294+
Source fooKeyModule =
295+
CompilerTests.javaSource(
296+
"test.FooKeyModule",
297+
"package test;",
298+
"",
299+
"import dagger.Module;",
300+
"import dagger.Provides;",
301+
"import dagger.multibindings.LazyClassKey;",
302+
"import dagger.multibindings.IntoMap;",
303+
"",
304+
"@Module",
305+
"public interface FooKeyModule {",
306+
" @Provides",
307+
" @IntoMap",
308+
" @LazyClassKey(FooKey.class)",
309+
" static String provideString() { return \"\"; }",
310+
"}");
311+
CompilerTests.daggerCompiler(fooKey, fooKeyModule)
312+
.withProcessingOptions(compilerMode.processorOptions())
313+
.compile(
314+
subject -> {
315+
subject.hasErrorCount(0);
316+
PrimitiveByteArraySubject proguardFile =
317+
subject.generatedResourceFileWithPath(
318+
"META-INF/proguard/test_FooKeyModule_LazyClassKeys.pro");
319+
assertThatContentAsUtf8String(proguardFile)
320+
.isEqualTo("-keep,allowobfuscation,allowshrinking class test.FooKey");
321+
});
322+
}
323+
324+
@Test
325+
public void testProguardFile_nestedModule() throws Exception {
326+
Source fooKey =
327+
CompilerTests.javaSource(
328+
"test.FooKey",
329+
"package test;",
330+
"",
331+
"interface FooKey {}");
332+
Source outerClass =
333+
CompilerTests.javaSource(
334+
"test.OuterClass",
335+
"package test;",
336+
"",
337+
"import dagger.Module;",
338+
"import dagger.Provides;",
339+
"import dagger.multibindings.LazyClassKey;",
340+
"import dagger.multibindings.IntoMap;",
341+
"",
342+
"public interface OuterClass {",
343+
" @Module",
344+
" public interface FooKeyModule {",
345+
" @Provides",
346+
" @IntoMap",
347+
" @LazyClassKey(FooKey.class)",
348+
" static String provideString() { return \"\"; }",
349+
" }",
350+
"}");
351+
CompilerTests.daggerCompiler(fooKey, outerClass)
352+
.withProcessingOptions(compilerMode.processorOptions())
353+
.compile(
354+
subject -> {
355+
subject.hasErrorCount(0);
356+
PrimitiveByteArraySubject proguardFile =
357+
subject.generatedResourceFileWithPath(
358+
"META-INF/proguard/test_OuterClass_FooKeyModule_LazyClassKeys.pro");
359+
assertThatContentAsUtf8String(proguardFile)
360+
.isEqualTo("-keep,allowobfuscation,allowshrinking class test.FooKey");
361+
});
362+
}
363+
364+
@Test
365+
public void testProguardFile_multipleModules() throws Exception {
366+
Source fooKey =
367+
CompilerTests.javaSource(
368+
"test.FooKey",
369+
"package test;",
370+
"",
371+
"interface FooKey {}");
372+
Source barKey =
373+
CompilerTests.javaSource(
374+
"test.BarKey",
375+
"package test;",
376+
"",
377+
"interface BarKey {}");
378+
Source fooKeyModule =
379+
CompilerTests.javaSource(
380+
"test.FooKeyModule",
381+
"package test;",
382+
"",
383+
"import dagger.Module;",
384+
"import dagger.Provides;",
385+
"import dagger.multibindings.LazyClassKey;",
386+
"import dagger.multibindings.IntoMap;",
387+
"",
388+
"@Module",
389+
"public interface FooKeyModule {",
390+
" @Provides",
391+
" @IntoMap",
392+
" @LazyClassKey(FooKey.class)",
393+
" static String provideString() { return \"\"; }",
394+
"}");
395+
Source barKeyModule =
396+
CompilerTests.javaSource(
397+
"test.BarKeyModule",
398+
"package test;",
399+
"",
400+
"import dagger.Module;",
401+
"import dagger.Provides;",
402+
"import dagger.multibindings.LazyClassKey;",
403+
"import dagger.multibindings.IntoMap;",
404+
"",
405+
"@Module",
406+
"public interface BarKeyModule {",
407+
" @Provides",
408+
" @IntoMap",
409+
" @LazyClassKey(BarKey.class)",
410+
" static String provideString() { return \"\"; }",
411+
"}");
412+
CompilerTests.daggerCompiler(fooKey, fooKeyModule, barKey, barKeyModule)
413+
.withProcessingOptions(compilerMode.processorOptions())
414+
.compile(
415+
subject -> {
416+
subject.hasErrorCount(0);
417+
PrimitiveByteArraySubject fooKeyModuleProguardFile =
418+
subject.generatedResourceFileWithPath(
419+
"META-INF/proguard/test_FooKeyModule_LazyClassKeys.pro");
420+
assertThatContentAsUtf8String(fooKeyModuleProguardFile)
421+
.isEqualTo("-keep,allowobfuscation,allowshrinking class test.FooKey");
422+
423+
PrimitiveByteArraySubject barKeyModuleProguardFile =
424+
subject.generatedResourceFileWithPath(
425+
"META-INF/proguard/test_BarKeyModule_LazyClassKeys.pro");
426+
assertThatContentAsUtf8String(barKeyModuleProguardFile)
427+
.isEqualTo("-keep,allowobfuscation,allowshrinking class test.BarKey");
428+
});
429+
}
430+
431+
@Test
432+
public void testProguardFile_multipleKeys() throws Exception {
433+
Source fooKey =
434+
CompilerTests.javaSource(
435+
"test.FooKey",
436+
"package test;",
437+
"",
438+
"interface FooKey {}");
439+
Source barKey =
440+
CompilerTests.javaSource(
441+
"test.BarKey",
442+
"package test;",
443+
"",
444+
"interface BarKey {}");
445+
Source fooKeyAndBarKeyModule =
446+
CompilerTests.javaSource(
447+
"test.FooKeyAndBarKeyModule",
448+
"package test;",
449+
"",
450+
"import dagger.Module;",
451+
"import dagger.Provides;",
452+
"import dagger.multibindings.LazyClassKey;",
453+
"import dagger.multibindings.IntoMap;",
454+
"",
455+
"@Module",
456+
"public interface FooKeyAndBarKeyModule {",
457+
" @Provides",
458+
" @IntoMap",
459+
" @LazyClassKey(FooKey.class)",
460+
" static String provideFooKeyString() { return \"\"; }",
461+
"",
462+
" @Provides",
463+
" @IntoMap",
464+
" @LazyClassKey(BarKey.class)",
465+
" static String provideBarKeyString() { return \"\"; }",
466+
"}");
467+
CompilerTests.daggerCompiler(fooKey, barKey, fooKeyAndBarKeyModule)
468+
.withProcessingOptions(compilerMode.processorOptions())
469+
.compile(
470+
subject -> {
471+
subject.hasErrorCount(0);
472+
PrimitiveByteArraySubject proguardFile =
473+
subject.generatedResourceFileWithPath(
474+
"META-INF/proguard/test_FooKeyAndBarKeyModule_LazyClassKeys.pro");
475+
assertThatContentAsUtf8String(proguardFile)
476+
.isEqualTo(
477+
"-keep,allowobfuscation,allowshrinking class test.FooKey\n"
478+
+ "-keep,allowobfuscation,allowshrinking class test.BarKey");
479+
});
480+
}
481+
482+
// TODO(b/386213524): Add support for getting a resource file as a StringSubject.
483+
// Use reflection to get the subject's byte array and then convert it to a StringSubject.
484+
private static StringSubject assertThatContentAsUtf8String(PrimitiveByteArraySubject subject) {
485+
try {
486+
Method protectedActualMethod = Subject.class.getDeclaredMethod("actual");
487+
protectedActualMethod.setAccessible(true);
488+
byte[] actualBytes = (byte[]) protectedActualMethod.invoke(subject);
489+
return assertThat(new String(actualBytes, UTF_8));
490+
} catch (Exception e) {
491+
throw new RuntimeException(e);
492+
}
493+
}
279494
}

0 commit comments

Comments
 (0)