Fix Apple simulator and Swift 6 build plumbing

This commit is contained in:
Conrad Kramer 2026-03-31 12:50:28 -07:00
parent cdf8d22055
commit f9062eae33
7 changed files with 188 additions and 50 deletions

View file

@ -40,5 +40,4 @@ APP_GROUP_IDENTIFIER = group.$(APP_BUNDLE_IDENTIFIER)
APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER)
NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network
// https://github.com/grpc/grpc-swift/issues/683#issuecomment-1130118953 OTHER_SWIFT_FLAGS = $(inherited)
OTHER_SWIFT_FLAGS = $(inherited) -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIOAtomics.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIODarwin.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CGRPCZlib.modulemap

View file

@ -1,4 +1,5 @@
@_implementationOnly import CConstants @_implementationOnly import CConstants
import Foundation
import OSLog import OSLog
public enum Constants { public enum Constants {
@ -27,9 +28,30 @@ public enum Constants {
private static let _groupContainerURL: Result<URL, Error> = { private static let _groupContainerURL: Result<URL, Error> = {
switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) { switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
case .some(let url): .success(url) case .some(let url): .success(url)
case .none: .failure(.invalidAppGroupIdentifier) case .none:
fallbackContainerURL().mapError { _ in .invalidAppGroupIdentifier }
} }
}() }()
private static func fallbackContainerURL() -> Result<URL, any Swift.Error> {
#if targetEnvironment(simulator)
Result {
let baseURL = try FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
let url = baseURL
.appending(component: bundleIdentifier, directoryHint: .isDirectory)
.appending(component: "SimulatorFallback", directoryHint: .isDirectory)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
#else
.failure(Error.invalidAppGroupIdentifier)
#endif
}
} }
extension Logger { extension Logger {

View file

@ -0,0 +1,64 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option cc_enable_arenas = true;
option go_package = "google.golang.org/protobuf/types/known/timestamppb";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
// A Timestamp represents a point in time independent of any time zone or local
// calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
// January 1, 1970, in the proleptic Gregorian calendar which extends the
// Gregorian calendar backwards to year one.
//
// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// second table is needed for interpretation, using a 24-hour linear smear.
//
// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from RFC
// 3339 date strings.
message Timestamp {
// Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
// Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999 inclusive.
int32 nanos = 2;
}

View file

@ -5,7 +5,15 @@ import libburrow
import NetworkExtension import NetworkExtension
import os import os
class PacketTunnelProvider: NEPacketTunnelProvider { private final class SendableCallbackBox<Callback>: @unchecked Sendable {
let callback: Callback
init(_ callback: Callback) {
self.callback = callback
}
}
final class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
enum Error: Swift.Error { enum Error: Swift.Error {
case missingTunnelConfiguration case missingTunnelConfiguration
} }
@ -30,7 +38,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
} }
override func startTunnel(options: [String: NSObject]? = nil) async throws { override func startTunnel(
options: [String: NSObject]?,
completionHandler: @escaping (Swift.Error?) -> Void
) {
let completion = SendableCallbackBox(completionHandler)
Task {
do { do {
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
guard let settings = configuration?.settings else { guard let settings = configuration?.settings else {
@ -39,19 +52,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
try await setTunnelNetworkSettings(settings) try await setTunnelNetworkSettings(settings)
_ = try await client.tunnelStart(.init()) _ = try await client.tunnelStart(.init())
logger.log("Started tunnel with network settings: \(settings)") logger.log("Started tunnel with network settings: \(settings)")
completion.callback(nil)
} catch { } catch {
logger.error("Failed to start tunnel: \(error)") logger.error("Failed to start tunnel: \(error)")
throw error completion.callback(error)
}
} }
} }
override func stopTunnel(with reason: NEProviderStopReason) async { override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: @escaping () -> Void
) {
let completion = SendableCallbackBox(completionHandler)
Task {
do { do {
_ = try await client.tunnelStop(.init()) _ = try await client.tunnelStop(.init())
logger.log("Stopped client") logger.log("Stopped client")
} catch { } catch {
logger.error("Failed to stop tunnel: \(error)") logger.error("Failed to stop tunnel: \(error)")
} }
completion.callback()
}
} }
} }

View file

@ -73,7 +73,21 @@ CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH"
# Run cargo without the various environment variables set by Xcode. # Run cargo without the various environment variables set by Xcode.
# Those variables can confuse cargo and the build scripts it runs. # Those variables can confuse cargo and the build scripts it runs.
env -i PATH="$CARGO_PATH" PROTOC="$PROTOC" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}" CARGO_ENV=(
"PATH=$CARGO_PATH"
"PROTOC=$PROTOC"
"CARGO_TARGET_DIR=${CONFIGURATION_TEMP_DIR}/target"
)
if [[ -n "$IPHONEOS_DEPLOYMENT_TARGET" ]]; then
CARGO_ENV+=("IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET")
fi
if [[ -n "$MACOSX_DEPLOYMENT_TARGET" ]]; then
CARGO_ENV+=("MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET")
fi
env -i "${CARGO_ENV[@]}" cargo build "${CARGO_ARGS[@]}"
mkdir -p "${BUILT_PRODUCTS_DIR}" mkdir -p "${BUILT_PRODUCTS_DIR}"

View file

