diff --git a/.circleci/config.yml b/.circleci/config.yml index c376128..e74b119 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ version: 2.1 orbs: - qqq-orb: kingsrook/qqq-orb@0.5.0 + qqq-orb: kingsrook/qqq-orb@0.6.8 commands: configure-package-install: diff --git a/CLAUDE.md b/CLAUDE.md index a3bca1b..2077f8c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,5 +15,6 @@ Key orientation facts (details and caveats in the dossier): README's `io.qrun:qbit-bom` coordinates do not exist on Maven Central. - v1.6.0 imports `com.kingsrook.qqq:qqq-bom-pom:0.40.0` (Java 21, junit-bom 6.0.1); develop pins `0.41.0-SNAPSHOT`, which no longer resolves (qqq renumbered to 4.0.x). -- Known licensing inconsistency: LICENSE/NOTICE = Apache-2.0, pom `` = AGPL-3.0 - (shipped to Central in 1.6.0), README says Proprietary. See dossier "Maturity & risks". +- First-party license declarations in the current source are Apache-2.0, consistent + with LICENSE and NOTICE. Previously published artifacts retain their original + metadata; publish a new version for declaration corrections. diff --git a/README.md b/README.md index 3565101..0ac08dd 100644 --- a/README.md +++ b/README.md @@ -95,4 +95,33 @@ The running core version is read from `META-INF/maven/com.kingsrook.qqq/qqq-back ## License -See [LICENSE](LICENSE) and [NOTICE](NOTICE). +### Packaged notices + +The packaging change in this source tree copies each consumer's own root +`LICENSE` and `NOTICE` into `META-INF` in main/test archives and Maven shared +archive resources used by source/Javadoc archives. Existing main/test resources +remain included. Consumers must adopt a newly published parent version to receive +this change; the existing public 2.0.0 artifact is unchanged. + +The default `qbit.noticesDirectory` is `${project.basedir}`. In a nested module +whose files live at the repository root, set it to `${project.basedir}/..` (adjust +for the actual layout). Do not point it at the build-parent checkout: these are +the consuming project's notices. If a child overrides `` or +``, retain the inherited shared archive resource directory. + +After packaging the consumer, explicitly check every produced archive using +Python 3 and the guard from this checkout: + +```bash +mvn -Prelease -DskipTests -Dgpg.skip=true clean package +python3 /path/to/qbit-bom/scripts/check-packaged-notices.py --root . target/*.jar +``` + +For a multi-module consumer, pass each module's actual JAR paths and its repository +root. The guard rejects missing archives, missing/duplicate entries and changed +notice bytes. Packaging-only checks skip runtime tests; existing tests, review +and release requirements still apply. The publishing workflow does not invoke +this guard automatically. + +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). + diff --git a/pom.xml b/pom.xml index 635cf43..51a8542 100644 --- a/pom.xml +++ b/pom.xml @@ -19,8 +19,8 @@ https://github.com/Kingsrook/qbit-bom - GNU Affero General Public License v3.0 - https://www.gnu.org/licenses/agpl-3.0-standalone.html + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 repo @@ -44,6 +44,8 @@ 21 UTF-8 UTF-8 + + ${project.basedir} 2.1.2 @@ -79,6 +81,52 @@ + + + src/main/resources + + + ${project.build.directory}/maven-shared-archive-resources + + + + + src/test/resources + + + ${project.build.directory}/maven-shared-archive-resources + + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.4.0 + + + package-root-notices + generate-resources + + copy-resources + + + ${project.build.directory}/maven-shared-archive-resources/META-INF + + + ${qbit.noticesDirectory} + false + + LICENSE + NOTICE + + + + + + + + diff --git a/scripts/check-packaged-notices.py b/scripts/check-packaged-notices.py new file mode 100644 index 0000000..e583d31 --- /dev/null +++ b/scripts/check-packaged-notices.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Verify exact project notices in explicitly listed Maven archives.""" + +import argparse +from pathlib import Path +import sys +from zipfile import BadZipFile, ZipFile + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--root", type=Path, required=True, + help="consumer repository containing LICENSE and NOTICE") + parser.add_argument("--prefix", default="META-INF", + help="directory containing the project's archive copies") + parser.add_argument("archives", type=Path, nargs="+") + args = parser.parse_args() + expected = {name: (args.root / name).read_bytes() for name in ("LICENSE", "NOTICE")} + failures = [] + for archive in args.archives: + try: + with ZipFile(archive) as jar: + for name, content in expected.items(): + entry = f"{args.prefix.rstrip('/')}/{name}" + if jar.namelist().count(entry) != 1: + failures.append(f"{archive}: expected exactly one {entry}") + elif jar.read(entry) != content: + failures.append(f"{archive}: {entry} differs from root {name}") + except (OSError, BadZipFile) as error: + failures.append(f"{archive}: {error}") + if failures: + print("\n".join(failures), file=sys.stderr) + return 1 + print(f"PASS: exact LICENSE/NOTICE in {len(args.archives)} archives") + return 0 + + +if __name__ == "__main__": + sys.exit(main())