diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f36f11..007b57c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 1.5.0 + +##### Add +* [issue#58] Add new `publishSocketTimeoutInSeconds` parameter to change the socket timeout for publish requests in seconds. +* [issue#59] Add new `credentials` parameter to provide credentials as a base64 encoded string. + +##### Breaking Changes +* Remove support of Sonatype. It means that you can't use the plugin from Maven Central. You must to use the Gradle Portal. + To do this, you need to add the following code to your `settings.gradle.kts`: + ```kotlin + pluginManagement { + repositories { + gradlePluginPortal() + } + } + ``` +* Change classpath dependency from `ru.cian:huawei-publish-gradle-plugin:` to `ru.cian.huawei-plugin:plugin:`. +* Remove support of `clientId` and `clientSecret` CLI params. Use `credentials` or `credentialsPath` params instead. + # 1.4.2 ##### Add @@ -6,7 +25,7 @@ ##### Fix * Fix correct mustRunAfter publish task for `assemble*` and `bundle*` tasks for Gradle 8. -##### Breaking changes +##### Breaking Changes Changed `releaseNotes` configuration block. Instead of ``` diff --git a/README.md b/README.md index 764ff7f..840e94a 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,12 @@ Huawei AppGallery Publishing Gradle Plugin -[![Maven Central](https://img.shields.io/maven-central/v/ru.cian/huawei-publish-gradle-plugin.svg)](https://search.maven.org/search?q=a:huawei-publish-gradle-plugin) - + [![License](https://img.shields.io/github/license/srs/gradle-node-plugin.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) -The plugin allows to publish the android release build files (`*.apk` and `*.aab`) to the Huawei AppGallery by use official [Huawei Publish API (v2)](https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-References/agcapi-appid-list_v2) +The plugin allows to publish the android release build files (`*.apk` and `*.aab`) to the Huawei AppGallery by use official [Huawei Publish API (v2)](https://developer.huawei.com/consumer/en/doc/AppGallery-connect-References/agcapi-obtain_token-0000001158365043) -:construction: _That's unofficial plugin. We did it for himself and share it for you._ +:construction: _That's unofficial plugin. We made it for ourselves and are sharing it for you._ # Table of contents @@ -23,7 +22,7 @@ The plugin allows to publish the android release build files (`*.apk` and `*.aab - [Adding the plugin to your project](#adding-the-plugin-to-your-project) - [Using the Gradle plugin DSL](#using-the-gradle-plugin-dsl) - [Using the `apply` method](#using-the-apply-method) - - [Quickstart Plugin Configuration](#quickstart-plugin-configuration) + - [Quick Start Plugin Configuration](#quick-start-plugin-configuration) - [Full Plugin Configuration](#full-plugin-configuration) - [Plugin usage](#plugin-usage) - [CLI Plugin Configuration](#cli-plugin-configuration) @@ -79,80 +78,26 @@ plugins { } ``` -
-Snapshot builds are also available -___ - -You'll need to add the Sonatype snapshots repository. -Look for the actual version of the snapshot in the name of the opened `snapshot-` repository branch. - -in `./settings.gradle` - -```kotlin -pluginManagement { - - resolutionStrategy { - eachPlugin { - if(requested.id.namespace == "ru.cian") { - useModule("ru.cian:huawei-publish-gradle-plugin:") - } - } - } - - plugins { - id("ru.cian.huawei-publish-gradle-plugin") version huaweiPublish apply false - } - - repositories { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - } -} -``` -___ -
- ## Using the `apply` method ```groovy buildscript { repositories { - mavenCentral() // or gradlePluginPortal() - } - - dependencies { - classpath "ru.cian:huawei-publish-gradle-plugin:" - } -} - -apply plugin: 'com.android.application' -apply plugin: 'ru.cian.huawei-publish-gradle-plugin' -``` -
-Snapshot builds are also available -___ - -You'll need to add the Sonatype snapshots repository. -Look for the actual version of the snapshot in the name of the opened `snapshot-` repository branch. - -```kotlin -buildscript { - repositories { - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + gradlePluginPortal() } dependencies { - classpath "ru.cian:huawei-publish-gradle-plugin:-SNAPSHOT" + classpath "ru.cian.huawei-plugin:plugin:" } } apply plugin: 'com.android.application' apply plugin: 'ru.cian.huawei-publish-gradle-plugin' ``` -___ -
+## Quick Start Plugin Configuration -## Quickstart Plugin Configuration +Before using the plugin you should get `client_id` and `client_secret` from [AppGallery Connect API Getting Started](https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agcapi-getstarted). Minimal configuration for plugin usage: @@ -164,19 +109,21 @@ huaweiPublish { instances { create("release") { /** - * Path to json file with AppGallery credentials params (`client_id` and `client_secret`). + * Description: The AppGallery credentials params (`client_id` and `client_secret`) in json format witch encoded to Base64. * How to get credentials see [AppGallery Connect API Getting Started](https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agcapi-getstarted). - * Plugin credential json example: + * + * Credential json example: * { * "client_id": "", * "client_secret": "" * } + * Base64 encoded value example: "ewogICAgImNsaWVudF9pZCI6ICI8Q0xJRU5UX0lEPiIsCiAgICAiY2xpZW50X3NlY3JldCI6ICI8Q0xJRU5UX1NFQ1JFVD4iCn0=" * * Type: String (Optional) * Default value: `null` (but plugin wait that you provide credentials by CLI params) - * CLI: `--credentialsPath` - */ - credentialsPath = "$rootDir/huawei-credentials-release.json" + * CLI: `--credentials` + */ + credentials = "" /** * 'apk' or 'aab' for corresponding build format. @@ -216,8 +163,28 @@ huaweiPublish { instances { create("release") { /** - * Path to json file with AppGallery credentials params (`client_id` and `client_secret`). + * Description: The AppGallery credentials params (`client_id` and `client_secret`) in json format witch encoded to Base64. * How to get credentials see [AppGallery Connect API Getting Started](https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agcapi-getstarted). + * High priority than `credentialsPath` parameter. + * + * Credential json example: + * { + * "client_id": "", + * "client_secret": "" + * } + * Base64 encoded value example: "ewogICAgImNsaWVudF9pZCI6ICI8Q0xJRU5UX0lEPiIsCiAgICAiY2xpZW50X3NlY3JldCI6ICI8Q0xJRU5UX1NFQ1JFVD4iCn0=" + * + * Type: String (Optional) + * Default value: `null` (but plugin wait that you provide credentials by CLI params) + * CLI: `--credentials` + */ + credentials = "" + + /** + * Description: Path to json file with AppGallery credentials params (`client_id` and `client_secret`). + * How to get credentials see [AppGallery Connect API Getting Started](https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agcapi-getstarted). + * Low priority than `credentials` parameter. + * * Plugin credential json example: * { * "client_id": "", @@ -232,22 +199,47 @@ huaweiPublish { /** * Deploy type. Available values: - * '`publish`' to deploy and submit build on users; - * '`draft`' to deploy and save as draft without submit on users; - * '`upload-only`' to deploy without draft saving and submit on users; * Type String (Optional) * Default value: `publish` - * CLI `--deployType` + * Gradle: available values: + * ru.cian.huawei.publish.DeployType.PUBLISH + * ru.cian.huawei.publish.DeployType.UPLOAD_ONLY + * ru.cian.huawei.publish.DeployType.DRAFT + * CLI: `--deployType`, available values: + * 'publish' to deploy and submit build on users; + * 'draft' to deploy and save as draft without submit on users; + * 'upload-only' to deploy without draft saving and submit on users; */ deployType = ru.cian.huawei.publish.DeployType.PUBLISH /** - * 'apk' or 'aab' for corresponding build format. + * Description: Build file format. * Type: String (Optional) * Default value: `apk` - * CLI: `--buildFormat` + * Gradle: available values: + * ru.cian.huawei.publish.BuildFormat.APK + * ru.cian.huawei.publish.BuildFormat.AAB + * CLI: `--buildFormat`, available values: + * 'apk' – for APK build format; + * 'aab' – for AAB build format. */ buildFormat = ru.cian.huawei.publish.BuildFormat.APK + + /** + * Description: By default, the plugin searches for the assembly file at the standard file path. Use param to change file path. + * Type: String (Optional) + * Default value: null + * CLI: `--buildFile` + */ + buildFile = "${buildDir}/app/outputs/apk/release/app-release.apk" + + /** + * The socket timeout for publish requests in seconds. + * Type: Long (Optional) + * Default value: `60` // (1min) + * CLI: `--publishSocketTimeoutInSeconds` + */ + publishSocketTimeoutInSeconds = 60 /** * API use chunks to upload the build file. So after last file part server needs some time to join and check whole file. @@ -379,9 +371,12 @@ huaweiPublish { huaweiPublish { instances { release { - credentialsPath = "$rootDir/huawei-credentials-release.json" + credentials = "" // High priority than `credentialsPath`; + credentialsPath = "$rootDir/huawei-credentials-release.json" // Low priority than `credentials`; deployType = "publish" buildFormat = "apk" + buildFile = "${buildDir}/app/outputs/apk/release/app-release.apk" + publishSocketTimeoutInSeconds = 60 publishTimeoutMs = 600_000 publishPeriodMs = 15_000 releaseTime = "2025-10-21T06:00:00+0300" @@ -454,9 +449,12 @@ CLI params are more priority than gradle configuration params. ```bash ./gradlew assembleRelease publishHuaweiAppGalleryRelease \ - --credentialsPath="/sample-kotlin/huawei-credentials.json" \ + --credentials = "" \ # High priority than `credentialsPath`; + --credentialsPath="./sample-kotlin/huawei-credentials.json" \ # Low priority than `credentials`; --deployType=publish \ --buildFormat=apk \ + --buildFile="./app/outputs/apk/release/app-release.apk" + --publishSocketTimeoutInSeconds=60 \ --publishTimeoutMs=600000 \ --publishPeriodMs=15000 \ --releaseTime="2025-10-21T06:00:00+0300" \ @@ -479,7 +477,7 @@ From gradle build script: huaweiPublish { instances { release { - credentialsPath = "$rootDir/sample1/huawei-credentials.json" + credentials = "" deployType = "draft" } } @@ -490,7 +488,7 @@ or execute from command line: ```bash ./gradlew assembleRelease publishHuaweiAppGalleryRelease \ - --credentialsPath="$rootDir/sample1/huawei-credentials.json" \ + --credentials = "" \ --deployType=draft ``` @@ -506,7 +504,7 @@ From gradle build script: huaweiPublish { instances { release { - credentialsPath = "$rootDir/sample1/huawei-credentials.json" + credentials = "" buildFormat = "aab" } } @@ -516,7 +514,7 @@ or execute from command line: ```bash ./gradlew assembleRelease publishHuaweiAppGalleryRelease \ - --credentialsPath="$rootDir/sample1/huawei-credentials.json" \ + --credentials = "" \ --buildFormat=aab ``` @@ -547,7 +545,7 @@ From gradle build script: huaweiPublish { instances { release { - credentialsPath = "$rootDir/sample1/huawei-credentials.json" + credentials = "" releasePhase { startTime = "2020-11-13T08:01:02+0300" endTime = "2020-11-20T15:30:00+0300" @@ -562,8 +560,7 @@ or execute from command line: ```bash ./gradlew assembleRelease publishHuaweiAppGalleryRelease \ - --clientId= \ - --clientSecret= \ + --credentials = "" \ --releasePhaseStartTime=2020-11-13T08:01:02+0300 \ --releasePhaseEndTime=2020-11-20T15:30:00+0300 \ --releasePhasePercent=10.0 diff --git a/RELEASING.md b/RELEASING.md index 3acbb19..f0f775f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,23 +6,18 @@ elaborate and requires a good deal of local configuration. This guide should wa you through it. It won't do anyone outside of KeepSafe any good, but the workflow is representative of just about any project deploying via Sonatype. -We currently deploy to both Maven Central (via Sonatype's OSS Nexus instance) and to -plugins.gradle.org. +We currently deploy to plugins.gradle.org. ## Prerequisites 1. A *published* GPG code-signing key -2. A Sonatype Nexus OSS account with permission to publish in ru.cian -3. A plugins.gradle.org account with permission to publish in ru.cian -4. Permission to push directly to https://github.com/cianru/huawei-publish-gradle-plugin +1. A plugins.gradle.org account with permission to publish in ru.cian +1. Permission to push directly to https://github.com/cianru/huawei-publish-gradle-plugin ## Contents page 1. [Setup](docs/releasing/01-setup.md) -2. [Prepare Release Commit](docs/releasing/02-prepare-release-commit.md) -3. [Pushing a SNAPSHOT build to local repository](docs/releasing/03-publish-a-snapshot-to-local-repository.md) -4. [Pushing a SNAPSHOT build to Sonatype](docs/releasing/04-publish-a-snapshot-to-sonatype.md) -5. [Pushing a release build to Sonatype](docs/releasing/05-publish-a-release-build-to-sonatype.md) -6. [Pushing a release build to Bintray (DEPRECATED)](docs/releasing/06-publish-a-release-build-to-bintray.md) -7. [Pushing a release build to Gradle Plugin Portal](docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md) -8. [Prepare Next Snapshot Version Commit](docs/releasing/08-prepare-next-snapshot-version-commit.md) +1. [Prepare Release Commit](docs/releasing/02-prepare-release-commit.md) +1. [Pushing a build to local repository](docs/releasing/03-publish-to-local-repository) +1. [Pushing a release build to Gradle Plugin Portal](docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md) +1. [Prepare Next Alpha Version Commit](docs/releasing/08-prepare-alpha-version-commit) diff --git a/docs/releasing/01-setup.md b/docs/releasing/01-setup.md index bf284f8..64e0ff8 100644 --- a/docs/releasing/01-setup.md +++ b/docs/releasing/01-setup.md @@ -1,18 +1,5 @@ ## Setup -1. Add your GPG key to your github profile - this is required - for github to know that your commits and tags are "verified". -1. Configure your code-signing key in ~/.gradle.properties: - ```gradle - signing.keyId= - signing.password= - signing.secretKeyRingFile=/path/to/your/secring.gpg - ``` -1. Configure your Sonatype credentials in ~/.gradle/gradle.properties: - ```gradle - SONATYPE_NEXUS_USERNAME= - SONATYPE_NEXUS_PASSWORD= - ``` 1. Configure git with your codesigning key; make sure it's the same as the one you use to sign binaries (i.e. it's the same one you adaded to gradle.properties): ```bash diff --git a/docs/releasing/02-prepare-release-commit.md b/docs/releasing/02-prepare-release-commit.md index 3abe41e..5810803 100644 --- a/docs/releasing/02-prepare-release-commit.md +++ b/docs/releasing/02-prepare-release-commit.md @@ -1,14 +1,15 @@ ## Prepare Release Commit -1. Edit ./plugin/gradle.properties, remove '-SNAPSHOT' from the VERSION property -2. Make a *signed* commit: +1. Edit the `gradle.properties` file: + Remove `-alpha` from the `VERSION_NAME` and set the version to the release version. For example: `1.0.0`. +1. Make a *signed* commit: ```bash git commit -m "Release vX.Y.Z" ``` -3. Make a *signed* tag (check existing tags for message format): +1. Make a *signed* tag (check existing tags for message format): ```bash git tag -a "vX.Y.Z" -m "vX.Y.Z" ``` -4. Push all of our work to Github to make it official: +1. Push all of our work to Github to make it official: ```bash git push --tags origin master diff --git a/docs/releasing/03-publish-a-snapshot-to-local-repository.md b/docs/releasing/03-publish-to-local-repository.md similarity index 70% rename from docs/releasing/03-publish-a-snapshot-to-local-repository.md rename to docs/releasing/03-publish-to-local-repository.md index 34a62ad..c80ee8e 100644 --- a/docs/releasing/03-publish-a-snapshot-to-local-repository.md +++ b/docs/releasing/03-publish-to-local-repository.md @@ -1,13 +1,9 @@ -## Pushing a SNAPSHOT build to local repository +## Pushing a build to local repository 1. Open the plugin directory: ``` cd ./plugin ``` -2. Edit the `gradle.properties` file: - ```bash - IS_SNAPSHOT=true - ``` 3. Publish to local repository ```bash ./gradlew :plugin:publishToMavenLocal diff --git a/docs/releasing/04-publish-a-snapshot-to-sonatype.md b/docs/releasing/04-publish-a-snapshot-to-sonatype.md deleted file mode 100644 index 24aba6f..0000000 --- a/docs/releasing/04-publish-a-snapshot-to-sonatype.md +++ /dev/null @@ -1,15 +0,0 @@ -## Pushing a SNAPSHOT build to Sonatype - -1. Open the plugin directory: - ``` - cd ./plugin - ``` -2. Edit the `gradle.properties` file: - ```bash - IS_SNAPSHOT=true - ``` -3. Upload binaries to Sonatype: - ```bash - ./gradlew :plugin:publishHuaweiPublicationToMavenRepository - ``` -4. Check snapshot: [nexus-search](https://oss.sonatype.org/#nexus-search;quick~ru.cian) diff --git a/docs/releasing/05-publish-a-release-build-to-sonatype.md b/docs/releasing/05-publish-a-release-build-to-sonatype.md deleted file mode 100644 index ab112d4..0000000 --- a/docs/releasing/05-publish-a-release-build-to-sonatype.md +++ /dev/null @@ -1,32 +0,0 @@ -## Pushing a release build to Sonatype - -1. Open the plugin directory: - ``` - cd ./plugin - ``` -1. Edit the `gradle.properties` file: - ```bash - IS_SNAPSHOT=false - ``` -1. Edit `README.md` so that Gradle examples point to the new version -1. Edit changelog, add relevant changes, note the date and new version (follow the existing pattern) -1. Verify that the everything works: - ```bash - ./gradlew clean check - ``` -1. Verify that the code style is correct: - ```bash - ./gradlew detekt - ``` -1. Upload binaries to Sonatype: - ```bash - ./gradlew :plugin:publishHuaweiPublicationToMavenRepository - ``` -1. Check uploaded files and version Sonatype site: [search.maven.org](https://search.maven.org/search?q=ru.cian) - and [repo1.maven.org](https://repo1.maven.org/maven2/ru/cian/huawei-publish-gradle-plugin/) -1. Go to [oss.sonatype.org](https://oss.sonatype.org), log in with your credentials -1. Click "Staging Repositories" -1. Find the "ru.cian" repo, usually at the bottom of the list -1. "Close" the repository (select it then click the "close" button up top), the text field doesn't matter so put whatever you want in it -1. Wait until that's done -1. "Release" the repository, leave the checkbox "Automatically Drop" checked. Yeap, we're in Maven Central now! diff --git a/docs/releasing/06-publish-a-release-build-to-bintray.md b/docs/releasing/06-publish-a-release-build-to-bintray.md deleted file mode 100644 index 419ac3d..0000000 --- a/docs/releasing/06-publish-a-release-build-to-bintray.md +++ /dev/null @@ -1,15 +0,0 @@ -## Pushing a release build to Bintray - -1. Open the plugin directory: - ``` - cd ./plugin - ``` -1. Edit the `gradle.properties` file: - ```bash - IS_SNAPSHOT=false - ``` -1. Upload binaries to Bintray: - ```bash - ./gradlew build :plugin:bintrayUpload - ``` -1. Check uploaded files and version Bintray site: [bintray.com](https://bintray.com/myumatov/ru.cian/huawei-publish-gradle-plugin) diff --git a/docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md b/docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md index f81d49c..38788a4 100644 --- a/docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md +++ b/docs/releasing/07-publish-a-release-build-to-gradle-plugin-portal.md @@ -5,9 +5,7 @@ cd ./plugin ``` 1. Edit the `gradle.properties` file: - ```bash - IS_SNAPSHOT=false - ``` + Remove `-alpha` from the `VERSION_NAME` and set the version to the release version. For example: `1.0.0`. 1. Upload binaries to Gradle's plugin portal: ```bash ./gradlew :plugin:publishPlugins diff --git a/docs/releasing/08-prepare-alpha-version-commit.md b/docs/releasing/08-prepare-alpha-version-commit.md new file mode 100644 index 0000000..c225ad1 --- /dev/null +++ b/docs/releasing/08-prepare-alpha-version-commit.md @@ -0,0 +1,12 @@ +## Prepare Next Alpha Version Commit + +1. Create new `snapshot-` Git branch +1. Open the plugin directory: + ``` + cd ./plugin + ``` +1. Edit the `gradle.properties` file to set new `VERSION_NAME`+ `-alpha` version. For example: `1.0.0-alpha01`. +1. Make a *signed* commit: + ```bash + git commit -m "Prepare next development version" + ``` diff --git a/docs/releasing/08-prepare-next-snapshot-version-commit.md b/docs/releasing/08-prepare-next-snapshot-version-commit.md deleted file mode 100644 index 46f026d..0000000 --- a/docs/releasing/08-prepare-next-snapshot-version-commit.md +++ /dev/null @@ -1,16 +0,0 @@ -## Prepare Next Snapshot Version Commit - -1. Create new `snapshot-` Git branch -2. Open the plugin directory: - ``` - cd ./plugin - ``` -3. Edit the `gradle.properties` file to set new `VERSION_NAME` version. -4. Edit the `gradle.properties` file: -```bash -IS_SNAPSHOT=true -``` -5. Make a *signed* commit: - ```bash - git commit -m "Prepare next development version" - ``` diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 223df18..5925359 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,14 +12,15 @@ jvm = "17" kotlin = "1.9.22" detekt = "1.23.4" junitJupiter = "5.9.3" -androidGradlePlugin = "8.2.0" -sampleHuaweiPlugin = "1.4.3-SNAPSHOT" +androidGradlePlugin = "8.6.0" +sampleHuaweiPlugin = "1.5.0" [libraries] appcompat = "androidx.appcompat:appcompat:1.6.1" kotlinBom = { group = "org.jetbrains.kotlin", name = "kotlin-bom", version.ref = "kotlin" } gson = "com.google.code.gson:gson:2.8.6" okHttp = "com.squareup.okhttp3:okhttp:4.12.0" +mockServer = "com.squareup.okhttp3:mockwebserver:4.9.3" androidGradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } detektFormating = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } detektLibraries = { group = "io.gitlab.arturbosch.detekt", name = "detekt-rules-libraries", version.ref = "detekt" } @@ -39,5 +40,5 @@ dcendents = { id = "com.github.dcendents", version = "plugin:2.1" } bintray = { id = "com.jfrog.bintray", version = "1.8.5" } pluginPublish = { id = "com.gradle.plugin-publish", version = "0.15.0" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } -benManesVersions = { id = "com.github.ben-manes.versions", version = "0.46.0" } +benManesVersions = { id = "com.github.ben-manes.versions", version = "0.47.0" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 564b973..cced4b3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Dec 27 15:37:46 CET 2020 +#Mon Oct 07 08:14:46 WEST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 850e572..ec99a3c 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -4,14 +4,11 @@ plugins { `signing` alias(libs.plugins.detekt) alias(libs.plugins.pluginPublish) - alias(libs.plugins.bintray) alias(libs.plugins.dokka) alias(libs.plugins.kotlinJvm) } apply(from = "$projectDir/config/check-jdk.gradle") -apply(from = "$projectDir/config/maven-publish.gradle") -apply(from = "$projectDir/config/bintray-publish.gradle") apply(from = "$projectDir/config/gradle-portal.gradle") detekt { @@ -83,6 +80,7 @@ dependencies { implementation(platform(libs.kotlinBom)) implementation(libs.gson) implementation(libs.okHttp) + implementation(libs.mockServer) compileOnly(libs.androidGradlePlugin) detektPlugins(libs.detektFormating) diff --git a/plugin/config/detekt/detekt-config.yml b/plugin/config/detekt/detekt-config.yml index bbdb0e2..7d747fc 100644 --- a/plugin/config/detekt/detekt-config.yml +++ b/plugin/config/detekt/detekt-config.yml @@ -100,7 +100,7 @@ formatting: active: false MaximumLineLength: active: true - maxLineLength: 120 + maxLineLength: 160 ignoreBackTickedIdentifier: true MultiLineIfElse: active: true @@ -110,6 +110,8 @@ formatting: active: false PackageName: active: true + StringTemplate: + active: false SpacingAroundAngleBrackets: active: true SpacingAroundDoubleColon: diff --git a/plugin/config/maven-publish.gradle b/plugin/config/maven-publish.gradle deleted file mode 100644 index f72d76c..0000000 --- a/plugin/config/maven-publish.gradle +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2020 Aleksandr Mirko - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -apply from: "$projectDir/config/publish-methods.gradle" - -private String getRepositoryUsername() { - return hasProperty('SONATYPE_NEXUS_USERNAME') - ? SONATYPE_NEXUS_USERNAME - : System.env.SONATYPE_NEXUS_USERNAME -} - -private String getRepositoryPassword() { - return hasProperty('SONATYPE_NEXUS_PASSWORD') - ? SONATYPE_NEXUS_PASSWORD - : System.env.SONATYPE_NEXUS_PASSWORD -} - -private String getRepositoryUrl() { - return isReleaseBuild() - ? "https://oss.sonatype.org/service/local/staging/deploy/maven2/" - : "https://oss.sonatype.org/content/repositories/snapshots/" -} - -def hasDeploymentTask(Project project) { - def deploymentTasks = [ - "publishPlugins", - "publish" - ] - deploymentTasks.any { project.gradle.taskGraph.hasTask(it) } -} - -task sourceJar(type: Jar) { - archiveClassifier.set("sources") - from sourceSets.main.allSource -} - -task javadocJar(type: Jar, dependsOn: dokkaHtml) { - archiveClassifier.set("javadoc") - from dokkaHtml.outputDirectory -} - -jar { - manifest { - attributes( - "Implementation-Title": POM_ARTIFACT_ID, - "Implementation-Version": getVersionName() - ) - } -} - -artifacts { - archives jar - archives sourceJar - archives javadocJar -} - -publishing { - publications { - huawei(MavenPublication) { - groupId = GROUP - artifactId = POM_ARTIFACT_ID - version = getVersionName() - - from components.java - artifact sourceJar - artifact javadocJar - - pom { - name = POM_NAME - packaging = POM_PACKAGING - description = POM_DESCRIPTION - url = POM_URL - - scm { - url = POM_SCM_URL - connection = POM_SCM_CONNECTION - developerConnection = POM_SCM_DEV_CONNECTION - } - - licenses { - license { - name = POM_LICENSE_NAME - url = POM_LICENSE_URL - distribution = POM_LICENSE_DIST - } - } - - developers { - developer { - id = POM_DEVELOPER_ID - name = POM_DEVELOPER_NAME - email = POM_DEVELOPER_EMAIL - } - } - - organization { - name = POM_DEVELOPER_NAME - url = POM_DEVELOPER_CITE - } - } - } - } - - repositories { - maven { - url = getRepositoryUrl() - credentials { - username = getRepositoryUsername() - password = getRepositoryPassword() - } - } - } -} - -signing { - required { isReleaseBuild() && hasDeploymentTask(project) } - sign project.publishing.publications -} - -// At the moment, signing kotlin metadata fails with a Gradle error for snapshots. -// To work around, we'll need to suppress signing for them, which is different from -// 'signing.required = false'. -if (!isReleaseBuild()) { - tasks.withType(Sign) { - onlyIf { isReleaseBuild() && hasDeploymentTask(project) } - } -} diff --git a/plugin/config/publish-methods.gradle b/plugin/config/publish-methods.gradle index 51231c1..c9a07c1 100644 --- a/plugin/config/publish-methods.gradle +++ b/plugin/config/publish-methods.gradle @@ -1,15 +1,7 @@ ext { - isReleaseBuild = this.&isReleaseBuild getVersionName = this.&getVersionName } -private boolean isReleaseBuild() { - return IS_SNAPSHOT != "true" -} - private String getVersionName() { - if (!isReleaseBuild()) { - return VERSION_NAME + "-SNAPSHOT" - } return VERSION_NAME } diff --git a/plugin/gradle.properties b/plugin/gradle.properties index 082b9ad..706f94a 100644 --- a/plugin/gradle.properties +++ b/plugin/gradle.properties @@ -8,9 +8,8 @@ android.enableJetifier=true #################################################################################################### -GROUP=ru.cian -VERSION_NAME=1.4.3 -IS_SNAPSHOT=true +GROUP=ru.cian.huawei-plugin +VERSION_NAME=1.5.0 REQUIRED_JDK_VERSION=17 diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishConfig.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishConfig.kt index f90b611..98d829a 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishConfig.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishConfig.kt @@ -9,6 +9,7 @@ internal data class HuaweiPublishConfig( val artifactFile: File, val publishTimeoutMs: Long, val publishPeriodMs: Long, + val publishSocketTimeoutInSeconds: Long, val releaseTime: String?, val releasePhase: ReleasePhaseConfig?, val releaseNotes: ReleaseNotesConfig?, @@ -30,9 +31,9 @@ internal data class HuaweiPublishCliParam( val deployType: DeployType? = null, val publishTimeoutMs: String? = null, val publishPeriodMs: String? = null, + val publishSocketTimeoutInSeconds: String? = null, + val credentials: String? = null, val credentialsPath: String? = null, - val clientId: String? = null, - val clientSecret: String? = null, val buildFormat: BuildFormat? = null, val buildFile: String? = null, val releaseTime: String? = null, diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishExtension.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishExtension.kt index 64fb8fc..86351df 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishExtension.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishExtension.kt @@ -3,6 +3,7 @@ package ru.cian.huawei.publish import groovy.lang.Closure import org.gradle.api.Project +private const val DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS = 60L private const val DEFAULT_PUBLISH_TIMEOUT_MS = 10 * 60 * 1000L private const val DEFAULT_PUBLISH_PERIOD_MS = 15 * 1000L @@ -30,10 +31,12 @@ class HuaweiPublishExtensionConfig( * For example: * var param by GradleProperty(project, String::class.java) */ + var credentials: String? = null var credentialsPath: String? = null var deployType = DeployType.PUBLISH var publishTimeoutMs: Long = DEFAULT_PUBLISH_TIMEOUT_MS var publishPeriodMs: Long = DEFAULT_PUBLISH_PERIOD_MS + var publishSocketTimeoutInSeconds: Long = DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS var buildFormat: BuildFormat = BuildFormat.APK var buildFile: String? = null var releaseTime: String? = null @@ -62,10 +65,12 @@ class HuaweiPublishExtensionConfig( override fun toString(): String { return "HuaweiPublishExtensionConfig(" + "name='$name', " + + "credentials='$credentials', " + "credentialsPath='$credentialsPath', " + "deployType='$deployType', " + "publishTimeoutMs='$publishTimeoutMs', " + "publishPeriodMs='$publishPeriodMs', " + + "publishSocketTimeoutInSeconds='$publishSocketTimeoutInSeconds', " + "buildFormat='$buildFormat', " + "buildFile='$buildFile', " + "releaseTime='$releaseTime', " + diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishPlugin.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishPlugin.kt index 491173b..bd5134d 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishPlugin.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishPlugin.kt @@ -1,5 +1,6 @@ package ru.cian.huawei.publish +import com.android.build.api.artifact.SingleArtifact import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.ApplicationVariant import com.android.build.api.variant.VariantSelector @@ -11,6 +12,7 @@ import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType +import java.io.File class HuaweiPublishPlugin : Plugin { @@ -42,6 +44,23 @@ class HuaweiPublishPlugin : Plugin { val publishTaskName = "${HuaweiPublishTask.TASK_NAME}$variantName" val publishTask = project.tasks.register(publishTaskName, variant) scheduleTasksOrder(publishTask, project, variantName) +// +// +// val variantName = variant.name +// val variantApplicationId = variant.applicationId.get() +// val variantApkBuildFilePath = getFinalApkArtifactCompat(variant).singleOrNull()?.absolutePath +// val variantAabBuildFilePath = getFinalBundleArtifactCompat(variant).singleOrNull()?.absolutePath +// // val variantApkBuildFilePath = project.rootProject.path + "/app/build/outputs/bundle/release/app-release.aab" +// // val variantAabBuildFilePath = project.rootProject.path + "/app/build/outputs/apk/release/app-release.apk" +// val publishTaskName = "${HuaweiPublishTask.TASK_NAME}${variantName.capitalize()}" +// val publishTask = project.tasks.register( +// publishTaskName, +// variantApplicationId, +// variantName, +// Optional.ofNullable(variantApkBuildFilePath), +// Optional.ofNullable(variantAabBuildFilePath), +// ) +// // scheduleTasksOrder(publishTask, project, variantName) } private fun scheduleTasksOrder( @@ -65,4 +84,21 @@ class HuaweiPublishPlugin : Plugin { publishTask.get().mustRunAfter(assembleTask) } } + + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16777 + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16775 + @SuppressWarnings("UnusedPrivateMember") + private fun getFinalApkArtifactCompat(variant: ApplicationVariant): List { + val apkDirectory = variant.artifacts.get(SingleArtifact.APK).get() + return variant.artifacts.getBuiltArtifactsLoader().load(apkDirectory) + ?.elements?.map { element -> File(element.outputFile) } + ?: apkDirectory.asFileTree.matching { include("*.apk") }.map { it.absolutePath }.map { File(it) } + ?: emptyList() + } + + @SuppressWarnings("UnusedPrivateMember") + private fun getFinalBundleArtifactCompat(variant: ApplicationVariant): List { + val aabFile = variant.artifacts.get(SingleArtifact.BUNDLE).get().asFile + return listOf(aabFile) + } } diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishTask.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishTask.kt index 5abe842..7e91682 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishTask.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/HuaweiPublishTask.kt @@ -12,8 +12,10 @@ import ru.cian.huawei.publish.models.response.FileServerOriResultResponse import ru.cian.huawei.publish.models.response.SubmitResponse import ru.cian.huawei.publish.service.HuaweiService import ru.cian.huawei.publish.service.HuaweiServiceImpl -import ru.cian.huawei.publish.service.MockHuaweiService -import ru.cian.huawei.publish.utils.BuildFileProvider +import ru.cian.huawei.publish.service.mock.MockServerWrapper +import ru.cian.huawei.publish.service.mock.MockServerWrapperImpl +import ru.cian.huawei.publish.service.mock.MockServerWrapperStub +import ru.cian.huawei.publish.utils.BuildFileProviderDeprecated import ru.cian.huawei.publish.utils.ConfigProvider import ru.cian.huawei.publish.utils.Logger import ru.cian.huawei.publish.utils.RELEASE_DATE_TIME_FORMAT @@ -25,16 +27,32 @@ import ru.cian.huawei.publish.utils.FileWrapper @DisableCachingByDefault open class HuaweiPublishTask @Inject constructor( - private val variant: ApplicationVariant + private val variant: ApplicationVariant, +// private val variantApplicationId: String, +// private val variantName: String, +// private val variantApkBuildFilePath: Optional, +// private val variantAabBuildFilePath: Optional, ) : DefaultTask() { + private val logger by lazy { Logger(project) } + private lateinit var huaweiPublishExtension: HuaweiPublishExtension + + private val variantName = variant.name + + private val variantApplicationId = variant.applicationId.get() + init { group = PublishingPlugin.PUBLISH_TASK_GROUP description = "Upload and publish application build file " + - "to Huawei AppGallery Store for ${variant.name} buildType" + "to Huawei AppGallery Store for ${variantName} buildType" + + huaweiPublishExtension = project.extensions + .findByName(HuaweiPublishExtension.MAIN_EXTENSION_NAME) as? HuaweiPublishExtension + ?: throw IllegalArgumentException( + "Plugin extension '${HuaweiPublishExtension.MAIN_EXTENSION_NAME}' " + + "is not available at build.gradle of the application module" + ) } - - private val logger by lazy { Logger(project) } @get:Internal @set:Option( @@ -60,26 +78,26 @@ open class HuaweiPublishTask @get:Internal @set:Option( - option = "credentialsPath", - description = "File path with AppGallery credentials params ('client_id' and 'client_secret')" + option = "publishSocketTimeoutInSeconds", + description = "The socket timeout for publish requests in seconds" ) - var credentialsPath: String? = null + var publishSocketTimeoutInSeconds: String? = null @get:Internal @set:Option( - option = "clientId", - description = "'client_id' param from AppGallery credentials. " + - "The key more priority than value from 'credentialsPath'" + option = "credentials", + description = "AppGallery credentials in Base64 format. " + + "Decoded json example: {\"client_id\": \"\", \"client_secret\": \"\"})" ) - var clientId: String? = null + var credentials: String? = null @get:Internal @set:Option( - option = "clientSecret", - description = "'client_secret' param from AppGallery credentials. " + - "The key more priority than value from 'credentialsPath'" + option = "credentialsPath", + description = "File path with AppGallery credentials params ('client_id' and 'client_secret') in json format. " + + "Json example: {\"client_id\": \"\", \"client_secret\": \"\"})" ) - var clientSecret: String? = null + var credentialsPath: String? = null @get:Internal @set:Option( @@ -91,7 +109,8 @@ open class HuaweiPublishTask @get:Internal @set:Option( option = "buildFile", - description = "Path to build file. 'null' means use standard path for 'apk' and 'aab' files." + description = "By default, the plugin searches for the assembly file at the standard file path. " + + "Use param to change file path." ) var buildFile: String? = null @@ -162,28 +181,19 @@ open class HuaweiPublishTask @TaskAction fun action() { - val huaweiService: HuaweiService = if (apiStub == true) MockHuaweiService() else HuaweiServiceImpl(logger) - val huaweiPublishExtension = project.extensions - .findByName(HuaweiPublishExtension.MAIN_EXTENSION_NAME) as? HuaweiPublishExtension + val extension = huaweiPublishExtension.instances.find { it.name.equals(variantName, ignoreCase = true) } ?: throw IllegalArgumentException( "Plugin extension '${HuaweiPublishExtension.MAIN_EXTENSION_NAME}' " + - "is not available at build.gradle of the application module" - ) - - val buildTypeName = variant.name - val extension = huaweiPublishExtension.instances.find { it.name.equals(buildTypeName, ignoreCase = true) } - ?: throw IllegalArgumentException( - "Plugin extension '${HuaweiPublishExtension.MAIN_EXTENSION_NAME}' " + - "instance with name '$buildTypeName' is not available" + "instance with name '$variantName' is not available" ) val cli = HuaweiPublishCliParam( deployType = deployType, publishTimeoutMs = publishTimeoutMs, publishPeriodMs = publishPeriodMs, + publishSocketTimeoutInSeconds = publishSocketTimeoutInSeconds, + credentials = credentials, credentialsPath = credentialsPath, - clientId = clientId, - clientSecret = clientSecret, buildFormat = buildFormat, buildFile = buildFile, releaseTime = releaseTime, @@ -199,8 +209,14 @@ open class HuaweiPublishTask logger.i("extension=$extension") logger.i("cli=$cli") - logger.v("Prepare input config") - val buildFileProvider = BuildFileProvider(variant = variant, logger = logger) + logger.v("1. Prepare input config") +// val buildFileProvider = BuildFileProviderNew( +// variantApkBuildFilePath = variantApkBuildFilePath.orElseGet(null), +// variantAabBuildFilePath = variantAabBuildFilePath.orElseGet(null), +// logger = logger, +// ) + val buildFileProvider = BuildFileProviderDeprecated(variant = variant, logger = logger) + val config = ConfigProvider( extension = extension, cli = cli, @@ -211,23 +227,31 @@ open class HuaweiPublishTask logger.v("Found build file: `${config.artifactFile.name}`") - logger.v("Get Access Token") + val mockServerWrapper = getMockServerWrapper() + mockServerWrapper.start() + + val huaweiService = HuaweiServiceImpl( + logger = logger, + baseEntryPoint = mockServerWrapper.getBaseUrl(), + publishSocketTimeoutInSeconds = config.publishSocketTimeoutInSeconds, + ) + + logger.v("2. Get Access Token") val token = huaweiService.getToken( clientId = config.credentials.clientId, clientSecret = config.credentials.clientSecret ) logger.i("token=$token") - logger.v("Get App ID") - val applicationId = variant.applicationId.get() + logger.v("3. Get App ID") val appInfo = huaweiService.getAppID( clientId = config.credentials.clientId, accessToken = token, - packageName = applicationId + packageName = variantApplicationId ) logger.i("appInfo=$appInfo") - logger.v("Get Upload Url") + logger.v("4. Get Upload Url") val uploadUrl = huaweiService.getUploadingBuildUrl( clientId = config.credentials.clientId, accessToken = token, @@ -236,7 +260,7 @@ open class HuaweiPublishTask ) logger.i("uploadUrl=$uploadUrl") - logger.v("Upload build file '${config.artifactFile.path}'") + logger.v("5. Upload build file '${config.artifactFile.path}'") val fileInfoListResult = huaweiService.uploadBuildFile( uploadUrl = uploadUrl.uploadUrl, authCode = uploadUrl.authCode, @@ -248,7 +272,7 @@ open class HuaweiPublishTask config.releaseNotes?.descriptions?.forEachIndexed { index, releaseNote -> val newFeatures = releaseNote.newFeatures logger.v( - "Upload release notes: ${index + 1}/${config.releaseNotes.descriptions.size}, " + + "6. Upload release notes: ${index + 1}/${config.releaseNotes.descriptions.size}, " + "lang=${releaseNote.lang}" ) logger.i( @@ -266,11 +290,11 @@ open class HuaweiPublishTask ) } } else { - logger.v("Skip release notes uploading") + logger.v("6. Skip release notes uploading") } if (config.deployType != DeployType.UPLOAD_ONLY) { - logger.v("Update App File Info") + logger.v("7. Update App File Info") val fileInfoRequestList = mapFileInfo(fileInfoListResult, config.artifactFile.name) val appId = appInfo.value val releasePercent = config.releasePhase?.percent ?: FULL_USER_SUBMISSION_PERCENT @@ -301,7 +325,7 @@ open class HuaweiPublishTask } if (config.deployType == DeployType.PUBLISH) { - logger.v("Submit Review") + logger.v("8. Submit Review") val submitRequestFunction: () -> SubmitResponse = { getSubmitResponse( @@ -331,6 +355,7 @@ open class HuaweiPublishTask } else { logger.v("Upload build file without draft and submit on users - Successfully Done!") } + mockServerWrapper.shutdown() } @Suppress("LongParameterList") @@ -408,6 +433,16 @@ open class HuaweiPublishTask return fileInfoRequestList } + private fun getMockServerWrapper(): MockServerWrapper { + return if (apiStub == true) { + MockServerWrapperImpl( + logger = logger, + ) + } else { + MockServerWrapperStub() + } + } + internal enum class ReleaseType(val type: Int) { FULL(type = 1), PHASE(type = 3) diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/models/request/FileInfoRequest.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/models/request/FileInfoRequest.kt index 1c5732f..8ae3ab3 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/models/request/FileInfoRequest.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/models/request/FileInfoRequest.kt @@ -8,5 +8,5 @@ internal data class FileInfoRequest( @SerializedName("fileDestUrl") var fileDestUrl: String, @SerializedName("size") - var size: Int + var size: Long ) diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/models/response/FileInfo.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/models/response/FileInfo.kt index a254ebb..0fde729 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/models/response/FileInfo.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/models/response/FileInfo.kt @@ -10,5 +10,5 @@ internal data class FileInfo( @SerializedName("imageResolutionSingature") var imageResolutionSignature: String, @SerializedName("size") - var size: Int + var size: Long ) diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HttpClientHelper.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HttpClientHelper.kt index 8056a71..220f1fe 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HttpClientHelper.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HttpClientHelper.kt @@ -7,9 +7,11 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import ru.cian.huawei.publish.utils.Logger +import java.util.concurrent.TimeUnit -internal class HttpClientHelper constructor( - private val logger: Logger +internal class HttpClientHelper( + private val logger: Logger, + private val socketTimeoutInSeconds: Long, ) { private val gson by lazy { Gson() } @@ -26,7 +28,12 @@ internal class HttpClientHelper constructor( @Suppress("ThrowsCount") inline fun execute(requestBuilder: Request.Builder, url: String, headers: Map?): T { try { - val client = OkHttpClient() + val client = OkHttpClient.Builder() + .connectTimeout(socketTimeoutInSeconds, TimeUnit.SECONDS) + .writeTimeout(socketTimeoutInSeconds, TimeUnit.SECONDS) + .readTimeout(socketTimeoutInSeconds, TimeUnit.SECONDS) + .build() + val request = requestBuilder .url(url) .apply { headers?.forEach { header(it.key, it.value) } } diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HuaweiServiceImpl.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HuaweiServiceImpl.kt index c8ffb8e..477fbfc 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HuaweiServiceImpl.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/HuaweiServiceImpl.kt @@ -26,19 +26,17 @@ import ru.cian.huawei.publish.service.HttpClientHelper.Companion.MEDIA_TYPE_AAB import ru.cian.huawei.publish.service.HttpClientHelper.Companion.MEDIA_TYPE_JSON import ru.cian.huawei.publish.utils.Logger -private const val DOMAIN_URL = "https://connect-api.cloud.huawei.com/api" -private const val PUBLISH_API_URL = "$DOMAIN_URL/publish/v2" -private const val GRANT_TYPE = "client_credentials" -private const val SUBMIT_LONG_PUBLICATION_ERROR = 204144660 -private const val SUBMIT_REPEAT_TIMEOUT_MS = 3 * 60 * 1000L // 3 min - @Suppress("StringLiteralDuplication", "TooManyFunctions") -internal class HuaweiServiceImpl constructor( - private val logger: Logger +internal class HuaweiServiceImpl( + private val logger: Logger, + private val baseEntryPoint: String, + private val publishSocketTimeoutInSeconds: Long, ) : HuaweiService { private val gson = Gson() - private val httpClient = HttpClientHelper(logger) + private val httpClient = HttpClientHelper(logger, publishSocketTimeoutInSeconds) + + private val publishEntryPoint = "$baseEntryPoint/publish/v2" override fun getToken( clientId: String, @@ -52,7 +50,7 @@ internal class HuaweiServiceImpl constructor( ) val accessTokenResponse = httpClient.post( - url = "$DOMAIN_URL/oauth2/v1/token", + url = "$baseEntryPoint/oauth2/v1/token", body = gson.toJson(bodyRequest).toRequestBody(MEDIA_TYPE_JSON), headers = null, ) @@ -71,7 +69,7 @@ internal class HuaweiServiceImpl constructor( headers["client_id"] = clientId val appIdResponse = httpClient.get( - url = "$PUBLISH_API_URL/appid-list?packageName=$packageName", + url = "$publishEntryPoint/appid-list?packageName=$packageName", headers = headers, ) if (appIdResponse.appids.isEmpty()) { @@ -92,7 +90,7 @@ internal class HuaweiServiceImpl constructor( headers["client_id"] = clientId return httpClient.get( - url = "$PUBLISH_API_URL/upload-url?appId=$appId&suffix=$suffix", + url = "$publishEntryPoint/upload-url?appId=$appId&suffix=$suffix", headers = headers ) } @@ -146,7 +144,7 @@ internal class HuaweiServiceImpl constructor( ) val result = httpClient.put( - url = "$PUBLISH_API_URL/app-file-info?appId=$appId&releaseType=$releaseType", + url = "$publishEntryPoint/app-file-info?appId=$appId&releaseType=$releaseType", body = gson.toJson(bodyRequest).toRequestBody(MEDIA_TYPE_JSON), headers = headers, ) @@ -176,7 +174,7 @@ internal class HuaweiServiceImpl constructor( ) val result = httpClient.put( - url = "$PUBLISH_API_URL/app-language-info?appId=$appId", + url = "$publishEntryPoint/app-language-info?appId=$appId", body = gson.toJson(bodyRequest).toRequestBody(MEDIA_TYPE_JSON), headers = headers, ) @@ -278,7 +276,7 @@ internal class HuaweiServiceImpl constructor( headers["client_id"] = clientId val result = httpClient.put( - url = "$PUBLISH_API_URL/app-info?appId=$appId&releaseType=$releaseType", + url = "$publishEntryPoint/app-info?appId=$appId&releaseType=$releaseType", body = appBasicInfo.toRequestBody(MEDIA_TYPE_JSON), headers = headers, ) @@ -304,7 +302,7 @@ internal class HuaweiServiceImpl constructor( headers["Authorization"] = "Bearer $token" headers["client_id"] = clientId - val uriBuilder = "$PUBLISH_API_URL/app-submit".toHttpUrl() + val uriBuilder = "$publishEntryPoint/app-submit".toHttpUrl() .newBuilder() .addQueryParameter("appId", appId) .addQueryParameter("releaseType", releaseType.toString()) @@ -321,4 +319,11 @@ internal class HuaweiServiceImpl constructor( return result } + + companion object { + const val DOMAIN_URL = "https://connect-api.cloud.huawei.com/api" + private const val GRANT_TYPE = "client_credentials" + private const val SUBMIT_LONG_PUBLICATION_ERROR = 204144660 + private const val SUBMIT_REPEAT_TIMEOUT_MS = 3 * 60 * 1000L // 3 min + } } diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/MockHuaweiService.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/MockHuaweiService.kt deleted file mode 100644 index 2325354..0000000 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/MockHuaweiService.kt +++ /dev/null @@ -1,133 +0,0 @@ -package ru.cian.huawei.publish.service - -import org.gradle.internal.resource.transport.http.HttpRequestException -import ru.cian.huawei.publish.models.request.FileInfoRequest -import ru.cian.huawei.publish.models.response.AppInfo -import ru.cian.huawei.publish.models.response.FileServerOriResultResponse -import ru.cian.huawei.publish.models.response.Result -import ru.cian.huawei.publish.models.response.Ret -import ru.cian.huawei.publish.models.response.SubmitResponse -import ru.cian.huawei.publish.models.response.UpdateAppFileInfoResponse -import ru.cian.huawei.publish.models.response.UpdateAppBasicInfoResponse -import ru.cian.huawei.publish.models.response.UpdateReleaseNotesResponse -import ru.cian.huawei.publish.models.response.UploadFileRsp -import ru.cian.huawei.publish.models.response.UploadUrlResponse -import java.io.File - -private const val REQUEST_RETRIES = 5 - -@Suppress("StringLiteralDuplication", "TooManyFunctions") -internal class MockHuaweiService : HuaweiService { - - private var retries = 0 - - override fun getToken(clientId: String, clientSecret: String) = "MockToken" - - override fun getAppID( - clientId: String, - accessToken: String, - packageName: String - ) = AppInfo( - key = "MockKey", - value = "MockValue" - ) - - override fun getUploadingBuildUrl( - clientId: String, - accessToken: String, - appId: String, - suffix: String - ) = UploadUrlResponse( - ret = Ret( - code = -1, - msg = "MockMessage" - ), - uploadUrl = "MockUploadUrl", - chunkUploadUrl = "MockChunkUploadUrl", - authCode = "MockAuthCode" - ) - - override fun uploadBuildFile( - uploadUrl: String, - authCode: String, - buildFile: File - ) = FileServerOriResultResponse( - result = Result( - resultCode = -1, - uploadFileRsp = UploadFileRsp( - ifSuccess = 1, - fileInfoList = emptyList() - ) - ) - ) - - override fun updateAppFileInformation( - clientId: String, - accessToken: String, - appId: String, - releaseType: Int, - fileInfoRequestList: List - ) = UpdateAppFileInfoResponse( - ret = Ret( - code = -1, - msg = "MockMessage" - ) - ) - - override fun updateReleaseNotes( - clientId: String, - accessToken: String, - appId: String, - lang: String, - newFeatures: String - ) = UpdateReleaseNotesResponse( - ret = Ret( - code = -1, - msg = "MockMessage" - ) - ) - - override fun submitReviewImmediately( - clientId: String, - accessToken: String, - appId: String, - releaseTime: String? - ) = getSubmitResponseWithRetries() - - override fun submitReviewWithReleasePhase( - clientId: String, - accessToken: String, - appId: String, - startRelease: String?, - endRelease: String?, - releasePercent: Double - ) = getSubmitResponseWithRetries() - - override fun updateAppBasicInfo( - clientId: String, - accessToken: String, - appId: String, - releaseType: Int, - appBasicInfo: String - ) = UpdateAppBasicInfoResponse( - ret = Ret( - code = -1, - msg = "MockMessage" - ) - ) - - @Suppress("ThrowingExceptionsWithoutMessageOrCause") - private fun getSubmitResponseWithRetries(): SubmitResponse { - if (retries < REQUEST_RETRIES) { - retries++ - throw HttpRequestException("That's work as well for MockServer, attempt=$retries", Throwable()) - } else { - return SubmitResponse( - ret = Ret( - code = -1, - msg = "MockMessage" - ) - ) - } - } -} diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapper.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapper.kt new file mode 100644 index 0000000..71b72c6 --- /dev/null +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapper.kt @@ -0,0 +1,10 @@ +package ru.cian.huawei.publish.service.mock + +interface MockServerWrapper { + + fun getBaseUrl(): String + + fun start() + + fun shutdown() +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperImpl.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperImpl.kt new file mode 100644 index 0000000..ed91d61 --- /dev/null +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperImpl.kt @@ -0,0 +1,187 @@ +package ru.cian.huawei.publish.service.mock + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import okhttp3.mockwebserver.RecordedRequest +import ru.cian.huawei.publish.utils.Logger +import java.util.concurrent.TimeUnit + +private const val DELAY_REQUEST_BODY_SECONDS = 1L + +@SuppressWarnings("MaxLineLength", "MagicNumber", "LongMethod") +class MockServerWrapperImpl( + val logger: Logger, +) : MockServerWrapper { + + private lateinit var mockWebServer: MockWebServer + + override fun getBaseUrl(): String { + return mockWebServer.url("/").toString() + } + + override fun start() { + logger.v(":: start mock server") + + mockWebServer = MockWebServer() + mockWebServer.start() + + val dispatcher = object : Dispatcher() { + @Throws(InterruptedException::class) + @SuppressWarnings("ReturnCount") + override fun dispatch(request: RecordedRequest): MockResponse { + when { + request.path!!.contains("/oauth2/v1/token") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "access_token": "abcd1234token", + "expires_in": 3600, + "ret": { + "code": 0, + "msg": "Success" + } + } + """.trimMargin() + ) + + request.path!!.contains("/appid-list") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + }, + "appids": [ + { + "key": "app1", + "value": "App One" + }, + { + "key": "app2", + "value": "App Two" + } + ] + } + """.trimMargin() + ) + + request.path!!.contains("/upload-url") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + }, + "uploadUrl": "${getBaseUrl()}/upload_file_stub", + "chunkUploadUrl": "${getBaseUrl()}/upload/chunk_stub", + "authCode": "abc123securetoken" + } + """.trimMargin() + ) + + request.path!!.contains("/upload_file_stub") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "result": { + "resultCode": 0, + "UploadFileRsp": { + "ifSuccess": 1, + "fileInfoList": [ + { + "fileDestUlr": "https://example.com/files/file1.jpg", + "imageResolution": "1920x1080", + "imageResolutionSingature": "signature123", + "size": 2048 + }, + { + "fileDestUlr": "https://example.com/files/file2.jpg", + "imageResolution": "1280x720", + "imageResolutionSingature": "signature456", + "size": 1024 + } + ] + } + } + } + """.trimMargin() + ) + + request.path!!.contains("/app-file-info") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + } + } + """.trimMargin() + ) + + request.path!!.contains("/app-language-info") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + } + } + """.trimMargin() + ) + + request.path!!.contains("/app-submit") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + } + } + """.trimMargin() + ) + + request.path!!.contains("/app-info") -> return MockResponse() + .setResponseCode(200) + .setBodyDelay(DELAY_REQUEST_BODY_SECONDS, TimeUnit.SECONDS) + .setBody( + """ + { + "ret": { + "code": 0, + "msg": "Success" + } + } + """.trimMargin() + ) + } + return MockResponse().setResponseCode(404) + } + } + mockWebServer.dispatcher = dispatcher + } + + override fun shutdown() { + logger.v(":: shutdown mock server") + mockWebServer.shutdown() + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperStub.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperStub.kt new file mode 100644 index 0000000..a211406 --- /dev/null +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/service/mock/MockServerWrapperStub.kt @@ -0,0 +1,18 @@ +package ru.cian.huawei.publish.service.mock + +import ru.cian.huawei.publish.service.HuaweiServiceImpl + +class MockServerWrapperStub : MockServerWrapper { + + override fun getBaseUrl(): String { + return HuaweiServiceImpl.DOMAIN_URL + } + + override fun start() { + // nothing; + } + + override fun shutdown() { + // nothing; + } +} \ No newline at end of file diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProvider.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProvider.kt index b2107db..e4d1204 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProvider.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProvider.kt @@ -1,35 +1,9 @@ package ru.cian.huawei.publish.utils -import com.android.build.api.artifact.SingleArtifact -import com.android.build.api.variant.ApplicationVariant import ru.cian.huawei.publish.BuildFormat import java.io.File -internal class BuildFileProvider( - private val variant: ApplicationVariant, - private val logger: Logger, -) { +internal interface BuildFileProvider { - fun getBuildFile(buildFormat: BuildFormat): File? { - return when (buildFormat) { - BuildFormat.APK -> getFinalApkArtifactCompat(variant).singleOrNull() - BuildFormat.AAB -> getFinalBundleArtifactCompat(variant).singleOrNull() - } - } - - // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16777 - // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16775 - private fun getFinalApkArtifactCompat(variant: ApplicationVariant): List { - val apkDirectory = variant.artifacts.get(SingleArtifact.APK).get() - logger.v("Build File Directory: $apkDirectory") - return variant.artifacts.getBuiltArtifactsLoader().load(apkDirectory) - ?.elements?.map { element -> File(element.outputFile) } - ?: apkDirectory.asFileTree.matching { include("*.apk") }.map { it.absolutePath }.map { File(it) } - ?: emptyList() - } - - private fun getFinalBundleArtifactCompat(variant: ApplicationVariant): List { - val aabFile = variant.artifacts.get(SingleArtifact.BUNDLE).get().asFile - return listOf(aabFile) - } + fun getBuildFile(buildFormat: BuildFormat): File? } diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderDeprecated.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderDeprecated.kt new file mode 100644 index 0000000..57c08c1 --- /dev/null +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderDeprecated.kt @@ -0,0 +1,35 @@ +package ru.cian.huawei.publish.utils + +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationVariant +import ru.cian.huawei.publish.BuildFormat +import java.io.File + +internal class BuildFileProviderDeprecated( + private val variant: ApplicationVariant, + private val logger: Logger, +) : BuildFileProvider { + + override fun getBuildFile(buildFormat: BuildFormat): File? { + return when (buildFormat) { + BuildFormat.APK -> getFinalApkArtifactCompat(variant).singleOrNull() + BuildFormat.AAB -> getFinalBundleArtifactCompat(variant).singleOrNull() + } + } + + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16777 + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16775 + private fun getFinalApkArtifactCompat(variant: ApplicationVariant): List { + val apkDirectory = variant.artifacts.get(SingleArtifact.APK).get() + logger.v("Build File Directory: $apkDirectory") + return variant.artifacts.getBuiltArtifactsLoader().load(apkDirectory) + ?.elements?.map { element -> File(element.outputFile) } + ?: apkDirectory.asFileTree.matching { include("*.apk") }.map { it.absolutePath }.map { File(it) } + ?: emptyList() + } + + private fun getFinalBundleArtifactCompat(variant: ApplicationVariant): List { + val aabFile = variant.artifacts.get(SingleArtifact.BUNDLE).get().asFile + return listOf(aabFile) + } +} diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderNew.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderNew.kt new file mode 100644 index 0000000..2a877cb --- /dev/null +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/BuildFileProviderNew.kt @@ -0,0 +1,37 @@ +package ru.cian.huawei.publish.utils + +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationVariant +import ru.cian.huawei.publish.BuildFormat +import java.io.File + +@SuppressWarnings("UnusedPrivateMember") +internal class BuildFileProviderNew( + private val variantApkBuildFilePath: String?, + private val variantAabBuildFilePath: String?, + private val logger: Logger, +) : BuildFileProvider { + + override fun getBuildFile(buildFormat: BuildFormat): File? { + return when (buildFormat) { + BuildFormat.APK -> variantApkBuildFilePath?.let { File(it) } + BuildFormat.AAB -> variantAabBuildFilePath?.let { File(it) } + } + } + + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16777 + // TODO(a.mirko): Remove after https://github.com/gradle/gradle/issues/16775 + private fun getFinalApkArtifactCompat(variant: ApplicationVariant): List { + val apkDirectory = variant.artifacts.get(SingleArtifact.APK).get() + logger.v("Build File Directory: $apkDirectory") + return variant.artifacts.getBuiltArtifactsLoader().load(apkDirectory) + ?.elements?.map { element -> File(element.outputFile) } + ?: apkDirectory.asFileTree.matching { include("*.apk") }.map { it.absolutePath }.map { File(it) } + ?: emptyList() + } + + private fun getFinalBundleArtifactCompat(variant: ApplicationVariant): List { + val aabFile = variant.artifacts.get(SingleArtifact.BUNDLE).get().asFile + return listOf(aabFile) + } +} diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/ConfigProvider.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/ConfigProvider.kt index d9c2d2f..2b11b89 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/ConfigProvider.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/ConfigProvider.kt @@ -13,6 +13,8 @@ import ru.cian.huawei.publish.HuaweiPublishExtensionConfig import ru.cian.huawei.publish.ReleaseNotesConfig import ru.cian.huawei.publish.ReleaseNotesDescriptionsConfig import ru.cian.huawei.publish.ReleasePhaseConfig +import ru.cian.huawei.publish.models.Credential +import java.util.Base64 internal class ConfigProvider( private val extension: HuaweiPublishExtensionConfig, @@ -26,6 +28,7 @@ internal class ConfigProvider( val deployType = cli.deployType ?: extension.deployType val publishTimeoutMs = cli.publishTimeoutMs?.toLong() ?: extension.publishTimeoutMs val publishPeriodMs = cli.publishPeriodMs?.toLong() ?: extension.publishPeriodMs + val publishSocketTimeoutInSeconds = cli.publishSocketTimeoutInSeconds?.toLong() ?: extension.publishSocketTimeoutInSeconds val artifactFormat = cli.buildFormat ?: extension.buildFormat val customBuildFilePath: String? = cli.buildFile ?: extension.buildFile val releaseTime: String? = cli.releaseTime ?: extension.releaseTime @@ -53,6 +56,7 @@ internal class ConfigProvider( artifactFile = artifactFile, publishTimeoutMs = publishTimeoutMs, publishPeriodMs = publishPeriodMs, + publishSocketTimeoutInSeconds = publishSocketTimeoutInSeconds, releaseTime = releaseTime, releasePhase = releasePhase, releaseNotes = releaseNotes, @@ -87,10 +91,17 @@ internal class ConfigProvider( @Suppress("ThrowsCount") fun getCredentialsConfig(): Credentials { + val credentialsBase64 = cli.credentials ?: extension.credentials + val credentialsFromBase64 = lazy { + if (credentialsBase64 != null) { + decodeCredentials(credentialsBase64) + } else { + null + } + } + val credentialsFilePath = cli.credentialsPath ?: extension.credentialsPath - val clientIdPriority: String? = cli.clientId - val clientSecretPriority: String? = cli.clientSecret - val credentials = lazy { + val credentialsResult = lazy { if (credentialsFilePath.isNullOrBlank()) { throw FileNotFoundException( "$extension (File path for credentials is null or empty. " + @@ -104,14 +115,17 @@ internal class ConfigProvider( "with 'client_id' and 'client_secret' for access to Huawei Publish API is not found)" ) } - CredentialHelper.getCredentials(credentialsFile) + CredentialHelper.getCredentialsFromFile(credentialsFile) } - val clientId = clientIdPriority ?: credentials.value.clientId.nullIfBlank() + + val clientId = credentialsFromBase64.value?.clientId + ?: credentialsResult.value.clientId.nullIfBlank() ?: throw IllegalArgumentException( "(Huawei credential `clientId` param is null or empty). " + "Please check your credentials file content or as single parameter." ) - val clientSecret = clientSecretPriority ?: credentials.value.clientSecret.nullIfBlank() + val clientSecret = credentialsFromBase64.value?.clientSecret + ?: credentialsResult.value.clientSecret.nullIfBlank() ?: throw IllegalArgumentException( "(Huawei credential `clientSecret` param is null or empty). " + "Please check your credentials file content or as single parameter." @@ -119,6 +133,13 @@ internal class ConfigProvider( return Credentials(clientId, clientSecret) } + @Throws(IllegalArgumentException::class) + fun decodeCredentials(encodedCredentials: String): Credential { + val decodedBytes = Base64.getDecoder().decode(encodedCredentials) + val decodedString = String(decodedBytes, Charsets.UTF_8) + return CredentialHelper.getCredentialsFromJson(decodedString) + } + @Suppress("ThrowsCount") fun getReleasePhaseConfig(): ReleasePhaseConfig? { val releasePhaseStartTime = cli.releasePhaseStartTime ?: extension.releasePhase?.startTime diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/CredentialHelper.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/CredentialHelper.kt index 1dbbafc..dd45b27 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/CredentialHelper.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/CredentialHelper.kt @@ -8,9 +8,14 @@ import java.io.File import java.io.FileReader internal object CredentialHelper { - fun getCredentials(credentialsFile: File): Credential { + fun getCredentialsFromFile(credentialsFile: File): Credential { val reader = JsonReader(FileReader(credentialsFile.absolutePath)) val type = object : TypeToken() {}.type return Gson().fromJson(reader, type) } + + fun getCredentialsFromJson(json: String): Credential { + val type = object : TypeToken() {}.type + return Gson().fromJson(json, type) + } } diff --git a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/Logger.kt b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/Logger.kt index 67b1fab..5d69718 100644 --- a/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/Logger.kt +++ b/plugin/src/main/kotlin/ru/cian/huawei/publish/utils/Logger.kt @@ -5,7 +5,7 @@ import org.gradle.api.Project private const val LOG_TAG = "Huawei AppGallery Publishing API" -internal class Logger constructor( +class Logger constructor( private val project: Project ) { diff --git a/plugin/src/test/kotlin/ru/cian/huawei/publish/utils/ConfigProviderTest.kt b/plugin/src/test/kotlin/ru/cian/huawei/publish/utils/ConfigProviderTest.kt index 904bd3e..dad610c 100644 --- a/plugin/src/test/kotlin/ru/cian/huawei/publish/utils/ConfigProviderTest.kt +++ b/plugin/src/test/kotlin/ru/cian/huawei/publish/utils/ConfigProviderTest.kt @@ -1,7 +1,9 @@ package ru.cian.huawei.publish.utils +import assertk.assertFailure import assertk.assertThat import assertk.assertions.isEqualTo +import assertk.assertions.messageContains import assertk.tableOf import io.mockk.every import io.mockk.mockk @@ -21,13 +23,13 @@ import ru.cian.huawei.publish.HuaweiPublishConfig import ru.cian.huawei.publish.HuaweiPublishExtensionConfig import ru.cian.huawei.publish.ReleasePhaseConfig import ru.cian.huawei.publish.ReleasePhaseExtension -import ru.cian.huawei.publish.models.Credential import java.io.File import ru.cian.huawei.publish.ReleaseNote import ru.cian.huawei.publish.ReleaseNotesConfig import ru.cian.huawei.publish.ReleaseNotesExtension import ru.cian.huawei.publish.ReleaseNotesDescriptionsConfig +private const val DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS = 60L private const val DEFAULT_PUBLISH_TIMEOUT_MS = 10 * 60 * 1000L private const val DEFAULT_PUBLISH_PERIOD_MS = 15 * 1000L private const val BUILD_DIRECTORY_PATH = "./build" @@ -49,6 +51,7 @@ private const val CREDENTIALS_SECOND_JSON = "{\"client_id\": \"no_id\", \"client private const val APP_BASIC_INFO_FILE_PATH = "$BUILD_DIRECTORY_PATH/app_info.json" private const val APP_BASIC_INFO_FILE_SECOND_PATH = "$BUILD_DIRECTORY_PATH/app_info_second.json" +@Suppress("LongMethod") @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class ConfigProviderTest { @@ -128,17 +131,19 @@ internal class ConfigProviderTest { releaseNotesFileProvider = releaseNotesFileProvider, ) - assertThat { configProvider.getConfig() }.hasException(IllegalArgumentException::class) + assertFailure { configProvider.getConfig() } + .messageContains("has wrong file extension that doesn't match with announced buildFormat(APK) plugin extension param") } @Test - fun `correct config for default params`() = mockkObject(CredentialHelper) { + fun `correct config with overriding credentials params`() = mockkObject(CredentialHelper) { val expectedConfig = HuaweiPublishConfig( credentials = Credentials("id", "secret"), deployType = DeployType.PUBLISH, artifactFormat = BuildFormat.APK, artifactFile = File(ARTIFACT_APK_FILE_PATH), + publishSocketTimeoutInSeconds = DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS, publishTimeoutMs = DEFAULT_PUBLISH_TIMEOUT_MS, publishPeriodMs = DEFAULT_PUBLISH_PERIOD_MS, releaseTime = null, @@ -147,11 +152,7 @@ internal class ConfigProviderTest { appBasicInfoFile = null ) - every { - CredentialHelper.getCredentials(match { it.absolutePath == CREDENTIALS_FILE_PATH }) - } returns Credential(clientId = "id", clientSecret = "secret") - - tableOf("expectedValue", "actualValue") + tableOf("expectedValue", "configProvider") .row( expectedConfig, ConfigProvider( @@ -162,18 +163,118 @@ internal class ConfigProviderTest { ) ) .row( - expectedConfig, + expectedConfig.copy( + credentials = Credentials("no_id", "no_secret") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { + credentialsPath = CREDENTIALS_FILE_SECOND_PATH + }, + cli = emptyCliConfig, + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("no_id", "no_secret") + ), ConfigProvider( extension = extensionConfigInstance(), cli = HuaweiPublishCliParam( + credentialsPath = CREDENTIALS_FILE_SECOND_PATH + ), + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("no_id", "no_secret") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { credentialsPath = CREDENTIALS_FILE_PATH + }, + cli = HuaweiPublishCliParam( + credentialsPath = CREDENTIALS_FILE_SECOND_PATH ), buildFileProvider = buildFileProvider, releaseNotesFileProvider = releaseNotesFileProvider, ) ) - .forAll { expectedValue, actualValue -> - assertThat(actualValue.getConfig()).isEqualTo(expectedValue) + .row( + expectedConfig.copy( + credentials = Credentials("id_base64", "secret_base64") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { + credentials = "ewogICJjbGllbnRfaWQiOiAiaWRfYmFzZTY0IiwKICAiY2xpZW50X3NlY3JldCI6ICJzZWNyZXRfYmFzZTY0Igp9" + }, + cli = emptyCliConfig, + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("id_base64", "secret_base64") + ), + ConfigProvider( + extension = extensionConfigInstance(), + cli = HuaweiPublishCliParam( + credentials = "ewogICJjbGllbnRfaWQiOiAiaWRfYmFzZTY0IiwKICAiY2xpZW50X3NlY3JldCI6ICJzZWNyZXRfYmFzZTY0Igp9" + ), + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("id_base64", "secret_base64") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { + credentials = "must_not_be_used" + }, + cli = HuaweiPublishCliParam( + credentials = "ewogICJjbGllbnRfaWQiOiAiaWRfYmFzZTY0IiwKICAiY2xpZW50X3NlY3JldCI6ICJzZWNyZXRfYmFzZTY0Igp9" + ), + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("id_base64", "secret_base64") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { + credentials = "ewogICJjbGllbnRfaWQiOiAiaWRfYmFzZTY0IiwKICAiY2xpZW50X3NlY3JldCI6ICJzZWNyZXRfYmFzZTY0Igp9" + }, + cli = HuaweiPublishCliParam( + credentialsPath = CREDENTIALS_FILE_SECOND_PATH + ), + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .row( + expectedConfig.copy( + credentials = Credentials("no_id", "no_secret") + ), + ConfigProvider( + extension = extensionConfigInstance().apply { + credentialsPath = CREDENTIALS_FILE_SECOND_PATH + }, + cli = emptyCliConfig, + buildFileProvider = buildFileProvider, + releaseNotesFileProvider = releaseNotesFileProvider, + ) + ) + .forAll { expectedValue, configProvider -> + val actualValue = configProvider.getConfig() + assertThat(actualValue).isEqualTo(expectedValue) } } @@ -181,10 +282,11 @@ internal class ConfigProviderTest { fun `correct config with overriding common values at cli params`() { val expectedConfig = HuaweiPublishConfig( - credentials = Credentials("id123", "secret123"), + credentials = Credentials("id_base64", "secret_base64"), deployType = DeployType.DRAFT, artifactFormat = BuildFormat.AAB, artifactFile = File(ARTIFACT_AAB_FILE_SECOND_PATH), + publishSocketTimeoutInSeconds = 3003L, publishTimeoutMs = 1001L, publishPeriodMs = 2002L, releaseTime = "2019-10-18T21:00:00+0300", @@ -198,7 +300,7 @@ internal class ConfigProviderTest { ) val inputExtensionConfig = extensionConfigInstance().apply { - credentialsPath = CREDENTIALS_FILE_PATH + credentialsPath = CREDENTIALS_FILE_SECOND_PATH deployType = DeployType.PUBLISH publishTimeoutMs = 3003 publishPeriodMs = 4004 @@ -213,12 +315,12 @@ internal class ConfigProviderTest { appBasicInfo = APP_BASIC_INFO_FILE_PATH } val inputCliConfig = HuaweiPublishCliParam( + credentials = "ewogICJjbGllbnRfaWQiOiAiaWRfYmFzZTY0IiwKICAiY2xpZW50X3NlY3JldCI6ICJzZWNyZXRfYmFzZTY0Igp9", deployType = DeployType.DRAFT, + publishSocketTimeoutInSeconds = "3003", publishTimeoutMs = "1001", publishPeriodMs = "2002", credentialsPath = CREDENTIALS_FILE_SECOND_PATH, - clientId = "id123", - clientSecret = "secret123", buildFormat = BuildFormat.AAB, buildFile = ARTIFACT_AAB_FILE_SECOND_PATH, releaseTime = "2019-10-18T21:00:00+0300", @@ -248,6 +350,7 @@ internal class ConfigProviderTest { deployType = DeployType.PUBLISH, artifactFormat = BuildFormat.APK, artifactFile = File(ARTIFACT_APK_FILE_PATH), + publishSocketTimeoutInSeconds = DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS, publishTimeoutMs = DEFAULT_PUBLISH_TIMEOUT_MS, publishPeriodMs = DEFAULT_PUBLISH_PERIOD_MS, releaseTime = null, @@ -319,7 +422,6 @@ internal class ConfigProviderTest { } } - @Suppress("LongMethod") @Test fun `correct config with overriding release notes`() { val expectedConfig = HuaweiPublishConfig( @@ -327,6 +429,7 @@ internal class ConfigProviderTest { deployType = DeployType.PUBLISH, artifactFormat = BuildFormat.APK, artifactFile = File(ARTIFACT_APK_FILE_PATH), + publishSocketTimeoutInSeconds = DEFAULT_PUBLISH_SOCKET_TIMEOUT_IN_SECONDS, publishTimeoutMs = DEFAULT_PUBLISH_TIMEOUT_MS, publishPeriodMs = DEFAULT_PUBLISH_PERIOD_MS, releaseTime = null, diff --git a/sample-aar/build.gradle b/sample-aar/build.gradle index a2f7bbb..5819c23 100644 --- a/sample-aar/build.gradle +++ b/sample-aar/build.gradle @@ -5,8 +5,8 @@ apply plugin: 'ru.cian.huawei-publish' buildscript { repositories { + gradlePluginPortal() mavenCentral() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } dependencies { @@ -48,9 +48,3 @@ android { abortOnError false } } - -beforeEvaluate { - println("-------------------------------------------------------------------------") - println("Should get error!") - println("-------------------------------------------------------------------------") -} diff --git a/sample-groovy/build.gradle b/sample-groovy/build.gradle index 949f324..b9db997 100644 --- a/sample-groovy/build.gradle +++ b/sample-groovy/build.gradle @@ -1,18 +1,17 @@ buildscript { repositories { mavenLocal() - maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } - mavenCentral() + gradlePluginPortal() } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:" + libs.versions.kotlin.get() - classpath "ru.cian:huawei-publish-gradle-plugin:" + libs.versions.sampleHuaweiPlugin.get() + classpath "ru.cian.huawei-plugin:plugin:" + libs.versions.sampleHuaweiPlugin.get() } } apply plugin: "com.android.application" -apply plugin: "ru.cian.huawei-publish" +apply plugin: "ru.cian.huawei-publish-gradle-plugin" huaweiPublish { instances { @@ -22,9 +21,11 @@ huaweiPublish { buildFormat = "apk" } fullRelease { + credentials = "ewogICJjbGllbnRfaWQiOiAiPENMSUVOVF9JRF9CQVNFNjQ+IiwKICAiY2xpZW50X3NlY3JldCI6ICI8Q0xJRU5UX1NFQ1JFVF9CQVNFNjQ+Igp9" credentialsPath = "$projectDir/huawei-credentials-2.json" deployType = "publish" buildFormat = "aab" + publishSocketTimeoutInSeconds = 60 publishTimeoutMs = 15_000 publishPeriodMs = 3_000 releaseTime = "2025-10-21T06:00:00+0300" diff --git a/sample-kotlin/build.gradle.kts b/sample-kotlin/build.gradle.kts index 291fa2f..72eeb6c 100644 --- a/sample-kotlin/build.gradle.kts +++ b/sample-kotlin/build.gradle.kts @@ -7,9 +7,11 @@ plugins { huaweiPublish { instances { create("release") { - credentialsPath = "$projectDir/huawei-credentials.json" + credentials = "ewogICJjbGllbnRfaWQiOiAiPENMSUVOVF9JRF9CQVNFNjQ+IiwKICAiY2xpZW50X3NlY3JldCI6ICI8Q0xJRU5UX1NFQ1JFVF9CQVNFNjQ+Igp9" +// credentialsPath = "$projectDir/huawei-credentials.json" deployType = ru.cian.huawei.publish.DeployType.DRAFT buildFormat = ru.cian.huawei.publish.BuildFormat.AAB + publishSocketTimeoutInSeconds = 60 publishTimeoutMs = 15_000 publishPeriodMs = 3_000 releaseTime = "2025-10-21T06:00:00+0300" diff --git a/settings.gradle.kts b/settings.gradle.kts index 81f75bf..ec600b4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,22 +8,24 @@ include( pluginManagement { - val huaweiPublish = "1.4.2" + val libsVersionFile = file("gradle/libs.versions.toml") + val properties = java.util.Properties().apply { + libsVersionFile.reader().use { load(it) } + } + val samplePublishVersion = properties.getProperty("sampleHuaweiPlugin").replace("\"", "") resolutionStrategy { eachPlugin { if(requested.id.namespace == "ru.cian") { - useModule("ru.cian:huawei-publish-gradle-plugin:${huaweiPublish}") + useModule("ru.cian.huawei-plugin:plugin:${samplePublishVersion}") } } } repositories { mavenLocal() - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } google() gradlePluginPortal() - mavenCentral() maven { url = uri("https://plugins.gradle.org/m2/") } } }