Skip to content

Conversation

@rachelcarmena
Copy link
Contributor

Please, this pull request adds the ability to provide pluginClasspaths for the compilation.

Thanks in advance!

Copy link
Owner

@tschuchortdev tschuchortdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR. I thought it was already possible to add compiler plugins via the pluginClasspath, but I guess I forgot about that.

This PR overlaps a lot with #15 which @Foso is working on. So it might make sense to start from what he already did, or rather combine it.

In particular we need to make sure that the compiler plugins are run in the correct phases.

Comment on lines +413 to +419
pluginClasspaths.forEach { filepath ->
if (!filepath.exists()) {
error("Plugin $filepath not found")
return ExitCode.INTERNAL_ERROR
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should probably be moved to the calling function, where writing of the source files happens as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, which function? stubsAndApt is called from compile:

            // step 1 and 2: generate stubs and run annotation processors
            try {
                val exitCode = stubsAndApt(sourceFiles)
                if (exitCode != ExitCode.OK) {
                    val messages = internalMessageBuffer.readUtf8()
                    searchSystemOutForKnownErrors(messages)
                    return Result(exitCode, classesDir, messages)
                }
            } finally {
                KaptComponentRegistrar.threadLocalParameters.remove()
            }

And previously, the writing of the source files appears:

        // write given sources to working directory
        val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }

Though I don't find an error checking section like the one I found in stubsAndApt.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would just put it after val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) } in the compile function where all the initialization of directories is done. But you are right that it's kinda confusing where to put this stuff. There should be a clear separation between initialization and the different compile stages. Maybe I will rewrite it in the future.

it.destination = classesDir.absolutePath
it.classpath = commonClasspaths().joinToString(separator = File.pathSeparator)

it.pluginClasspaths = pluginClasspaths.map(File::getAbsolutePath).toTypedArray()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commonK2JVMArgs will be used in both steps: stubsAndApt and compileKotlin. So given compiler plugins will be run both times. Is this the desired behaviour? Unless the kapt plugin prevents the compiler from continuing with other plugins, like it short circuits the regular compilation. @Foso may know more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't have that problem because I didn't provide annotationProcessors, so stubsAndApt finished in the first conditional. Let's see the opinion by @Foso.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue still deserves some attention. Are we really sure that compiler plugins should be executed in both steps?

@rachelcarmena
Copy link
Contributor Author

Thanks @tschuchortdev for the review!! I really appreciate it!
Do you know where I can find which @Foso is working on?

@rachelcarmena
Copy link
Contributor Author

I pushed a new commit with 3 improvements from your comments.
Thanks again for your attention, the review and such an awesome plugin!! Congrats for it!!

@rachelcarmena
Copy link
Contributor Author

Hi again @tschuchortdev, I found the development by @Foso and I think it's not necessary to combine both developments: see loadPluginsSafe (without componentRegistrars) and loadPlugins. Also I think his development is ad-hoc to his context. That being said, maybe I'm wrong!

Copy link
Owner

@tschuchortdev tschuchortdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see how PluginCLIParser is relevant here? It looks like loadPluginsSafe is just a wrapper around loadPlugins that catches exceptions, or am I missing something? Thanks for linking it though, might come in handy.

By "combining both developments" I don't mean necessarily on the same branch. But for example you both need to know during which stages the compiler plugins should be run, so it would be best to be on the same page here so you don't need to do the debugging twice. Similarly, you will both need to define some kind of test compiler plugin in order to test if it works; That could be shared as well.

Regarding the test: We can define a simple TestComponentRegistrar in the project that can be used in both cases. @Foso can just pass it by value and you will have to pass this over the classpath. One (complicated) way to do this is as follows: Add a inheritPluginClasspath option to inherit the host applications classpath as pluginClasspath similarly to the already present inheritClassPath option. Alternatively you can get the path of just the host project classes with the getResourcesPath() function. Then all classes of the host application will be on the pluginClasspath, but that is not enough: For ServiceLoaderLite to find them we also need to provide the service registrations in the corresponding META-INF file. This can be done by manually creating another jar containing just the META-INF file and adding it to the pluginClasspath (see example here). Perhaps there is an easier way to do this by just defining the TestComponentRegistrar in another Gradle module and then declaring it as some kind of jar dependency. But I don't know enough Gradle to do this.

Comment on lines +413 to +419
pluginClasspaths.forEach { filepath ->
if (!filepath.exists()) {
error("Plugin $filepath not found")
return ExitCode.INTERNAL_ERROR
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would just put it after val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) } in the compile function where all the initialization of directories is done. But you are right that it's kinda confusing where to put this stuff. There should be a clear separation between initialization and the different compile stages. Maybe I will rewrite it in the future.

@rachelcarmena
Copy link
Contributor Author

I'll follow your ideas to create another test. Thanks!!

@rachelcarmena
Copy link
Contributor Author

Hi @tschuchortdev,

Done. There is a new test to check that the new property pluginClasspaths works properly and the provided plugin is detected for the compilation. I'm not absolutely happy with the test because it's not shown how the plugin works. However, I think it's enough to check that the new property is working. Just adding a plugin in test runtime and getting its classpath for pluginClasspaths, shows a message about the provided plugin.

Please, let me know if I must do something more.

Copy link
Owner

@tschuchortdev tschuchortdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the missing documentation for classpathOf this looks fine to me. BUT we still don't know if given compiler plugins should be executed in both the stubsAndApt and compileKotlin steps! So I'm reluctant to merge this until we can be sure that it's correct... Or we could merge it now and fix it later. Naturally, I would prefer to get it right the first time.


private fun classpathOf(dependency: String): File {
val regex = Regex(".*$dependency\\.jar")
val classGraph = ClassGraph().whitelistJars()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using whitelistJars() here? If I read the documentation correctly, this method should return an empty ClassGraph if no arguments are given.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry, fixed!

it.destination = classesDir.absolutePath
it.classpath = commonClasspaths().joinToString(separator = File.pathSeparator)

it.pluginClasspaths = pluginClasspaths.map(File::getAbsolutePath).toTypedArray()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue still deserves some attention. Are we really sure that compiler plugins should be executed in both steps?

@Foso
Copy link
Contributor

Foso commented Oct 6, 2019

Hi @tschuchortdev @rachelcarmena ,
i took a quick look at the PR and hope i can kind of help.

In my branch i used a list of ComponentRegistrars because i wanted to be able to directly interact with my plugin classes in the tests, without compiling the plugin and figuring out the classpath first.

I think it's independent from this PR. I can think of usecases where you want add a componentRegistrar and check it in combination with other plugins, that you add to the classpath.

Unfortunately i'm no expert when it comes to compiler phases, but the arrow-kt/arrow-meta-prototype@f7c4ea2 contains a few informations. I experienced that the attached ComponentRegistrar gets called when the compiler starts and then the compiler calls the attached Extension-classes, like AbstractKapt3Extension, when the compiler is in the right phase for it. So i'm not sure if we could add the plugins to wrong phase, because the compiler decides when they get called.

Btw:
Maybe the test checks configuration with pluginClasspaths should be renamed to something more specific (what exactly is the configuration/pluginClasspaths checked for?)

@tschuchortdev
Copy link
Owner

I think it's independent from this PR. I can think of usecases where you want add a componentRegistrar and check it in combination with other plugins, that you add to the classpath.

We should definitely have both, but I thought that maybe the test plugin could be shared.

So i'm not sure if we could add the plugins to wrong phase, because the compiler decides when they get called.

I think theses phases are actually "outside" the compiler and get orchestrated by the Kotlin Gradle and Kapt Gradle plugin. So Gradle decides what phases should receive which compiler plugins as arguments. But it's also possible that both phases receive all third-party compiler plugins and kapt actually prevents the registration of further compiler plugins by terminating the compiler before it gets there. Or the third option: Compiler plugins really have to be executed in both phases.

@rachelcarmena
Copy link
Contributor Author

Thanks @tschuchortdev and @Foso ! I appreciate a lot your reviews.

Btw:
Maybe the test checks configuration with pluginClasspaths should be renamed to something more specific (what exactly is the configuration/pluginClasspaths checked for?)

Absolutely, it wasn't a good name. It exists in the first commit and it was replaced by returns an internal error when adding a non existing plugin for compilation in the second commit.

I added a warning message in the last commit in case the plugin is executed twice. However, as far as I know, annotation processors and compiler plugins are different alternatives to add code at compile-time and stubsAndApt has the following conditional at the beginning:

if(annotationProcessors.isEmpty()) {
	log("No services were given. Not running kapt steps.")
	return ExitCode.OK
}

so if a user provides pluginClasspaths instead of annotationProcessors, the plugin will be executed once for sure.

Could a user be interested in adding annotation processors and compiler plugins at the same time? In that case, will there be problems with two executions? I don't know. Maybe we could postpone that development. Now, the user receives a warning in case the plugin is executed twice (though the user will also see two traces).

Please, let me know if I must improve something else.

Thanks again!

@tschuchortdev
Copy link
Owner

Could a user be interested in adding annotation processors and compiler plugins at the same time?

Definitely. For example dagger + arrow. Jetpack Compose also uses compiler plugins iirc.

In that case, will there be problems with two executions?

It's possible. If the compiler plugin is used to generate code, it might try to generate the same class twice which will lead to errors. But similarly if we don't execute compiler plugins during the stubsAndApt phase, there may be the problem that Kotlin sources later generated by a compiler plugin are not subject to annotation processing.

Maybe we could postpone that development.

I have asked on the Kotlin slack about this (we could also summon someone from the compiler team here). If I don't receive an answer until tomorrow, I will probably merge this as it is and we can fix it later if someone complains. I assume you are also waiting for this to be available as a release so you can use it to test arrow-meta?

@rachelcarmena
Copy link
Contributor Author

Thank you very much @tschuchortdev ! Yes, that's the reason why I'm interested in this change. Thanks again Thilo!

@tschuchortdev tschuchortdev merged commit e2d6378 into tschuchortdev:master Oct 7, 2019
@tschuchortdev
Copy link
Owner

tschuchortdev commented Oct 7, 2019

@rachelcarmena @raulraja Do you need a proper release or will a SNAPSHOT release suffice? I'd like to wait with the next real release until #15 is done because releasing on sonatype nexus is a real pain.

@raulraja
Copy link

raulraja commented Oct 7, 2019

@tschuchortdev we are fine depending on the snapshot until the release it's ready. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants