chore: Added environment config gradle plugin

This commit is contained in:
Artiom Košelev
2023-09-14 08:29:57 +00:00
parent dffa8620bd
commit 00cdc0648c
25 changed files with 676 additions and 68 deletions
+23 -29
View File
@@ -17,6 +17,7 @@
*/
import com.android.build.gradle.TestedExtension
import configuration.extensions.protonEnvironment
import studio.forface.easygradle.dsl.*
import studio.forface.easygradle.dsl.android.*
import java.io.FileNotFoundException
@@ -25,6 +26,7 @@ import java.util.Properties
plugins {
protonAndroidApp
protonDagger
id("me.proton.core.gradle-plugins.environment-config")
kotlin("plugin.serialization")
}
@@ -90,11 +92,6 @@ android {
fun setupFlavors(testedExtension: TestedExtension) {
testedExtension.apply {
val buildConfigFieldKeys = object {
val PROXY_TOKEN = "PROXY_TOKEN"
val API_HOST = "API_HOST"
val HV3_HOST = "HV3_HOST"
val QUARK_HOST = "QUARK_HOST"
val USE_DEFAULT_PINS = "USE_DEFAULT_PINS"
val CAN_USE_DOH = "USE_DOH"
val KEY_TRANSPARENCY_ENV = "KEY_TRANSPARENCY_ENV"
val SENTRY_DSN = "SENTRY_DSN"
@@ -107,8 +104,6 @@ fun setupFlavors(testedExtension: TestedExtension) {
flavorDimensions(flavorDimensions.env)
defaultConfig {
buildConfigField("String", buildConfigFieldKeys.PROXY_TOKEN, null.toBuildConfigValue())
buildConfigField("Boolean", buildConfigFieldKeys.USE_DEFAULT_PINS, true.toBuildConfigValue())
buildConfigField("Boolean", buildConfigFieldKeys.CAN_USE_DOH, false.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.KEY_TRANSPARENCY_ENV, null.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.SENTRY_DSN, null.toBuildConfigValue())
@@ -118,19 +113,22 @@ fun setupFlavors(testedExtension: TestedExtension) {
productFlavors.register("dev") {
dimension = flavorDimensions.env
applicationIdSuffix = ".dev"
buildConfigField("String", buildConfigFieldKeys.API_HOST, "api.proton.black".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.HV3_HOST, "verify.proton.black".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, "proton.black".toBuildConfigValue())
buildConfigField("Boolean", buildConfigFieldKeys.USE_DEFAULT_PINS, false.toBuildConfigValue())
protonEnvironment {
host = "proton.black"
}
buildConfigField("String", buildConfigFieldKeys.KEY_TRANSPARENCY_ENV, "black".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.SENTRY_DSN, null.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.ACCOUNT_SENTRY_DSN, null.toBuildConfigValue())
}
productFlavors.register("prod") {
dimension = flavorDimensions.env
buildConfigField("String", buildConfigFieldKeys.API_HOST, "mail-api.proton.me".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.HV3_HOST, "verify.proton.me".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, "".toBuildConfigValue())
protonEnvironment {
apiPrefix = "mail-api"
}
buildConfigField("Boolean", buildConfigFieldKeys.CAN_USE_DOH, true.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.KEY_TRANSPARENCY_ENV, "production".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.SENTRY_DSN, null.toBuildConfigValue())
@@ -146,21 +144,17 @@ fun setupFlavors(testedExtension: TestedExtension) {
logger.warn("No local.properties found")
}
}
val proxyToken: String? = localProperties.getProperty(buildConfigFieldKeys.PROXY_TOKEN)
val host: String = localProperties.getProperty("HOST") ?: "proton.me"
val apiHost = localProperties.getProperty(buildConfigFieldKeys.API_HOST) ?: "mail-api.$host"
val hv3Host = localProperties.getProperty(buildConfigFieldKeys.HV3_HOST) ?: "verify.$host"
val quarkHost = localProperties.getProperty(buildConfigFieldKeys.QUARK_HOST) ?: host
val useDefaultPins: String = localProperties.getProperty(buildConfigFieldKeys.USE_DEFAULT_PINS) ?: "false"
val atlasHost: String = localProperties.getProperty("HOST") ?: "proton.me"
val keyTransparencyEnv: String? = localProperties.getProperty(buildConfigFieldKeys.KEY_TRANSPARENCY_ENV)
val sentryDsn: String? = localProperties.getProperty(buildConfigFieldKeys.SENTRY_DSN)
val accountSentryDsn: String? = localProperties.getProperty(buildConfigFieldKeys.ACCOUNT_SENTRY_DSN)
buildConfigField("Boolean", buildConfigFieldKeys.USE_DEFAULT_PINS, useDefaultPins.toBoolean().toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.PROXY_TOKEN, proxyToken.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.API_HOST, apiHost.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.HV3_HOST, hv3Host.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, quarkHost.toBuildConfigValue())
protonEnvironment {
useProxy = true
host = atlasHost
}
buildConfigField(
"String",
buildConfigFieldKeys.KEY_TRANSPARENCY_ENV,
@@ -170,10 +164,10 @@ fun setupFlavors(testedExtension: TestedExtension) {
buildConfigField("String", buildConfigFieldKeys.ACCOUNT_SENTRY_DSN, accountSentryDsn.toBuildConfigValue())
}
productFlavors.register("mock") {
buildConfigField("String", buildConfigFieldKeys.API_HOST, "api.mock".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.HV3_HOST, "verify.mock".toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.QUARK_HOST, "quark.mock".toBuildConfigValue())
buildConfigField("Boolean", buildConfigFieldKeys.USE_DEFAULT_PINS, false.toBuildConfigValue())
protonEnvironment {
host = "mock"
}
buildConfigField("String", buildConfigFieldKeys.KEY_TRANSPARENCY_ENV, null.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.SENTRY_DSN, null.toBuildConfigValue())
buildConfigField("String", buildConfigFieldKeys.ACCOUNT_SENTRY_DSN, null.toBuildConfigValue())
@@ -25,7 +25,6 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.rule.GrantPermissionRule
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.core.accountmanager.data.AccountStateHandler
@@ -76,7 +75,7 @@ class AccountRecoveryNotificationTest : MinimalAccountRecoveryNotificationTest {
@Inject
override lateinit var waitForPrimaryAccount: WaitForPrimaryAccount
override val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
override val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
@BeforeTest
override fun prepare() {
@@ -5,7 +5,6 @@ import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -33,8 +32,7 @@ import kotlin.time.Duration.Companion.seconds
@UninstallModules(ApplicationModule::class)
class ConvertExternalToInternalAccountTests : BaseConvertExternalToInternalAccountTests,
ProtonTest(MainActivity::class.java) {
override val quark: Quark =
Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
override val quark: Quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
override lateinit var testUser: User
override lateinit var testUsername: String
@@ -24,7 +24,6 @@ import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -270,6 +269,6 @@ class ExternalAccountSupportedLoginTests : ProtonTest(MainActivity::class.java,
}
companion object {
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
}
}
@@ -23,7 +23,6 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -91,6 +90,6 @@ class ExternalAccountUnsupportedLoginTests : ProtonTest(MainActivity::class.java
}
companion object {
val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
}
}
@@ -25,7 +25,6 @@ import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -174,6 +173,6 @@ class InternalLoginWithAccountSetupTests : ProtonTest(MainActivity::class.java,
}
companion object {
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
}
}
@@ -22,7 +22,6 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -43,7 +42,7 @@ import kotlin.test.BeforeTest
@HiltAndroidTest
@UninstallModules(ApplicationModule::class)
class VpnUsernameAccountLoginTests : BaseUsernameAccountLoginTests, ProtonTest(MainActivity::class.java) {
override val quark: Quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
override val quark: Quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
override val internalUsers: User.Users = User.Users.fromDefaultResources()
override val vpnUsers: User.Users = User.Users.fromJavaResources(
User::class.java.classLoader!!,
@@ -22,7 +22,6 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.api.CoreExampleApiClient
import me.proton.android.core.coreexample.di.ApplicationModule
@@ -44,7 +43,7 @@ import kotlin.test.BeforeTest
class DriveExternalAccountSignupTests : BaseExternalAccountSignupTests {
override lateinit var testUser: User
override val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
override val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
@get:Rule
val hiltRule = HiltAndroidRule(this)
@@ -24,7 +24,6 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.api.CoreExampleApiClient
import me.proton.android.core.coreexample.di.ApplicationModule
@@ -92,7 +91,8 @@ open class ExternalAccountChooseUsernameTest {
private companion object {
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
private val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
private fun launchSignupActivity(accountType: AccountType): ActivityScenario<SignupActivity> =
ActivityScenario.launch(
StartSignup().createIntent(
@@ -24,7 +24,6 @@ import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.android.core.coreexample.api.CoreExampleApiClient
@@ -48,7 +47,7 @@ import kotlin.test.BeforeTest
@UninstallModules(ApplicationModule::class)
class VpnUsernameAccountSignupTests : BaseUsernameAccountSignupTests,
ProtonTest(MainActivity::class.java, defaultTimeout = 20_000) {
override val quark: Quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
override val quark: Quark = Quark.fromDefaultResources(Constants.QUARK_HOST, Constants.PROXY_TOKEN)
override val vpnUsers: User.Users = User.Users.fromJavaResources(
User::class.java.classLoader!!,
"sensitive/users-vpn-username.json"
@@ -23,19 +23,18 @@ import android.util.Log
import androidx.test.core.app.ApplicationProvider
import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.runBlocking
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.core.auth.presentation.testing.ProtonTestEntryPoint
import me.proton.core.payment.domain.usecase.PaymentProvider
import me.proton.core.test.android.instrumented.ProtonTest
import me.proton.core.test.android.instrumented.utils.Shell.setupDeviceForAutomation
import me.proton.core.test.quark.data.User.Users
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.quark.Quark
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import me.proton.core.test.quark.data.User.Users
import org.junit.After
import org.junit.BeforeClass
@@ -61,7 +60,11 @@ open class BaseTest(
companion object {
val users = Users.fromDefaultResources()
val quark = Quark.fromDefaultResources(Constants.QUARK_HOST, BuildConfig.PROXY_TOKEN)
val quark = Quark.fromDefaultResources(
Constants.QUARK_HOST,
Constants.PROXY_TOKEN
)
val authHelper by lazy { protonTestEntryPoint.loginTestHelper }
private val protonTestEntryPoint by lazy {
@@ -18,11 +18,13 @@
package me.proton.android.core.coreexample
import me.proton.core.configuration.EnvironmentConfigurationDefaults
object Constants {
const val API_HOST = BuildConfig.API_HOST
const val HV3_HOST = BuildConfig.HV3_HOST
const val BASE_URL = "https://$API_HOST"
const val QUARK_HOST = BuildConfig.QUARK_HOST
const val HV3_HOST = EnvironmentConfigurationDefaults.hv3Host
const val BASE_URL = EnvironmentConfigurationDefaults.baseUrl
const val QUARK_HOST = EnvironmentConfigurationDefaults.host
const val PROXY_TOKEN = EnvironmentConfigurationDefaults.proxyToken
}
const val PLAN_PLUS_ID =
@@ -23,9 +23,9 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import me.proton.android.core.coreexample.BuildConfig
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.api.CoreExampleApiClient
import me.proton.core.configuration.EnvironmentConfigurationDefaults
import me.proton.core.network.data.client.ExtraHeaderProviderImpl
import me.proton.core.network.data.di.AlternativeApiPins
import me.proton.core.network.data.di.BaseProtonApiUrl
@@ -46,7 +46,7 @@ class NetworkModule {
@Provides
@Singleton
fun provideExtraHeaderProvider(): ExtraHeaderProvider = ExtraHeaderProviderImpl().apply {
BuildConfig.PROXY_TOKEN?.takeIfNotBlank()?.let { addHeaders("X-atlas-secret" to it) }
Constants.PROXY_TOKEN.takeIfNotBlank()?.let { addHeaders("X-atlas-secret" to it) }
}
}
@@ -63,7 +63,7 @@ class NetworkConstantsModule {
@CertificatePins
@Provides
fun provideCertificatePins() = if (BuildConfig.USE_DEFAULT_PINS) {
fun provideCertificatePins() = if (EnvironmentConfigurationDefaults.useDefaultPins) {
NetWorkDataConstants.DEFAULT_SPKI_PINS
} else {
emptyArray()
@@ -71,7 +71,7 @@ class NetworkConstantsModule {
@AlternativeApiPins
@Provides
fun provideAlternativeApiPins() = if (BuildConfig.USE_DEFAULT_PINS) {
fun provideAlternativeApiPins() = if (EnvironmentConfigurationDefaults.useDefaultPins) {
NetWorkDataConstants.ALTERNATIVE_API_SPKI_PINS
} else {
emptyList()
+71 -8
View File
@@ -87,7 +87,7 @@ coverage report:
- ./build/reports/cobertura-coverage.xml
```
It also allows for further customization. See the [plugin's README](./jacoco/README.md) for more info.
It also allows for further customization. See the [plugin's README](jacoco/README.md) for more info.
## Include Core Build plugin
- Plugin id: `me.proton.core.gradle-plugins.include-core-build`
@@ -128,29 +128,92 @@ includeCoreBuild {
```
Override include with local:
```kotlin
```
gradle.properties
local.git.proton-libs=../proton-libs
```
**Note: This plugin in based on [IncludeGit Gradle Plugin](https://melix.github.io/includegit-gradle-plugin).**
**Note: This plugin in based
on [IncludeGit Gradle Plugin](https://melix.github.io/includegit-gradle-plugin).**
## Publish-core-libraries plugin
- Plugin id: `publish-core-libraries`
- Not published on MavenCentral.
Use internally in core project to orchestrate [core libraries publication](../README.md#release).
## Environment configuration plugin
- Plugin id: `me.proton.core.gradle-plugins.environment-config`
- Published on MavenCentral.
This plugin should be applied to either build flavor or application build type android extension
in `build.gradle.kts` file.
*
Generates `build/generated/source/envConfig/{flavor}/{buildType}/EnvironmentConfigurationDefaults.java`
similarly to `BuildConfig.java`.
Generated class is then added to source directories and can be accessed at runtime
* Automatically obtains and sets proxy token if `useProxy` is set to `true`
* By default (if no configuration provided in build.gradle.kts) generates a default production
config (`api.proton.me`)
```kotlin
build.gradle.kts
plugins {
id("me.proton.core.gradle-plugins.environment-config")
}
android {
buildTypes {
debug {
environmentConfig {
useProxy = true // use proxy on all debug builds. Defaults to 'false'
}
}
}
defaultConfig {
environmentConfig {
apiPrefix = "mail-api" // will result in base url https://mail-api.proton.me
}
}
productFlavors.register("dev") {
environmentConfig {
host = "proton.black" // will result in base url https://mail-api.proton.black
}
}
productFlavors.register("atlas") {
environmentConfig {
apiPrefix = "test"
baseUrl = "https://special.url" // will result in base url https://test.special.url
}
}
}
```
## Tests
- Plugin id: `me.proton.core.gradle-plugins.tests`
- Published on MavenCentral.
This plugin should be applied to the root `build.gradle` file. It adds an `allTest` task which run all unit tests in jvm and Android subprojects.
This plugin should be applied to the root `build.gradle` file. It adds an `allTest` task which run
all unit tests in jvm and Android subprojects.
# Release
Release process is based on [trunk branch for release process](https://trunkbaseddevelopment.com/branch-for-release/).
Release is done by the CI. To trigger a release for version `X.Y.Z`, just push a branch named `release/gradle-plugins/X.Y.Z`.
When the release is successfully done, a tag `release/gradle-plugins/X.Y.Z` is created from the commit used to do the release.
Release implementation is orchestrated by project [publish-core-plugins](./publish-core-plugins).
Release process is based
on [trunk branch for release process](https://trunkbaseddevelopment.com/branch-for-release/).
Release is done by the CI. To trigger a release for version `X.Y.Z`, just push a branch
named `release/gradle-plugins/X.Y.Z`.
When the release is successfully done, a tag `release/gradle-plugins/X.Y.Z` is created from the
commit used to do the release.
Release implementation is orchestrated by project [publish-core-plugins](publish-core-plugins).
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
`kotlin-dsl`
kotlin("jvm")
`java-gradle-plugin`
}
publishOption.shouldBePublishedAsPlugin = true
gradlePlugin {
plugins {
create("protonEnvironment") {
id = "me.proton.core.gradle-plugins.environment-config"
displayName = "Proton environment configuration plugin"
description = "Generates build/generated/source/envConfig/{flavor}/{buildType}/" +
"EnvironmentConfigurationDefaults.java"
implementationClass = "configuration.ProtonEnvironmentConfigurationPlugin"
}
}
}
repositories {
google()
mavenCentral()
maven("https://plugins.gradle.org/m2/")
}
dependencies {
implementation(gradleApi())
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
compileOnly(libs.android.gradle)
api(libs.easyGradle.androidDsl)
}
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration
@SuppressWarnings("LongParameterList")
open class EnvironmentConfig(
open val host: String? = null,
open val proxyToken: String? = null,
open val apiPrefix: String? = null,
open val baseUrl: String? = null,
open val apiHost: String? = null,
open val hv3Host: String? = null,
open val hv3Url: String? = null,
open val useDefaultPins: Boolean? = host == "proton.me"
)
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration
import configuration.util.getTokenFromCurl
open class EnvironmentConfigSettings : EnvironmentConfig() {
private var _host: String? = null
override var host: String
get() = _host ?: "proton.me"
set(value) {
_host = value
}
private var _apiPrefix: String? = null
override var apiPrefix: String
get() = _apiPrefix ?: "api"
set(value) {
_apiPrefix = value
}
private var _apiHost: String? = null
override var apiHost: String
get() = _apiHost ?: "$apiPrefix.$host"
set(value) {
_apiHost = value
}
private var _baseUrl: String? = null
override var baseUrl: String
get() = _baseUrl ?: "https://$apiHost"
set(value) {
_baseUrl = value
}
private var _hv3Host: String? = null
override var hv3Host: String
get() = _hv3Host ?: "verify.$host"
set(value) {
_hv3Host = value
}
private var _hv3Url: String? = null
override var hv3Url: String
get() = _hv3Url ?: "https://$hv3Host"
set(value) {
_hv3Url = value
}
private var _useDefaultPins: Boolean? = null
final override var useDefaultPins: Boolean
get() = _useDefaultPins ?: true
private set(value) {
_useDefaultPins = value
}
private var _useProxy: Boolean? = null
var useProxy: Boolean
get() = _useProxy ?: false
set(value) {
_useProxy = value
}
private var _proxyToken: String? = null
final override var proxyToken: String?
get() = proxyTokenFromCurl.takeIf { useProxy } ?: ""
private set(value) {
_proxyToken = value
}
}
private val proxyTokenFromCurl: String by lazy {
val atlasProxyUrl: String? = System.getenv("ATLAS_PROXY_URL")
if (atlasProxyUrl.isNullOrEmpty()) {
println("ATLAS_PROXY_URL is not set. skipping proxy token setting")
return@lazy ""
}
getTokenFromCurl(atlasProxyUrl)
}
@@ -0,0 +1,124 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration
import com.android.build.api.dsl.ApplicationBuildType
import com.android.build.api.dsl.DefaultConfig
import com.android.build.api.dsl.ProductFlavor
import com.android.build.gradle.AppExtension
import configuration.extensions.environmentConfiguration
import configuration.extensions.mergeWith
import configuration.extensions.printEnvironmentConfigDetails
import configuration.extensions.sourceClassContent
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.configurationcache.extensions.capitalized
class ProtonEnvironmentConfigurationPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.logger.info("Applying Proton environment configurations")
target.afterEvaluate {
handleConfigurations(target)
}
}
/**
* Handles the configurations for the given project.
*
* Goes through all the product flavors and build types of the project,
* merges their configurations, and then creates the necessary directories and files.
*
* @param project The project for which the configurations are being handled.
*/
private fun handleConfigurations(project: Project) {
project.extensions.getByType(AppExtension::class.java).apply {
productFlavors.all { flavor ->
buildTypes.all { buildType ->
val mergedConfig = mergeConfigurations(defaultConfig, buildType, flavor)
val variantName = flavor.name + buildType.name.capitalized()
project.logger.debug("environment configuration :$variantName")
project.printEnvironmentConfigDetails(mergedConfig)
project.createJavaFileInDir(flavor, buildType, mergedConfig.sourceClassContent())
true
}
}
}
}
/**
* Creates a [javaClassName].java file in [generatedSourceLocation]/[flavor].name/[buildType].name/[packagePath]
* adds flavor directories to source sets
*
* @param flavor The product flavor.
* @param buildType The build type.
* @param sourceClassContent The content to be written to the source class file.
* @param generatedSourceLocation Generated files dir
* @param javaClassName Java class name to be generated
* @param packagePath Package directory
*/
@Suppress("LongParameterList")
private fun Project.createJavaFileInDir(
flavor: ProductFlavor,
buildType: ApplicationBuildType,
sourceClassContent: String,
generatedSourceLocation: String = ENV_CONFIG_LOCATION,
javaClassName: String = DEFAULTS_CLASS_NAME,
packagePath: String = PACKAGE_NAME.replace(".", "/")
) {
val variantLocation = "$generatedSourceLocation/${flavor.name}/${buildType.name}"
val variantName = flavor.name + buildType.name.capitalized()
val configDirectory = project.buildDir.resolve("$variantLocation/$packagePath")
val variantDirectory = project.buildDir.resolve(variantLocation)
configDirectory.mkdirs()
val environmentConfigFile = configDirectory
.resolve("$javaClassName.java").apply {
writeText(sourceClassContent)
}
project.logger.info("generated ${environmentConfigFile.path}")
project.extensions.getByType(AppExtension::class.java).sourceSets
.findByName(variantName)
?.java?.apply {
// avoid duplicate content roots
if (!srcDirs.contains(variantDirectory)) {
srcDir(variantDirectory)
}
}
}
private fun mergeConfigurations(
defaultConfig: DefaultConfig,
buildType: ApplicationBuildType,
flavor: ProductFlavor
): EnvironmentConfig = defaultConfig.environmentConfiguration
.mergeWith(buildType.environmentConfiguration)
.mergeWith(flavor.environmentConfiguration)
companion object {
const val ENV_CONFIG_LOCATION: String = "generated/source/envConfig"
const val PACKAGE_NAME: String = "me.proton.core.configuration"
const val DEFAULTS_CLASS_NAME: String = "EnvironmentConfigurationDefaults"
}
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.extensions
import com.android.build.api.dsl.ApplicationBuildType
import configuration.EnvironmentConfig
import configuration.EnvironmentConfigSettings
import org.jetbrains.kotlin.gradle.plugin.extraProperties
var ApplicationBuildType.environmentConfiguration: EnvironmentConfig
get() = extraProperties.getEnvironmentConfigurationByName(name)
private set(config) = extraProperties.setEnvironmentConfigurationByName(name, config)
fun ApplicationBuildType.protonEnvironment(action: EnvironmentConfigSettings.() -> Unit) {
environmentConfiguration = EnvironmentConfigSettings().apply(action)
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.extensions
import com.android.build.api.dsl.BaseFlavor
import configuration.EnvironmentConfig
import configuration.EnvironmentConfigSettings
import org.jetbrains.kotlin.gradle.plugin.extraProperties
var BaseFlavor.environmentConfiguration: EnvironmentConfig
get() = extraProperties.getEnvironmentConfigurationByName(getName())
private set(config) = extraProperties.setEnvironmentConfigurationByName(getName(), config)
fun BaseFlavor.protonEnvironment(action: EnvironmentConfigSettings.() -> Unit) {
environmentConfiguration = EnvironmentConfigSettings().apply(action)
}
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.extensions
import configuration.EnvironmentConfig
import configuration.ProtonEnvironmentConfigurationPlugin.Companion.DEFAULTS_CLASS_NAME
import configuration.ProtonEnvironmentConfigurationPlugin.Companion.PACKAGE_NAME
import configuration.util.toBuildConfigValue
fun EnvironmentConfig.mergeWith(other: EnvironmentConfig) = EnvironmentConfig(
host = other.host ?: host,
apiPrefix = other.apiPrefix ?: apiPrefix,
baseUrl = other.baseUrl ?: baseUrl,
apiHost = other.apiHost ?: apiHost,
hv3Host = other.hv3Host ?: hv3Host,
hv3Url = other.hv3Url ?: hv3Url,
proxyToken = other.proxyToken.takeIf { it?.isNotEmpty()!! } ?: proxyToken,
)
fun EnvironmentConfig.sourceClassContent(
namespace: String = PACKAGE_NAME,
className: String = DEFAULTS_CLASS_NAME,
fields: String = configData.joinToString("\n")
): String = """package $namespace;
public class $className {
$fields
}
"""
private val EnvironmentConfig.configData: List<String>
get() = EnvironmentConfig::class.java.declaredFields.map {
it.trySetAccessible()
val (type, name) = it.type.simpleName to it.name
val value = it.get(this).toBuildConfigValue()
" public static final $type $name = $value;"
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.extensions
import configuration.EnvironmentConfig
import configuration.EnvironmentConfigSettings
import org.gradle.api.plugins.ExtraPropertiesExtension
private const val PROP_SUFFIX = "ProtonEnvironmentConfig"
fun ExtraPropertiesExtension.getEnvironmentConfigurationByName(
name: String,
fallbackSettings: EnvironmentConfigSettings = EnvironmentConfigSettings()
): EnvironmentConfig =
if (properties.contains(name + PROP_SUFFIX))
this[name + PROP_SUFFIX] as? EnvironmentConfigSettings ?: fallbackSettings
else
fallbackSettings
fun ExtraPropertiesExtension.setEnvironmentConfigurationByName(
name: String,
config: EnvironmentConfig
) {
this[name + PROP_SUFFIX] = config
}
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.extensions
import configuration.EnvironmentConfig
import org.gradle.api.Project
fun Project.printEnvironmentConfigDetails(config: EnvironmentConfig) {
logger.debug(" host: ${config.host}")
logger.debug(" api prefix: ${config.apiPrefix}")
logger.debug(" api host: ${config.apiHost}")
logger.debug(" base url: ${config.baseUrl}")
logger.debug(" hv3 host: ${config.hv3Host}")
logger.debug(" hv3 url: ${config.hv3Url}}")
logger.debug(" proxy token: ${config.proxyToken}")
}
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package configuration.util
import java.util.concurrent.TimeUnit
fun getTokenFromCurl(proxyUrl: String): String {
val process = ProcessBuilder("curl", proxyUrl)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
try {
val error: String = process.errorStream.bufferedReader().readText()
val output = process.inputStream.bufferedReader().readText()
// Avoid potential hangs
require(process.waitFor(1, TimeUnit.SECONDS)) {
"Curl process timed out."
}
require(process.exitValue() == 0) {
"Could not obtain proxy token with exit code ${process.exitValue()}: $error"
}
require(Regex("^[a-zA-Z0-9]+$").matches(output)) {
"Invalid token format. Expected a single word, got: $output"
}
return output.trim()
} finally {
process.destroy()
}
}
fun Any?.toBuildConfigValue(): String {
return when (this) {
null -> "null"
is Boolean -> "$this"
is String -> "\"$this\""
else -> throw IllegalArgumentException("Unknown build field conversion for type ${this::class.java.simpleName}")
}
}
+2 -1
View File
@@ -27,7 +27,8 @@ include(
"jacoco",
"tests",
"include-core-build",
"publish-core-libraries"
"publish-core-libraries",
"environment-configuration"
)
pluginManagement {