diff --git a/release/maplibre-particle/ios/Package.swift.template b/release/maplibre-particle/ios/Package.swift.template index cb7025fb8..7bb7cb7e9 100644 --- a/release/maplibre-particle/ios/Package.swift.template +++ b/release/maplibre-particle/ios/Package.swift.template @@ -1,5 +1,3 @@ -// Replace every <...> placeholder only after a reproducible XCFramework zip exists. -// The module/product name must remain MapLibre for MLRN compatibility. // swift-tools-version: 5.7 import PackageDescription @@ -11,8 +9,8 @@ let package = Package( targets: [ .binaryTarget( name: "MapLibre", - url: "", - checksum: "" + url: "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.7/MapLibre.dynamic.xcframework.zip", + checksum: "08cf09d1152e249b4a459bf30e765a0816acfd90390855b3f092077ff171ec11" ), ] ) diff --git a/release/maplibre-particle/ios/README.md b/release/maplibre-particle/ios/README.md index 674f5ed0a..25289cd0a 100644 --- a/release/maplibre-particle/ios/README.md +++ b/release/maplibre-particle/ios/README.md @@ -1,22 +1,17 @@ -# MapLibre Particle iOS Binary Distribution 基线 +# MapLibre Particle iOS 发行 -本目录只保存后续独立 distribution repository 所需的可审计模板;不发布二进制、不创建远端仓库,也不改变 MapLibre iOS 上游版本号。 +当前正式发行:6.28.0-particle.7 -模块和 product 始终为 `MapLibre`。MLRN 11.3.6 与后续 Particle Bridge 必须通过同一份自定义 MapLibre binary 消费,不能再链接第二个同功能但不同模块名的 framework。 +- Renderer:Metal +- Product / Module:MapLibre +- Binary:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.7/MapLibre.dynamic.xcframework.zip +- SHA-256:08cf09d1152e249b4a459bf30e765a0816acfd90390855b3f092077ff171ec11 +- SwiftPM:https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git +- Exact Version:6.28.0-particle.7 +- Source Commit:de935818d94b027c3c18427238a284fdc9ee7271 -Particle iOS 发行固定使用 Metal renderer。必须在 macOS 对目标 commit 执行: +构建命令固定为: -```sh bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic -``` -裸命令 `bazel build //platform/ios:MapLibre.dynamic` 会采用默认 OpenGL renderer;即使成功生成 XCFramework,也不得作为 Particle iOS 发行基线。发行前还应核验 device binary 含 `MLNMapView`、Metal/`MTLDevice` 与 `ParticleGPU` 标记,且无 OpenGL marker。 - -发布时: - -1. 在 macOS 上从对应 Git commit 成功生成 `MapLibre.dynamic.xcframework`; -2. 压缩为 `MapLibre.dynamic.xcframework.zip`,对 zip 计算 SHA-256; -3. 用真实 URL、checksum、commit 和构建记录填充 `release-manifest.json`; -4. 将相同 URL/checksum 代入 `Package.swift.template`,再在独立 distribution repository 发布。 - -外部 binary distribution 版本独立于 `platform/ios/VERSION`;首个建议坐标为 `6.28.0-particle.1`。Metal XCFramework 已在本地构建和 Pleiades iPad mini 5 RN Test Host signed Release 路径验证;artifact 尚未发布远端,manifest 的 URL 仍为 `LOCAL_BUILD`,不得伪造下载地址。 +不得用默认 OpenGL XCFramework 覆盖同版本。新版本必须使用新的 Particle revision。 diff --git a/release/maplibre-particle/ios/release-manifest.json b/release/maplibre-particle/ios/release-manifest.json index b0cfdb85e..48d4279e8 100644 --- a/release/maplibre-particle/ios/release-manifest.json +++ b/release/maplibre-particle/ios/release-manifest.json @@ -1,14 +1,20 @@ { - "schemaVersion": 1, - "status": "VALIDATED", - "distributionVersion": "6.28.0-particle.1", + "schemaVersion": 2, + "status": "PUBLISHED_PUBLIC_READBACK_PASS", + "distributionVersion": "6.28.0-particle.7", "moduleName": "MapLibre", "artifact": { "fileName": "MapLibre.dynamic.xcframework.zip", - "url": "LOCAL_BUILD", - "sizeBytes": 24943196, - "sha256": "ae88975fc20f4d9961b95079638d0e9c0d8e61d4394d5c0a8ffcc48d173a827f" + "sizeBytes": 24941382, + "sha256": "08cf09d1152e249b4a459bf30e765a0816acfd90390855b3f092077ff171ec11", + "url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.7/MapLibre.dynamic.xcframework.zip" }, - "sourceCommit": "bdd7f40f9029031816c9bf4db6e1cd6c7c69b7bd", - "notes": "Metal renderer artifact built with bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic. Validated by the Pleiades iPad mini 5 RN Test Host signed Release path; user confirmed particle rendering recovered. The artifact has not been published; no remote URL is available." + "sourceCommit": "de935818d94b027c3c18427238a284fdc9ee7271", + "swiftPackage": { + "url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git", + "requirement": "exactVersion", + "version": "6.28.0-particle.7", + "productName": "MapLibre" + }, + "notes": "Metal renderer release built with bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic" } diff --git a/release/maplibre-particle/release-manifest.json b/release/maplibre-particle/release-manifest.json index 4b4c1357c..feb5c92a6 100644 --- a/release/maplibre-particle/release-manifest.json +++ b/release/maplibre-particle/release-manifest.json @@ -1,132 +1,51 @@ { - "release": "13.4.1-particle.3", - "releaseState": "android_device_acceptance_pass", - "upstreamMapLibreNative": "13.4.1", - "upstreamCoreCommit": "18545ca10d296704b2cba675b182ff2c8da24588", - "extensionLine": "particle", - "extensionRevision": 3, - "platform": { - "android": { - "renderer": "opengl" - } - }, - "reactNativePackage": { + "schemaVersion": 2, + "releaseState": "PUBLISHED_PUBLIC_READBACK_PASS", + "particleRevision": 7, + "sourceCommit": "de935818d94b027c3c18427238a284fdc9ee7271", + "generatedAtUtc": "2026-09-19T01:30:21.118772+00:00", + "reactNative": { "name": "@navsea/maplibre-particle-rn", - "version": "13.4.1-particle.3", + "version": "13.4.1-particle.7", + "artifact": { + "fileName": "navsea-maplibre-particle-rn-13.4.1-particle.7.tgz", + "sizeBytes": 23018, + "sha256": "12a6c8ca8d4933040b1311d25693c047f998f6c8a01b05cfdecf3f06099d3722", + "url": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.7.tgz" + }, "mapLibreReactNative": "11.3.6" }, - "nativeArtifact": { - "group": "cc.navsea.maplibre", - "artifact": "android-sdk-particle-opengl", - "version": "13.4.1-particle.2", - "state": "published_validated" - }, - "releaseRelationship": "13.4.1-particle.3 的 v2 multi-plane RN/TS API 目标为新的 Native artifact 13.4.1-particle.2;不得复用、覆盖或宣称发布旧 13.4.1-particle.1。", - "canonicalField": { - "schema": "maplibre-particle-field/v2", - "transport": "metadataJson + validity local ref + optional scalar/vector local refs", - "encoding": "float32-le", - "layout": "plane-specific; vector is interleaved-uv" - }, - "validation": { - "android": { - "status": "pass", - "required": [ - "Native .2 Release OpenGL AAR build and Maven Local publish", - "AAR SHA consistency check", - "javap WeatherRuntime v2 API check", - "Test Host clean prebuild/build/install/start", - "five field datasets and Runtime READY acceptance" - ], - "validatedBuildEnvironment": { - "gradle": "8.14.3", - "agp": "8.12.0", - "kotlin": "2.1.20", - "jdk": "17.0.18", - "ndk": "27.1.12297006", - "compileSdk": 36, - "targetSdk": 36, - "minSdk": 24 - }, - "buildResult": { - "status": "pass", - "task": ":app:assembleDebug", - "duration": "6m20s", - "apkSha256": "49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2", - "apkSizeBytes": 185729464 - }, - "deviceAcceptance": { - "status": "pass", - "validatedAt": "2026-09-11", - "device": "192.168.1.146", - "package": "org.maplibre.particle.rntesthost", - "datasets": [ - "wind-real", - "rain-decoder-fixture", - "wave-real", - "tide-real", - "wave-synthetic-independence" - ], - "runtimeGate": { - "T0": "ACCEPTED_PENDING_INITIAL_WINDOW", - "T1": "INITIAL_WINDOW_READY", - "final": "READY" - }, - "controls": [ - "PLAY/PAUSE", - "HALF/NORMAL/DOUBLE", - "FPS AUTO/30/60", - "seek T0/T1", - "Field/Particle visibility", - "Field stops", - "Particle color", - "Trail color", - "Apply visual" - ], - "fatalScan": "pass" - } + "android": { + "version": "13.4.1-particle.7", + "coordinate": "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.7", + "renderer": "opengl", + "artifact": { + "fileName": "android-sdk-particle-opengl-13.4.1-particle.7.aar", + "sizeBytes": 167980881, + "sha256": "e5ce55067e961107900b27b42f5f95fcd68dd6a42f9cd68a1374bd63028f7bde", + "url": "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.7/android-sdk-particle-opengl-13.4.1-particle.7.aar" }, - "previousBaseline": { - "reactNativePackage": "13.4.1-particle.2", - "nativeArtifact": "13.4.1-particle.1", - "status": "pass", - "validatedAt": "2026-09-10", - "consumer": "integration/react-native/MapLibreParticlePackageConsumerTestHost", - "dataset": "ECMWF HRES 20260807 T0-T3", - "transport": "FILE_REF / FLOAT32_LE / INTERLEAVED_UV", - "checks": [ - "FieldRuntime attach", - "T0-T3 ordered submit", - "runtime READY", - "field and particle independent visibility", - "no fatal native bridge errors" - ], - "releaseTag": "maplibre-particle/rn/13.4.1-particle.2", - "releaseSourceCommit": "e43b58707cfd0f87965424477ed3f95625b3f724", - "npmTarball": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz", - "npmTarballSha256": "ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5", - "npmTarballSizeBytes": 10660, - "publicHttpStatus": 200, - "publicBytesVerified": true - } + "ndk": "28.2.13676358", + "gradle": "9.5.1", + "jdk": "openjdk version \"17.0.20.1\" 2026-08-18" }, - "publication": { - "nativeArtifact": { - "status": "published", - "aarSha256": "f890f834692d3cacb8a281b2d0f82cf4a7f8ba7756132092be800505b961e9a2", - "aarSizeBytes": 166519805, - "publishedAt": "2026-09-11", - "publicHttpStatus": 200, - "publicBytesVerified": true + "ios": { + "distributionVersion": "6.28.0-particle.7", + "renderer": "metal", + "moduleName": "MapLibre", + "artifact": { + "fileName": "MapLibre.dynamic.xcframework.zip", + "sizeBytes": 24941382, + "sha256": "08cf09d1152e249b4a459bf30e765a0816acfd90390855b3f092077ff171ec11", + "url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle/6.28.0-particle.7/MapLibre.dynamic.xcframework.zip" }, - "releaseTag": null, - "reactNativePackage": { - "status": "published", - "tarball": "https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz", - "sha256": "16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a", - "sizeBytes": 12849, - "publicHttpStatus": 200, - "publicBytesVerified": true - } + "swiftPackage": { + "url": "https://pbf.jpcn.ccwu.cc/swift/maplibre-particle-spm.git", + "requirement": "exactVersion", + "version": "6.28.0-particle.7", + "productName": "MapLibre" + }, + "xcode": "Xcode 26.3 Build version 17C529 ", + "bazel": "bazel 8.6.0" } } diff --git a/tools/particle_release_pipeline/release.sh b/tools/particle_release_pipeline/release.sh index 8a843d7d7..3b0052d85 100755 --- a/tools/particle_release_pipeline/release.sh +++ b/tools/particle_release_pipeline/release.sh @@ -23,7 +23,7 @@ if [[ -d /usr/local/opt/gnu-sed/libexec/gnubin ]]; then export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" fi -for cmd in git python3 node npm java curl ssh scp shasum bazel xcodebuild ruby tar cmp awk sed; do require "$cmd"; done +for cmd in git python3 node npm java curl ssh shasum bazel xcodebuild ruby tar cmp awk sed; do require "$cmd"; done [[ -d "$ANDROID_NDK_HOME" ]] || die "NDK missing: $ANDROID_NDK_HOME" [[ -x "$ROOT/platform/android/gradlew" ]] || die "Gradle wrapper missing" @@ -129,20 +129,61 @@ publish_release() { require_absent "$ANDROID_URL" require_absent "$RN_URL" require_absent "$IOS_URL" - ssh nas "test ! -e /volume1/work/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/$ANDROID_VERSION" || die "remote Android version exists" - ssh nas "test ! -e /volume1/work/pbf/npm/$TGZ_NAME" || die "remote RN tgz exists" - ssh nas "test ! -e /volume1/work/pbf/swift/maplibre-particle/$IOS_VERSION" || die "remote iOS version exists" - ssh nas "mkdir -p /volume1/work/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/$ANDROID_VERSION /volume1/work/pbf/swift/maplibre-particle/$IOS_VERSION" - scp "$M2_DIR"/* "nas:/volume1/work/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/$ANDROID_VERSION/" - scp "$ARTIFACT_DIR/$TGZ_NAME" "nas:/volume1/work/pbf/npm/$TGZ_NAME" - scp "$ARTIFACT_DIR/$IOS_ZIP_NAME" "nas:/volume1/work/pbf/swift/maplibre-particle/$IOS_VERSION/$IOS_ZIP_NAME" - IOS_SHA="$(shasum -a 256 "$ARTIFACT_DIR/$IOS_ZIP_NAME" | awk '{print $1}')" - printf '%s %s\n' "$IOS_SHA" "$IOS_ZIP_NAME" > "$ARTIFACT_DIR/$IOS_ZIP_NAME.sha256" - scp "$ARTIFACT_DIR/$IOS_ZIP_NAME.sha256" "nas:/volume1/work/pbf/swift/maplibre-particle/$IOS_VERSION/$IOS_ZIP_NAME.sha256" + + local source_commit + local android_root android_final android_tmp + local rn_final rn_tmp + local ios_root ios_final ios_tmp + local aar_sha rn_sha ios_sha + local remote_aar_sha remote_rn_sha remote_ios_sha + local pom_name module_name + + source_commit="$(git -C "$ROOT" rev-parse HEAD)" + android_root="/volume1/work/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl" + android_final="$android_root/$ANDROID_VERSION" + android_tmp="$android_root/.$ANDROID_VERSION.uploading" + rn_final="/volume1/work/pbf/npm/$TGZ_NAME" + rn_tmp="/volume1/work/pbf/npm/.$TGZ_NAME.uploading" + ios_root="/volume1/work/pbf/swift/maplibre-particle" + ios_final="$ios_root/$IOS_VERSION" + ios_tmp="$ios_root/.$IOS_VERSION.uploading" + pom_name="android-sdk-particle-opengl-$ANDROID_VERSION.pom" + module_name="android-sdk-particle-opengl-$ANDROID_VERSION.module" + + ssh nas "test ! -e '$android_final'" || die "remote Android version exists" + ssh nas "test ! -e '$rn_final'" || die "remote RN tgz exists" + ssh nas "test ! -e '$ios_final'" || die "remote iOS version exists" + + aar_sha="$(shasum -a 256 "$ARTIFACT_DIR/$AAR_NAME" | awk '{print $1}')" + rn_sha="$(shasum -a 256 "$ARTIFACT_DIR/$TGZ_NAME" | awk '{print $1}')" + ios_sha="$(shasum -a 256 "$ARTIFACT_DIR/$IOS_ZIP_NAME" | awk '{print $1}')" + printf '%s %s\n' "$ios_sha" "$IOS_ZIP_NAME" > "$ARTIFACT_DIR/$IOS_ZIP_NAME.sha256" + + note "uploading release artifacts through SSH staging paths" + ssh nas "rm -rf '$android_tmp' '$ios_tmp' '$rn_tmp'; mkdir -p '$android_tmp' '$ios_tmp'" + tar -C "$M2_DIR" -cf - . | ssh nas "tar -C '$android_tmp' -xf -" + ssh nas "cat > '$rn_tmp'" < "$ARTIFACT_DIR/$TGZ_NAME" + ssh nas "cat > '$ios_tmp/$IOS_ZIP_NAME'" < "$ARTIFACT_DIR/$IOS_ZIP_NAME" + ssh nas "cat > '$ios_tmp/$IOS_ZIP_NAME.sha256'" < "$ARTIFACT_DIR/$IOS_ZIP_NAME.sha256" + + remote_aar_sha="$(ssh nas "sha256sum '$android_tmp/$AAR_NAME' | awk '{print \$1}'")" + remote_rn_sha="$(ssh nas "sha256sum '$rn_tmp' | awk '{print \$1}'")" + remote_ios_sha="$(ssh nas "sha256sum '$ios_tmp/$IOS_ZIP_NAME' | awk '{print \$1}'")" + [[ "$remote_aar_sha" == "$aar_sha" ]] || die "NAS Android checksum mismatch" + [[ "$remote_rn_sha" == "$rn_sha" ]] || die "NAS RN checksum mismatch" + [[ "$remote_ios_sha" == "$ios_sha" ]] || die "NAS iOS checksum mismatch" + + note "atomically promoting NAS staging paths" + ssh nas "test ! -e '$android_final' && test ! -e '$rn_final' && test ! -e '$ios_final' && mv '$android_tmp' '$android_final' && mv '$rn_tmp' '$rn_final' && mv '$ios_tmp' '$ios_final'" + verify_public_file "$ARTIFACT_DIR/$AAR_NAME" "$ANDROID_URL" + verify_public_file "$M2_DIR/$pom_name" "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/$ANDROID_VERSION/$pom_name" + verify_public_file "$M2_DIR/$module_name" "https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/$ANDROID_VERSION/$module_name" verify_public_file "$ARTIFACT_DIR/$TGZ_NAME" "$RN_URL" verify_public_file "$ARTIFACT_DIR/$IOS_ZIP_NAME" "$IOS_URL" - python3 "$SCRIPT_DIR/publish_swiftpm.py" "$IOS_VERSION" "$IOS_SHA" + verify_public_file "$ARTIFACT_DIR/$IOS_ZIP_NAME.sha256" "$IOS_URL.sha256" + + python3 "$SCRIPT_DIR/publish_swiftpm.py" "$IOS_VERSION" "$ios_sha" git ls-remote "$SPM_URL" "refs/tags/$IOS_VERSION" | grep -q . || die "public SwiftPM tag missing" export PARTICLE_NDK_VERSION="28.2.13676358" @@ -150,7 +191,7 @@ publish_release() { export PARTICLE_JDK_VERSION="$(java -version 2>&1 | head -1)" export PARTICLE_XCODE_VERSION="$(xcodebuild -version | tr '\n' ' ')" export PARTICLE_BAZEL_VERSION="$(bazel --version)" - python3 "$SCRIPT_DIR/finalize_release.py" "$REV" "$(git -C "$ROOT" rev-parse HEAD)" "$ARTIFACT_DIR" + python3 "$SCRIPT_DIR/finalize_release.py" "$REV" "$source_commit" "$ARTIFACT_DIR" git -C "$ROOT" diff --check note "publish and public readback passed" } @@ -163,10 +204,13 @@ finalize_release() { release/maplibre-particle/ios/README.md if ! git -C "$ROOT" diff --cached --quiet; then git -C "$ROOT" commit -m "release: finalize unified particle.$REV"; fi TAG="maplibre-particle/rn/$ANDROID_VERSION" - [[ -z "$(git -C "$ROOT" tag -l "$TAG")" ]] || die "local tag exists: $TAG" git -C "$ROOT" fetch origin particle/main git -C "$ROOT" merge-base --is-ancestor origin/particle/main HEAD || die "origin/particle/main is not an ancestor of release HEAD" - git -C "$ROOT" tag -a "$TAG" -m "Unified Android/iOS Particle release $ANDROID_VERSION" + if [[ -n "$(git -C "$ROOT" tag -l "$TAG")" ]]; then + [[ "$(git -C "$ROOT" rev-list -n 1 "$TAG")" == "$(git -C "$ROOT" rev-parse HEAD)" ]] || die "local tag points to a different commit: $TAG" + else + git -C "$ROOT" tag -a "$TAG" -m "Unified Android/iOS Particle release $ANDROID_VERSION" + fi git -C "$ROOT" push origin HEAD:particle/main git -C "$ROOT" push origin "$TAG" note "RELEASE_PASS version=$ANDROID_VERSION ios=$IOS_VERSION tag=$TAG"