Skip to content

Commit 88c2613

Browse files
committed
build(secrets): centralize validation and modernize CI bypass
Centralize the secrets.properties enforcement logic into a single authoritative check in the root build.gradle.kts file, removing heavy duplication across six subprojects. Replace brittle runtime environment checks with safe, build-time BuildConfig.IS_CI constants for automated testing pipelines. - Create root build.gradle.kts for workspace-wide validation - Remove duplicated secrets-checking blocks from subproject scripts - Inject BuildConfig.IS_CI and apply manifest placeholders in CI - Update Application classes to respect BuildConfig.IS_CI early returns - Untrack LOG.md and add to local exclude file - Fill out missing PLACES_API_KEY in defaults properties
1 parent e995f80 commit 88c2613

12 files changed

Lines changed: 128 additions & 294 deletions

File tree

LOG.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

Maps3DSamples/ApiDemos/java-app/build.gradle.kts

Lines changed: 8 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,63 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import java.util.Properties
18-
import org.gradle.api.GradleException
19-
20-
// Check for secrets.properties file and valid API key before proceeding with build tasks.
21-
val secretsFile = rootProject.file("secrets.properties")
22-
val isCI = System.getenv("CI")?.toBoolean() ?: false
23-
24-
if (!isCI) {
25-
val requestedTasks = gradle.startParameter.taskNames
26-
if (requestedTasks.isEmpty() && !secretsFile.exists()) {
27-
// It's likely an IDE sync if no tasks are specified, so just issue a warning.
28-
println("Warning: secrets.properties not found. Gradle sync may succeed, but building/running the app will fail.")
29-
} else if (requestedTasks.isNotEmpty()) {
30-
val buildTaskKeywords = listOf("build", "install", "assemble")
31-
val isBuildTask = requestedTasks.any { task ->
32-
buildTaskKeywords.any { keyword ->
33-
task.contains(keyword, ignoreCase = true)
34-
}
35-
}
36-
37-
val testTaskKeywords = listOf("test", "report", "lint")
38-
val isTestTask = requestedTasks.any { task ->
39-
testTaskKeywords.any { keyword ->
40-
task.contains(keyword, ignoreCase = true)
41-
}
42-
}
43-
44-
val isDebugTask = requestedTasks.any { task ->
45-
task.contains("Debug", ignoreCase = true) || task.contains("installAndLaunch", ignoreCase = true)
46-
}
47-
48-
if (isBuildTask && !isTestTask && isDebugTask) {
49-
val defaultsFile = rootProject.file("local.defaults.properties")
50-
val requiredKeysMessage = if (defaultsFile.exists()) {
51-
defaultsFile.readText()
52-
} else {
53-
"MAPS3D_API_KEY=<YOUR_API_KEY>"
54-
}
55-
56-
if (!secretsFile.exists()) {
57-
throw GradleException("secrets.properties file not found. Please create a 'secrets.properties' file in the root project directory with the following content:\n\n$requiredKeysMessage")
58-
}
59-
60-
val secrets = Properties()
61-
secretsFile.inputStream().use { secrets.load(it) }
62-
val apiKey = secrets.getProperty("MAPS3D_API_KEY")
63-
64-
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
65-
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
66-
}
67-
68-
if (secrets.getProperty("MAPS_API_KEY") != null) {
69-
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
70-
}
71-
}
72-
}
73-
}
17+
val isCI = rootProject.extra["isCI"] as? Boolean ?: false
7418

