diff --git a/.github/workflows/native-themes-sync.yml b/.github/workflows/native-themes-sync.yml
new file mode 100644
index 0000000000..05d57b44fd
--- /dev/null
+++ b/.github/workflows/native-themes-sync.yml
@@ -0,0 +1,80 @@
+name: Native Themes Sync
+
+# After every push to master that touches the native-theme CSS sources,
+# the CSS compiler, or the build script, regenerate the shipped
+# `.res` files and commit them straight back to master (no PR). Every
+# downstream pipeline (port builds, BuildDaemon, archetypes, website)
+# just consumes the committed `.res` instead of rebuilding it, so this
+# workflow is the single producer.
+#
+# Pushes made with GITHUB_TOKEN do not retrigger workflow runs, so the
+# commit-back step cannot loop.
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - master
+ paths:
+ - 'native-themes/**'
+ - 'maven/css-compiler/**'
+ - 'scripts/build-native-themes.sh'
+ - '.github/workflows/native-themes-sync.yml'
+
+permissions:
+ contents: write
+
+concurrency:
+ group: native-themes-sync
+ cancel-in-progress: false
+
+jobs:
+ rebuild-and-commit:
+ runs-on: ubuntu-latest
+ container: ghcr.io/codenameone/codenameone/pr-ci-container:latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Use JDK 8
+ run: |
+ echo "JAVA_HOME=${JAVA_HOME_8}" >> $GITHUB_ENV
+ echo "${JAVA_HOME_8}/bin" >> $GITHUB_PATH
+
+ - name: Cache Maven dependencies
+ uses: actions/cache@v4
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-m2
+
+ - name: Build css-compiler
+ run: |
+ cd maven
+ mvn -B -pl css-compiler -am install -DskipTests -Dmaven.javadoc.skip=true -Plocal-dev-javase
+
+ - name: Rebuild native themes
+ run: ./scripts/build-native-themes.sh
+
+ - name: Commit and push regenerated .res files
+ run: |
+ set -euo pipefail
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ # Themes/ holds the single source of truth. Each downstream consumer
+ # (nativeios.jar, the Android port jar, the simulator fat-jar, the
+ # BuildDaemon's sibling cn1 checkout) copies from Themes/ at its own
+ # build time, so this workflow only commits these two files.
+ git add Themes/iOSModernTheme.res Themes/AndroidMaterialTheme.res
+ if git diff --staged --quiet; then
+ echo "No .res changes; nothing to commit."
+ exit 0
+ fi
+ git commit -m "ci: auto-regenerate native theme .res files"
+ git push
diff --git a/Ports/Android/build.xml b/Ports/Android/build.xml
index 7fbc590bae..6be4303383 100644
--- a/Ports/Android/build.xml
+++ b/Ports/Android/build.xml
@@ -74,12 +74,10 @@
-
-
+
+
diff --git a/Ports/JavaScriptPort/src/main/webapp/assets/.gitignore b/Ports/JavaScriptPort/src/main/webapp/assets/.gitignore
index caa62305a9..48c6b4dfae 100644
--- a/Ports/JavaScriptPort/src/main/webapp/assets/.gitignore
+++ b/Ports/JavaScriptPort/src/main/webapp/assets/.gitignore
@@ -1,5 +1,5 @@
-# Generated by scripts/build-native-themes.sh. Mirrors of Themes/ so the
-# JS port runtime picks up the modern native themes. The CSS sources in
-# native-themes/ are authoritative.
+# Mirrors of Themes/*.res written by scripts/build-native-themes.sh so a local
+# JS port run can fetch them as webapp assets. The canonical .res lives in
+# Themes/ and is committed; this mirror is a build artifact.
iOSModernTheme.res
AndroidMaterialTheme.res
diff --git a/Ports/iOSPort/build.xml b/Ports/iOSPort/build.xml
index c7aafebde3..605f37fd29 100644
--- a/Ports/iOSPort/build.xml
+++ b/Ports/iOSPort/build.xml
@@ -74,11 +74,10 @@
-
-
+
+
diff --git a/Themes/.gitignore b/Themes/.gitignore
deleted file mode 100644
index 36ce6ac0c2..0000000000
--- a/Themes/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-# Generated by scripts/build-native-themes.sh from native-themes/*/theme.css.
-# These are build artifacts; the CSS sources in native-themes/ are authoritative.
-iOSModernTheme.res
-AndroidMaterialTheme.res
diff --git a/Themes/AndroidMaterialTheme.res b/Themes/AndroidMaterialTheme.res
new file mode 100644
index 0000000000..24e035cc17
Binary files /dev/null and b/Themes/AndroidMaterialTheme.res differ
diff --git a/Themes/iOSModernTheme.res b/Themes/iOSModernTheme.res
new file mode 100644
index 0000000000..a5ad76267f
Binary files /dev/null and b/Themes/iOSModernTheme.res differ
diff --git a/maven/android/pom.xml b/maven/android/pom.xml
index 43b617ddee..a85d8f2b50 100644
--- a/maven/android/pom.xml
+++ b/maven/android/pom.xml
@@ -86,9 +86,22 @@
+
+
+
+
@@ -280,6 +293,18 @@
${src.dir}
+
+
+ ${project.basedir}/../../Themes
+
+ AndroidMaterialTheme.res
+
+
diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/BorderAndPlafTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/BorderAndPlafTest.java
index d8f69104d4..57f6c60e71 100644
--- a/maven/core-unittests/src/test/java/com/codename1/ui/plaf/BorderAndPlafTest.java
+++ b/maven/core-unittests/src/test/java/com/codename1/ui/plaf/BorderAndPlafTest.java
@@ -15,6 +15,7 @@
import com.codename1.ui.plaf.StyleParser.PaddingInfo;
import com.codename1.ui.plaf.StyleParser.ScalarValue;
import com.codename1.ui.plaf.StyleParser.StyleInfo;
+import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
@@ -215,7 +216,7 @@ void testStyleParserImageAndBorderParsing() {
}
@FormTest
- void testRoundBorderShadowSpreadAndPaintingCaches() {
+ void testRoundBorderShadowSpreadAndPaintingCaches() throws Exception {
RoundBorder border = RoundBorder.create().shadowSpread(3).shadowBlur(4f).shadowOpacity(128).uiid(false);
com.codename1.ui.Label label = new com.codename1.ui.Label();
label.setWidth(20);
@@ -223,19 +224,17 @@ void testRoundBorderShadowSpreadAndPaintingCaches() {
label.setX(0);
label.setY(0);
border.paintBorderBackground(graphics, label);
- RoundBorder.CacheValue cacheValue = null;
- Object baseCache = label.getClientProperty("cn1$$-rbcache");
- if (baseCache instanceof RoundBorder.CacheValue) {
- cacheValue = (RoundBorder.CacheValue) baseCache;
- }
- for (int i = 0; cacheValue == null && i < 50; i++) {
- Object cached = label.getClientProperty("cn1$$-rbcache" + (i + 1));
- if (cached instanceof RoundBorder.CacheValue) {
- cacheValue = (RoundBorder.CacheValue) cached;
- break;
- }
- }
- assertNotNull(cacheValue);
+ // RoundBorder stores its cache under "cn1$$-rbcache" + instanceVal where
+ // instanceVal is a per-instance id off a static counter. Read the actual
+ // instanceVal off this border so the lookup doesn't depend on how many
+ // RoundBorder instances earlier tests in the same JVM happened to mint.
+ Field instanceValField = RoundBorder.class.getDeclaredField("instanceVal");
+ instanceValField.setAccessible(true);
+ int instanceVal = instanceValField.getInt(border);
+ Object cached = label.getClientProperty("cn1$$-rbcache" + instanceVal);
+ assertNotNull(cached, "RoundBorder.paintBorderBackground should populate the cache under cn1$$-rbcache" + instanceVal);
+ assertTrue(cached instanceof RoundBorder.CacheValue);
+ RoundBorder.CacheValue cacheValue = (RoundBorder.CacheValue) cached;
assertEquals(label.getWidth(), cacheValue.img.getWidth());
assertTrue(border.getMinimumHeight() > 0);
assertTrue(border.getMinimumWidth() > 0);
diff --git a/maven/ios/pom.xml b/maven/ios/pom.xml
index 3efbc557b5..a8a6f68858 100644
--- a/maven/ios/pom.xml
+++ b/maven/ios/pom.xml
@@ -109,8 +109,21 @@
+
-
+
+
+
+
+
+
diff --git a/maven/javase/pom.xml b/maven/javase/pom.xml
index bf65ded8c7..2585df0123 100644
--- a/maven/javase/pom.xml
+++ b/maven/javase/pom.xml
@@ -146,11 +146,10 @@
simulator fat jar so JavaSEPort.loadSkinFile can
load the right .res based on the current skin's
platformName (or the Simulator "Native Theme"
- menu override). failonerror=false lets the
- simulator still build if
- scripts/build-native-themes.sh hasn't produced
- the modern .res files yet. -->
-
+ menu override). All .res files are committed
+ under Themes/; the modern pair is regenerated
+ by .github/workflows/native-themes-sync.yml. -->
+
diff --git a/scripts/build-android-port.sh b/scripts/build-android-port.sh
index 54e959964b..d184a78cf5 100755
--- a/scripts/build-android-port.sh
+++ b/scripts/build-android-port.sh
@@ -131,13 +131,11 @@ if [ ! -f "$BUILD_CLIENT" ]; then
fi
fi
-# Compile native CSS themes (AndroidMaterialTheme.res) and stage them in the
-# Android port's src/ so the Maven build packages them into the port jar. The
-# runtime falls back to android_holo_light.res if AndroidMaterialTheme.res is
-# missing, which loses the Material 3 palette + all $DarkUIID entries.
-./scripts/build-native-themes.sh
-mkdir -p Ports/Android/src
-cp Themes/AndroidMaterialTheme.res Ports/Android/src/AndroidMaterialTheme.res
+# maven/android/pom.xml pulls Themes/AndroidMaterialTheme.res directly into
+# the Android port jar (resource entry on Themes/), so no pre-staging copy
+# under Ports/Android/src/ is needed. The .res is committed under Themes/ and
+# kept in sync by .github/workflows/native-themes-sync.yml. For local iteration
+# on native-themes/android-material/theme.css, run scripts/build-native-themes.sh.
# Rebuild the `designer` module first so changes under maven/css-compiler/
# are picked up by the maven plugin's CSS compile step. The designer module's
diff --git a/scripts/build-ios-port.sh b/scripts/build-ios-port.sh
index f17e058e32..f9346263da 100755
--- a/scripts/build-ios-port.sh
+++ b/scripts/build-ios-port.sh
@@ -37,14 +37,11 @@ if [ ! -f "$BUILD_CLIENT" ]; then
fi
fi
-# Compile native CSS themes (iOSModernTheme.res) and copy into the iOS port's
-# native sources so the Maven iOS build packages them into nativeios.jar. The
-# iOS runtime falls back to iOS7Theme.res when iOSModernTheme.res is missing,
-# which loses all $DarkUIID entries (dark mode appears broken) and the liquid-
-# glass styling — so make sure this runs before the port is built.
-./scripts/build-native-themes.sh
-mkdir -p Ports/iOSPort/nativeSources
-cp Themes/iOSModernTheme.res Ports/iOSPort/nativeSources/iOSModernTheme.res
+# maven/ios/pom.xml pulls Themes/iOSModernTheme.res directly into nativeios.jar,
+# so no pre-staging copy under Ports/iOSPort/nativeSources/ is needed. The .res
+# is committed under Themes/ and kept in sync by
+# .github/workflows/native-themes-sync.yml. For local iteration on
+# native-themes/ios-modern/theme.css, run scripts/build-native-themes.sh.
# Rebuild the `designer` module first so changes under maven/css-compiler/
# are picked up by the maven plugin's CSS compile step. The designer module's
diff --git a/scripts/build-native-themes.sh b/scripts/build-native-themes.sh
index 6befddb66a..2010eb6534 100755
--- a/scripts/build-native-themes.sh
+++ b/scripts/build-native-themes.sh
@@ -28,7 +28,9 @@ CSS_COMPILER_MODULE="$REPO_ROOT/maven/css-compiler"
CSS_SRC_ROOT="$REPO_ROOT/native-themes"
OUT_DIR="$REPO_ROOT/Themes"
# JavaScriptPort's runtime serves themes out of its webapp assets folder;
-# mirror the generated .res files there too so the JS port picks them up.
+# mirror the generated .res files there too so a local JS port run picks them
+# up. The mirror is gitignored - Themes/ is the single source of truth, and
+# the port poms (maven/ios, maven/android) consume it directly at build time.
JS_ASSETS_DIR="$REPO_ROOT/Ports/JavaScriptPort/src/main/webapp/assets"
# Resolve the compiler jar. Prefer a freshly-built target/ jar (so CSS compiler
diff --git a/scripts/cn1playground/common/pom.xml b/scripts/cn1playground/common/pom.xml
index 04b672d8b3..06b87e131e 100644
--- a/scripts/cn1playground/common/pom.xml
+++ b/scripts/cn1playground/common/pom.xml
@@ -417,12 +417,11 @@
org.apache.maven.plugins
@@ -436,8 +435,8 @@
-
-
+
+
diff --git a/scripts/initializr/common/pom.xml b/scripts/initializr/common/pom.xml
index 829b8b4834..4fde35c158 100644
--- a/scripts/initializr/common/pom.xml
+++ b/scripts/initializr/common/pom.xml
@@ -390,12 +390,11 @@
org.apache.maven.plugins
@@ -409,8 +408,8 @@
-
-
+
+
diff --git a/scripts/website/build.sh b/scripts/website/build.sh
index a921d3e9aa..7d9d19880a 100755
--- a/scripts/website/build.sh
+++ b/scripts/website/build.sh
@@ -36,15 +36,6 @@ if [ "${WEBSITE_INCLUDE_INITIALIZR}" = "auto" ]; then
fi
fi
-ensure_native_themes() {
- if [ -f "${REPO_ROOT}/Themes/iOSModernTheme.res" ] \
- && [ -f "${REPO_ROOT}/Themes/AndroidMaterialTheme.res" ]; then
- return
- fi
- echo "Generating native theme .res files via build-native-themes.sh..." >&2
- bash "${REPO_ROOT}/scripts/build-native-themes.sh"
-}
-
bootstrap_local_cn1_snapshots() {
if [ "${WEBSITE_BOOTSTRAP_CN1_SNAPSHOTS}" != "true" ]; then
return
@@ -571,12 +562,6 @@ build_initializr_for_site() {
return
fi
- # The initializr's live preview overlays the iOS Modern theme, which is
- # bundled from the gitignored Themes/iOSModernTheme.res. Generate it now
- # so the antrun copy in scripts/initializr/common/pom.xml has something
- # to pick up.
- ensure_native_themes
-
echo "Building Initializr JavaScript bundle for website..." >&2
(
cd "${REPO_ROOT}/scripts/initializr"
@@ -638,12 +623,6 @@ build_playground_for_site() {
bootstrap_local_cn1_snapshots
- # The playground's live preview switches between iOS Modern and Android
- # Material via Resources.openLayered. Both .res files are gitignored
- # build artifacts; generate them now so the antrun copy in
- # scripts/cn1playground/common/pom.xml has something to pick up.
- ensure_native_themes
-
echo "Building Playground JavaScript bundle for website..." >&2
(
cd "${REPO_ROOT}/scripts/cn1playground"