@ -15,6 +15,8 @@ tokio = { version = "1.37", features = [
"macros", "macros",
"sync", "sync",
"io-util", "io-util",
"net",
"process",
"rt-multi-thread", "rt-multi-thread",
"signal", "signal",
"time", "time",
@ -25,7 +27,6 @@ tun = { version = "0.1", path = "../tun", features = ["serde", "tokio"] }
clap = { version = "4.4", features = ["derive"] } clap = { version = "4.4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" }
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@ -47,13 +48,14 @@ base64 = "0.21"
fehler = "1.0" fehler = "1.0"
ip_network_table = "0.2" ip_network_table = "0.2"
ip_network = "0.4" ip_network = "0.4"
ipnetwork = "0.21" ipnetwork = { version = "0.21", features = ["serde"] }
async-channel = "2.1" async-channel = "2.1"
schemars = "0.8" schemars = "0.8"
futures = "0.3.28" futures = "0.3.28"
once_cell = "1.19" once_cell = "1.19"
arti-client = "0.40.0" arti-client = "0.40.0"
hickory-proto = "0.25.2" hickory-proto = "0.25.2"
netstack-smoltcp = "0.2.1"
tokio-util = { version = "0.7.18", features = ["compat"] } tokio-util = { version = "0.7.18", features = ["compat"] }
tor-rtcompat = "0.40.0" tor-rtcompat = "0.40.0"
console-subscriber = { version = "0.2.0", optional = true } console-subscriber = { version = "0.2.0", optional = true }
@ -65,7 +67,6 @@ reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls", "rustls-tls",
] } ] }
rusqlite = { version = "0.38.0", features = ["blob"] } rusqlite = { version = "0.38.0", features = ["blob"] }
iroh = "0.94.0"
dotenv = "0.15.0" dotenv = "0.15.0"
tonic = "0.12.0" tonic = "0.12.0"
prost = "0.13.1" prost = "0.13.1"
@ -82,12 +83,16 @@ subtle = "2.6"
caps = "0.5" caps = "0.5"
libc = "0.2" libc = "0.2"
libsystemd = "0.7" libsystemd = "0.7"
nix = { version = "0.27", features = ["fs", "socket", "uio"] }
tracing-journald = "0.3" tracing-journald = "0.3"
[target.'cfg(target_vendor = "apple")'.dependencies] [target.'cfg(target_vendor = "apple")'.dependencies]
nix = { version = "0.27" } nix = { version = "0.27" }
rusqlite = { version = "0.38.0", features = ["bundled", "blob"] } rusqlite = { version = "0.38.0", features = ["bundled", "blob"] }
[target.'cfg(target_os = "macos")'.dependencies]
tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" }
[dev-dependencies] [dev-dependencies]
insta = { version = "1.32", features = ["yaml"] } insta = { version = "1.32", features = ["yaml"] }
tempfile = "3.13" tempfile = "3.13"

View file

@ -3,8 +3,7 @@ use std::sync::Once;
use tracing::{error, info}; use tracing::{error, info};
use tracing_subscriber::{ use tracing_subscriber::{
layer::{Layer, SubscriberExt}, layer::{Layer, SubscriberExt},
EnvFilter, EnvFilter, Registry,
Registry,
}; };
static TRACING: Once = Once::new(); static TRACING: Once = Once::new();
@ -15,10 +14,24 @@ pub fn initialize() {
error!("Failed to initialize LogTracer: {}", e); error!("Failed to initialize LogTracer: {}", e);
} }
let make_stderr = || {
tracing_subscriber::fmt::layer()
.with_level(true)
.with_writer(std::io::stderr)
.with_line_number(true)
.compact()
.with_filter(EnvFilter::from_default_env())
};
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
let subscriber = {
let system_log = Some(tracing_subscriber::fmt::layer()); let system_log = Some(tracing_subscriber::fmt::layer());
let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr);
Registry::default().with(stderr).with(system_log)
};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let subscriber = {
let system_log = match tracing_journald::layer() { let system_log = match tracing_journald::layer() {
Ok(layer) => Some(layer), Ok(layer) => Some(layer),
Err(e) => { Err(e) => {
@ -28,23 +41,22 @@ pub fn initialize() {
None None
} }
}; };
let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr);
Registry::default().with(stderr).with(system_log)
};
#[cfg(target_vendor = "apple")] #[cfg(target_os = "macos")]
let subscriber = {
let system_log = Some(tracing_oslog::OsLogger::new( let system_log = Some(tracing_oslog::OsLogger::new(
"com.hackclub.burrow", "com.hackclub.burrow",
"tracing", "tracing",
)); ));
let stderr = (console::user_attended_stderr() || system_log.is_none()).then(make_stderr);
Registry::default().with(stderr).with(system_log)
};
let stderr = (console::user_attended_stderr() || system_log.is_none()).then(|| { #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
tracing_subscriber::fmt::layer() let subscriber = Registry::default().with(Some(make_stderr()));
.with_level(true)
.with_writer(std::io::stderr)
.with_line_number(true)
.compact()
.with_filter(EnvFilter::from_default_env())
});
let subscriber = Registry::default().with(stderr).with(system_log);
#[cfg(feature = "tokio-console")] #[cfg(feature = "tokio-console")]
let subscriber = subscriber.with( let subscriber = subscriber.with(