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
15 changes: 14 additions & 1 deletion .github/workflows/scaffolding-parity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,37 @@ name: Scaffolding artifact integrity
# android.onDeviceDebug, ...) were added to the archetype but never to the initializr's
# common.zip, so they were silently missing from initializr projects.
#
# The check only needs python3 + unzip, so it runs fast and is path-gated to the two
# It also guards the Codename One repository declaration in the three generated root
# POMs. Dropping it is invisible at generation time -- Maven Central still serves every
# already-published version -- and only surfaces after the migration cutover, as newly
# generated projects that cannot see new releases.
#
# The check only needs python3 + unzip, so it runs fast and is path-gated to the
# templates and the test harness itself.
on:
pull_request:
branches: [ master ]
paths:
- 'maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties'
- 'maven/cn1app-archetype/src/main/resources/archetype-resources/pom.xml'
- 'maven/cn1lib-archetype/src/main/resources/archetype-resources/pom.xml'
- 'scripts/initializr/common/src/main/resources/common.zip'
- 'maven/integration-tests/scaffolding-settings-parity-test.sh'
- 'maven/integration-tests/normalize_cn1_settings.py'
- 'maven/integration-tests/validate_initializr_pom_coordinates.py'
- 'maven/integration-tests/validate_generated_repositories.py'
- '.github/workflows/scaffolding-parity.yml'
push:
branches: [ master ]
paths:
- 'maven/cn1app-archetype/src/main/resources/archetype-resources/common/codenameone_settings.properties'
- 'maven/cn1app-archetype/src/main/resources/archetype-resources/pom.xml'
- 'maven/cn1lib-archetype/src/main/resources/archetype-resources/pom.xml'
- 'scripts/initializr/common/src/main/resources/common.zip'
- 'maven/integration-tests/scaffolding-settings-parity-test.sh'
- 'maven/integration-tests/normalize_cn1_settings.py'
- 'maven/integration-tests/validate_initializr_pom_coordinates.py'
- 'maven/integration-tests/validate_generated_repositories.py'
- '.github/workflows/scaffolding-parity.yml'

concurrency:
Expand All @@ -43,5 +54,7 @@ jobs:
- uses: actions/checkout@v6
- name: Validate Initializr embedded Maven coordinates
run: python3 maven/integration-tests/validate_initializr_pom_coordinates.py
- name: Validate Codename One repository is declared by every scaffold
run: python3 maven/integration-tests/validate_generated_repositories.py
- name: Compare archetype vs initializr codenameone_settings.properties
run: bash maven/integration-tests/scaffolding-settings-parity-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,36 @@
</build>


<!--
Codename One publishes its releases to the Codename One repository. Both lists
below are needed: Maven resolves ordinary dependencies through the repositories
list and build plugins through the pluginRepositories list, so a project that
declared only the first could download the framework and still fail to find a
newer codenameone-maven-plugin.
-->
<repositories>


<repository>
<id>codenameone</id>
<url>https://repo.codenameone.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>

<pluginRepository>
<id>codenameone-plugins</id>
<url>https://repo.codenameone.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,37 @@
</plugins>
</build>

<!--
Codename One publishes its releases to the Codename One repository. Both lists
below are needed: Maven resolves ordinary dependencies through the repositories
list and build plugins through the pluginRepositories list, so a project that
declared only the first could download the framework and still fail to find a
newer codenameone-maven-plugin.
-->
<repositories>
<repository>
<id>codenameone</id>
<url>https://repo.codenameone.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>codenameone-plugins</id>
<url>https://repo.codenameone.com/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

<!-- The sign-artifacts profile is used for signing artifacts when you deploy to Maven
central. In order for this to work, you also need to add the following to your
Expand Down
128 changes: 128 additions & 0 deletions maven/integration-tests/validate_generated_repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
#
# Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Codename One designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code 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
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Codename One through http://www.codenameone.com/ if you
# need additional information or have any questions.

"""Guard that every project-scaffolding path declares the Codename One repository.

Codename One releases are moving off Maven Central to the repository at
https://repo.codenameone.com/maven2. A generated project that does not declare it
resolves normally today -- Central still has every published version -- and then
silently stops seeing new releases after the cutover. The failure is therefore
invisible at generation time, which is exactly why it needs a build-time gate.

Both lists are checked because Maven resolves ordinary dependencies through
<repositories> and build plugins through <pluginRepositories>. A project holding only
the first can download codenameone-core and still fail to find a newer
codenameone-maven-plugin, which reads as a corrupt install rather than a missing
repository.

The three scaffolds must agree; they are the only ways a user gets a new project:

1. cn1app-archetype -- mvn archetype:generate / cn1:generate-app-project
2. cn1lib-archetype -- library projects
3. common.zip -- the Initializr root pom (start.codenameone.com)
"""

from pathlib import Path
import sys
import xml.etree.ElementTree as ET
import zipfile


