Skip to content
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

Integrate mongodb-crypt module #1487

Merged
merged 27 commits into from
Sep 20, 2024
Merged

Integrate mongodb-crypt module #1487

merged 27 commits into from
Sep 20, 2024

Conversation

vbabanin
Copy link
Member

@vbabanin vbabanin commented Aug 27, 2024

Description

This PR relocates the mongocrypt Java bindings from the libmongocrypt repository to the mongo-java-driver repository to streamline maintenance and the release process.

Key changes to the Gradle build script, originally copied from the libmongocrypt repository, include:

  • Removal of Maven publication configuration; now managed by the publish.gradle script.
  • Removal of signing configurations; managed by the the publish.gradle script.
  • Removal of explicit Slf4j dependency, as it is inherited from the root build script located here.

Review Process

To aid the review process, I have taken several steps:

  • Downloaded the mongocrypt version 1.11 from Maven Central and unzipped the source, Javadoc, runtime jar, and pom into the git folder.
  • Run a Maven local publication specifically for the mongocrypt module using the command gradle mongocrypt:clean mongocrypt:publishToMavenLocal -x signMavenJavaPublication to produce a publication. Subsequently, I unzipped and applied a git diff over the ones from version 1.11. The diff can be viewed here.

Notable differences include many Javadoc variations due to the mongocrypt module inheriting configurations from javadoc.gradle. Key areas to focus on are the pom and manifest files, along with some source files modified by the Spotless plugin, which corrected redundant imports.

I have built the jar files using JDK 11.0.24 (Homebrew), which is the same version used to build 1.11 currently on Maven Central.

Note for Reviewers:

I would appreciate it if reviewers could be particularly vigilant when reviewing the build configurations and produced artifacts in this PR. If you have any doubts or cannot be certain that the changes do not impact previously guaranteed functionalities, please do not hesitate to raise your concerns.

NOTE: Publishing snapshot to Sonatype has not been tested yet.

JAVA-5582
JAVA-5583

@vbabanin vbabanin changed the title Integrate mongodb-crypt module into mongo-java-driver as a new Gradle… Integrate mongodb-crypt module into mongo-java-driver Aug 27, 2024
@vbabanin vbabanin changed the title Integrate mongodb-crypt module into mongo-java-driver Integrate mongodb-crypt module Aug 27, 2024

# local settings
**/gradle.properties
local.properties
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu Aug 27, 2024

Choose a reason for hiding this comment

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

This is super minor, just for fun of tech discussion. In .gitignore, "filrname" means the same as "**/filename" but the latter takes effect only after git v1.8.2

The only reason to use "**" is for such example: a/**/b

No need to change if it has been a pattern elsewhere already. Feel free to ignore, without saying.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the insightful comment! We currently lack consistency in the root .gitignore. We could consider addressing this if it becomes a source of confusion.

As for now, I've removed the .gitignore from this module since we don’t need an additional one; there’s already a .gitignore in the root folder of our repo.

@vbabanin vbabanin self-assigned this Aug 28, 2024
}

