-
Notifications
You must be signed in to change notification settings - Fork 105
Check if maven has published current version before deploying #2799
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
ef863d5
f9ae4bf
95de44f
07685f2
5344dc9
5246140
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,3 +79,41 @@ tasks.named<org.jetbrains.dokka.gradle.DokkaMultiModuleTask>("dokkaHtmlMultiModu | |
| outputDirectory.set(file("docs/${project.property("VERSION_NAME")}")) | ||
| includes.from("README.md") | ||
| } | ||
|
|
||
| tasks.register("listPublications") { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly not sure if it's worth the extra complexity here. The idea is to be able to know what we publish to maven dynamically so, if we add more modules we publish in the future, we don't need to update any hardcoded list.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is nice! |
||
| description = "Lists all Maven publications with their coordinates (groupId:artifactId:version)" | ||
| group = "publishing" | ||
|
|
||
| // Ensure all subprojects are evaluated before running | ||
| dependsOn(subprojects.map { "${it.path}:tasks" }) | ||
|
|
||
| doLast { | ||
| val groupId = project.findProperty("GROUP") as String? ?: "com.revenuecat.purchases" | ||
| val version = project.findProperty("VERSION_NAME") as String? ?: project.version.toString() | ||
| val pomArtifactId = project.findProperty("POM_ARTIFACT_ID") as String? | ||
|
|
||
| subprojects.forEach { subproject -> | ||
| // Check if maven publish plugin is applied | ||
| val hasMavenPublish = subproject.plugins.hasPlugin("com.vanniktech.maven.publish") | ||
|
|
||
| if (hasMavenPublish) { | ||
| // Get the publishing extension | ||
| val publishing = subproject.extensions.findByType<org.gradle.api.publish.PublishingExtension>() | ||
tonidero marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (publishing != null) { | ||
| publishing.publications.forEach { publication -> | ||
| if (publication is org.gradle.api.publish.maven.MavenPublication) { | ||
| // Use POM_ARTIFACT_ID property if set, otherwise use publication's artifactId | ||
| val artifactId = pomArtifactId ?: publication.artifactId | ||
| val pubGroupId = publication.groupId ?: groupId | ||
| val pubVersion = publication.version ?: version | ||
|
|
||
| // Output in format: groupId:artifactId:version | ||
| println("$pubGroupId:$artifactId:$pubVersion") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,6 +105,24 @@ platform :android do | |
| ) | ||
| end | ||
|
|
||
| desc "Check if version already exists in Maven Central" | ||
| lane :validate_version_not_in_maven_central do |options| | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we going to move this to the Fastlane plugin? That way we can reuse for PHC and KMP.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, I think it will be needed there as well. Will try to move it there instead Edit: Waiting until it's ready to have all the reviews in one place. Once it's ready, I will move to the plugin.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I extracted part of the logic here to the plugin: RevenueCat/fastlane-plugin-revenuecat_internal#101. We would still need to keep some logic to get the artifacts to check... but this felt more like a gradle task, and separate from the plugin action... I guess we could assume the gradle command would be available in all SDKs using it but preferred to keep it separate for now. lmk what you think there! (still need to adapt this PR to use that lane from the plugin) |
||
| version = options[:version] || current_version_number | ||
| UI.message("Checking if version #{version} already exists in Maven Central...") | ||
|
|
||
| group_id = "com.revenuecat.purchases" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming the group id, which should be fine for this repo. |
||
|
|
||
| artifact_ids = discover_published_artifacts | ||
|
|
||
| if artifact_ids.empty? | ||
| UI.user_error!("No artifacts discovered. There should be artifacts deployed") | ||
| else | ||
| UI.message("Found #{artifact_ids.length} artifacts to check: #{artifact_ids.join(', ')}") | ||
| check_version_not_in_maven_central(version, group_id, artifact_ids) | ||
| UI.success("Version #{version} does not exist in Maven Central. Proceeding with deployment.") | ||
| end | ||
| end | ||
|
|
||
| desc "Upload and close a release" | ||
| lane :deploy do |options| | ||
| version = current_version_number | ||
|
|
@@ -116,6 +134,10 @@ platform :android do | |
| "mavenCentralPassword" => ENV['MAVEN_CENTRAL_PORTAL_PASSWORD'], | ||
| "RELEASE_SIGNING_ENABLED" => true | ||
| } | ||
|
|
||
| # Check if version already exists in Maven Central before deploying | ||
| validate_version_not_in_maven_central(version: version) | ||
|
|
||
| UI.verbose("Deploying #{version}") | ||
| gradle( | ||
| tasks: [ | ||
|
|
@@ -143,6 +165,94 @@ platform :android do | |
| github_release(version: version) unless is_snapshot_version?(version) | ||
| end | ||
|
|
||
| def discover_published_artifacts | ||
| artifacts = Set.new | ||
|
|
||
| UI.message("Discovering all publications...") | ||
|
|
||
| # Run gradle command from project root to discover all publications | ||
| Dir.chdir("..") do | ||
| cmd = ["./gradlew", "listPublications", "--no-daemon", "--quiet"] | ||
| output = sh(cmd.join(" "), log: false) | ||
|
|
||
| # Parse output to extract artifact IDs | ||
| output.split("\n").each do |line| | ||
| line.strip! | ||
| # Format is: groupId:artifactId:version | ||
| # Only accept lines that match the format and have artifact IDs starting with "purchases-" | ||
| if line.match?(/^[^:]+:[^:]+:[^:]+$/) | ||
| parts = line.split(":") | ||
| artifact_id = parts[1] if parts.length == 3 | ||
| # Only add artifact IDs that start with "purchases-" to filter out project names and other noise | ||
| if artifact_id && artifact_id.start_with?("purchases-") | ||
| artifacts.add(artifact_id) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # Add the custom artifact IDs that are published via deploy_specific_package | ||
| # These use POM_ARTIFACT_ID to override the artifact ID, so they might not appear | ||
| # in the default publications list, but they will be published with these IDs | ||
| artifacts.add("purchases-custom-entitlement-computation") | ||
| artifacts.add("purchases-debug-view") | ||
|
|
||
| artifacts.to_a | ||
| end | ||
|
|
||
| def check_version_not_in_maven_central(version, group_id, artifact_ids) | ||
| base_url = "https://repo1.maven.org/maven2" | ||
| existing_artifacts = [] | ||
|
|
||
| artifact_ids.each do |artifact_id| | ||
| # Convert group ID dots to slashes for URL path | ||
| group_path = group_id.gsub(".", "/") | ||
| url = "#{base_url}/#{group_path}/#{artifact_id}/#{version}/" | ||
tonidero marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| UI.verbose("Checking Maven Central: #{url}") | ||
|
|
||
| # Use curl to check if the version directory exists (HTTP HEAD request) | ||
| # Don't use -f flag so we can check the HTTP status code | ||
| # Use -L to follow redirects | ||
| begin | ||
| # Capture both stdout (HTTP code) and stderr separately | ||
| result = sh("curl -s -S -o /dev/null -w '%{http_code}' -L -I '#{url}' 2>&1", log: false).strip | ||
|
|
||
| # Extract the HTTP status code (last 3 digits at the end of the output) | ||
| match_result = result.match(/(\d{3})\s*$/) | ||
| if match_result.nil? | ||
| UI.important("Could not parse HTTP status code for #{url}. Response: #{result}") | ||
| next | ||
| end | ||
|
|
||
| http_code = match_result[1] | ||
|
|
||
| if http_code == "200" | ||
| existing_artifacts << artifact_id | ||
| UI.important("Artifact #{group_id}:#{artifact_id}:#{version} already exists in Maven Central") | ||
| elsif http_code != "404" | ||
| # If we get an unexpected status code, warn but don't fail | ||
| UI.important("Unexpected HTTP status #{http_code} when checking #{url}") | ||
| end | ||
| rescue => e | ||
| UI.important("Failed to check #{url}: #{e.message}") | ||
| # Continue checking other artifacts even if one fails | ||
| next | ||
| end | ||
| end | ||
|
|
||
| unless existing_artifacts.empty? | ||
| error_message = "Version #{version} already exists in Maven Central for the following artifacts:\n" | ||
| existing_artifacts.each do |artifact_id| | ||
| group_path = group_id.gsub(".", "/") | ||
| maven_url = "#{base_url}/#{group_path}/#{artifact_id}/#{version}/" | ||
| error_message += " - #{group_id}:#{artifact_id}:#{version}\n #{maven_url}\n" | ||
| end | ||
| error_message += "\nDeployment cancelled to prevent duplicate releases." | ||
| UI.user_error!(error_message) | ||
| end | ||
| end | ||
|
|
||
| def deploy_specific_package(variant, artifact_id, module_to_deploy, gradle_properties) | ||
| gradle_properties["ANDROID_VARIANT_TO_PUBLISH"] = variant | ||
| gradle_properties["POM_ARTIFACT_ID"] = artifact_id | ||
|
|
||
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.
(This comment is not related to this file at all 😄)
Another idea I had was to separate the upload step from the publish step, (plugin docs), and only publish at the end, once we verify that all packages have been uploaded. (And if not, we could e.g. drop whatever we did upload to try again.)
We can publish (and drop) uploaded packages using the API.
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 indeed I think it's another valid approach... But I guess there is still a chance of the process being stopped midway in this approach when publishing right? Not sure if having all the artifacts uploaded would be enough for the plugin to fail on the next attempt though...
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 good point, but this should be much faster (seconds vs minutes), so the chance is very low I'd say.
If we (are able to) check uploaded artifacts, we could make the release job fail if it finds any.