MAVEN_NS = "http://maven.apache.org/POM/4.0.0"
NS = {"m": MAVEN_NS}
REPOSITORY_URL = "https://repo.codenameone.com/maven2"

# (container element, child element) for the two independent resolution paths.
REQUIRED_LISTS = (
("repositories", "repository"),
("pluginRepositories", "pluginRepository"),
)

APP_ARCHETYPE_POM = "maven/cn1app-archetype/src/main/resources/archetype-resources/pom.xml"
LIB_ARCHETYPE_POM = "maven/cn1lib-archetype/src/main/resources/archetype-resources/pom.xml"
INITIALIZR_ZIP = "scripts/initializr/common/src/main/resources/common.zip"
INITIALIZR_ZIP_POM = "pom.xml"


def fail(message):
print("ERROR: " + message, file=sys.stderr)
raise SystemExit(1)


def child_text(element, name):
child = element.find("m:" + name, NS)
if child is None or child.text is None:
return None
return child.text.strip()


def validate_pom(label, data):
try:
project = ET.fromstring(data)
except ET.ParseError as error:
fail(label + " is not valid XML: " + str(error))

for list_name, entry_name in REQUIRED_LISTS:
container = project.find("m:" + list_name, NS)
if container is None:
fail(label + " has no <" + list_name + ">; generated projects stop seeing "
+ "Codename One releases once publication to Maven Central ends")

matches = [entry for entry in container.findall("m:" + entry_name, NS)
if child_text(entry, "url") == REPOSITORY_URL]
if not matches:
fail(label + " does not declare " + REPOSITORY_URL + " in <" + list_name + ">")

for entry in matches:
releases = entry.find("m:releases", NS)
if releases is not None and child_text(releases, "enabled") == "false":
fail(label + " declares " + REPOSITORY_URL + " in <" + list_name
+ "> with releases disabled, so no release can resolve from it")

Comment on lines +86 to +102

def main():
repo_root = Path(__file__).resolve().parents[2]

for relative_path in (APP_ARCHETYPE_POM, LIB_ARCHETYPE_POM):
pom_path = repo_root / relative_path
if not pom_path.is_file():
fail("archetype POM not found: " + str(pom_path))
validate_pom(relative_path, pom_path.read_bytes())

archive_path = repo_root / INITIALIZR_ZIP
if not archive_path.is_file():
fail("Initializr artifact not found: " + str(archive_path))
with zipfile.ZipFile(str(archive_path), "r") as archive:
try:
data = archive.read(INITIALIZR_ZIP_POM)
except KeyError:
fail(INITIALIZR_ZIP + " is missing " + INITIALIZR_ZIP_POM)
validate_pom(INITIALIZR_ZIP + "!" + INITIALIZR_ZIP_POM, data)

print("All project scaffolds declare " + REPOSITORY_URL
+ " for both dependencies and plugins.")


if __name__ == "__main__":
main()
Binary file modified scripts/initializr/common/src/main/resources/common.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,32 @@ private void validateClaudeSkillBundled() throws Exception {
String rootPom = getText(entries, "pom.xml");
assertContains(rootPom, "<id>win</id>",
"Java 17 root pom should retain the win32 module activation profile");
assertCodenameOneRepository(rootPom, "Java 17");
}

/**
* Codename One releases are moving off Maven Central to repo.codenameone.com, so a
* generated project must declare that repository. Losing it is invisible at
* generation time -- Central still serves every already-published version -- and
* only surfaces after the cutover, as a project that never sees a new release.
*
* Both lists are asserted because Maven resolves dependencies through
* &lt;repositories&gt; and build plugins through &lt;pluginRepositories&gt;: a
* project holding only the first downloads codenameone-core and still fails to find
* a newer codenameone-maven-plugin.
*/
private void assertCodenameOneRepository(String rootPom, String label) {
int repositories = rootPom.indexOf("<repositories>");
int pluginRepositories = rootPom.indexOf("<pluginRepositories>");
assertTrue(repositories >= 0, label + " root pom should declare <repositories>");
assertTrue(pluginRepositories > repositories,
label + " root pom should declare <pluginRepositories> after <repositories>");
assertContains(rootPom.substring(repositories, pluginRepositories),
"https://repo.codenameone.com/maven2",
label + " root pom should resolve dependencies from the Codename One repository");
assertContains(rootPom.substring(pluginRepositories),
"https://repo.codenameone.com/maven2",
label + " root pom should resolve plugins from the Codename One repository");
}
Comment on lines +225 to 237

private void validateLegacyJava8Generation() throws Exception {
Expand Down Expand Up @@ -248,6 +274,7 @@ private void validateLegacyJava8Generation() throws Exception {
String rootPom = getText(entries, "pom.xml");
assertContains(rootPom, "<id>win</id>",
"Java 8 root pom should retain the win32 module activation profile");
assertCodenameOneRepository(rootPom, "Java 8");
}

private void validateCoordinateGuardRejectsBrokenArtifacts() throws Exception {
Expand Down
Loading