7519
plugins {
7620
alias(libs.plugins.android.application)
@@ -92,6 +36,13 @@ android {
9236
versionName = "1.0"
9337

9438
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
39+
40+
if (isCI) {
41+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
42+
manifestPlaceholders["PLACES_API_KEY"] = "DEFAULT_API_KEY"
43+
}
44+
45+
buildConfigField("Boolean", "IS_CI", "${isCI}")
9546
}
9647

9748
buildTypes {

Maps3DSamples/ApiDemos/java-app/src/main/java/com/example/maps3djava/Maps3DJavaApplication.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public void onCreate() {
5858
* incorrectly configured, and a RuntimeException is thrown.
5959
*/
6060
private void checkApiKey() {
61+
if (BuildConfig.IS_CI) {
62+
return;
63+
}
6164
try {
6265
ApplicationInfo appInfo =
6366
getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);

Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,64 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import org.gradle.api.GradleException
18-
import java.io.File
19-
import java.util.Properties
20-
21-
// Check for secrets.properties file and valid API key before proceeding with build tasks.
22-
val secretsFile = rootProject.file("secrets.properties")
23-
val isCI = System.getenv("CI")?.toBoolean() ?: false
24-
25-
if (!isCI) {
26-
val requestedTasks = gradle.startParameter.taskNames
27-
if (requestedTasks.isEmpty() && !secretsFile.exists()) {
28-
// It's likely an IDE sync if no tasks are specified, so just issue a warning.
29-
println("Warning: secrets.properties not found. Gradle sync may succeed, but building/running the app will fail.")
30-
} else if (requestedTasks.isNotEmpty()) {
31-
val buildTaskKeywords = listOf("build", "install", "assemble")
32-
val isBuildTask = requestedTasks.any { task ->
33-
buildTaskKeywords.any { keyword ->
34-
task.contains(keyword, ignoreCase = true)
35-
}
36-
}
37-
38-
val testTaskKeywords = listOf("test", "report", "lint")
39-
val isTestTask = requestedTasks.any { task ->
40-
testTaskKeywords.any { keyword ->
41-
task.contains(keyword, ignoreCase = true)
42-
}
43-
}
44-
45-
val isDebugTask = requestedTasks.any { task ->
46-
task.contains("Debug", ignoreCase = true) || task.contains("installAndLaunch", ignoreCase = true)
47-
}
48-
49-
if (isBuildTask && !isTestTask && isDebugTask) {
50-
val defaultsFile = rootProject.file("local.defaults.properties")
51-
val requiredKeysMessage = if (defaultsFile.exists()) {
52-
defaultsFile.readText()
53-
} else {
54-
"MAPS3D_API_KEY=<YOUR_API_KEY>"
55-
}
56-
57-
if (!secretsFile.exists()) {
58-
throw GradleException("secrets.properties file not found. Please create a 'secrets.properties' file in the root project directory with the following content:\n\n$requiredKeysMessage")
59-
}
60-
61-
val secrets = Properties()
62-
secretsFile.inputStream().use { secrets.load(it) }
63-
val apiKey = secrets.getProperty("MAPS3D_API_KEY")
64-
65-
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
66-
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
67-
}
68-
69-
if (secrets.getProperty("MAPS_API_KEY") != null) {
70-
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
71-
}
72-
}
73-
}
74-
}
17+
val isCI = rootProject.extra["isCI"] as? Boolean ?: false
7518

7619
plugins {
7720
alias(libs.plugins.android.application)
@@ -95,6 +38,13 @@ android {
9538
versionName = "1.7.0"
9639

9740
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
41+
42+
if (isCI) {
43+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
44+
manifestPlaceholders["PLACES_API_KEY"] = "DEFAULT_API_KEY"
45+
}
46+
47+
buildConfigField("Boolean", "IS_CI", "${isCI}")
9848
}
9949

10050
buildTypes {

Maps3DSamples/ApiDemos/kotlin-app/src/main/java/com/example/maps3dkotlin/Maps3DKotlinApplication.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class Maps3DKotlinApplication : Application() {
4949
* incorrectly configured, and a RuntimeException is thrown.
5050
*/
5151
private fun checkApiKey() {
52+
if (BuildConfig.IS_CI) {
53+
return
54+
}
5255
try {
5356
val appInfo =
5457
packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)

Maps3DSamples/advanced/app/build.gradle.kts

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -14,63 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import java.util.Properties
18-
import org.gradle.api.GradleException
19-
20-
// Check for secrets.properties file and valid API key before proceeding with build tasks.
21-
val secretsFile = rootProject.file("secrets.properties")
22-
val isCI = System.getenv("CI")?.toBoolean() ?: false
23-
24-
if (!isCI) {
25-
val requestedTasks = gradle.startParameter.taskNames
26-
if (requestedTasks.isEmpty() && !secretsFile.exists()) {
27-
// It's likely an IDE sync if no tasks are specified, so just issue a warning.
28-
println("Warning: secrets.properties not found. Gradle sync may succeed, but building/running the app will fail.")
29-
} else if (requestedTasks.isNotEmpty()) {
30-
val buildTaskKeywords = listOf("build", "install", "assemble")
31-
val isBuildTask = requestedTasks.any { task ->
32-
buildTaskKeywords.any { keyword ->
33-
task.contains(keyword, ignoreCase = true)
34-
}
35-
}
36-
37-
val testTaskKeywords = listOf("test", "report", "lint")
38-
val isTestTask = requestedTasks.any { task ->
39-
testTaskKeywords.any { keyword ->
40-
task.contains(keyword, ignoreCase = true)
41-
}
42-
}
43-
44-
val isDebugTask = requestedTasks.any { task ->
45-
task.contains("Debug", ignoreCase = true) || task.contains("installAndLaunch", ignoreCase = true)
46-
}
47-
48-
if (isBuildTask && !isTestTask && isDebugTask) {
49-
val defaultsFile = rootProject.file("local.defaults.properties")
50-
val requiredKeysMessage = if (defaultsFile.exists()) {
51-
defaultsFile.readText()
52-
} else {
53-
"MAPS3D_API_KEY=<YOUR_API_KEY>"
54-
}
55-
56-
if (!secretsFile.exists()) {
57-
throw GradleException("secrets.properties file not found. Please create a 'secrets.properties' file in the root project directory with the following content:\n\n$requiredKeysMessage")
58-
}
59-
60-
val secrets = Properties()
61-
secretsFile.inputStream().use { secrets.load(it) }
62-
val apiKey = secrets.getProperty("MAPS3D_API_KEY")
63-
64-
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
65-
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
66-
}
67-
68-
if (secrets.getProperty("MAPS_API_KEY") != null) {
69-
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
70-
}
71-
}
72-
}
73-
}
17+
val isCI = rootProject.extra["isCI"] as? Boolean ?: false
7418

7519
plugins {
7620
alias(libs.plugins.android.application)
@@ -98,6 +42,11 @@ android {
9842
versionName = "1.7.0"
9943

10044
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
45+
46+
if (isCI) {
47+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
48+
manifestPlaceholders["PLACES_API_KEY"] = "DEFAULT_API_KEY"
49+
}
10150
}
10251

10352
buildTypes {

PlacesUIKit3D/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
val isCI = rootProject.extra["isCI"] as? Boolean ?: false
18+
1719
// The `plugins` block is where we apply Gradle plugins to this module.
1820
// Plugins add new tasks and configurations to our build process.
1921
plugins {
@@ -57,6 +59,13 @@ android {
5759

5860
// Specifies the instrumentation runner for running Android tests.
5961
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
62+
63+
if (isCI) {
64+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
65+
manifestPlaceholders["PLACES_API_KEY"] = "DEFAULT_API_KEY"
66+
}
67+
68+
buildConfigField("Boolean", "IS_CI", "${isCI}")
6069
}
6170

6271
buildTypes {

PlacesUIKit3D/src/main/java/com/example/placesuikit3d/Maps3DPlacesApplication.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class Maps3DPlacesApplication : Application() {
3232
}
3333

3434
private fun initializePlaces() {
35+
if (BuildConfig.IS_CI) {
36+
return
37+
}
3538
val apiKey = BuildConfig.PLACES_API_KEY
3639

3740
if (apiKey == null || apiKey.isBlank() || apiKey == "DEFAULT_API_KEY") {
@@ -58,6 +61,9 @@ class Maps3DPlacesApplication : Application() {
5861
* incorrectly configured, and a RuntimeException is thrown.
5962
*/
6063
private fun checkApiKey() {
64+
if (BuildConfig.IS_CI) {
65+
return
66+
}
6167
try {
6268
val appInfo =
6369
packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)

build.gradle.kts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import java.util.Properties
18+
19+
// Evaluate if we are in a CI environment
20+
val isCI = System.getenv("CI")?.toBoolean() ?: false
21+
22+
// Share the isCI flag with all subprojects via Gradle's extra properties
23+
extra["isCI"] = isCI
24+
25+
if (!isCI) {
26+
val secretsFile = file("secrets.properties")
27+
val requestedTasks = gradle.startParameter.taskNames
28+
29+
if (requestedTasks.isEmpty() && !secretsFile.exists()) {
30+
// It's likely an IDE sync if no tasks are specified, so just issue a warning.
31+
println("Warning: secrets.properties not found. Gradle sync may succeed, but building/running the app will fail.")
32+
} else if (requestedTasks.isNotEmpty()) {
33+
val buildTaskKeywords = setOf("build", "install", "assemble")
34+
val testTaskKeywords = setOf("test", "report", "lint")
35+
36+
val isBuildTask = requestedTasks.any { name ->
37+
buildTaskKeywords.any { kw -> name.contains(kw, ignoreCase = true) }
38+
}
39+
val isTestTask = requestedTasks.any { name ->
40+
testTaskKeywords.any { kw -> name.contains(kw, ignoreCase = true) }
41+
}
42+
val isDebugTask = requestedTasks.any { task ->
43+
task.contains("Debug", ignoreCase = true) || task.contains("installAndLaunch", ignoreCase = true)
44+
}
45+
46+
if (isBuildTask && !isTestTask && isDebugTask) {
47+
val defaultsFile = file("local.defaults.properties")
48+
val requiredKeysMessage = if (defaultsFile.exists()) {
49+
defaultsFile.readText()
50+
} else {
51+
"MAPS3D_API_KEY=<YOUR_API_KEY>\nPLACES_API_KEY=<YOUR_API_KEY>"
52+
}
53+
54+
if (!secretsFile.exists()) {
55+
throw GradleException("secrets.properties file not found. Please create a 'secrets.properties' file in the root project directory with the following content:\n\n$requiredKeysMessage")
56+
}
57+
58+
val secrets = Properties()
59+
secretsFile.inputStream().use { secrets.load(it) }
60+
val mapsApiKey = secrets.getProperty("MAPS3D_API_KEY")
61+
val placesApiKey = secrets.getProperty("PLACES_API_KEY")
62+
63+
if (mapsApiKey.isNullOrBlank() || !mapsApiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
64+
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
65+
}
66+
67+
if (placesApiKey.isNullOrBlank() || !placesApiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
68+
throw GradleException("Invalid or missing PLACES_API_KEY in secrets.properties. Please provide a valid Google Places API key (starts with 'AIza').")
69+
}
70+
}
71+
}
72+
}

local.defaults.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
MAPS3D_API_KEY=DEFAULT_API_KEY
2+
PLACES_API_KEY=DEFAULT_API_KEY

0 commit comments

Comments
 (0)