diff --git a/app/src/main/cpp/rosetta_e2ee.cpp b/app/src/main/cpp/rosetta_e2ee.cpp index 320876b..8dc7ccb 100644 --- a/app/src/main/cpp/rosetta_e2ee.cpp +++ b/app/src/main/cpp/rosetta_e2ee.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -125,6 +126,12 @@ struct AdditionalTsState { uint64_t base_timestamp = 0; }; +struct SenderTsOffsetState { + bool initialized = false; + bool enabled = false; + uint64_t offset = 0; +}; + static inline uint16_t load16_be(const uint8_t* p) { return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]); } @@ -334,6 +341,25 @@ static inline void fill_nonce_from_ts32(uint32_t ts, uint8_t nonce[24]) { nonce[7] = (uint8_t)(ts); } +static inline void fill_nonce_from_ts64(uint64_t ts, uint8_t nonce[24]) { + nonce[0] = (uint8_t)(ts >> 56); + nonce[1] = (uint8_t)(ts >> 48); + nonce[2] = (uint8_t)(ts >> 40); + nonce[3] = (uint8_t)(ts >> 32); + nonce[4] = (uint8_t)(ts >> 24); + nonce[5] = (uint8_t)(ts >> 16); + nonce[6] = (uint8_t)(ts >> 8); + nonce[7] = (uint8_t)(ts); +} + +static inline uint64_t monotonic_48k_ticks() { + struct timespec ts {}; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; + const uint64_t sec = (uint64_t)ts.tv_sec; + const uint64_t nsec = (uint64_t)ts.tv_nsec; + return sec * 48000ULL + (nsec * 48000ULL) / 1000000000ULL; +} + static inline uint32_t opus_base_frame_samples(uint8_t config) { // RFC 6716 TOC config mapping at 48 kHz. if (config <= 11) { @@ -550,6 +576,7 @@ public: bool nonce_from_generated_ts = false; bool nonce_from_additional_data = false; bool additional_was_rtp_header = false; + bool additional_used_mono_offset = false; uint32_t generated_ts_used = 0; // Build nonce from RTP timestamp in additional_data (preferred). @@ -579,23 +606,36 @@ public: } } - if (nonce_from_rtp_header && header_size <= frame.size()) { - // Keep RTP header clear, encrypt payload only. - if (header_size > 0) { - memcpy(encrypted_frame.data(), frame.data(), header_size); + // Some Android sender pipelines expose stream-relative ad8 timestamps + // (0, 960, 1920, ...), while desktop receiver expects an absolute base. + // For interop, add a monotonic 48k offset once when first ad8 is tiny. + if (nonce_from_additional_data && + additional_data.size() == 8 && + !additional_was_rtp_header && + additional_data.data() != nullptr) { + const uint64_t ad_ts64 = load64_be(additional_data.data()); + if (!sender_ts_offset_.initialized) { + sender_ts_offset_.initialized = true; + // Keep pure raw-abs mode by default; desktop is the source of truth. + sender_ts_offset_.enabled = false; + sender_ts_offset_.offset = 0ULL; + diag_event("ENC ad8-base init ssrc=%u ad_ts=%llu use_mono=%d mono_off=%llu\n", + ssrc, + (unsigned long long)ad_ts64, + sender_ts_offset_.enabled ? 1 : 0, + (unsigned long long)sender_ts_offset_.offset); + } + if (sender_ts_offset_.enabled) { + const uint64_t ts_adj = ad_ts64 + sender_ts_offset_.offset; + fill_nonce_from_ts64(ts_adj, nonce); + additional_used_mono_offset = true; } - const size_t payload_size = frame.size() - header_size; - rosetta_xchacha20_xor( - encrypted_frame.data() + header_size, - frame.data() + header_size, - payload_size, - nonce, - key_); - } else { - // Legacy path: frame is payload-only. - rosetta_xchacha20_xor(encrypted_frame.data(), - frame.data(), frame.size(), nonce, key_); } + + // Desktop createEncodedStreams encrypts full encoded chunk. + // To stay wire-compatible, do not preserve any leading RTP-like bytes. + rosetta_xchacha20_xor(encrypted_frame.data(), + frame.data(), frame.size(), nonce, key_); *bytes_written = frame.size(); if (nonce_from_generated_ts) { @@ -628,7 +668,9 @@ public: : (nonce_from_additional_data ? (additional_was_rtp_header ? "ad-rtp" - : (additional_used_relative_ts ? "raw-rel" : "raw-abs")) + : (additional_used_mono_offset + ? "raw-abs+mono" + : (additional_used_relative_ts ? "raw-rel" : "raw-abs"))) : "raw-abs")); LOGI("ENC frame#%d mt=%s ssrc=%u sz=%zu ad=%zu hdr=%zu mode=%s nonce_ts=%u gen_ts=%u next_step=%u rtp_ok=%d rtp_seq=%u rtp_ts=%u rtp_ssrc=%u opus_ok=%d key_fp=%08x in_h=%08x out_h=%08x ad8=%02x%02x%02x%02x%02x%02x%02x%02x", n, media_type_name(media_type), ssrc, frame.size(), additional_data.size(), header_size, mode, @@ -663,6 +705,7 @@ private: mutable std::atomic diag_count_{0}; mutable RtpProbeState rtp_probe_; mutable GeneratedTsState generated_ts_; + mutable SenderTsOffsetState sender_ts_offset_; uint32_t key_fingerprint_ = 0; uint8_t key_[32]; }; @@ -746,20 +789,8 @@ public: bool used_generated_resync = false; - if (nonce_from_rtp_header && header_size <= encrypted_frame.size()) { - if (header_size > 0) { - memcpy(frame.data(), encrypted_frame.data(), header_size); - } - const size_t payload_size = encrypted_frame.size() - header_size; - rosetta_xchacha20_xor( - frame.data() + header_size, - encrypted_frame.data() + header_size, - payload_size, - nonce, - key_); - } else { - rosetta_xchacha20_xor(frame.data(), encrypted_frame.data(), encrypted_frame.size(), nonce, key_); - } + // Desktop createEncodedStreams decrypts full encoded chunk. + rosetta_xchacha20_xor(frame.data(), encrypted_frame.data(), encrypted_frame.size(), nonce, key_); if (nonce_from_additional_data) { bool plausible = is_plausible_decrypted_audio_frame(frame.data(), encrypted_frame.size()); diff --git a/app/src/main/java/com/rosetta/messenger/network/CallManager.kt b/app/src/main/java/com/rosetta/messenger/network/CallManager.kt index c0824ac..55b3a3e 100644 --- a/app/src/main/java/com/rosetta/messenger/network/CallManager.kt +++ b/app/src/main/java/com/rosetta/messenger/network/CallManager.kt @@ -6,7 +6,6 @@ import android.util.Log import com.rosetta.messenger.data.MessageRepository import java.security.MessageDigest import java.security.SecureRandom -import java.util.IdentityHashMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -137,8 +136,8 @@ object CallManager { // E2EE (XChaCha20 — compatible with Desktop) private var sharedKeyBytes: ByteArray? = null - private val senderEncryptors = IdentityHashMap() - private val receiverDecryptors = IdentityHashMap() + private val senderEncryptors = LinkedHashMap() + private val receiverDecryptors = LinkedHashMap() private var pendingAudioSenderForE2ee: RtpSender? = null private var lastRemoteOfferFingerprint: String = "" private var lastLocalOfferFingerprint: String = "" @@ -1063,11 +1062,22 @@ object CallManager { breadcrumb("STATE[$marker] ${buildStateSnapshot()}") } + private fun senderMapKey(sender: RtpSender): String { + val id = runCatching { sender.id() }.getOrNull().orEmpty() + return if (id.isNotBlank()) "sid:$id" else "sender@${System.identityHashCode(sender)}" + } + + private fun receiverMapKey(receiver: RtpReceiver): String { + val id = runCatching { receiver.id() }.getOrNull().orEmpty() + return if (id.isNotBlank()) "rid:$id" else "recv@${System.identityHashCode(receiver)}" + } + private fun attachSenderE2EE(sender: RtpSender?) { if (!e2eeAvailable) return val key = sharedKeyBytes ?: return if (sender == null) return - val existing = senderEncryptors[sender] + val mapKey = senderMapKey(sender) + val existing = senderEncryptors[mapKey] if (existing != null) { runCatching { sender.setFrameEncryptor(existing) } return @@ -1086,7 +1096,7 @@ object CallManager { breadcrumb("4. calling sender.setFrameEncryptor…") sender.setFrameEncryptor(enc) breadcrumb("5. setFrameEncryptor OK!") - senderEncryptors[sender] = enc + senderEncryptors[mapKey] = enc pendingAudioSenderForE2ee = null } catch (e: Throwable) { saveCrashReport("attachSenderE2EE failed", e) @@ -1104,7 +1114,8 @@ object CallManager { if (!e2eeAvailable) return val key = sharedKeyBytes ?: return if (receiver == null) return - val existing = receiverDecryptors[receiver] + val mapKey = receiverMapKey(receiver) + val existing = receiverDecryptors[mapKey] if (existing != null) { runCatching { receiver.setFrameDecryptor(existing) } return @@ -1123,7 +1134,7 @@ object CallManager { breadcrumb("9. calling receiver.setFrameDecryptor…") receiver.setFrameDecryptor(dec) breadcrumb("10. setFrameDecryptor OK!") - receiverDecryptors[receiver] = dec + receiverDecryptors[mapKey] = dec } catch (e: Throwable) { saveCrashReport("attachReceiverE2EE failed", e) Log.e(TAG, "E2EE: receiver decryptor failed", e) diff --git a/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt index 614af52..6f6f94e 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/crashlogs/CrashLogsScreen.kt @@ -1,5 +1,6 @@ package com.rosetta.messenger.ui.crashlogs +import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -8,13 +9,16 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.BugReport +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Share import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -263,6 +267,8 @@ private fun CrashDetailScreen( onDelete: () -> Unit ) { var showDeleteDialog by remember { mutableStateOf(false) } + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current Scaffold( topBar = { @@ -274,6 +280,14 @@ private fun CrashDetailScreen( } }, actions = { + IconButton( + onClick = { + clipboardManager.setText(AnnotatedString(crashReport.content)) + Toast.makeText(context, "Full log copied", Toast.LENGTH_SHORT).show() + } + ) { + Icon(Icons.Default.ContentCopy, contentDescription = "Copy Full Log") + } IconButton(onClick = { /* TODO: Share */ }) { Icon(Icons.Default.Share, contentDescription = "Share") } diff --git a/tools/webrtc-custom/README.md b/tools/webrtc-custom/README.md index cb0baa5..6bc2eb6 100644 --- a/tools/webrtc-custom/README.md +++ b/tools/webrtc-custom/README.md @@ -9,7 +9,8 @@ Stock `io.github.webrtc-sdk:android:125.6422.07` can call audio frame encryptor `additional_data` (`ad=0`), so nonce derivation based on timestamp is unavailable. Desktop uses frame timestamp for nonce. This patch aligns Android with that approach by passing -an 8-byte big-endian timestamp payload in `additional_data`: +an 8-byte big-endian timestamp payload in `additional_data` (absolute RTP timestamp, +including sender start offset): - bytes `0..3` = `0` - bytes `4..7` = RTP timestamp (big-endian) @@ -18,10 +19,14 @@ an 8-byte big-endian timestamp payload in `additional_data`: - `build_custom_webrtc.sh` — reproducible build script - `patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch` — WebRTC patch +- `patches/0002-android-build-on-mac-host.patch` — allows Android target build on macOS host +- `patches/0003-macos-host-java-ijar.patch` — enables host tools (`ijar`/`jdk`) on macOS +- `patches/0004-macos-linker-missing-L-dirs.patch` — skips invalid host `-L...` paths for lld +- `patches/0005-macos-server-utils-socket.patch` — handles macOS socket errno in Android Java compile helper ## Build -Recommended on Linux (macOS can work but is less predictable for long WebRTC builds). +Recommended on Linux (macOS is supported via additional patches in this folder). Bootstrap `depot_tools` first: @@ -47,6 +52,7 @@ Optional env vars: - `SYNC_JOBS` — `gclient sync` jobs (default: `1`, safer for googlesource limits) - `SYNC_RETRIES` — sync retry attempts (default: `8`) - `SYNC_RETRY_BASE_SEC` — base retry delay in seconds (default: `20`) +- `MAC_ANDROID_NDK_ROOT` — local Android NDK path on macOS (default: `~/Library/Android/sdk/ndk/27.1.12297006`) ## Troubleshooting (HTTP 429 / RESOURCE_EXHAUSTED) diff --git a/tools/webrtc-custom/build_custom_webrtc.sh b/tools/webrtc-custom/build_custom_webrtc.sh index 1e307b5..a3a676b 100755 --- a/tools/webrtc-custom/build_custom_webrtc.sh +++ b/tools/webrtc-custom/build_custom_webrtc.sh @@ -9,7 +9,13 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROSETTA_ANDROID_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" -PATCH_FILE="${SCRIPT_DIR}/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch" +PATCH_FILES=( + "${SCRIPT_DIR}/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch" + "${SCRIPT_DIR}/patches/0002-android-build-on-mac-host.patch" + "${SCRIPT_DIR}/patches/0003-macos-host-java-ijar.patch" + "${SCRIPT_DIR}/patches/0004-macos-linker-missing-L-dirs.patch" + "${SCRIPT_DIR}/patches/0005-macos-server-utils-socket.patch" +) # Default target: WebRTC M125 family used by app dependency 125.6422.07. WEBRTC_BRANCH="${WEBRTC_BRANCH:-branch-heads/6422}" @@ -132,21 +138,63 @@ sync_with_retry echo "[webrtc-custom] applying Rosetta patch..." git reset --hard -git apply --check "${PATCH_FILE}" -git apply "${PATCH_FILE}" +for patch in "${PATCH_FILES[@]}"; do + echo "[webrtc-custom] apply $(basename "${patch}")" + git apply --check "${patch}" + git apply "${patch}" +done + +# macOS host tweaks: +# - point third_party/jdk/current to local JDK +# - use locally installed Android NDK (darwin toolchain) +if [[ "$(uname -s)" == "Darwin" ]]; then + if [[ -z "${JAVA_HOME:-}" ]]; then + JAVA_HOME="$(/usr/libexec/java_home 2>/dev/null || true)" + fi + if [[ -z "${JAVA_HOME:-}" || ! -d "${JAVA_HOME}" ]]; then + echo "[webrtc-custom] ERROR: JAVA_HOME not found on macOS" + exit 1 + fi + JAVA_HOME_CANDIDATE="${JAVA_HOME}" + if [[ ! -f "${JAVA_HOME_CANDIDATE}/conf/logging.properties" ]] && [[ -d "${JAVA_HOME_CANDIDATE}/libexec/openjdk.jdk/Contents/Home" ]]; then + JAVA_HOME_CANDIDATE="${JAVA_HOME_CANDIDATE}/libexec/openjdk.jdk/Contents/Home" + fi + if [[ ! -f "${JAVA_HOME_CANDIDATE}/conf/logging.properties" ]]; then + echo "[webrtc-custom] ERROR: invalid JAVA_HOME (conf/logging.properties not found): ${JAVA_HOME}" + exit 1 + fi + JAVA_HOME="${JAVA_HOME_CANDIDATE}" + ln -sfn "${JAVA_HOME}" "${WEBRTC_SRC}/third_party/jdk/current" + echo "[webrtc-custom] macOS JDK linked: ${WEBRTC_SRC}/third_party/jdk/current -> ${JAVA_HOME}" +fi mkdir -p "$(dirname "${OUT_AAR}")" echo "[webrtc-custom] building AAR (this can take a while)..." +GN_ARGS=( + is_debug=false + is_component_build=false + rtc_include_tests=false + rtc_build_examples=false +) + +if [[ "$(uname -s)" == "Darwin" ]]; then + MAC_ANDROID_NDK_ROOT="${MAC_ANDROID_NDK_ROOT:-$HOME/Library/Android/sdk/ndk/27.1.12297006}" + if [[ ! -d "${MAC_ANDROID_NDK_ROOT}" ]]; then + echo "[webrtc-custom] ERROR: Android NDK not found at ${MAC_ANDROID_NDK_ROOT}" + echo "[webrtc-custom] Set MAC_ANDROID_NDK_ROOT to your local NDK path." + exit 1 + fi + GN_ARGS+=("android_ndk_root=\"${MAC_ANDROID_NDK_ROOT}\"") + GN_ARGS+=("android_ndk_version=\"27.1.12297006\"") + echo "[webrtc-custom] macOS Android NDK: ${MAC_ANDROID_NDK_ROOT}" +fi + python3 tools_webrtc/android/build_aar.py \ --build-dir out_rosetta_aar \ --output "${OUT_AAR}" \ --arch "${ARCHS[@]}" \ - --extra-gn-args \ - is_debug=false \ - is_component_build=false \ - rtc_include_tests=false \ - rtc_build_examples=false + --extra-gn-args "${GN_ARGS[@]}" echo "[webrtc-custom] done" echo "[webrtc-custom] AAR: ${OUT_AAR}" diff --git a/tools/webrtc-custom/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch b/tools/webrtc-custom/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch index e236385..42a7396 100644 --- a/tools/webrtc-custom/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch +++ b/tools/webrtc-custom/patches/0001-audio-e2ee-pass-rtp-timestamp-as-additional-data.patch @@ -25,22 +25,24 @@ index 17cf859ed8..b9d9ab14c8 100644 decrypted_audio_payload); diff --git a/audio/channel_send.cc b/audio/channel_send.cc -index 4a2700177b..93283c2e78 100644 +index 4a2700177b..7ebb501704 100644 --- a/audio/channel_send.cc +++ b/audio/channel_send.cc -@@ -320,10 +320,21 @@ int32_t ChannelSend::SendRtpAudio(AudioFrameType frameType, +@@ -320,10 +320,23 @@ int32_t ChannelSend::SendRtpAudio(AudioFrameType frameType, // Encrypt the audio payload into the buffer. size_t bytes_written = 0; ++ const uint32_t additional_data_timestamp = ++ rtp_timestamp_without_offset + rtp_rtcp_->StartTimestamp(); + const uint8_t additional_data_bytes[8] = { + 0, + 0, + 0, + 0, -+ static_cast((rtp_timestamp_without_offset >> 24) & 0xff), -+ static_cast((rtp_timestamp_without_offset >> 16) & 0xff), -+ static_cast((rtp_timestamp_without_offset >> 8) & 0xff), -+ static_cast(rtp_timestamp_without_offset & 0xff), ++ static_cast((additional_data_timestamp >> 24) & 0xff), ++ static_cast((additional_data_timestamp >> 16) & 0xff), ++ static_cast((additional_data_timestamp >> 8) & 0xff), ++ static_cast(additional_data_timestamp & 0xff), + }; + int encrypt_status = frame_encryptor_->Encrypt( diff --git a/tools/webrtc-custom/patches/0002-android-build-on-mac-host.patch b/tools/webrtc-custom/patches/0002-android-build-on-mac-host.patch new file mode 100644 index 0000000..0499b81 --- /dev/null +++ b/tools/webrtc-custom/patches/0002-android-build-on-mac-host.patch @@ -0,0 +1,27 @@ +diff --git a/build/config/BUILDCONFIG.gn b/build/config/BUILDCONFIG.gn +index 26fad5adf..7a614f334 100644 +--- a/build/config/BUILDCONFIG.gn ++++ b/build/config/BUILDCONFIG.gn +@@ -239,7 +239,8 @@ if (host_toolchain == "") { + _default_toolchain = "" + + if (target_os == "android") { +- assert(host_os == "linux", "Android builds are only supported on Linux.") ++ assert(host_os == "linux" || host_os == "mac", ++ "Android builds are only supported on Linux/macOS.") + _default_toolchain = "//build/toolchain/android:android_clang_$target_cpu" + } else if (target_os == "chromeos" || target_os == "linux") { + # See comments in build/toolchain/cros/BUILD.gn about board compiles. +diff --git a/build/config/android/config.gni b/build/config/android/config.gni +index 427739d70..6a5ab0594 100644 +--- a/build/config/android/config.gni ++++ b/build/config/android/config.gni +@@ -327,7 +327,7 @@ if (is_android || is_chromeos) { + + # Defines the name the Android build gives to the current host CPU + # architecture, which is different than the names GN uses. +- if (host_cpu == "x64") { ++ if (host_cpu == "x64" || host_cpu == "arm64") { + android_host_arch = "x86_64" + } else if (host_cpu == "x86") { + android_host_arch = "x86" diff --git a/tools/webrtc-custom/patches/0003-macos-host-java-ijar.patch b/tools/webrtc-custom/patches/0003-macos-host-java-ijar.patch new file mode 100644 index 0000000..bb09549 --- /dev/null +++ b/tools/webrtc-custom/patches/0003-macos-host-java-ijar.patch @@ -0,0 +1,34 @@ +diff --git a/third_party/ijar/BUILD.gn b/third_party/ijar/BUILD.gn +index 8dc9fe21cf8..49c50e6636f 100644 +--- a/third_party/ijar/BUILD.gn ++++ b/third_party/ijar/BUILD.gn +@@ -4,7 +4,7 @@ + + # A tool that removes all non-interface-specific parts from a .jar file. + +-if (is_linux || is_chromeos) { ++if (is_linux || is_chromeos || is_mac) { + config("ijar_compiler_flags") { + if (is_clang) { + cflags = [ +diff --git a/third_party/jdk/BUILD.gn b/third_party/jdk/BUILD.gn +index e003eef94d7..ec49922942b 100644 +--- a/third_party/jdk/BUILD.gn ++++ b/third_party/jdk/BUILD.gn +@@ -3,10 +3,12 @@ + # found in the LICENSE file. + + config("jdk") { +- include_dirs = [ +- "current/include", +- "current/include/linux", +- ] ++ include_dirs = [ "current/include" ] ++ if (host_os == "mac") { ++ include_dirs += [ "current/include/darwin" ] ++ } else { ++ include_dirs += [ "current/include/linux" ] ++ } + } + + group("java_data") { diff --git a/tools/webrtc-custom/patches/0004-macos-linker-missing-L-dirs.patch b/tools/webrtc-custom/patches/0004-macos-linker-missing-L-dirs.patch new file mode 100644 index 0000000..c591589 --- /dev/null +++ b/tools/webrtc-custom/patches/0004-macos-linker-missing-L-dirs.patch @@ -0,0 +1,102 @@ +diff --git a/build/toolchain/apple/linker_driver.py b/build/toolchain/apple/linker_driver.py +index 0632230cf..798442534 100755 +--- a/build/toolchain/apple/linker_driver.py ++++ b/build/toolchain/apple/linker_driver.py +@@ -7,6 +7,7 @@ + import os + import os.path + import re ++import shlex + import shutil + import subprocess + import sys +@@ -113,6 +114,53 @@ class LinkerDriver(object): + # The temporary directory for intermediate LTO object files. If it + # exists, it will clean itself up on script exit. + self._object_path_lto = None ++ self._temp_rsp_files = [] ++ ++ def _sanitize_rsp_arg(self, arg): ++ if not arg.startswith('@'): ++ return arg ++ rsp_path = arg[1:] ++ if not os.path.isfile(rsp_path): ++ return arg ++ ++ try: ++ with open(rsp_path, 'r', encoding='utf-8') as f: ++ rsp_content = f.read() ++ except OSError: ++ return arg ++ ++ tokens = shlex.split(rsp_content, posix=True) ++ sanitized = [] ++ changed = False ++ i = 0 ++ while i < len(tokens): ++ tok = tokens[i] ++ if tok == '-L' and i + 1 < len(tokens): ++ lib_dir = tokens[i + 1] ++ if not os.path.isdir(lib_dir): ++ changed = True ++ i += 2 ++ continue ++ elif tok.startswith('-L') and len(tok) > 2: ++ lib_dir = tok[2:] ++ if not os.path.isdir(lib_dir): ++ changed = True ++ i += 1 ++ continue ++ sanitized.append(tok) ++ i += 1 ++ ++ if not changed: ++ return arg ++ ++ fd, temp_path = tempfile.mkstemp(prefix='linker_driver_', suffix='.rsp') ++ os.close(fd) ++ with open(temp_path, 'w', encoding='utf-8') as f: ++ for tok in sanitized: ++ f.write(tok) ++ f.write('\n') ++ self._temp_rsp_files.append(temp_path) ++ return '@' + temp_path + + def run(self): + """Runs the linker driver, separating out the main compiler driver's +@@ -135,11 +183,25 @@ class LinkerDriver(object): + assert driver_action[0] not in linker_driver_actions + linker_driver_actions[driver_action[0]] = driver_action[1] + else: ++ if arg.startswith('@'): ++ arg = self._sanitize_rsp_arg(arg) + # TODO(crbug.com/1446796): On Apple, the linker command line + # produced by rustc for LTO includes these arguments, but the + # Apple linker doesn't accept them. + # Upstream bug: https://github.com/rust-lang/rust/issues/60059 + BAD_RUSTC_ARGS = '-Wl,-plugin-opt=O[0-9],-plugin-opt=mcpu=.*' ++ if arg == '-Wl,-fatal_warnings': ++ # Some host link steps on Apple Silicon produce benign ++ # warnings from injected search paths (e.g. /usr/local/lib ++ # missing). Don't fail the whole build on those warnings. ++ continue ++ if arg.startswith('-L') and len(arg) > 2: ++ # Some environments inject non-existent library search ++ # paths (e.g. /usr/local/lib on Apple Silicon). lld treats ++ # them as hard errors, so skip missing -L entries. ++ lib_dir = arg[2:] ++ if not os.path.isdir(lib_dir): ++ continue + if not re.match(BAD_RUSTC_ARGS, arg): + compiler_driver_args.append(arg) + +@@ -185,6 +247,9 @@ class LinkerDriver(object): + + # Re-report the original failure. + raise ++ finally: ++ for path in self._temp_rsp_files: ++ _remove_path(path) + + def _get_linker_output(self): + """Returns the value of the output argument to the linker.""" diff --git a/tools/webrtc-custom/patches/0005-macos-server-utils-socket.patch b/tools/webrtc-custom/patches/0005-macos-server-utils-socket.patch new file mode 100644 index 0000000..2facc56 --- /dev/null +++ b/tools/webrtc-custom/patches/0005-macos-server-utils-socket.patch @@ -0,0 +1,15 @@ +diff --git a/build/android/gyp/util/server_utils.py b/build/android/gyp/util/server_utils.py +index 6d5ed79d3..c05b57529 100644 +--- a/build/android/gyp/util/server_utils.py ++++ b/build/android/gyp/util/server_utils.py +@@ -36,7 +36,9 @@ def MaybeRunCommand(name, argv, stamp_file, force): + except socket.error as e: + # [Errno 111] Connection refused. Either the server has not been started + # or the server is not currently accepting new connections. +- if e.errno == 111: ++ # [Errno 2] Abstract Unix sockets are unsupported on macOS, so treat ++ # this the same way (build server unavailable). ++ if e.errno in (111, 2): + if force: + raise RuntimeError( + '\n\nBuild server is not running and '