[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
@@ -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):