dependencies {
api(project(path = ":bson", configuration = "default"))
Copy link
Member Author

Choose a reason for hiding this comment

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

The latest version of BSON is now used, as opposed to the version specified in the libmongocrypt repository. For reference, see the previous dependency listed here: libmongocrypt build.gradle.kts.

JAVA-5582
@vbabanin vbabanin requested a review from rozza August 30, 2024 21:50
Comment on lines 190 to 195
"-exportcontents" to "com.mongodb.crypt.capi.*;-noimport:=true",
"Automatic-Module-Name" to "com.mongodb.crypt.capi",
"Import-Package" to "org.slf4j.*;resolution:=optional,org.bson.*",
"Bundle-Name" to "MongoCrypt",
"Bundle-SymbolicName" to "com.mongodb.crypt.capi",
"Private-Package" to ""
Copy link
Member Author

@vbabanin vbabanin Sep 10, 2024

Choose a reason for hiding this comment

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

Manifest configuration is preserved as it is from libmongocrypt, except for:

  • "Import-Package" - now uses a wildcard "org.slf4j.*;resolution:=optional,org.bson.*",
  • "Build-Version" to gitVersion- is now handled by publish.gradle#L67.
  • "Bundle-Version" to gitVersion - is now handled by publish.gradle#L68.

Comment on lines 96 to 134
tasks.register<Download>("downloadJava") {
src(downloadUrl)
dest("${jnaDownloadsDir}/$localBinariesArchiveName")
overwrite(true)
/* To make sure we don't download archive with binaries if it hasn't been changed in S3 bucket since last download.*/
onlyIfModified(true)
}

tasks.register<Copy>("unzipJava") {
/*
Clean up the directory first if the task is not UP-TO-DATE.
This can happen if the download revision has been changed and the archive is downloaded again.
*/
doFirst {
println("Cleaning up $jnaResourcesDir")
delete(fileTree(jnaResourcesDir).matching {
include(jnaMapping.keys.flatMap {
listOf(
"${it}/nocrypto/**/libmongocrypt.so",
"${it}/lib/**/libmongocrypt.dylib",
"${it}/bin/**/mongocrypt.dll"
)
})
})
}
from(tarTree(resources.gzip("${jnaDownloadsDir}/$localBinariesArchiveName")))
include(jnaMapping.keys.flatMap {
listOf("${it}/nocrypto/**/libmongocrypt.so", "${it}/lib/**/libmongocrypt.dylib", "${it}/bin/**/mongocrypt.dll" )
})
eachFile {
path = "${jnaMapping[path.substringBefore("/")]}/${name}"
}
into(jnaResourcesDir)
mustRunAfter("downloadJava")

doLast {
println("jna.library.path contents: \n ${fileTree(jnaResourcesDir).files.joinToString(",\n ")}")
}
}
Copy link
Member Author

@vbabanin vbabanin Sep 11, 2024

Choose a reason for hiding this comment

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

Gradle tests whether task inputs or outputs have changed since the last build as part of the incremental build process. If no changes are detected, it considers the task up-to-date and skips execution.

There are two time-consuming tasks that now leverage incremental builds:

  • downloadJava: Downloads libmongocrypt binaries from S3. Optimized with the onlyIfModified(true) option, this task avoids redundant downloads by fetching binaries only if they are absent from the build output. The localBinariesArchiveName variable in the ./build folder, containing the zip archive file name with the revision number, ensures that binaries are downloaded just once per revision/tag unless a gradle clean command explicitly clears the build folder.

  • unzipJava: Unzips binaries into the temporary resource folder. Previously, this task was configured with outputs.upToDateWhen { false } to always execute. Now, it triggers only under these conditions:

    • The input file name changes, detected from the downloadRevision variable, prompting the unzipping of the latest or previously saved binaries.
    • Any alteration or corruption in the output file structure within jnaResourcesDir. To ensure correct unzipping, it first cleans jnaResourcesDir and then proceeds to unzip the binaries.

With mongocrypt as a project dependency of driver-core, the incremental build process conserves time when running or testing code unrelated to the mongocrypt module in sync/async modules. It avoids unnecessary rebuilding of the mongocrypt jar for the driver-core module. This is also useful during active development as libmongocrypt commits may update frequently.

Copy link
Member Author

@vbabanin vbabanin Sep 11, 2024

Choose a reason for hiding this comment

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

Please note, the benchmark runner is directly copied from mongocrypt with minor adjustments (added MongoCryptBenchmarkResult). It has not been fully refactored to better align with the benchmark framework; we may consider refactoring in a separate scope. Functionally, it executes the tests, which can be verified by running performance tests on this build and checking the trends graph.

@vbabanin vbabanin marked this pull request as ready for review September 12, 2024 06:26
…to be used by consumers,

to retrieve all the elements necessary to run against this library immitating trasative dependency resolution.

JAVA-5582
Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

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

Note that static checks are failing:

The class name com.mongodb.crypt.capi.CAPI$cstring doesn't start with an upper case letter

so we'll have to suppress that. This is actually a spotbugs error not checkstyle, which surprises me.


// Tests
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("ch.qos.logback:logback-classic:1.2.13")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this use $logbackVersion?

Copy link
Member

Choose a reason for hiding this comment

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

This line can be removed as its set in the main build.gradle

mongocrypt/build.gradle.kts Outdated Show resolved Hide resolved
Copy link
Member

@rozza rozza left a comment

Choose a reason for hiding this comment

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

LGTM - fixed a couple of nits, I had discovered doing a diff between the jar contents.

Also, fixed the spotbugs issue.

Only thing that could be improved is the javadoc warnings for the CAPI file.

mongocrypt/build.gradle.kts Outdated Show resolved Hide resolved
@jyemin jyemin self-requested a review September 19, 2024 02:11
Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

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

LGTM!

@vbabanin
Copy link
Member Author

I'm holding off on merging this as I noticed an issue after running the publish snapshot. The driver-core POM in the snapshot no longer marks mongodb-crypt as an optional dependency, despite it being specified as such in the build configuration: implementation project(path: ':mongocrypt', configuration: 'default'), optional. The setting isn’t being applied correctly. Here's the POM for reference.

@rozza
Copy link
Member

rozza commented Sep 19, 2024

Hoping to have fixed the 'optional' support - will review the build in the morning

@jyemin jyemin closed this Sep 19, 2024
@jyemin jyemin reopened this Sep 19, 2024
@jyemin
Copy link
Contributor

jyemin commented Sep 19, 2024

@rozza do you expect/desire that the optional mongodb-crypt dependency appears in driver-core, driver-sync, and driver-reactive-streams? Currently it's only in driver-core (even though driver-core doesn't actually use it!).

