[build] remove license validation from mobile builds

This commit is contained in:
dijunkun
2026-10-26 13:12:11 +08:00
parent 778c3575cd
commit 38d0ce7728
13 changed files with 142 additions and 300 deletions
-4
View File
@@ -36,7 +36,6 @@ on:
- "apps/android/**"
- "apps/ios/CrossDeskMobile/Resources/ThirdPartyLicenses.json"
- "apps/ios/licenses/**"
- "apps/ios/scripts/generate_third_party_licenses.py"
- "deps/submodules/minirtc"
- "libs/wire/**"
@@ -155,9 +154,6 @@ jobs:
cache-provider: basic
cache-read-only: ${{ github.event_name == 'pull_request' }}
- name: Verify shared license catalog
run: python3 -B apps/ios/scripts/generate_third_party_licenses.py
- name: Resolve Android version
shell: python
env:
+1 -4
View File
@@ -169,9 +169,6 @@ jobs:
with:
python-version: "3.13"
- name: Verify iOS license catalog
run: python3 -B apps/ios/scripts/generate_third_party_licenses.py
- name: Install xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
@@ -181,7 +178,7 @@ jobs:
shell: bash
run: |
xmake --version | tee "${RUNNER_TEMP}/xmake-version.txt"
python3 -B apps/ios/scripts/generate_third_party_licenses.py \
python3 -B apps/ios/scripts/verify_native_build.py \
--xmake-version "${RUNNER_TEMP}/xmake-version.txt"
- name: Pin xmake package repository
+1 -1
View File
@@ -246,4 +246,4 @@ AV1 硬件路径尚无对应实机验证;Retroid 上使用 dav1d 软件解码
设置 → 关于使用与 iOS 相同的分组及入口:版本、软件许可、开源组件、源码与构建说明。软件许可中可分别阅读 GNU GPL 第 3 版及开源软件权利说明;开源组件页单独展示 OpenFEC 告知,其余组件列出名称和许可证,详情包含版本、源码、适用的构建配方和每一份许可文档。内容按 Android 构建生成,不显示未使用的 iOS 依赖,Android 签名说明替换 Apple 签名说明。
APK 保留完整 `THIRD_PARTY_NOTICES.txt`,并生成轻量的 `ThirdPartyLicenses.json` 索引、72 份独立许可文档及 `SourceMetadata.json`。打开文档时才读取正文,长文按有限长度分块显示,支持文本选择、复制与网页链接;打包验证核对每份正文的 SHA-256。源码页面显示标签或提交版本及未发布修改提示,源码链接指向基础提交。当前 Android 构建说明、构建脚本、MiniRTC、OpenSSL 及 SVT-AV1 配方随 APK 离线提供,避免开发版链接指向基础提交中尚不存在的 Android 文件。
APK 保留完整 `THIRD_PARTY_NOTICES.txt`,并生成轻量的 `ThirdPartyLicenses.json` 索引、72 份独立许可文档及 `SourceMetadata.json`。打开文档时才读取正文,长文按有限长度分块显示,支持文本选择、复制与网页链接。构建继续生成并打包许可资料,CI 和 APK 验证不再检查许可证内容、哈希或组件清单。源码页面显示标签或提交版本及未发布修改提示,源码链接指向基础提交。当前 Android 构建说明、构建脚本、MiniRTC、OpenSSL 及 SVT-AV1 配方随 APK 离线提供,避免开发版链接指向基础提交中尚不存在的 Android 文件。
+1 -3
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env python3
"""Package existing reviewed notices for the Android runtime dependency set."""
import json
import hashlib
import os
from pathlib import Path
import subprocess
@@ -70,8 +69,7 @@ def write_pages(output, catalog, additional, revision, minirtc):
target = output / asset
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(document['text'], encoding='utf-8')
documents.append({'title': document['title'], 'asset': asset,
'sha256': hashlib.sha256(document['text'].encode('utf-8')).hexdigest()})
documents.append({'title': document['title'], 'asset': asset})
component['documents'] = documents
(output / 'ThirdPartyLicenses.json').write_text(json.dumps(
{'schemaVersion': 1, 'components': components}, ensure_ascii=False, indent=2)+'\n', encoding='utf-8')
+1 -20
View File
@@ -2,7 +2,6 @@
"""Check the shipped native ABI, ELF load alignment and APK zip alignment."""
from pathlib import Path
import argparse
import hashlib
import json
import re
import struct
@@ -40,33 +39,15 @@ def verify(path):
name, extra = struct.unpack('<HH', raw.read(4))
if (library.header_offset + 30 + name + extra) % 16384:
raise ValueError('APK native library entry is not 16 KB aligned')
if 'assets/THIRD_PARTY_NOTICES.txt' not in archive.namelist():
raise ValueError('Missing third-party notices')
if archive.read('assets/PRIVACY.md') != (Path(__file__).resolve().parents[3] / 'PRIVACY.md').read_bytes():
raise ValueError('Missing or outdated bundled privacy policy')
catalog = json.loads(archive.read('assets/ThirdPartyLicenses.json'))
source = json.loads(archive.read('assets/SourceMetadata.json'))
components = catalog['components']
ids = [item['id'] for item in components]
if catalog['schemaVersion'] != 1 or len(ids) != len(set(ids)) or not {
'crossdesk', 'minirtc', 'openfec', 'openh264', 'libiconv', 'android-ndk', 'svt-av1'}.issubset(ids):
raise ValueError('Incomplete About catalog')
for component in components:
if not component['documents']:
raise ValueError('Missing component documents')
for document in component['documents']:
text = archive.read('assets/'+document['asset'])
if not text or hashlib.sha256(text).hexdigest() != document['sha256']:
raise ValueError('Incomplete license document: '+document['asset'])
for document in component.get('buildDocuments', []):
if not archive.read('assets/'+document['asset']):
raise ValueError('Missing build document')
if source['schemaVersion'] != 1 or len(source['revision']) != 40:
raise ValueError('Missing source revision')
for document in source['buildDocuments']:
if not archive.read('assets/'+document['asset']):
raise ValueError('Missing source build instructions')
print(f'PASS: {Path(path).name}: arm64, 16 KB ELF/ZIP alignment, privacy policy, notices, About catalog and documents')
print(f'PASS: {Path(path).name}: arm64, 16 KB ELF/ZIP alignment, privacy policy and source metadata')
if __name__ == '__main__':
@@ -274,7 +274,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/zsh;
shellScript = "set -e\npython3 \"${PROJECT_DIR}/scripts/generate_third_party_licenses.py\"\npython3 \"${PROJECT_DIR}/scripts/prepare_source_metadata.py\" --output \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SourceMetadata.json\"\n\"${PROJECT_DIR}/scripts/build_minirtc_ios.sh\"\npython3 \"${PROJECT_DIR}/scripts/generate_third_party_licenses.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --target-info \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/minirtc-target-info.txt\" --xmake-version \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/xmake-version.txt\" --xmake-repository \"$(cat \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/xmake-repository.txt\")\"\npython3 \"${PROJECT_DIR}/scripts/package_openssl_ios.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --output \"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\" --platform \"${PLATFORM_NAME}\"\n";
shellScript = "set -e\npython3 \"${PROJECT_DIR}/scripts/prepare_source_metadata.py\" --output \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SourceMetadata.json\"\n\"${PROJECT_DIR}/scripts/build_minirtc_ios.sh\"\npython3 \"${PROJECT_DIR}/scripts/verify_native_build.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --xmake-version \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/xmake-version.txt\" --xmake-repository \"$(cat \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/xmake-repository.txt\")\"\npython3 \"${PROJECT_DIR}/scripts/package_openssl_ios.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --output \"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\" --platform \"${PLATFORM_NAME}\"\n";
};
/* End PBXShellScriptBuildPhase section */
+13 -13
View File
@@ -145,7 +145,7 @@ workflow on branch/tag pushes and manual runs, and waits for its result before
publishing a version-tag release.
The workflow builds with the minimum supported Xmake version (3.1.1) and retains
the pinned Xcode/SDK, dependency cache, license catalog validation, unsigned arm64
the pinned Xcode/SDK, dependency cache, native input validation, unsigned arm64
Release build, and app-bundle verification. Unit tests run locally as needed.
It uploads
`crossdesk-ios-arm64-unsigned-v<version>-<date>.zip`,
@@ -366,16 +366,16 @@ collected from the reviewed runtime source directories; the catalog is an
attribution superset, not a claim that every source file survives linker
dead stripping.
Normal Xcode builds verify the checked-in resource, local license files and
package recipes offline before compiling native code. After compilation they
also compare all resolved dependency versions (including header-only packages)
with the catalog and check the merged archive's input hashes and pinned
toolchain. A missing, stale or altered catalog fails the build. Ordinary MiniRTC
code changes do not require changing the notice-review revision or regenerating
the catalog. Changes to dependencies, license texts or embedded third-party
notices still require reviewing and updating the affected catalog entries;
regeneration alone does not select a new dependency version.
`MINIRTC_ENABLE_AOM=true` also requires a reviewed AOM entry.
Builds package the license resources without validating their contents, hashes
or agreement with resolved dependency versions. License generation is a manual
maintenance step and is not run by CI or Xcode. Update the catalog and regenerate
the resource when the notices need to change; regeneration does not select a
new dependency version.
`scripts/verify_native_build.py` independently checks the native archive input
hashes and pinned Xmake toolchain after compilation. These checks do not read
the license catalog. Downloaded source archives retain their SHA-256 checks
when manually regenerating notices.
The source screen links to the exact application tag/commit, MiniRTC commit,
and custom dependency recipes/patches. `SourceMetadata.json` is generated in
@@ -395,8 +395,8 @@ archives available for recipients of that release.
To change a library, edit MiniRTC in its submodule, or modify the relevant
package recipe/patch under `deps/submodules/minirtc/thirdparty`. If a package
recipe changes, change its package configuration revision (or rebuild that
package) so Xmake does not reuse a previous binary. Review affected notices,
update recipe hashes, and commit MiniRTC. When the notice catalog changes,
package) so Xmake does not reuse a previous binary. Update affected notices
and commit MiniRTC. When the notice catalog changes,
record the reviewed revision in `sources.json` and regenerate the catalog.
Build the application again to relink it with
the modified library. Use your own team and bundle identifier to install the
+47 -114
View File
@@ -14,15 +14,12 @@
},
"documents": [
{
"path": "asio/COPYING",
"sha256": "5cc69ca9afd3417ed8e58bcd9c0bb730375e9e3e92b5aaf6483b93a2837e0cac"
"path": "asio/COPYING"
},
{
"path": "asio/LICENSE_1_0.txt",
"sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566"
"path": "asio/LICENSE_1_0.txt"
}
],
"recipe_sha256": "52bddc12cf0837117e935ea5d71aa5c35e7fd6ae802aa7401c6dba598cae0989",
"recipe_path": "xmake-repo/packages/a/asio/xmake.lua",
"package": "asio",
"notice_prefixes": [
@@ -41,11 +38,9 @@
},
"documents": [
{
"path": "LICENSE.md",
"sha256": "22331571e5e3dbe4c79ad715e23127bdf3beabd75052c953ddabf191ef799e14"
"path": "LICENSE.md"
}
],
"recipe_sha256": "23c69dc09488527b0d6adf1d25f795a9f0afdc7a1d594c44603c2dd07162faa4",
"recipe_path": "xmake-repo/packages/c/concurrentqueue/xmake.lua",
"package": "concurrentqueue",
"notice_prefixes": [
@@ -66,12 +61,10 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986"
"path": "LICENSE"
},
{
"path": "apps/ios/licenses/OPEN_SOURCE_NOTICE.txt",
"sha256": "34720d126e6373603d0c1802a8c3276802b93f2a8fbbf2b61837c6fdefea8877"
"path": "apps/ios/licenses/OPEN_SOURCE_NOTICE.txt"
}
]
},
@@ -88,12 +81,10 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "b327887de263238deaa80c34cdd2ff3e0ba1d35db585ce14a37ce3e74ee389e9"
"path": "COPYING"
},
{
"path": "doc/PATENTS",
"sha256": "335eca574598bf4ca181b12f708d6669e5a5e78c8e1513e5b35fa1f03901484b"
"path": "doc/PATENTS"
}
],
"package": "dav1d",
@@ -114,39 +105,30 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "fa6f36630bb1e0c571d34b2bbdf188d08495c9dbf58f28cac112f303fc1f58fb"
"path": "COPYING"
},
{
"path": "LICENSES/Apache-2.0.txt",
"sha256": "074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff"
"path": "LICENSES/Apache-2.0.txt"
},
{
"path": "LICENSES/CC0-1.0.txt",
"sha256": "a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499"
"path": "LICENSES/CC0-1.0.txt"
},
{
"path": "LICENSES/LGPL-2.1-only.txt",
"sha256": "fa6f36630bb1e0c571d34b2bbdf188d08495c9dbf58f28cac112f303fc1f58fb"
"path": "LICENSES/LGPL-2.1-only.txt"
},
{
"path": "LICENSES/LLVM-exception.txt",
"sha256": "e34c58338bd89d43e709e226610d8f32b3e3c47f4ad9a99a8dc1d4ac7842488e"
"path": "LICENSES/LLVM-exception.txt"
},
{
"path": "LICENSES/MIT.txt",
"sha256": "b85dcd3e453d05982552c52b5fc9e0bdd6d23c6f8e844b984a88af32570b0cc0"
"path": "LICENSES/MIT.txt"
},
{
"path": "LICENSES/MPL-1.1.txt",
"sha256": "6214f8b1300bb9f37b16ebc146f8f61af0187e0025042ff95c8b3030744a8795"
"path": "LICENSES/MPL-1.1.txt"
},
{
"path": "subprojects/gvdb/COPYING",
"sha256": "bdbcdaa7858872393e9d6b216bbdc8a23e99d98dd6cdb7cda19458025b9e19fc"
"path": "subprojects/gvdb/COPYING"
}
],
"recipe_sha256": "650e2f9a1c4b5079694a5626cfcb625c650a938f32878cf0caab3b0cf58216b9",
"recipe_path": "deps/submodules/minirtc/thirdparty/glib/xmake.lua",
"package": "glib",
"notice_prefixes": [
@@ -169,8 +151,7 @@
},
"documents": [
{
"path": "deps/submodules/minirtc/src/inih/LICENSE",
"sha256": "bd2a675a5a26fbff77f171f3a15afb1a9c7855be1a01c7e3fd16b8d2617acdda"
"path": "deps/submodules/minirtc/src/inih/LICENSE"
}
]
},
@@ -186,11 +167,9 @@
},
"documents": [
{
"path": "LICENSE.MIT",
"sha256": "86b998c792894ccb911a1cb7994f7a9652894e7a094c0b5e45be2f553f45cf14"
"path": "LICENSE.MIT"
}
],
"recipe_sha256": "eadd211f0521a18207e9544c0c5a08c41dca578fce99df5c2577f6fcf2a0fdce",
"recipe_path": "xmake-repo/packages/n/nlohmann_json/xmake.lua",
"package": "nlohmann_json",
"notice_prefixes": [
@@ -209,11 +188,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "6587fd02a924b97dbd6758b0573ddec056fb494a84f3709c5aaa33ce1f7f029c"
"path": "LICENSE"
}
],
"recipe_sha256": "f86cac2c4b884f3347a39a59f3dd8de566c257c7fd481c6e49b05f4730f12c4f",
"recipe_path": "xmake-repo/packages/k/kcp/xmake.lua",
"package": "kcp",
"notice_prefixes": [
@@ -233,11 +210,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85"
"path": "LICENSE"
}
],
"recipe_sha256": "b3ec15652a47d65e0a1f85f62d02c098c5cf5b8d8c3cf7b03ff099efa7016326",
"recipe_path": "deps/submodules/minirtc/thirdparty/libdatachannel/xmake.lua",
"package": "libdatachannel",
"notice_prefixes": [
@@ -257,11 +232,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "67894089811f93fca47a76f85e017da6f8582d4ba0905963c6e0f1ad6df7a195"
"path": "LICENSE"
}
],
"recipe_sha256": "de19ac4e3e91ef5715ac582c75e0d10404dc14ec4e33b2e764dc2637a78d24e9",
"recipe_path": "xmake-repo/packages/l/libffi/xmake.lua",
"package": "libffi",
"notice_prefixes": [
@@ -281,23 +254,18 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "e5504e5ffe894870ea1a652df957a120ead9c3f9a5e9d314ca41ba06f2d72024"
"path": "COPYING"
},
{
"path": "COPYING.LGPL",
"sha256": "a9bdde5616ecdd1e980b44f360600ee8783b1f99b8cc83a2beb163a0a390e861"
"path": "COPYING.LGPL"
},
{
"path": "COPYING.MPL",
"sha256": "3b3c75ae6c663c4f93987647eb5463d33dc1602a1aded3ac479773f2c3928e3a"
"path": "COPYING.MPL"
},
{
"path": "AUTHORS",
"sha256": "d72eb6fa9d5e93abb11d13b6c6f04fda2ba183d0705e703887cda243f6f1eead"
"path": "AUTHORS"
}
],
"recipe_sha256": "dbb3cd85fd197f6495c611a08f99b1c70b88c48c91f6e6ea24946a1b0701a3d5",
"recipe_path": "deps/submodules/minirtc/thirdparty/libnice/xmake.lua",
"package": "libnice",
"notice_prefixes": [
@@ -320,11 +288,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "8e19d42a1eec9561f3f347253ddf2e385c55f392f025bb0fd41b88dbf38db5ae"
"path": "LICENSE"
}
],
"recipe_sha256": "9629edb8d395ea3017af1702cafea57173ca0a6a1ea305a6174b1936598faec6",
"recipe_path": "deps/submodules/minirtc/thirdparty/libsrtp/xmake.lua",
"package": "libsrtp",
"notice_prefixes": [
@@ -346,16 +312,13 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "2b2cc1180c7e6988328ad2033b04b80117419db9c4c584918bbb3cfec7e9364f"
"path": "LICENSE"
},
{
"path": "PATENTS",
"sha256": "4da84211f947c6f9f7dd8fbce513e1522e01833407ae732b8c9a2bbb75fed495"
"path": "PATENTS"
},
{
"path": "AUTHORS",
"sha256": "0c4a51c72a06b3431bb2b750e3b04dbe3694b22e2fcae6e370216424eec56d52"
"path": "AUTHORS"
}
],
"package": "libyuv",
@@ -376,8 +339,7 @@
},
"documents": [
{
"path": "deps/submodules/minirtc/LICENSE",
"sha256": "ea8af5e789cb2d4e9b10bce3874982ade163b749b6bfbdb32e2df21c4d106de1"
"path": "deps/submodules/minirtc/LICENSE"
}
]
},
@@ -394,11 +356,9 @@
"documents": [
{
"path": "LICENCE_CeCILL-C_V1-en.txt",
"sha256": "b48f5dc16f144cd79ddaadce3cc8ef183778a0b4f75ab4362e134b347ca20f3d",
"encoding": "iso-8859-1"
}
],
"recipe_sha256": "a9bc4a460195c77543a91c0a2b40a094f4a3869dc917e4eb98e7e8005ea441ab",
"recipe_path": "deps/submodules/minirtc/thirdparty/openfec/xmake.lua",
"package": "openfec",
"notice_prefixes": [
@@ -422,11 +382,9 @@
},
"documents": [
{
"path": "LICENSE.txt",
"sha256": "7d5450cb2d142651b8afa315b5f238efc805dad827d91ba367d8516bc9d49e7a"
"path": "LICENSE.txt"
}
],
"recipe_sha256": "230631f3cddc72674fa21d2639044b00120ee346ef186eddfb5dc04bf4df3ee9",
"recipe_path": "deps/submodules/minirtc/thirdparty/openssl/xmake.lua",
"package": "openssl3",
"notice_prefixes": [
@@ -448,11 +406,9 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "01e1167d54a096d123cf6dfbbeb19587278845c6481d2d66d545669846079551"
"path": "COPYING"
}
],
"recipe_sha256": "c965643eb4e093858c27c402e6cde544620460f2169c89d8ff2bab01111d022b",
"recipe_path": "xmake-repo/packages/l/libopus/xmake.lua",
"package": "libopus",
"notice_prefixes": [
@@ -475,19 +431,15 @@
},
"documents": [
{
"path": "LICENCE.md",
"sha256": "197d8a73ffee0d6b09adba2f9c677b5f5aede24edf89258a68e48248d010d811"
"path": "LICENCE.md"
},
{
"path": "COPYING",
"sha256": "99272c55f3dcfa07a8a7e15a5c1a33096e4727de74241d65fa049fccfdd59507"
"path": "COPYING"
},
{
"path": "deps/sljit/LICENSE",
"sha256": "5f216505c0f6ea3273caec89e766eef93cdeb7bbb0c429f9360116d7c938feeb"
"path": "deps/sljit/LICENSE"
}
],
"recipe_sha256": "821f1fbc91434f4324be88eeea754db09c5d7be31446242764861ca2aee136b7",
"recipe_path": "xmake-repo/packages/p/pcre2/xmake.lua",
"package": "pcre2",
"notice_prefixes": [
@@ -508,11 +460,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "e4d01796524cbc13b1571a1f0914823c6a23b316ba039e7014f47a4eef7fd4c3"
"path": "LICENSE"
}
],
"recipe_sha256": "deb525d09b5b79351eeb524ab3bc6a3009e434974e585e5e8852c491d7fa07c8",
"recipe_path": "xmake-repo/packages/p/plog/xmake.lua",
"package": "plog",
"notice_prefixes": [
@@ -532,8 +482,7 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "d245807f90032872d1438d741ed21e2490e1175dc8aa3afa5ddb6c8e529b58e5"
"path": "COPYING"
}
],
"bundled_by": "glib",
@@ -555,11 +504,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "4ccecab18d1ff0b61174fe3d6c430541625d3ddb865b0d5887db296f883c76e7"
"path": "LICENSE"
}
],
"recipe_sha256": "5b299effb366420b4920c5b89e6535b76173136d91ee01f8f7d3423486a93549",
"recipe_path": "xmake-repo/packages/s/spdlog/xmake.lua",
"package": "spdlog",
"notice_prefixes": [
@@ -579,24 +526,19 @@
},
"documents": [
{
"path": "LICENSE.md",
"sha256": "0acc2fcb27472bdc9aaf8b71f37055bbdac4f54671b7d922f241bd7fcd0dd3e6"
"path": "LICENSE.md"
},
{
"path": "LICENSE-BSD2.md",
"sha256": "c582fcbeb9cf972a60d298bfaf41dc5adff2dd14208946edcb9bb56a281e8438"
"path": "LICENSE-BSD2.md"
},
{
"path": "PATENTS.md",
"sha256": "20678ab10402659106dc4c147c97b2a6e94b5c0695415e15a8f195ebc3547922"
"path": "PATENTS.md"
},
{
"path": "third_party/fastfeat/LICENSE",
"sha256": "043dcfd059386f9facd376351b2bd79325778744aa442177390cdfcca54babed"
"path": "third_party/fastfeat/LICENSE"
},
{
"path": "third_party/safestringlib/LICENSE",
"sha256": "c0f99a208799ca20c9ccf31a30533d49334eb458dca5a3d14f9afe3e884e5d7d"
"path": "third_party/safestringlib/LICENSE"
}
],
"package": "svt-av1",
@@ -618,11 +560,9 @@
},
"documents": [
{
"path": "LICENSE.md",
"sha256": "fa53711b25af4b9a9b8dadfea3cb38166ec4b96760c8d62b284055554537d9ef"
"path": "LICENSE.md"
}
],
"recipe_sha256": "e3dac7d1db8e5bc79d99851e2486371af19962a4b8fca6eaac3156c00eb73b92",
"recipe_path": "xmake-repo/packages/u/usrsctp/xmake.lua",
"package": "usrsctp",
"notice_prefixes": [
@@ -641,12 +581,10 @@
},
"documents": [
{
"path": "deps/submodules/minirtc/thirdparty/webrtc/LICENSE",
"sha256": "ab00a482b6a3902e40211b43c5d0441962ea99b6cc7c25c0f243fa270b78d482"
"path": "deps/submodules/minirtc/thirdparty/webrtc/LICENSE"
},
{
"path": "deps/submodules/minirtc/thirdparty/webrtc/PATENTS",
"sha256": "01462e2068d1a04c2274f3389773014c14ed9bc3446b28303543bd3e3c064145"
"path": "deps/submodules/minirtc/thirdparty/webrtc/PATENTS"
}
]
},
@@ -662,11 +600,9 @@
},
"documents": [
{
"path": "COPYING",
"sha256": "b31e597bc777d884a39663c11d0557a0af40780e1f451de11ea5103924a69667"
"path": "COPYING"
}
],
"recipe_sha256": "beac245a717c2bc3bc8d17265d9fc9b54f699f3833b11513f8723c4dbaffd539",
"recipe_path": "deps/submodules/minirtc/thirdparty/websocketpp/xmake.lua",
"package": "websocketpp",
"notice_prefixes": [
@@ -685,11 +621,9 @@
},
"documents": [
{
"path": "LICENSE",
"sha256": "e32ff4e00d9d94930537635291da39e7e612703334bf6fde8c7f1686fe8a45a2"
"path": "LICENSE"
}
],
"recipe_sha256": "7832bcb086709a0739ae1abeff035f7a103c7169dc8af61ebf91dd887fcbd054",
"recipe_path": "xmake-repo/packages/z/zlib/xmake.lua",
"package": "zlib",
"notice_prefixes": [
@@ -697,7 +631,6 @@
]
}
],
"bundle_sha256": "a27c89beaaee2397a306417ff133d7f48c74fe6db9041e145fc08f18ec69a080",
"toolchain": {
"xmake_min_version": "3.1.1",
"xmake_repository_revision": "121109352da272cd1a3d4ed52f2dcda34f839955"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Collect iOS notices from pinned source archives; validate them offline at build time."""
"""Generate iOS notice resources from pinned source archives."""
import argparse
from collections import defaultdict
@@ -8,7 +8,6 @@ import hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
import tarfile
import urllib.request
@@ -82,7 +81,7 @@ def source_files(component, cache):
def component_documents(component, names, read):
documents = []
for item in component["documents"]:
data = checked(read(item["path"]), item["sha256"], item["path"])
data = read(item["path"])
text = data.decode(item.get("encoding", "utf-8"))
if "end_before" in item:
if item["end_before"] not in text:
@@ -134,79 +133,22 @@ def generate(lock, cache):
return json_bytes({"schemaVersion": 1, "components": components})
def verify(lock, inputs, target_info, xmake_repository=None, xmake_version=None):
checked(OUTPUT.read_bytes(), lock["bundle_sha256"], str(OUTPUT))
if xmake_version is not None:
minimum = lock["toolchain"]["xmake_min_version"]
version = re.search(r"xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)", xmake_version.read_text())
if not version or tuple(map(int, version.groups())) < tuple(map(int, minimum.split("."))):
raise ValueError(f"Xmake {minimum} or newer is required; see apps/ios/README.md")
if xmake_repository is not None:
revision = subprocess.check_output(
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
if revision != lock["toolchain"]["xmake_repository_revision"]:
raise ValueError("Xmake package repository differs from the reviewed iOS source catalog")
changes = subprocess.check_output(
["git", "-C", str(xmake_repository), "status", "--porcelain", "--untracked-files=normal"],
text=True).strip()
if changes:
raise ValueError("Xmake package repository has local changes; use the unmodified pinned revision")
for component in lock["components"]:
recipe = component.get("recipe_path", "")
if recipe.startswith("deps/"):
checked((REPO / recipe).read_bytes(), component["recipe_sha256"], recipe)
elif recipe.startswith("xmake-repo/") and xmake_repository is not None:
checked((xmake_repository / Path(recipe).relative_to("xmake-repo")).read_bytes(),
component["recipe_sha256"], recipe)
if component["source"]["kind"] == "repository":
for item in component["documents"]:
checked((REPO / item["path"]).read_bytes(), item["sha256"], item["path"])
if inputs is None:
return
if target_info is None:
raise ValueError("--target-info is required with --inputs (includes header-only dependencies)")
known = {c["package"]: c["version"] for c in lock["components"] if "package" in c}
seen = {}
# Package names/versions come from resolved Xmake link and include paths,
# never from an inventory of all packages left in the installation cache.
pattern = r"/packages(?:-simulator)?/[^/\s]+/([^/\s]+)/([^/\s]+)/[a-f0-9]+/"
for name, version in re.findall(pattern, inputs.read_text() + target_info.read_text()):
if name not in known or known[name] != version:
raise ValueError(f"Unreviewed iOS dependency: {name} {version}; regenerate licenses from its source")
seen[name] = version
if set(seen) != set(known):
raise ValueError(f"License/build dependency mismatch; missing: {sorted(set(known) - set(seen))}")
for line in inputs.read_text().splitlines():
sha, path = line.split(maxsplit=1)
checked(Path(path).read_bytes(), sha, path)
# Repository revisions identify the reviewed notices, not a required build
# commit. SourceMetadata records the actual MiniRTC revision and links for
# this build; the content and dependency checks above detect stale notices.
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--generate", action="store_true", help="download/check pinned sources and regenerate the bundled JSON")
parser.add_argument("--generate", action="store_true", required=True,
help="download pinned sources and regenerate the bundled JSON")
parser.add_argument("--cache", type=Path, default=Path.home() / ".cache/crossdesk/licenses")
parser.add_argument("--inputs", type=Path, help="native archive .inputs.sha256 manifest")
parser.add_argument("--target-info", type=Path, help="resolved Xmake minirtc target information")
parser.add_argument("--xmake-repository", type=Path, help="verify the active pinned package repository and recipes")
parser.add_argument("--xmake-version", type=Path, help="Xmake version output recorded by the native build")
args = parser.parse_args()
lock = json.loads(LOCK.read_text())
if args.generate:
data = generate(lock, args.cache)
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_bytes(data)
lock["bundle_sha256"] = digest(data)
LOCK.write_bytes(json_bytes(lock))
verify(lock, args.inputs, args.target_info, args.xmake_repository, args.xmake_version)
print(f"Verified {len(lock['components'])} iOS license components")
data = generate(lock, args.cache)
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
OUTPUT.write_bytes(data)
print(f"Generated {len(lock['components'])} iOS license components")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, KeyError, subprocess.CalledProcessError) as error:
except (OSError, ValueError, KeyError) as error:
print(f"Third-party notices: {error}", file=sys.stderr)
sys.exit(1)
@@ -2,7 +2,6 @@
"""Record the source corresponding to an iOS build without changing the checkout."""
import argparse
import hashlib
import json
import os
from pathlib import Path
@@ -66,8 +65,6 @@ def source_metadata(require_release=False, tag=None):
"miniRTCRevision": mini_revision,
"miniRTCSourceURL": f"{MINIRTC_URL}/tree/{mini_revision}",
"componentSources": component_sources(lock, mini_revision),
"licenseCatalogSHA256": lock["bundle_sha256"],
"sourceCatalogSHA256": hashlib.sha256(lock_path.read_bytes()).hexdigest(),
"toolchain": lock["toolchain"],
}
+3 -16
View File
@@ -38,12 +38,6 @@ def verify(app, require_release=False):
for category, codes in required.items():
if set(reasons.get(category, [])) != codes:
raise ValueError(f"Missing or changed reviewed privacy reasons: {category}")
data = (app / "ThirdPartyLicenses.json").read_bytes()
catalog = json.loads(data)
ids = {c["id"] for c in catalog["components"]}
if not {"crossdesk", "minirtc", "openfec"} <= ids or "openh264" in ids:
raise ValueError("Unexpected iOS license catalog")
openssl = next(c for c in catalog["components"] if c["id"] == "openssl3")
framework = app / "Frameworks/OpenSSL.framework"
sdk_info = plistlib.loads((framework / "Info.plist").read_bytes())
sdk_privacy_data = (framework / "PrivacyInfo.xcprivacy").read_bytes()
@@ -57,14 +51,9 @@ def verify(app, require_release=False):
if sdk_privacy != expected_privacy:
raise ValueError("OpenSSL SDK privacy declaration differs from the reviewed iOS configuration")
sdk_build = json.loads((framework / "BuildMetadata.json").read_text())
if (sdk_info["CFBundleShortVersionString"] != openssl["version"]
or sdk_build["version"] != openssl["version"]
or sdk_build["sourceURL"] != openssl["sourceURL"]
or sdk_build["buildSourceURL"] != openssl["buildSourceURL"]
if (sdk_info["CFBundleShortVersionString"] != sdk_build["version"]
or sdk_build["privacySHA256"] != hashlib.sha256(sdk_privacy_data).hexdigest()):
raise ValueError("OpenSSL SDK resources do not match the source/license catalog")
if (framework / "LICENSE.txt").read_text() != "\n\n".join(d["text"] for d in openssl["documents"]):
raise ValueError("OpenSSL SDK license resource differs from its source notices")
raise ValueError("OpenSSL SDK resources do not match its build metadata")
embedded_binary = framework / sdk_info["CFBundleExecutable"]
# Xcode removes the archive and may generate a small codeless-framework
# stub. It must not ship a second static archive or dynamic OpenSSL copy.
@@ -78,13 +67,11 @@ def verify(app, require_release=False):
if "OpenSSL.framework" in linked:
raise ValueError("OpenSSL must be statically linked into the application")
source = json.loads((app / "SourceMetadata.json").read_text())
if source["licenseCatalogSHA256"] != hashlib.sha256(data).hexdigest():
raise ValueError("Bundled licenses differ from the recorded source metadata")
if require_release and (source["isModified"] or not source["tag"]):
raise ValueError("The app was not built from an unmodified version tag")
if not re.fullmatch(r"[0-9a-f]{40}", source["revision"]):
raise ValueError("Invalid source revision")
print("Verified arm64 app, source/license resources, app privacy declarations and OpenSSL SDK privacy packaging")
print("Verified arm64 app, source metadata, app privacy declarations and OpenSSL SDK privacy packaging")
print("Signing, App Store validation and license authorization require separate review.")
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""Verify iOS native build inputs and toolchain without consulting license data."""
import argparse
import hashlib
from pathlib import Path
import re
import subprocess
import sys
XMAKE_MIN_VERSION = "3.1.1"
XMAKE_REPOSITORY_REVISION = "121109352da272cd1a3d4ed52f2dcda34f839955"
def verify(inputs=None, xmake_repository=None, xmake_version=None):
if xmake_version is not None:
version = re.search(r"xmake v(\d+)\.(\d+)\.(\d+)(?:\+|[,\s]|$)", xmake_version.read_text())
if not version or tuple(map(int, version.groups())) < tuple(map(int, XMAKE_MIN_VERSION.split("."))):
raise ValueError(f"Xmake {XMAKE_MIN_VERSION} or newer is required; see apps/ios/README.md")
if xmake_repository is not None:
revision = subprocess.check_output(
["git", "-C", str(xmake_repository), "rev-parse", "HEAD"], text=True).strip()
if revision != XMAKE_REPOSITORY_REVISION:
raise ValueError("Xmake package repository differs from the pinned iOS toolchain")
changes = subprocess.check_output(
["git", "-C", str(xmake_repository), "status", "--porcelain", "--untracked-files=normal"],
text=True).strip()
if changes:
raise ValueError("Xmake package repository has local changes; use the unmodified pinned revision")
if inputs is not None:
for line in inputs.read_text().splitlines():
sha, name = line.split(maxsplit=1)
if hashlib.sha256(Path(name).read_bytes()).hexdigest() != sha:
raise ValueError(f"Native input checksum mismatch: {name}")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--inputs", type=Path, help="native archive .inputs.sha256 manifest")
parser.add_argument("--xmake-repository", type=Path, help="active pinned Xmake package repository")
parser.add_argument("--xmake-version", type=Path, help="Xmake version output recorded by the native build")
args = parser.parse_args()
if not any((args.inputs, args.xmake_repository, args.xmake_version)):
parser.error("provide --inputs, --xmake-repository or --xmake-version")
verify(args.inputs, args.xmake_repository, args.xmake_version)
print("Verified iOS native build inputs and toolchain")
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, subprocess.CalledProcessError) as error:
print(f"iOS native build: {error}", file=sys.stderr)
sys.exit(1)
+11 -54
View File
@@ -1,13 +1,13 @@
"""Regression coverage for notice validation and build-specific source links."""
"""Regression coverage for native validation and build-specific source links."""
import copy
import hashlib
import importlib.util
import json
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch
def load_script(name):
@@ -18,11 +18,11 @@ def load_script(name):
return module
licenses = load_script("generate_third_party_licenses")
native = load_script("verify_native_build")
metadata = load_script("prepare_source_metadata")
class LicenseValidationTest(unittest.TestCase):
class NativeValidationTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="crossdesk-ios-licenses-")
self.addCleanup(temporary.cleanup)
@@ -47,34 +47,24 @@ class LicenseValidationTest(unittest.TestCase):
self.archive.parent.mkdir(parents=True)
self.archive.write_bytes(b"reviewed archive")
self.inputs = self.root / "inputs.sha256"
self.inputs.write_text(f"{licenses.digest(self.archive.read_bytes())} {self.archive}\n")
self.target = self.root / "target-info.txt"
self.target.write_text(str(self.root / "packages/a/asio/1.0/abcd/include") + "\n")
self.inputs.write_text(f"{hashlib.sha256(self.archive.read_bytes()).hexdigest()} {self.archive}\n")
self.lock = {
"bundle_sha256": licenses.digest(self.bundle.read_bytes()),
"toolchain": {"xmake_min_version": "3.1.1"},
"components": [
{
"id": "minirtc", "version": self.reviewed_revision,
"source": {"kind": "repository", "url": metadata.MINIRTC_URL,
"revision": self.reviewed_revision},
"documents": [{"path": str(self.notice.relative_to(self.root)),
"sha256": licenses.digest(self.notice.read_bytes())}],
"documents": [{"path": str(self.notice.relative_to(self.root))}],
},
{
"id": "opus", "package": "opus", "version": "1.0",
"source": {"kind": "archive", "url": "https://example.com/opus.tar.gz"},
"recipe_path": str(self.recipe.relative_to(self.root)),
"recipe_sha256": licenses.digest(self.recipe.read_bytes()),
},
{"id": "asio", "package": "asio", "version": "1.0",
"source": {"kind": "archive", "url": "https://example.com/asio.tar.gz"}},
],
}
for name, value in (("REPO", self.root), ("OUTPUT", self.bundle)):
patcher = patch.object(licenses, name, value)
patcher.start()
self.addCleanup(patcher.stop)
def git(self, *args):
return subprocess.check_output(
@@ -82,9 +72,8 @@ class LicenseValidationTest(unittest.TestCase):
"-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args],
text=True).strip()
def verify(self, preflight=False, **kwargs):
licenses.verify(self.lock, None if preflight else self.inputs,
None if preflight else self.target, **kwargs)
def verify(self, **kwargs):
native.verify(self.inputs, **kwargs)
def test_source_only_commit_keeps_reviewed_catalog_and_uses_actual_links(self):
self.source.write_text("int transport() { return 2; }\n")
@@ -94,7 +83,6 @@ class LicenseValidationTest(unittest.TestCase):
self.assertNotEqual(current, self.reviewed_revision)
original_lock = copy.deepcopy(self.lock)
original_bundle = self.bundle.read_bytes()
self.verify(preflight=True)
self.verify()
sources = metadata.component_sources(self.lock, current)
self.assertEqual(sources["minirtc"]["version"], current)
@@ -104,49 +92,18 @@ class LicenseValidationTest(unittest.TestCase):
self.assertEqual(self.lock, original_lock)
self.assertEqual(self.bundle.read_bytes(), original_bundle)
def test_changed_license_is_rejected_before_compilation(self):
self.notice.write_text("Changed license text\n")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*LICENSE"):
self.verify(preflight=True)
def test_changed_recipe_is_rejected_before_compilation(self):
self.recipe.write_text('package("different-opus")\n')
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*xmake.lua"):
self.verify(preflight=True)
def test_changed_bundle_is_rejected_before_compilation(self):
self.bundle.write_text("{}\n")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch:"):
self.verify(preflight=True)
def test_unreviewed_header_only_dependency_is_rejected(self):
self.target.write_text(str(self.root / "packages/a/asio/2.0/abcd/include") + "\n")
with self.assertRaisesRegex(ValueError, "Unreviewed iOS dependency: asio 2.0"):
self.verify()
def test_missing_dependency_is_rejected(self):
self.target.write_text("")
with self.assertRaisesRegex(ValueError, "License/build dependency mismatch; missing:.*asio"):
self.verify()
def test_simulator_cache_keeps_dependency_and_archive_validation(self):
def test_simulator_cache_keeps_archive_validation(self):
(self.root / "packages").rename(self.root / "packages-simulator")
self.archive = Path(str(self.archive).replace("/packages/", "/packages-simulator/"))
self.inputs.write_text(self.inputs.read_text().replace("/packages/", "/packages-simulator/"))
self.target.write_text(self.target.read_text().replace("/packages/", "/packages-simulator/"))
self.verify()
valid_target = self.target.read_text()
self.target.write_text(valid_target.replace("/asio/1.0/", "/asio/2.0/"))
with self.assertRaisesRegex(ValueError, "Unreviewed iOS dependency: asio 2.0"):
self.verify()
self.target.write_text(valid_target)
self.archive.write_bytes(b"changed simulator archive")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*libopus.a"):
with self.assertRaisesRegex(ValueError, "Native input checksum mismatch: .*libopus.a"):
self.verify()
def test_changed_native_archive_is_rejected(self):
self.archive.write_bytes(b"changed archive")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*libopus.a"):
with self.assertRaisesRegex(ValueError, "Native input checksum mismatch: .*libopus.a"):
self.verify()
def test_minimum_and_newer_xmake_versions_are_accepted(self):