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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ internal fun Project.configureTestOptions() {
}
maxHeapSize = "2g"

// Keep forked test JVMs headless: Robolectric/Compose tests otherwise initialize
// AWT on macOS, spawning a Dock icon per fork and stealing window focus.
// apple.awt.UIElement covers anything that flips headless back off.
systemProperty("java.awt.headless", "true")
jvmArgs("-Dapple.awt.UIElement=true")

// JUnit Jupiter parallel execution within each Gradle fork.
// Classes run sequentially ("same_thread") because 19+ ViewModel test classes use
// Dispatchers.setMain() — a JVM-global singleton that races when classes execute
Expand Down
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ ksp.project.isolation.enabled=true
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.isolated-projects=true
org.gradle.jvmargs=-Xmx8g -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:ReservedCodeCacheSize=512m -XX:MaxMetaspaceSize=2g -Xss2m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx8g -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:+UseStringDeduplication -XX:ReservedCodeCacheSize=512m -XX:MaxMetaspaceSize=2g -Xss2m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Djava.awt.headless=true -Dapple.awt.UIElement=true
org.gradle.parallel=true
# Run the daemon and its workers at low OS priority so full builds don't starve
# the foreground UI on developer laptops. Negligible on dedicated CI runners.
org.gradle.priority=low
org.gradle.welcome=never
18 changes: 7 additions & 11 deletions gradle/build-cache.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,13 @@ def getMeshProperty(String key) {

def currentDir = settingsDir
while (currentDir != null) {
def localFile = new File(currentDir, "local.properties")
if (localFile.exists()) {
def props = new Properties()
localFile.withInputStream { props.load(it) }
if (props.containsKey(key)) return props.getProperty(key)
}
def configFile = new File(currentDir, "config.properties")
if (configFile.exists()) {
def props = new Properties()
configFile.withInputStream { props.load(it) }
if (props.containsKey(key)) return props.getProperty(key)
for (name in ["local.properties", "config.properties", "secrets.properties"]) {
def file = new File(currentDir, name)
if (file.exists()) {
def props = new Properties()
file.withInputStream { props.load(it) }
if (props.containsKey(key)) return props.getProperty(key)
}
Comment on lines +24 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '1,120p' gradle/build-cache.settings.gradle || true

echo
echo "== searches for build-cache/settings/cache password usage =="
rg -n "build-cache|GRADLE_CACHE_PASSWORD|getOrCreateDirectory|cache|secrets.properties|local.properties|config.properties" gradle settings.gradle* settings.gradle 2>/dev/null || true

echo
echo "== git status =="
git status --porcelain

Repository: meshtastic/Meshtastic-Android

Length of output: 5306


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from properties import loadString  # only if installed; avoid
PY
python3 - <<'PY'
import io
from grdp.properties import loadString
PY

Repository: meshtastic/Meshtastic-Android

Length of output: 290


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files gradle settings.gradle settings.gradle.kts 2>/dev/null || true

echo
echo "== Kotlin/Java Properties.load behavior documentation if Gradle source is present =="
rg -n "class Properties|fun Properties|Properties\\.load|containsKey\\(key\\)" -S . 2>/dev/null | head -80 || true

echo
echo "== repository files with credentials/password checks around cache setup =="
rg -n "getMeshProperty\\(|GRADLE_CACHE_PASSWORD|credentials|cacheUsername|cachePassword" gradle settings.gradle settings.gradle.kts settings.gradle.kts 2>/dev/null || true

Repository: meshtastic/Meshtastic-Android

Length of output: 2041


🌐 Web query:

Java Properties load key=value empty value containsKey getProperty

💡 Result:

In Java, the java.util.Properties class handles key-value pairs where both keys and values are treated as strings [1][2]. When using the load method, an entry defined as key= (with no value following the equals sign) results in a key being mapped to an empty string ("") [3]. Consequently, calling getProperty(key) on such a key will return an empty string, not null [1][4]. Regarding the behavior of containsKey versus getProperty: 1. containsKey(key): This method checks only the properties explicitly set within the Properties object itself. It returns true if the key exists (even if its associated value is an empty string) and false otherwise [5][2]. It does not search the default property list [5]. 2. getProperty(key): This method searches for the key within the current Properties object and, if not found, recursively searches through any associated default property list [1][5]. It returns the value associated with the key or null if the key is not found in either the current object or the defaults [1][4]. Because of these differences, it is possible for containsKey to return false for a specific key (if it is not in the current Properties object) while getProperty returns a non-null value (because it was found in the default properties list) [5]. Conversely, if a key is present with an empty value, containsKey will return true, and getProperty will return "" [3][5].

Citations:


🌐 Web query:

Android Gradle Plugin GroovyDslPropertyProvider propertyProperties behavior trim empty properties

💡 Result:

There is no documented component, class, or method named GroovyDslPropertyProvider in the Android Gradle Plugin or Gradle's public API [1][2][3]. Furthermore, there is no automatic "trim empty properties" behavior for properties in Gradle [4][5]. In Gradle, property behavior regarding empty values is determined by the specific property type (e.g., Property, ListProperty, MapProperty) and how you configure it [1][2]. Common sources of confusion include: 1. Handling of Absent Providers: Adding a provider that has no value (is "absent") to a collection property (like ListProperty or SetProperty) can clear the property or cause it to throw a MissingValueException when accessed, rather than being ignored [4]. 2. Lazy Configuration: If you are working with properties, it is best practice to use methods like orElse to provide a fallback value if the underlying provider is empty or absent, ensuring the property remains in a defined state [4][5]. 3. Groovy DSL Syntax: Recent changes in Gradle have deprecated the "space-assignment" syntax (e.g., property value) in favor of explicit assignment (property = value) [6][7]. If you are encountering issues with property values not being set, ensure you are using the explicit assignment syntax [6]. If you are dealing with specific property data (e.g., loading from a.properties file), ensure you are explicitly sanitizing or filtering empty strings using standard Java/Groovy methods (such as.trim or.filter {}) before assigning those values to a Gradle Property, as Gradle does not automatically perform this cleanup [5][8].

Citations:


🌐 Web query:

Apache Commons Properties XMLParserFactory load key equals sign only properties key containsKey getProperty empty value

💡 Result:

In Apache Commons Configuration, the handling of keys and values in properties files is governed by the library's property parser, which supports standard key-value syntax [1][2]. Properties File Parsing The PropertiesConfiguration class, often used for loading properties, recognizes keys and values separated by '=', ':', or whitespace [1][3]. If a property line contains a key followed by an equals sign but no value (e.g., my.key =), this is treated as a valid property with an empty string as its value [4]. Recent updates (such as those addressing issue CONFIGURATION-847) have ensured that this distinction—between an unspecified property and a property explicitly set to an empty string—is correctly preserved [4]. containsKey and getProperty Behavior - containsKey(String key): This method returns true if the specified key exists in the configuration [5][6]. For standard key-value configurations, an entry defined as my.key = will result in containsKey returning true [4]. Note that in HierarchicalConfiguration (often used with XML), containsKey may return false if a node exists but has no associated value (only child nodes) [7]. - getProperty(String key): This method returns the raw value associated with the key [8][9]. If the key exists and is defined as an empty string (e.g., my.key =), getProperty will return an empty string ("") [4]. If the key is not present in the configuration at all, getProperty returns null [8][9]. In summary, Apache Commons Configuration explicitly treats a key defined with an equals sign but no trailing text as a key mapped to an empty string, which is detectable via containsKey (returning true) and retrievable via getProperty (returning "") [8][4][5].

Citations:


🌐 Web query:

Java Properties.load empty value key equals whitespace behavior trim

💡 Result:

In Java, the behavior of the java.util.Properties.load method regarding whitespace is strictly defined by its documented format [1][2]. Key Whitespace Handling: 1. Whitespace preceding the key: Ignored. The key starts at the first non-whitespace character on the line [1][3]. 2. Whitespace after the key: Any whitespace immediately following the key is skipped until the first non-whitespace character is encountered [1][2]. 3. Separators: If that first non-whitespace character is an equals sign (=) or colon (:), it is treated as the separator and is ignored, along with any subsequent whitespace characters that immediately follow it [1][2]. Value Whitespace Handling: 1. Leading whitespace in values: Once the separator is processed, all remaining characters on the line—including leading, trailing, and embedded whitespace—are preserved as part of the value [1][2]. This means leading whitespace after the separator is not trimmed; it becomes part of the property value [1][2]. 2. Trailing whitespace in values: Trailing whitespace is also considered significant and is included in the property value [4]. The Properties class does not automatically trim trailing whitespace from the value string [1][2]. 3. Empty values: If there are no characters remaining after the separator and its following whitespace, the element is set to an empty string ("") [1][2]. In summary, while the Properties.load method aggressively skips whitespace around the key and the separator, it preserves all whitespace within the value itself [1][2]. If you require trimmed values, you must manually perform the trim operation on the string returned by getProperty [1][2].

Citations:


Do not let blank properties shadow secrets.properties.

Properties.containsKey(key) || props.getProperty(key) maps an entry like GRADLE_CACHE_PASSWORD= to an empty string, so the lookup returns before reaching a valid value in secrets.properties. Since getMeshProperty(...)?.trim() treats empty values as absent, preserve that precedence consistently.

Proposed fix
-                if (props.containsKey(key)) return props.getProperty(key)
+                def value = props.getProperty(key)?.trim()
+                if (value) return value
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (name in ["local.properties", "config.properties", "secrets.properties"]) {
def file = new File(currentDir, name)
if (file.exists()) {
def props = new Properties()
file.withInputStream { props.load(it) }
if (props.containsKey(key)) return props.getProperty(key)
}
for (name in ["local.properties", "config.properties", "secrets.properties"]) {
def file = new File(currentDir, name)
if (file.exists()) {
def props = new Properties()
file.withInputStream { props.load(it) }
def value = props.getProperty(key)?.trim()
if (value) return value
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gradle/build-cache.settings.gradle` around lines 24 - 30, Update the property
lookup loop in the build-cache settings so blank or whitespace-only values do
not terminate the search. In the lookup method containing the local.properties,
config.properties, and secrets.properties iteration, retrieve and trim the
property value, return it only when non-empty, and continue to later files
otherwise, preserving the existing precedence and getMeshProperty behavior.

}
currentDir = currentDir.parentFile
}
Expand Down