Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions build.gradle.kts
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor Author

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...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there is still a chance of the process being stopped midway in this approach when publishing

Hmm good point, but this should be much faster (seconds vs minutes), so the chance is very low I'd say.

Not sure if having all the artifacts uploaded would be enough for the plugin to fail on the next attempt though...

If we (are able to) check uploaded artifacts, we could make the release job fail if it finds any.

Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,35 @@ tasks.named<org.jetbrains.dokka.gradle.DokkaMultiModuleTask>("dokkaHtmlMultiModu
outputDirectory.set(file("docs/${project.property("VERSION_NAME")}"))
includes.from("README.md")
}

tasks.register("listPublications") {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 ->
val publishing = subproject.extensions.findByType<PublishingExtension>()

if (publishing != null) {
publishing.publications.forEach { publication ->
if (publication is 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")
}
}
}
}
}
}
109 changes: 109 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

@tonidero tonidero Nov 4, 2025

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
Expand All @@ -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: [
Expand Down Expand Up @@ -143,6 +165,93 @@ 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)
require 'rest-client'
require 'json'
require 'uri'

auth_token = ENV["FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL"]
if auth_token.nil? || auth_token.empty?
UI.user_error!("FETCH_PUBLICATIONS_USER_TOKEN_MAVEN_CENTRAL environment variable is not set. Please provide a valid token to check Maven Central publications.")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This has already been set in CircleCI android (need to add it in PHC and KMP once we move this to the plugin)

end

base_url = "https://central.sonatype.com/api/v1/publisher/published"
existing_artifacts = []

artifact_ids.each do |artifact_id|
# Build query parameters for the API endpoint with proper URI encoding
# The API uses 'name' parameter which corresponds to the artifact_id
api_url = "#{base_url}?namespace=#{URI.encode_www_form_component(group_id)}&name=#{URI.encode_www_form_component(artifact_id)}&version=#{URI.encode_www_form_component(version)}"

UI.verbose("Checking Sonatype API for publication status: #{group_id}:#{artifact_id}:#{version}")

begin
response = RestClient.get(
api_url,
{
'Authorization' => "Bearer #{auth_token}",
'accept' => 'application/json'
}
)

# Parse JSON response
response_data = JSON.parse(response.body)

# Check if published field is true
if response_data["published"] == true
existing_artifacts << artifact_id
UI.important("Artifact #{group_id}:#{artifact_id}:#{version} already exists in Maven Central")
end
rescue => e
UI.user_error!("Failed to check #{group_id}:#{artifact_id}:#{version}: #{e.message}")
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|
error_message += " - #{group_id}:#{artifact_id}:#{version}\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
Expand Down
16 changes: 8 additions & 8 deletions fastlane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ Automatically bumps version, replaces version numbers, updates changelog and cre

Creates github release

### android validate_version_not_in_maven_central

```sh
[bundle exec] fastlane android validate_version_not_in_maven_central
```

Check if version already exists in Maven Central

### android deploy

```sh
Expand Down Expand Up @@ -233,14 +241,6 @@ Builds a Purchase Tester APK and prompts for:



### android send_slack_load_shedder_integration_test

```sh
[bundle exec] fastlane android send_slack_purchases_integration_test
```



### android update_paywall_preview_resources_submodule

```sh
Expand Down