5

I'm having an Android Studio project with 2 modules: A and B. (I do not include here the Annotation Processor and the Annotations module)

B depends on A.

B is an Android Library Module, and A is simple Java library Module. I'm also having an Annotation Processor on module B.

The problem I'm facing is:

I want to generate some code, based on annotated files placed in both modules - A and B. The problem comes from the way the Annotation Processor works - only with source code files *.java - not with compiled *.class ones. Unfortunately, during the compilation of B, the Annotation Processor doesn't have access to those source files from A...

The only thing, I was able to think about as a kind of solution, even an ugly one, was to include the folder with the annotated classes from module A as a source set to module B. This way I give module B access to those files during compilation.

sourceSets {
    main {
        java {
            srcDirs = ['src/main/java', '../module_A/src/main/java/path/to/annotated/classes/folder']
        }
    }
}

That solves the problem - now the Annotation Processor has access to all the annotated classes from both modules, but...

Unfortunately, it introduces another issue... those annotated classes from module A, are now compiled twice. And they are included in the module A's JAR file and in the module B's AAR file.

Question 1: Is there another way to access those source files of module A, from the Annotation Processor running on B??? (From what I was able to find, the answer is NO, but checking...)

Question 2: How can I exclude those compiled files (the repeated ones) from the AAR final package of module B?

Question 3: Maybe... that's an absolutely wrong approach? Any suggestions?

Thanks in advance!

ivtoto
  • 241
  • 1
  • 9

1 Answers1

2

Nop, you can not achieve what you want using just java.lang.model API. At least not without some additional tricks.

The issues is not with binary-vs-source. Annotation processors can use Elements#getTypeElement to interospect compiled classes as well as source-defined classes:

Elements elementUtil = processingEnvironment.getElementUtils();
TypeElement integerClass = elementUtil.getTypeElement("java.lang.Integer");
TypeElement myClass = elementUtil.getTypeElement("currently.compiled.Class");

But you still need to have class on compilation classpath to observe it, and the class must be in process of being compiled to be visible to getElementsAnnotatedWith.

You can work around later limitation by using a tool like FastClasspathScanner: it will use it's own mechanisms to find annotations in compiled bytecode, and report them to you separately from compilation process. But you can not work around the classpath issue: if you don't have some dependency in compilation classpath, it can not be processed. So you have to compile modules together — either by merging them into one (as you did) or via declaring one to depend on another. In later case you might not be able to use getElementsAnnotatedWith, but getTypeElement and FastClasspathScanner will work.

user1643723
  • 4,109
  • 1
  • 24
  • 48
  • Thanks for your answer, but I'm a little bit confused: in the current state module B depends on A. – ivtoto Mar 28 '17 at 12:58
  • Thanks for your answer, but I'm a little bit confused: in the current state **module B already depends on A**. So, it should have access to the compiled classes from A, as it is defined as a compile dependency: `dependencies { compile project(':A') }`. And, could be a little bit more specific please, about the `getTypeElement` you mentioned – ivtoto Mar 28 '17 at 13:07
  • @ivtoto My answer already has link to documentation of `Elements#getTypeElement`. I have also added an example of using it. Is there something in it's documentation, that is unclear for you? Maybe, you should do some research and possibly ask an additional question. – user1643723 Mar 28 '17 at 13:16
  • Really appreciate your answer. It gave me the missing parts I needed. As I mentioned, there is already dependency between both modules. Now I use the `elementUtils` to get the `PackageElement`, knowing its name. Then I have access to all the enclosed elements and can iterate and check them. Thanks! – ivtoto Mar 28 '17 at 21:10
  • @ivtoto yes, this is one of valid ways of approaching this task. Just beware, that iterating over lots of elements in huge packages might take a toll on compilation speed. This is why I have suggested an alternative way to find annotations. But as long as you are processing one of your modules, and another modules already depend on it's classes anyway, this should not be a significant issue. – user1643723 Mar 29 '17 at 02:59
  • This doesn't work at all. Even if you add it as dependency you will note have access to the class – kylie.zoltan Oct 11 '22 at 12:38
  • @kylie.zoltan if you are speaking about FastClasspathScanner, than you are probably right... That library uses a bunch of hacks to determine classpath of entire app. Those used to work well, but after Java 9 there is no longer such concept as "classpath". Build systems has also advanced (e.g. with incremental compilation many classes won't be fed to compiler at all). But if you can get filesystem paths to entire set of compilation deps and feed it to FCS, it can still be useful. – user1643723 Oct 15 '22 at 15:26