-
Notifications
You must be signed in to change notification settings - Fork 540
Allow registering DriverInitializer for VerifyMigrationTask with ServiceLoader mechanism #3986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow registering DriverInitializer for VerifyMigrationTask with ServiceLoader mechanism #3986
Conversation
sqldelight-gradle-plugin/src/main/kotlin/app/cash/sqldelight/gradle/VerifyMigrationTask.kt
Outdated
Show resolved
Hide resolved
sqldelight-gradle-plugin/src/main/kotlin/app/cash/sqldelight/gradle/VerifyMigrationTask.kt
Outdated
Show resolved
Hide resolved
@AlecStrong @hfhbd would appreciate a review on this. This PR solves our issue with loading a custom driver during migration verification that was caused by ClassLoader isolation. |
can you share what your implementation of the MigrationDriver is? I think the property stuff I'm okay with, the driver stuff is pretty obtuse so want to understand the use case |
sqldelight-gradle-plugin/src/main/kotlin/app/cash/sqldelight/gradle/SqlDelightDatabase.kt
Outdated
Show resolved
Hide resolved
Please add some tests otherwise new commits/refactoring could break it. |
@AlecStrong added some documentation to the Implementation of the driver:
@hfhbd is there a decent starting point for these tests? I couldn't find any tests currently written that verify |
* } | ||
* } | ||
*/ | ||
interface VerifyMigrationDriver: Driver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I understand now. Instead of using a service loader is it possible to just have this type (or a lambda) be another parameter to the verify migration task, I'm imagining an ideal API where you do something like
tasks.withType<VerifyMigrationTask>().configureEach {
driverInitializer = {
DriverManager.registerDriver(...)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also I'd wait to write tests until we get this API where we want it to save yourself the trouble
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I didn't think it was possible since lambdas can't be serialized. Let me look into it, but if you have an example, I'd really appreciate it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interface DriverInitializer {
fun execute()
}
VerifyMigrationTask:
@get:Input
abstract val driverInitializer: Property<DriverInitializer>
> Cannot fingerprint input property 'driverInitializer': value 'SetupSqldelightKt$setupSqldelight$1$2$1@695e62e3' cannot be serialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah interesting, adding Serializable
interface to the DriverInitializer
seems to do the trick
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(as in not annotated with @get:input
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm getting this after removing the @get: Input
:
- In plugin 'app.cash.sqldelight.gradle.SqlDelightPlugin$Inject' type 'app.cash.sqldelight.gradle.VerifyMigrationTask' property 'driverInitializer' is missing an input or output annotation.
Reason: A property without annotation isn't considered during up-to-date checking.
Possible solutions:
1. Add an input or output annotation.
2. Mark it as @Internal.
Please refer to https://docs.gradle.org/8.0.2/userguide/validation_problems.html#missing_annotation for more details about this problem.
So perhaps we do need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
option 2? Marking it as @Internal
? And then exposing a function on the task to set it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I didn't have to introduce another function to set it, it seems to work just as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some tests for the migration task are here |
The idea of ServiceLoader was to have a proper interface managing the connection instead of using the JDBC auto service. Having an empty interface as the requirement is (almost) useless. There is also another option, but I am unsure if we want it: |
I've definitely seen some gradle tasks expose their classpath as ways to do things like this, I'd be pretty cool with that change |
@hfhbd I need to pass the |
@AlecStrong I've greatly simplified this PR based on your suggestions 🙏 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, cool with this
I still try to understand the real problem. Isn't the real problem this hardcoded line? https://github.com/cashapp/sqldelight/blob/02edf13f390229d3eb8986b5c408fd795a085e0f/sqlite-migrations/src/main/kotlin/app/cash/sqlite/migrations/CatalogDatabase.kt#L41 |
@hfhbd now that I'm able to initialize the driver in the context of the worker's ClassLoader, I don't need to pass the properties down to the |
Could you add a test, eg using the GradleRunner, configure the parameter for example |
@AlecStrong @hfhbd if you don't have any objections, I'll merge this in and will revisit writing a unit test for this in the upcoming weeks. I spent some time on it yesterday but I haven't succeeded in writing a functional test and since there aren't any existing tests that do that (configure the |
fun execute() | ||
} | ||
|
||
class DriverInitializerImpl: DriverInitializer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do I place this class for testing purposes so that it is accessible during a test run?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could create a gradle project in sqldelight-gradle-plugin/src/test
, just copy an existing test project and add the test executor here too: https://github.com/cashapp/sqldelight/blob/68a4d849d234b0018c2c2b314915952564ecfe2b/sqldelight-gradle-plugin/src/test/kotlin/app/cash/sqldelight/tests/MigrationTest.kt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh okay, you already added it the project. This depends on your Gradle setup, for tests you could simple move this class into build.gradle
. This should work, I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yea, just having it live in the build.gradle file should work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like VerifyMigrationTask
needs the DriverInitializerImpl
on its classpath, since now I'm getting ClassNotFoundException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do I add the classpath of the build.gradle
file to the VerifyMigrationTask
? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hfhbd any Gradle tips for getting this working? I'm kind of running out of ideas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I took a look and played with different implementations. The only one I got to run is using service loaders. Other implementations failed with serialization errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a PR for this PR, because I can't push to this branch: https://github.com/plangrid/sqldelight/pull/2/files
@AlecStrong @hfhbd I added a test, curious if you have some ideas for placing the |
…properties Use service loader mechanism
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert these changes
@hfhbd Yeah, I'll push a commit that reverts the |
@C2H6O What properties do you want to pass? You can always use environment variables. |
@hfhbd I need the root path of the top-level project. When |
@AlecStrong @hfhbd I tested the PR as it is now and it works well for our use-case. I'm not sure about |
sqldelight-gradle-plugin/src/main/kotlin/app/cash/sqldelight/gradle/VerifyMigrationTask.kt
Outdated
Show resolved
Hide resolved
Please run |
@hfhbd I ran it but it didn't produce a diff |
@hfhbd @AlecStrong anything else that I should address in this PR? 🙏 |
The not required run of spotless is interesting 🤔 Let's finally merge it and do it in another PR. |
This PR will allow consumers to initialize and register their custom
java.sql.Driver
prior to running migrations.On the consumer side:
DriverInitializer
and add a
resources/META-INF/services/app.cash.sqldelight.gradle.DriverInitializer
file pointing to theDriverInitializerImpl
.Where
<migration driver dependency>
can either beproject(:your_local_project)
or coordinates to your published artifactgroup:artifact:version