mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-04 04:52:39 +08:00
[fix] include OpenSSL resource symbols in iOS archives
This commit is contained in:
@@ -276,7 +276,8 @@ jobs:
|
|||||||
if [[ "${CROSSDESK_RELEASE_BUILD:-NO}" == "YES" ]]; then
|
if [[ "${CROSSDESK_RELEASE_BUILD:-NO}" == "YES" ]]; then
|
||||||
VERIFY_FLAGS+=(--require-release)
|
VERIFY_FLAGS+=(--require-release)
|
||||||
fi
|
fi
|
||||||
python3 apps/ios/scripts/verify_app_bundle.py "${APP_PATH}" "${VERIFY_FLAGS[@]}"
|
python3 apps/ios/scripts/verify_app_bundle.py "${APP_PATH}" \
|
||||||
|
--dsym-dir "${DERIVED_DATA}/Build/Products/Release-iphoneos" "${VERIFY_FLAGS[@]}"
|
||||||
xcrun lipo -info "${EXECUTABLE_PATH}"
|
xcrun lipo -info "${EXECUTABLE_PATH}"
|
||||||
echo "Minimum iOS version: ${MINIMUM_OS}"
|
echo "Minimum iOS version: ${MINIMUM_OS}"
|
||||||
|
|
||||||
@@ -293,10 +294,10 @@ jobs:
|
|||||||
cp apps/ios/licenses/OPEN_SOURCE_NOTICE.txt "${PACKAGE_DIR}/OPEN_SOURCE_NOTICE.txt"
|
cp apps/ios/licenses/OPEN_SOURCE_NOTICE.txt "${PACKAGE_DIR}/OPEN_SOURCE_NOTICE.txt"
|
||||||
cp "${APP_PATH}/SourceMetadata.json" "${PACKAGE_DIR}/SourceMetadata.json"
|
cp "${APP_PATH}/SourceMetadata.json" "${PACKAGE_DIR}/SourceMetadata.json"
|
||||||
|
|
||||||
DSYM_PATH="${DERIVED_DATA}/Build/Products/Release-iphoneos/CrossDeskMobile.app.dSYM"
|
for DSYM_NAME in CrossDeskMobile.app.dSYM OpenSSL.framework.dSYM; do
|
||||||
if [[ -d "${DSYM_PATH}" ]]; then
|
DSYM_PATH="${DERIVED_DATA}/Build/Products/Release-iphoneos/${DSYM_NAME}"
|
||||||
cp -R "${DSYM_PATH}" "${PACKAGE_DIR}/"
|
cp -R "${DSYM_PATH}" "${PACKAGE_DIR}/"
|
||||||
fi
|
done
|
||||||
|
|
||||||
ditto -c -k --sequesterRsrc --keepParent "${PACKAGE_DIR}" "${PACKAGE_FILE}"
|
ditto -c -k --sequesterRsrc --keepParent "${PACKAGE_DIR}" "${PACKAGE_FILE}"
|
||||||
echo "PACKAGE_FILE=${PACKAGE_FILE}" >> "${GITHUB_ENV}"
|
echo "PACKAGE_FILE=${PACKAGE_FILE}" >> "${GITHUB_ENV}"
|
||||||
|
|||||||
@@ -222,6 +222,7 @@
|
|||||||
300000000000000000000001 /* Frameworks */,
|
300000000000000000000001 /* Frameworks */,
|
||||||
600000000000000000000002 /* Resources */,
|
600000000000000000000002 /* Resources */,
|
||||||
300000000000000000000002 /* Embed Frameworks */,
|
300000000000000000000002 /* Embed Frameworks */,
|
||||||
|
700000000000000000000002 /* Generate OpenSSL symbols */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -318,6 +319,24 @@
|
|||||||
shellPath = /bin/zsh;
|
shellPath = /bin/zsh;
|
||||||
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\"\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\"\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";
|
||||||
};
|
};
|
||||||
|
700000000000000000000002 /* Generate OpenSSL symbols */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/OpenSSL.framework/OpenSSL",
|
||||||
|
"$(PROJECT_DIR)/scripts/generate_openssl_symbols.py",
|
||||||
|
);
|
||||||
|
name = "Generate OpenSSL symbols";
|
||||||
|
outputPaths = (
|
||||||
|
"$(DWARF_DSYM_FOLDER_PATH)/OpenSSL.framework.dSYM/Contents/Resources/DWARF/OpenSSL",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/zsh;
|
||||||
|
shellScript = "set -e\nif [[ \"${DEBUG_INFORMATION_FORMAT}\" == \"dwarf-with-dsym\" ]]; then\n python3 -B \"${PROJECT_DIR}/scripts/generate_openssl_symbols.py\" --binary \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework/OpenSSL\" --output \"${DWARF_DSYM_FOLDER_PATH}/OpenSSL.framework.dSYM\"\nfi\n";
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -496,7 +515,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
@@ -548,7 +567,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 3;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
|||||||
@@ -586,6 +586,16 @@ Xcode removes the static archive from the embedded framework (it may insert a
|
|||||||
small resource-framework stub) and preserves `PrivacyInfo.xcprivacy`, source
|
small resource-framework stub) and preserves `PrivacyInfo.xcprivacy`, source
|
||||||
metadata and license texts. The application does not load a dynamic OpenSSL copy.
|
metadata and license texts. The application does not load a dynamic OpenSSL copy.
|
||||||
|
|
||||||
|
For builds using `DWARF with dSYM File`, the **Generate OpenSSL symbols** phase
|
||||||
|
runs after embedding. It verifies that Xcode's placeholder has no executable
|
||||||
|
code, generates `OpenSSL.framework.dSYM` from that exact binary, and checks its
|
||||||
|
UUIDs. This companion describes the empty resource stub, not the statically
|
||||||
|
linked OpenSSL implementation. Xcode includes it alongside the app's dSYM in
|
||||||
|
the archive so Organizer can upload symbols for every embedded Mach-O binary.
|
||||||
|
`verify_app_bundle.py` checks both UUID matches when given an archived app;
|
||||||
|
for an ordinary Release build, pass `--dsym-dir /path/to/Release-iphoneos`.
|
||||||
|
The CI ZIP includes both dSYM bundles.
|
||||||
|
|
||||||
The iOS-specific OpenSSL recipe disables POSIX metadata calls, automatic
|
The iOS-specific OpenSSL recipe disables POSIX metadata calls, automatic
|
||||||
`openssl.cnf` loading, and the unused file-URI store provider. It selects the
|
`openssl.cnf` loading, and the unused file-URI store provider. It selects the
|
||||||
upstream `getrandom` entropy backend, which uses Darwin's `getentropy` on the
|
upstream `getrandom` entropy backend, which uses Darwin's `getentropy` on the
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate matching symbols for Xcode's embedded, codeless OpenSSL framework."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def uuids(path):
|
||||||
|
output = subprocess.check_output(["xcrun", "dwarfdump", "--uuid", str(path)], text=True)
|
||||||
|
result = dict((arch, uuid.upper()) for uuid, arch in
|
||||||
|
re.findall(r"UUID: ([0-9A-Fa-f-]{36}) \(([^)]+)\)", output))
|
||||||
|
if not result:
|
||||||
|
raise ValueError(f"No Mach-O UUIDs found: {path}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def verify_symbols(binary, dsym):
|
||||||
|
dwarf = dsym / "Contents/Resources/DWARF" / binary.name
|
||||||
|
if not dwarf.is_file():
|
||||||
|
raise ValueError(f"Missing dSYM DWARF file: {dwarf}")
|
||||||
|
if uuids(binary) != uuids(dwarf):
|
||||||
|
raise ValueError(f"dSYM UUIDs do not match {binary}: {dwarf}")
|
||||||
|
|
||||||
|
|
||||||
|
def generate(binary, output):
|
||||||
|
# This is only for Xcode's resource placeholder. Real OpenSSL code remains
|
||||||
|
# statically linked into the app and must not receive an empty dSYM here.
|
||||||
|
commands = subprocess.check_output(["xcrun", "otool", "-l", str(binary)], text=True)
|
||||||
|
text_sizes = re.findall(
|
||||||
|
r"sectname __text\s+segname __TEXT\s+addr \S+\s+size (0x[0-9a-fA-F]+)", commands)
|
||||||
|
if not text_sizes or any(int(size, 16) for size in text_sizes):
|
||||||
|
raise ValueError(f"Expected a codeless OpenSSL resource stub: {binary}")
|
||||||
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
# No function-level debug info exists in this empty stub. dsymutil still
|
||||||
|
# emits a valid UUID-matched DWARF companion; quiet suppresses that expected
|
||||||
|
# warning only after verifying that there is no executable code to lose.
|
||||||
|
subprocess.run(["xcrun", "dsymutil", "--quiet", str(binary), "-o", str(output)], check=True)
|
||||||
|
verify_symbols(binary, output)
|
||||||
|
subprocess.run(["xcrun", "dwarfdump", "--verify", str(output)], check=True)
|
||||||
|
print(f"Generated UUID-matched OpenSSL resource symbols: {output}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--binary", required=True, type=Path)
|
||||||
|
parser.add_argument("--output", required=True, type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
generate(args.binary, args.output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except (OSError, ValueError, subprocess.CalledProcessError) as error:
|
||||||
|
print(f"OpenSSL symbols: {error}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
@@ -10,8 +10,10 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from generate_openssl_symbols import verify_symbols
|
||||||
|
|
||||||
def verify(app, require_release=False):
|
|
||||||
|
def verify(app, require_release=False, dsym_dir=None):
|
||||||
info = plistlib.loads((app / "Info.plist").read_bytes())
|
info = plistlib.loads((app / "Info.plist").read_bytes())
|
||||||
binary = app / info["CFBundleExecutable"]
|
binary = app / info["CFBundleExecutable"]
|
||||||
subprocess.run(["xcrun", "lipo", str(binary), "-verify_arch", "arm64"], check=True)
|
subprocess.run(["xcrun", "lipo", str(binary), "-verify_arch", "arm64"], check=True)
|
||||||
@@ -63,6 +65,14 @@ def verify(app, require_release=False):
|
|||||||
sdk_symbols = subprocess.check_output(["xcrun", "nm", "-gU", str(embedded_binary)], text=True)
|
sdk_symbols = subprocess.check_output(["xcrun", "nm", "-gU", str(embedded_binary)], text=True)
|
||||||
if re.search(r"\b_(?:SSL_new|OPENSSL_init_crypto)\b", sdk_symbols):
|
if re.search(r"\b_(?:SSL_new|OPENSSL_init_crypto)\b", sdk_symbols):
|
||||||
raise ValueError("Unexpected dynamic OpenSSL implementation in resource framework")
|
raise ValueError("Unexpected dynamic OpenSSL implementation in resource framework")
|
||||||
|
# Archive validation includes the exact files that Organizer uploads.
|
||||||
|
if dsym_dir is None and app.parents[2].suffix == ".xcarchive":
|
||||||
|
dsym_dir = app.parents[2] / "dSYMs"
|
||||||
|
if dsym_dir is not None:
|
||||||
|
verify_symbols(binary, dsym_dir / f"{app.name}.dSYM")
|
||||||
|
if embedded_binary.exists():
|
||||||
|
verify_symbols(embedded_binary, dsym_dir / "OpenSSL.framework.dSYM")
|
||||||
|
print("Verified app and embedded OpenSSL dSYM UUIDs")
|
||||||
linked = subprocess.check_output(["xcrun", "otool", "-L", str(binary)], text=True)
|
linked = subprocess.check_output(["xcrun", "otool", "-L", str(binary)], text=True)
|
||||||
if "OpenSSL.framework" in linked:
|
if "OpenSSL.framework" in linked:
|
||||||
raise ValueError("OpenSSL must be statically linked into the application")
|
raise ValueError("OpenSSL must be statically linked into the application")
|
||||||
@@ -79,8 +89,9 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("app", type=Path)
|
parser.add_argument("app", type=Path)
|
||||||
parser.add_argument("--require-release", action="store_true")
|
parser.add_argument("--require-release", action="store_true")
|
||||||
|
parser.add_argument("--dsym-dir", type=Path)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
verify(args.app, args.require_release)
|
verify(args.app.resolve(), args.require_release, args.dsym_dir)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user