Multi-module annotation processing in Android Studio

I have a project with several modules in Android Studio. A module may have a dependency on another module, for example:

PhoneApp module โ†’ FeatureOne module โ†’ Module services

I turned on annotation processing in the root module, but annotated annotation processing is only processed at the highest level (PhoneApp), so theoretically it should have access to all modules at compile time. However, what I see in the generated java file is only the classes annotated in PhoneApp and none of the other modules.

PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java 

In other modules, I find the generated file in the intermediate elements directory, which contains only annotated files from this module.

 FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.class FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.java 

My goal is to have one generated file in PhoneApp, which allows me to access annotated files from all modules. It is not entirely clear why the code generation process works for everyone and cannot fill out all annotations in PhoneApp. Any help appreciated.

The code is pretty simple and right up to now, checkIsValid () is omitted because it works correctly:

Annotation Handler:

 @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { try { for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GuiceModule.class)) { if (checkIsValid(annotatedElement)) { AnnotatedClass annotatedClass = new AnnotatedClass((TypeElement) annotatedElement); if (!annotatedClasses.containsKey(annotatedClass.getSimpleTypeName())) { annotatedClasses.put(annotatedClass.getSimpleTypeName(), annotatedClass); } } } if (roundEnv.processingOver()) { generateCode(); } } catch (ProcessingException e) { error(e.getElement(), e.getMessage()); } catch (IOException e) { error(null, e.getMessage()); } return true; } private void generateCode() throws IOException { PackageElement packageElement = elementUtils.getPackageElement(getClass().getPackage().getName()); String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString(); ClassName moduleClass = ClassName.get("com.google.inject", "Module"); ClassName contextClass = ClassName.get("android.content", "Context"); TypeName arrayOfModules = ArrayTypeName.of(moduleClass); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("juice") .addParameter(contextClass, "context") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(arrayOfModules); methodBuilder.addStatement("$T<$T> collection = new $T<>()", List.class, moduleClass, ArrayList.class); for (String key : annotatedClasses.keySet()) { AnnotatedClass annotatedClass = annotatedClasses.get(key); ClassName className = ClassName.get(annotatedClass.getElement().getEnclosingElement().toString(), annotatedClass.getElement().getSimpleName().toString()); if (annotatedClass.isContextRequired()) { methodBuilder.addStatement("collection.add(new $T(context))", className); } else { methodBuilder.addStatement("collection.add(new $T())", className); } } methodBuilder.addStatement("return collection.toArray(new $T[collection.size()])", moduleClass); TypeSpec classTypeSpec = TypeSpec.classBuilder("FreshlySqueezed") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodBuilder.build()) .build(); JavaFile.builder(packageName, classTypeSpec) .build() .writeTo(filer); } 

This is just a demo of annotation processing that works with Guice if anyone is interested.

So, how can I get all annotated classes to be included in the generated PhoneApp.java file from all modules?

+5
source share
2 answers

Instead of using Filer to save the generated file, use a regular java file instead. You will need to serialize objects to temporary files during processing, because even static variables will not be saved between modules. Configure gradle to remove temporary files before compilation.

0
source

It's never too late to answer a question about SO, so ...

I encountered very similar complications during one of the tasks at work.

And I was able to solve it.

Short version

All you need to know about the generated moduleB module classes in moduleA is the name of the package and class. This can be saved as a MyClassesRegistrar generated class, placed in a well-known package. Use suffixes to avoid name conflicts, get registrars by packages. Create them and use the data from them.

version of lond

First of all - you CANNOT turn on only compilation-dependent time only in the very top module (allows you to call this "application" module, as your typical Android project structure does). Annotation processing doesn't work that way, and as far as I can tell, there's nothing to be done.

Now to the details. My task was as follows: I have annotated classes written by man. I will call them "events." At compile time, I need to create helper classes for these events in order to include their structure and content (both statically accessible (annotation values, constants, etc.), and runtime (I pass event objects to these helpers when using the latter). the class name depends on the class name of the event with a suffix, so I do not know it until the code generation is complete.

So, after creating the helpers, I create a factory and generate code to provide a new helper instance based on MyEvent.class . Here's the problem: I need only one factory in the application module, but it should be able to provide helpers for events from the library module - this cannot be done simply.

What I've done:

  • skip creating a factory for the modules my application module depends on;

  • in modules other than the application, generates the so-called HelpersRegistrar implementation (s):

    - they all use the same package (you will find out why later);

    - their names do not collide due to a suffix (see below);

    - differentiation between the application module and the library module is performed using javac "-Amylib.suffix=MyModuleName" param, this user MUST set this restriction, but not significant. The suffix should not be specified for the application module;

    - the implementation created by HelpersRegistrar can provide everything that I need for the future generation of the factory code: the name of the event class, the name of the helper class, the package (these are two common packages for visibility of packages between the helper and the event) - all the lines included in the POJO;

  • in the application module. I generate helpers - as usual, I get HelperRegistrars with my package, create an instance, run their contents to enrich my factory with code that provides helpers from other modules. All I need for this is the class names and the package.

  • Voila! My factory can provide instances of helpers both from the application module and from other modules.

The only uncertainty is the procedure for creating and running instances of the processor class in the application module and in other modules. I did not find any solid information about this, but my example shows that the compiler (and therefore code generation) first starts in the module we depend on, and then in the application module (otherwise, compiling the application module will be f. .cked). This gives us reason to rely on a known order of code execution in different modules.

Another slightly similar approach is to skip the registrars, generate factories in all modules, and write factory in the application module to use other factories that you get and name the same way as the registrars above.

An example can be seen here: https://github.com/techery/janet-analytics - this is the library in which I applied this approach (the one who does not have registrars, since I have factories, but this may not be for you )

P. S .: the suffix param parameter can be switched to the simpler "-Amylibraryname.library = true", and the names of factories / registrars can be auto-generated / increased

0
source

All Articles