…actId, ensuring that the 'optional' plugin functions correctly.

JAVA-5582
@jyemin
Copy link
Contributor

jyemin commented Sep 19, 2024

I think the latest changes might have broken the build. I see a lot of

FAILURE: java.lang.NoClassDefFoundError: com/mongodb/crypt/capi/MongoCryptOptions (java.lang.NoClassDefFoundError)

@vbabanin
Copy link
Member Author

vbabanin commented Sep 20, 2024

Thanks, @rozza for your help on this issue!

I found the root cause of the issue with the existing nebula.optional-base plugin for optional dependencies. The mismatch between the mongocrypt submodule name and the artifactId mongodb-crypt used during publishing caused the plugin to skip appending the optional flag as detailed in this plugin code.

To address this, I renamed the module name to mongodb-crypt while keeping the directory named as mongocrypt. All tests (full set) now pass, and the driver-core POM now correctly includes the optional flag for mongodb-crypt. The dependency is only specified in driver-core as it was before the mongocrypt migration.

I've pushed these changes to a separate branch to avoid overriding current changes and I've run the full set of Evergreen tests, and all tests have passed successfully. You can review the results here.

The commit with the fix is here: 6e3e7068f1b0f2648a2fa0610e2c13a3f1dd2e3f

@jyemin @rozza please review it, and if everything looks good, @rozza feel free to cherry-pick it into this PR.

@rozza
Copy link
Member

rozza commented Sep 20, 2024

@vbabanin your change looks good, great catch with the naming. I'm thinking renaming the directory to match the archive name also makes sense. Then we don't have to worry about changing the projects name in settings.gradle

@rozza do you expect/desire that the optional mongodb-crypt dependency appears in driver-core, driver-sync, and driver-reactive-streams? Currently it's only in driver-core (even though driver-core doesn't actually use it!).

I was trying to workaround the feature support limitations but seems it failed anyway. driver-core has to take a dependency as some of the options classes shared between sync and reactivestreams use the library. Thankfully, @vbabanin discovered the cause of the plugin failing 👍

Copy link

There is an existing patch(es) for this commit SHA:

Please note that the status that is posted is not in the context of this PR but rather the (latest) existing patch and that may affect some tests that may depend on the particular PR. If your tests do not rely on any PR-specific values (like base or head branch name) then your tests will report the same status. If you would like a patch to run in the context of this PR and abort the other(s), comment 'evergreen retry'.

Copy link
Contributor

@jyemin jyemin left a comment

Choose a reason for hiding this comment

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

LGTM again!

@jyemin
Copy link
Contributor

jyemin commented Sep 20, 2024

evergreen retry

Copy link

There is an existing patch(es) for this commit SHA that will be aborted:

The status reported will be corresponding to this PR rather than the previous existing ones. If you would like a patch to run for another PR and to abort this one, comment 'evergreen retry' on the corresponding PR.

@evergreen-ci-prod evergreen-ci-prod bot mentioned this pull request Sep 20, 2024
@jyemin jyemin merged commit 7e3108b into mongodb:master Sep 20, 2024
56 of 59 checks passed
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