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"