Repository: johnno1962/InjectionNext
Branch: main
Commit: 7bae3044bc64
Files: 55
Total size: 337.7 KB
Directory structure:
gitextract_vqy0p27r/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .gitmodules
├── .swiftpm/
│ └── xcode/
│ └── package.xcworkspace/
│ └── contents.xcworkspacedata
├── App/
│ ├── InjectionBundle/
│ │ ├── Info.plist
│ │ └── InjectionBundle-Bridging-Header.h
│ ├── InjectionNext/
│ │ ├── App.icns
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ └── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ └── MainMenu.xib
│ │ ├── ControlServer.swift
│ │ ├── Defaults.swift
│ │ ├── Experimental.swift
│ │ ├── FrontendServer.swift
│ │ ├── Info.plist
│ │ ├── InjectionBusy.tif
│ │ ├── InjectionError.tif
│ │ ├── InjectionHybrid.swift
│ │ ├── InjectionIdle.tif
│ │ ├── InjectionNext-Bridging-Header.h
│ │ ├── InjectionNext.entitlements
│ │ ├── InjectionOK.tif
│ │ ├── InjectionReady.tif
│ │ ├── InjectionServer.swift
│ │ ├── MonitorXcode.swift
│ │ ├── NextCompiler.swift
│ │ ├── build_bundle.sh
│ │ ├── build_bundles.sh
│ │ ├── copy_bundle.sh
│ │ ├── main.m
│ │ └── swift-frontend.sh
│ ├── InjectionNext.xcodeproj/
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── xcshareddata/
│ │ └── xcschemes/
│ │ └── InjectionNext.xcscheme
│ ├── LICENSE
│ ├── NIMBLE.md
│ ├── QUICK.md
│ └── feedcommands/
│ └── main.mm
├── BAZEL.md
├── INTRO.md
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── InjectionNext/
│ │ └── InjectionNext.swift
│ └── InjectionNextC/
│ ├── ClientBoot.mm
│ ├── SimpleSocket.mm
│ └── include/
│ ├── InjectionClient.h
│ └── SimpleSocket.h
├── Tests/
│ └── InjectionNextTests/
│ └── InjectionNextTests.swift
└── mcp-server/
├── .gitignore
├── README.md
├── index.js
└── package.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: johnno1962
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
================================================
FILE: .gitmodules
================================================
[submodule "fishhook"]
path = fishhook
url = https://github.com/johnno1962/fishhook
[submodule "DLKit"]
path = DLKit
url = https://github.com/johnno1962/DLKit
[submodule "InjectionLite"]
path = InjectionLite
url = https://github.com/johnno1962/InjectionLite
[submodule "SwiftRegex5"]
path = SwiftRegex5
url = https://github.com/johnno1962/SwiftRegex5
[submodule "SwiftTrace"]
path = SwiftTrace
url = https://github.com/johnno1962/SwiftTrace
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: App/InjectionBundle/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
BNDL
CFBundleShortVersionString
1.0
CFBundleVersion
1
NSHumanReadableCopyright
Copyright © 2017 John Holdsworth. All rights reserved.
NSPrincipalClass
================================================
FILE: App/InjectionBundle/InjectionBundle-Bridging-Header.h
================================================
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import // Xcode 16.3??
#import "SimpleSocket.h"
#import "InjectionImplC.h"
#import "InjectionClient.h"
// InjectionBundle only
#import "fishhook.h"
#import "DLKitC.h"
#import "SwiftTrace.h"
================================================
FILE: App/InjectionNext/AppDelegate.swift
================================================
//
// AppDelegate.swift
// InjectionNext
//
// Created by John Holdsworth on 06/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/injectiond/AppDelegate.swift#76 $
//
// Implementation Toolbar menu "UI".
//
import Cocoa
import Popen
import SwiftRegex
enum InjectionState: String {
case ok = "OK" // Orange
case idle = "Idle" // Blue
case busy = "Busy" // Green
case ready = "Ready" // Purple
case error = "Error" // Yellow
}
@objc(AppDelegate)
class AppDelegate: NSObject, NSApplicationDelegate {
static var ui: AppDelegate!
// Status menu
@IBOutlet weak var statusMenu: NSMenu!
@IBOutlet var statusItem: NSStatusItem!
// Codesigning identity
@IBOutlet var identityField: NSTextField!
// Enable injection on devices
@IBOutlet var deviceTesting: NSButton!
// Testing libraries to link with
@IBOutlet var librariesField: NSTextField!
// Place to display last error that occured
@IBOutlet var lastErrorField: NSTextView!
// Restart XCode if crashed.
@IBOutlet weak var launchXcodeItem: NSMenuItem!
@IBOutlet weak var selectXcodeItem: NSMenuItem!
@IBOutlet weak var restartDeviceItem: NSMenuItem!
@IBOutlet weak var patchCompilerItem: NSMenuItem!
@IBOutlet weak var enableDevicesItem: NSMenuItem!
@IBOutlet weak var watchDirectoryItem: NSMenuItem!
// Interface to app's persistent state.
@objc let defaults = Defaults.userDefaults
@IBOutlet weak var codeSignBox: NSComboBox!
/// Code signing ID as parsed from the code signing box. If the content of the box is not
/// parsable as SHA1 code signing ID, an empty string.
var codeSigningID: String { codeSignBox.stringValue.containedSHA1 ?? "" }
let userIDComboBoxDataSaver = UserIDComboBoxDataSaver()
@objc func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
Self.ui = self
let appName = "InjectionNext"
if Bundle.main.infoDictionary?["LSUIElement"] as? Bool != true {
NSApp.mainMenu?.item(withTitle: "File")?.submenu = statusMenu
} else {
let statusBar = NSStatusBar.system
statusItem = statusBar.statusItem(withLength: statusBar.thickness)
statusItem.highlightMode = true
statusItem.menu = statusMenu
statusItem.isEnabled = true
statusItem.title = appName
setMenuIcon(.idle)
}
signal(SIGPIPE, { which in
print(APP_PREFIX+"⚠️ SIGPIPE #\(which)\n" +
Thread.callStackSymbols.map { var frame = $0
frame[#"(?:\S+\s+){3}(\S+)"#, 1] = {
(groups: [String], stop) in
return groups[1].swiftDemangle ?? groups[1] }
return frame
}.joined(separator: "\n")) })
if let quit = statusMenu.item(at: statusMenu.items.count-1) {
quit.title = "Quit "+appName
if let build = Bundle.main
.infoDictionary?[kCFBundleVersionKey as String] {
quit.toolTip = "Quit (build #\(build))"
}
}
librariesField.stringValue = Defaults.deviceLibraries
let enableDevicesSticky = false
if !enableDevicesSticky || Defaults.codesigningIdentity == nil {
enableDevicesItem.state = .on
}
deviceEnable(nil)
if let xcodePath = NSRunningApplication
.runningApplications(withBundleIdentifier: "com.apple.dt.Xcode")
.first?.bundleURL?.path {
if Defaults.xcodeDefault == nil {
Defaults.xcodeDefault = xcodePath
}
selectXcodeItem.toolTip = Defaults.xcodePath
if updatePatchUnpatch() == .unpatched &&
getenv(INJECTION_HIDE_XCODE_ALERT) == nil {
InjectionServer.alert("""
Please quit Xcode and
use this app to launch it
(unless you are using a file watcher).
""")
}
}
setupCodeSigningComboBox()
restartDeviceItem.state = Defaults.xcodeRestart ? .on : .off
selectXcodeItem.toolTip = Defaults.xcodePath
// #if DEBUG
// if NSHomeDirectory() == "/Users/johnholdsworth",
// let path = Bundle.main.path(forResource: "macOSInjection", ofType: "bundle"),
// let bundle = Bundle(path: path),
// bundle.load() {
// }
// #endif
if let project = Defaults.projectPath {
_ = MonitorXcode(args: " '\(project)'")
}
if Defaults.mcpServer {
LogBuffer.shared = LogBuffer()
ControlServer.start()
}
}
func setMenuIcon(_ state: InjectionState) {
DispatchQueue.main.async {
let tiffName = "Injection"+state.rawValue
if let path = Bundle.main.path(forResource: tiffName, ofType: "tif"),
let image = NSImage(contentsOfFile: path) {
// image.template = TRUE;
self.statusItem.image = image
self.statusItem.alternateImage = image
}
}
}
@IBAction func runXcode(_ sender: Any) {
if MonitorXcode.runningXcode == nil {
_ = MonitorXcode()
}
}
@IBAction func selectXcode(_ sender: NSMenuItem) {
let open = NSOpenPanel()
open.prompt = "Select Xcode"
open.directoryURL = URL(fileURLWithPath: Defaults.xcodePath)
open.canChooseDirectories = false
open.canChooseFiles = true
if open.runModal() == .OK, let path = open.url?.path {
selectXcodeItem.toolTip = path
Defaults.xcodeDefault = path
updatePatchUnpatch()
if Defaults.xcodeRestart {
runXcode(sender)
}
}
}
lazy var startHostLocatingServerOnce: () = {
InjectionServer.multicastServe(HOTRELOADING_MULTICAST,
port: HOTRELOADING_PORT)
}()
@IBAction func deviceEnable(_ sender: NSMenuItem?) {
var openPort = ""
if enableDevicesItem.state.toggle() == .on {
if sender != nil {
NSApplication.shared.activate(ignoringOtherApps: true)
codeSignBox.window?.makeKeyAndOrderFront(sender)
}
_ = startHostLocatingServerOnce
openPort = "*"
}
if sender != nil { InjectionServer.stopLastServer() }
InjectionServer.startServer(openPort+INJECTION_ADDRESS)
}
@IBAction func testingEnable(_ sender: NSButton) {
if sender.state == .on, let script = Bundle.main
.url(forResource: "copy_bundle", withExtension: "sh") {
let buildPhase = """
export RESOURCES="\(script.deletingLastPathComponent().path)"
if [ -f "$RESOURCES/\(script.lastPathComponent)" ]; then
"$RESOURCES/\(script.lastPathComponent)"
fi
"""
let pasteBoard = NSPasteboard.general
pasteBoard.declareTypes([.string], owner:nil)
pasteBoard.setString(buildPhase, forType:.string)
InjectionServer.error("Run Script, Build Phase to " +
"copy testing libraries added to clipboard.")
}
}
@IBAction func updateLibraries(_ sender: NSTextField) {
Defaults.deviceLibraries = librariesField.stringValue
}
@IBAction func updateXcodeRestart(_ sender: NSMenuItem) {
Defaults.xcodeRestart = sender.state.toggle() == .on
}
@IBAction func unhideSymbols(_ sender: NSMenuItem) {
Unhider.startUnhide()
}
@IBAction func resetUnhiding(_ sender: NSMenuItem) {
Unhider.unhiddens.removeAll()
}
@IBAction func showlastError(_ sender: NSMenuItem) {
lastErrorField.string = NextCompiler.lastError ?? "No error."
lastErrorField.window?.makeKeyAndOrderFront(sender)
NSApplication.shared.activate(ignoringOtherApps: true)
}
func setupCodeSigningComboBox() {
codeSignBox.removeAllItems()
codeSignBox.addItems(withObjectValues: userIDComboBoxDataSaver.validCodeSigningIDs)
if let savedID = userIDComboBoxDataSaver.savedID {
codeSignBox.stringValue = savedID
} else if let firstIdentity = userIDComboBoxDataSaver.validCodeSigningIDs.first {
codeSignBox.stringValue = firstIdentity
} else {
codeSignBox.stringValue = "No valid code signing IDs found"
}
codeSignBox.target = userIDComboBoxDataSaver
codeSignBox.action = #selector(UserIDComboBoxDataSaver.comboBoxValueDidChange(_:))
}
}
private extension String {
/// Returns the sha1 string contained in this string, or `nil` if no such string is contained.
var containedSHA1: String? { self[#"([0-9A-F]{40})"#] }
}
class UserIDComboBoxDataSaver {
/// List of valid IDs.
let validCodeSigningIDs: [String] = {
var identities: [String] = []
let security = Topen(exec: "/usr/bin/security",
arguments: ["find-identity", "-v", "-p", "codesigning"])
while let line = security.readLine() {
let components = line.split(separator: ")", maxSplits: 1)
if components.count >= 2 {
let identity = components[1]
identities.append(String(identity))
}
}
return identities
}()
/// Last savedID, if valid. `nil` otherwise.
var savedID: String? {
guard let savedValue = Defaults.codesigningIdentity else { return nil }
return validCodeSigningIDs.first(where: { $0.containedSHA1 == savedValue.containedSHA1} )
}
@objc func comboBoxValueDidChange(_ sender: NSComboBox) {
if let newValueSHA1 = sender.stringValue.containedSHA1,
validCodeSigningIDs.contains(where: { $0.containedSHA1 == newValueSHA1 }) {
Defaults.codesigningIdentity = newValueSHA1
} else {
NSLog("Selected value does not contain a valid ID")
return
}
}
}
private extension NSControl.StateValue {
@discardableResult
mutating func toggle() -> Self {
switch self {
case .on:
self = .off
case .off:
self = .on
case .mixed:
self = .mixed
default:
break
}
return self
}
}
================================================
FILE: App/InjectionNext/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: App/InjectionNext/Base.lproj/MainMenu.xib
================================================
Item 1
Item 2
Item 3
================================================
FILE: App/InjectionNext/ControlServer.swift
================================================
//
// ControlServer.swift
// InjectionNext
//
// Local TCP control server for MCP integration.
// Listens on localhost:8919 for JSON commands and
// maps them to existing AppDelegate actions.
//
import Cocoa
// MARK: - Log Buffer
class LogBuffer {
static var shared: LogBuffer?
struct Entry {
let timestamp: TimeInterval
let message: String
let level: String
}
private let lock = NSLock()
private var entries = [Entry]()
private let maxEntries = 2000
func append(_ message: String, level: String = "info") {
lock.lock()
defer { lock.unlock() }
entries.append(Entry(
timestamp: Date().timeIntervalSince1970,
message: message,
level: level
))
if entries.count > maxEntries {
entries.removeFirst(entries.count - maxEntries)
}
}
func get(since: TimeInterval = 0, limit: Int = 200) -> [[String: Any]] {
lock.lock()
defer { lock.unlock() }
let filtered = entries.filter { $0.timestamp > since }
let sliced = filtered.suffix(limit)
return sliced.map {
["timestamp": $0.timestamp, "message": $0.message, "level": $0.level]
}
}
func clear() {
lock.lock()
defer { lock.unlock() }
entries.removeAll()
}
var count: Int {
lock.lock()
defer { lock.unlock() }
return entries.count
}
}
// MARK: - Control Server
class ControlServer {
static let port: UInt16 = 8919
static var shared: ControlServer?
private var serverSocket: Int32 = -1
private let queue = DispatchQueue(label: "ControlServer", attributes: .concurrent)
static func start() {
guard shared == nil else { return }
shared = ControlServer()
shared?.listen()
}
private func listen() {
queue.async { [weak self] in
guard let self = self else { return }
self.serverSocket = socket(AF_INET, SOCK_STREAM, 0)
guard self.serverSocket >= 0 else {
NSLog("\(APP_PREFIX)ControlServer: socket() failed")
return
}
var reuse: Int32 = 1
setsockopt(self.serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, socklen_t(MemoryLayout.size))
var addr = sockaddr_in()
addr.sin_len = UInt8(MemoryLayout.size)
addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = Self.port.bigEndian
addr.sin_addr.s_addr = inet_addr("127.0.0.1")
let bindResult = withUnsafePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
bind(self.serverSocket, $0, socklen_t(MemoryLayout.size))
}
}
guard bindResult == 0 else {
NSLog("\(APP_PREFIX)ControlServer: bind() failed on port \(Self.port): \(String(cString: strerror(errno)))")
close(self.serverSocket)
return
}
guard Darwin.listen(self.serverSocket, 5) == 0 else {
NSLog("\(APP_PREFIX)ControlServer: listen() failed")
close(self.serverSocket)
return
}
NSLog("\(APP_PREFIX)ControlServer: listening on localhost:\(Self.port)")
while true {
var clientAddr = sockaddr_in()
var clientLen = socklen_t(MemoryLayout.size)
let clientSocket = withUnsafeMutablePointer(to: &clientAddr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
accept(self.serverSocket, $0, &clientLen)
}
}
guard clientSocket >= 0 else { continue }
self.queue.async {
self.handleClient(clientSocket)
}
}
}
}
private func handleClient(_ sock: Int32) {
defer { close(sock) }
let maxRequestSize = 64 * 1024
var data = Data()
var buf = [UInt8](repeating: 0, count: 4096)
while true {
let n = recv(sock, &buf, buf.count, 0)
guard n > 0 else { break }
data.append(contentsOf: buf[0.. maxRequestSize {
sendResponse(sock, success: false, error: "Request too large")
return
}
}
guard !data.isEmpty,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let action = json["action"] as? String else {
sendResponse(sock, success: false, error: "Invalid JSON or missing 'action'")
return
}
let result = executeAction(action, params: json)
sendResponse(sock, success: result.success, data: result.data, error: result.error)
}
private func sendResponse(_ sock: Int32, success: Bool, data: [String: Any]? = nil, error: String? = nil) {
var response: [String: Any] = ["success": success]
if let error = error { response["error"] = error }
if let data = data { response["data"] = data }
guard let jsonData = try? JSONSerialization.data(withJSONObject: response),
let jsonStr = String(data: jsonData, encoding: .utf8) else { return }
let line = jsonStr + "\n"
_ = line.withCString { ptr in
send(sock, ptr, strlen(ptr), 0)
}
}
struct ActionResult {
let success: Bool
let data: [String: Any]?
let error: String?
static func ok(_ data: [String: Any]? = nil) -> ActionResult {
ActionResult(success: true, data: data, error: nil)
}
static func fail(_ error: String) -> ActionResult {
ActionResult(success: false, data: nil, error: error)
}
}
private func executeAction(_ action: String, params: [String: Any]) -> ActionResult {
switch action {
case "status":
return getStatus()
case "watch_project":
guard let path = params["path"] as? String else {
return .fail("Missing 'path' parameter")
}
return watchProject(path: path)
case "stop_watching":
return stopWatching()
case "launch_xcode":
return launchXcode()
case "intercept_compiler":
return interceptCompiler()
case "enable_devices":
let enable = params["enable"] as? Bool ?? true
return enableDevices(enable: enable)
case "unhide_symbols":
return unhideSymbols()
case "get_last_error":
return getLastError()
case "prepare_swiftui_source":
return prepareSwiftUISource()
case "prepare_swiftui_project":
return prepareSwiftUIProject()
case "set_xcode_path":
guard let path = params["path"] as? String else {
return .fail("Missing 'path' parameter")
}
return setXcodePath(path: path)
case "get_logs":
let since = params["since"] as? TimeInterval ?? 0
let limit = max(0, min(params["limit"] as? Int ?? 200, 500))
return getLogs(since: since, limit: limit)
case "clear_logs":
return clearLogs()
default:
return .fail("Unknown action: \(action)")
}
}
// MARK: - Actions
private func getStatus() -> ActionResult {
var result = [String: Any]()
DispatchQueue.main.sync {
let delegate = AppDelegate.ui!
result["xcode_running"] = MonitorXcode.runningXcode != nil
result["xcode_path"] = Defaults.xcodePath
result["compiler_intercepted"] = delegate.updatePatchUnpatch() == .patched
result["devices_enabled"] = delegate.enableDevicesItem.state == .on
result["watching_directories"] = Array(AppDelegate.watchers.keys)
result["has_connected_client"] = InjectionServer.currentClient != nil
result["auto_restart_xcode"] = Defaults.xcodeRestart
result["last_error"] = NextCompiler.lastError
}
return .ok(result)
}
private func watchProject(path: String) -> ActionResult {
guard FileManager.default.fileExists(atPath: path) else {
return .fail("Path does not exist: \(path)")
}
DispatchQueue.main.sync {
Reloader.xcodeDev = Defaults.xcodePath + "/Contents/Developer"
AppDelegate.ui.watch(path: path)
}
return .ok(["watching": path])
}
private func stopWatching() -> ActionResult {
DispatchQueue.main.sync {
AppDelegate.watchers.removeAll()
AppDelegate.lastWatched = nil
AppDelegate.ui.watchDirectoryItem.state = .off
}
return .ok()
}
private func launchXcode() -> ActionResult {
DispatchQueue.main.sync {
if MonitorXcode.runningXcode == nil {
_ = MonitorXcode()
}
}
return .ok(["xcode_path": Defaults.xcodePath])
}
private func interceptCompiler() -> ActionResult {
var state = ""
DispatchQueue.main.sync {
let delegate = AppDelegate.ui!
let currentState = delegate.updatePatchUnpatch()
state = currentState == .patched ? "patched" : "unpatched"
}
return .ok(["compiler_state": state,
"note": "Use Xcode UI to toggle interception (requires user confirmation alert)"])
}
private func enableDevices(enable: Bool) -> ActionResult {
DispatchQueue.main.sync {
let delegate = AppDelegate.ui!
let currentlyEnabled = delegate.enableDevicesItem.state == .on
if enable != currentlyEnabled {
delegate.deviceEnable(delegate.enableDevicesItem)
}
}
return .ok(["devices_enabled": enable])
}
private func unhideSymbols() -> ActionResult {
Unhider.startUnhide()
return .ok()
}
private func getLastError() -> ActionResult {
let error = NextCompiler.lastError ?? "No error."
return .ok(["error": error])
}
private func prepareSwiftUISource() -> ActionResult {
guard let lastSource = NextCompiler.lastSource else {
return .fail("No source file currently being edited")
}
DispatchQueue.main.sync {
AppDelegate.ui.prepareSwiftUI(source: lastSource)
}
return .ok(["source": lastSource])
}
private func prepareSwiftUIProject() -> ActionResult {
DispatchQueue.main.sync {
AppDelegate.ui.prepareProject(AppDelegate.ui.patchCompilerItem)
}
return .ok()
}
private func setXcodePath(path: String) -> ActionResult {
guard FileManager.default.fileExists(atPath: path) else {
return .fail("Xcode not found at: \(path)")
}
DispatchQueue.main.sync {
Defaults.xcodeDefault = path
AppDelegate.ui.selectXcodeItem.toolTip = path
AppDelegate.ui.updatePatchUnpatch()
}
return .ok(["xcode_path": path])
}
private func getLogs(since: TimeInterval, limit: Int) -> ActionResult {
let logs = LogBuffer.shared?.get(since: since, limit: limit)
return .ok(["logs": logs ?? [], "count": LogBuffer.shared?.count ?? 0])
}
private func clearLogs() -> ActionResult {
LogBuffer.shared?.clear()
return .ok()
}
}
================================================
FILE: App/InjectionNext/Defaults.swift
================================================
//
// Defaults.swift
// InjectionNext
//
// Created by John Holdsworth on 24/07/2024.
// Copyright © 2024 John Holdsworth. All rights reserved.
//
import Foundation
struct Defaults {
/// App deauflts for persistent state
static let userDefaults = UserDefaults.standard
static let xcodePathDefault = "XcodePath"
static let librariesDefault = "libraries"
static let codesigningDefault = "codesigningIdentity"
private static let xcodeRestartDefault = "xcodeRestartDefault"
static var xcodePath: String {
xcodeDefault ?? "/Applications/Xcode.app" }
static var xcodeDefault: String? {
get {
userDefaults.string(forKey: xcodePathDefault)
}
set {
userDefaults.setValue(newValue,
forKey: xcodePathDefault)
}
}
static var deviceLibraries: String {
get {
userDefaults.string(forKey: librariesDefault) ??
"-framework XCTest -lXCTestSwiftSupport"
}
set {
userDefaults.setValue(newValue,
forKey: librariesDefault)
}
}
static var codesigningIdentity: String? {
get {
userDefaults.string(forKey: codesigningDefault)
}
set {
userDefaults.setValue(newValue,
forKey: codesigningDefault)
}
}
static var xcodeRestart: Bool {
get {
if userDefaults.value(forKey: xcodeRestartDefault) == nil { return true }
return userDefaults.bool(forKey: xcodeRestartDefault)
}
set {
userDefaults.setValue(newValue,
forKey: xcodeRestartDefault)
}
}
static let projectPathDefault = "projectPath"
static var projectPath: String? {
get {
userDefaults.string(forKey: projectPathDefault)
}
}
static var mcpServer = userDefaults.bool(forKey: "mcpServer")
}
================================================
FILE: App/InjectionNext/Experimental.swift
================================================
//
// Experimental.swift
// InjectionIII
//
// Created by User on 20/10/2020.
// Copyright © 2020 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/injectiond/Experimental.swift#35 $
//
// Some regular expressions to automatically prepare SwiftUI sources.
//
import Cocoa
import SwiftRegex
extension AppDelegate {
/// Prepare the SwiftUI source file currently being edited for injection.
@IBAction func prepareSource(_ sender: NSMenuItem) {
if let lastSource = NextCompiler.lastSource {
prepareSwiftUI(source: lastSource)
}
}
/// Prepare all sources in the current target for injection.
@IBAction func prepareProject(_ sender: NSMenuItem) {
var changes = 0, edited = 0
for source in ((MonitorXcode.runningXcode != nil ?
MonitorXcode.recompiler.lastCompilation : nil) ??
FrontendServer.frontendRecompiler().lastCompilation)?
.swiftFiles.components(separatedBy: "\n").dropLast() ??
Array(Recompiler.workspaceCache.keys) {
InjectionHybrid.lastInjected[source] = Date.timeIntervalSinceReferenceDate
prepareSwiftUI(source: source, changes: &changes)
edited += 1
}
let s = changes == 1 ? "" : "s"
InjectionServer.error("\(changes) automatic edit\(s) made to \(edited) files")
}
}
================================================
FILE: App/InjectionNext/FrontendServer.swift
================================================
//
// FrontendServer.swift
// InjectionNext
//
// Created by John Holdsworth on 23/02/2025.
// Copyright © 2025 John Holdsworth. All rights reserved.
//
// Code related to "Intercepting" version where the binary
// swift-frontend is replaced by a script which feeds all
// compilation commands to the app where they can be reused
// when a file is injected to recompile individual Swift files.
//
import Cocoa
import Popen
import Fortify
#if INJECTION_III_APP
struct Unhider { static var packageFrameworks: String? }
#endif
extension NextCompiler {
func writeCache() {
FrontendServer.writeCache(for: self.name, recompiler: self)
}
}
class FrontendServer: SimpleSocket {
enum State: String {
case unpatched = "Intercept Compiler"
case patched = "Unpatch Compiler"
}
/// Paths to unpatched/patched swift-frontend binary/script in toolchain.
static let frontendQueue = DispatchQueue(label: "InjectionCapture")
static var binURL: URL { URL(fileURLWithPath: Defaults.xcodePath +
"/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin") }
static var unpatchedURL: URL { binURL.appendingPathComponent("swift-frontend") }
static var patched: String { unpatchedURL.path + ".save" }
static var patchedURL: URL { URL(fileURLWithPath: patched) }
/// Path to swift-frontend last logged.
static var loggedFrontend: String?
/// Start server for command logging.
static var startOnce: Void = {
FrontendServer.startServer(COMMANDS_PORT)
}()
static var clientPlatform: String {
InjectionServer.currentClient?.platform ?? "iPhoneSimulator" }
static func cacheURL(platform: String) -> URL {
return URL(fileURLWithPath: Reloader.tmpbase+"_\(platform)_builds.json")
}
static private var recompilersLock = os_unfair_lock()
static private var recompilers = [String: NextCompiler]()
static func frontendRecompiler(for platform: String = clientPlatform) -> NextCompiler {
os_unfair_lock_lock(&recompilersLock)
defer { os_unfair_lock_unlock(&recompilersLock) }
if let recompiler = recompilers[platform] {
return recompiler
}
let recompiler = NextCompiler(name: platform)
do {
let compressed = cacheURL(platform: platform).path+".gz"
if Fstat(path: compressed)?.st_size ?? 0 != 0,
let stream = Popen(cmd: "gunzip <"+compressed),
let cached = stream.readAll().data(using: .utf8) {
let stored = try JSONDecoder().decode(
[String: NextCompiler.Compilation].self, from: cached)
for source in stored.keys.sorted() {
guard let compile = stored[source] else { continue }
recompiler.store(compilation: compile, for: source)
}
recompiler.modified = false
print("Loaded \(recompiler.compilations.count) \(platform) commands.")
}
} catch {
InjectionServer.error("Unable to read commands cache: \(error).")
}
recompilers[platform] = recompiler
return recompiler
}
static func writeCache(for platform: String, recompiler: NextCompiler? = nil) {
let recompiler = recompiler ?? frontendRecompiler(for: platform)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let cache = cacheURL(platform: platform)
let commands = recompiler.compilations
try encoder.encode(commands).write(to: cache, options: .atomic)
if let error = Popen.system("gzip -f "+cache.path, errors: true) {
InjectionServer.error("Unable to zip commands cache: \(error)")
} else {
print("Cached \(commands.count) \(platform) commands")
}
recompiler.modified = false
} catch {
InjectionServer.error("Unable to write commands cache: \(error)")
}
}
func validateConnection() -> Bool {
return readInt() == COMMANDS_VERSION && readString() == NSHomeDirectory()
}
override func run() {
Self.frontendQueue.async {
do {
try Fortify.protect { () -> () in
guard self.validateConnection(),
let vers = self.readString(), vers == "1.0" || vers == "2.0" else {
return Self.frontendRecompiler()
.error("Unpatch then repatch compiler to update script version")
}
try Self.processFrontendCommandFrom(feed: self)
}
} catch {
Self.error("Feed error: \(error)")
}
}
}
class func processFrontendCommandFrom(feed: SimpleSocket) throws {
guard var projectRoot = feed.readString(),
let frontendPath = feed.readString(),
frontendPath.hasSuffix(".save"),
feed.readString() == "-frontend" &&
feed.readString() == "-c" else { return }
// swift-frontend.sh 2.0+ capture environment
var env: String?
if let pwd: String = projectRoot["PWD=(.*)\n"] ??
projectRoot["HOME=(.*)\n"] {
env = projectRoot
projectRoot = pwd
}
var parser = CompilationArgParser()
while let arg = feed.readString() {
if arg.hasPrefix("llvmcas://") { return }
parser.process(arg: arg, next: feed.readString)
}
let update = NextCompiler.Compilation(arguments: parser.args,
swiftFiles: parser.swiftFiles, workingDir: projectRoot, env: env)
DispatchQueue.main.async {
if !projectRoot.hasSuffix(".xcodeproj") && projectRoot != "/" &&
// MonitorXcode.runningXcode == nil &&
AppDelegate.alreadyWatching(projectRoot) == nil {
let open = NSOpenPanel()
// open.titleVisibility = .visible
// open.title = "InjectionNext: add directory"
open.prompt = "InjectionNext - Watch Directory?"
open.directoryURL = URL(fileURLWithPath: projectRoot)
open.canChooseDirectories = true
open.canChooseFiles = false
if open.runModal() == .OK, let url = open.url {
AppDelegate.ui.watch(path: url.path)
}
}
}
NextCompiler.compileQueue.async {
let recompiler = Self.frontendRecompiler(for: parser.platform)
loggedFrontend = frontendPath
for source in parser.primaries {
#if !INJECTION_III_APP
// Don't update compilations while connected
if InjectionServer.currentClient != nil &&
recompiler.canCompile(source: source, for: parser.platform) {
continue
}
#endif
print("Updating \(parser.args.count) args for \(parser.platform)/" +
URL(fileURLWithPath: source).lastPathComponent)
recompiler.store(compilation: update, for: source)
}
}
}
/// Argument parser shared by FrontendServer and MonitorXcode to accumulate
/// compiler arguments into a NextCompiler.Compilation.
struct CompilationArgParser {
var swiftFiles = ""
var args = [String]()
var primaries = [String]()
var platform = "iPhoneSimulator"
var workingDir = "/tmp"
var swiftFileCount = 0
lazy var productDirSuffix = "-"+clientPlatform.lowercased()
/// Process one argument, calling `next()` to consume the following
/// token whenever the argument takes a value.
mutating func process(arg: String, next: () -> String?) {
switch arg {
case "-filelist":
if let filelist = next(), let files =
try? String(contentsOfFile: filelist, encoding: .utf8) {
swiftFiles += files
}
case "-primary-file":
if let source = next(), !source.isEmpty {
primaries.append(source)
if strstr(swiftFiles, source) == nil {
swiftFiles += source+"\n"
}
}
case "-o":
if let object = next(), !object.isEmpty &&
Unhider.packageFrameworks == nil {
var url = URL(fileURLWithPath: object)
for _ in 1...4 { url.deleteLastPathComponent() }
Unhider.packageFrameworks = url.path
}
default:
if let sdkPlatform: String = arg[#"/([A-Za-z]+)[\d\.]+\.sdk$"#] {
platform = sdkPlatform
} else if args.last == "-F" {
if arg.hasSuffix("/PackageFrameworks") {
Unhider.packageFrameworks = arg
} else if Unhider.packageFrameworks == nil,
arg.hasSuffix(productDirSuffix) {
Unhider.packageFrameworks = arg+"/PackageFrameworks"
}
}
let pathArgFlags: Set = ["-F", "-I", "-iquote", "-isystem"]
let isPathArgValue = pathArgFlags.contains(args.last ?? "")
|| arg.hasPrefix("-I") || arg.hasPrefix("-F")
if arg.hasSuffix(".swift") && !isPathArgValue {
swiftFiles += arg+"\n"
swiftFileCount += 1
} else if arg[Reloader.optionsToRemove] {
_ = next()
} else if !arg[
"-validate-clang-modules-once|-frontend-parseable-output"] {
args.append(arg)
}
}
}
}
}
extension AppDelegate {
@IBAction func patchCompiler(_ sender: NSMenuItem) {
let fm = FileManager.default
do {
let linksToMove = ["swift", "swiftc", "swift-symbolgraph-extract",
"swift-api-digester", "swift-cache-tool"]
if updatePatchUnpatch() == .unpatched {
if !fm.fileExists(atPath: FrontendServer.patched),
let feeder = Bundle.main
.url(forResource: "swift-frontend", withExtension: "sh") {
let alert: NSAlert = NSAlert()
alert.alertStyle = .warning
alert.messageText = APP_NAME
alert.informativeText = """
The Swift compiler of your current toolchain \
\(FrontendServer.unpatchedURL.path) will be \
replaced by a script that calls the compiler \
and captures all compilation commands. Use menu \
item "Unpatch Compiler" to revert this change.
"""
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
if alert.runModal() != .alertFirstButtonReturn {
return
}
try fm.moveItem(at: FrontendServer.unpatchedURL,
to: FrontendServer.patchedURL)
try fm.copyItem(at: feeder,
to: FrontendServer.unpatchedURL)
for binary in linksToMove {
let link = FrontendServer.binURL
.appendingPathComponent(binary)
try fm.removeItem(at: link)
symlink("swift-frontend.save", link.path)
}
}
} else if fm.fileExists(atPath: FrontendServer.patched) {
try? fm.removeItem(at: FrontendServer.unpatchedURL)
try fm.moveItem(at: FrontendServer.patchedURL,
to: FrontendServer.unpatchedURL)
for binary in linksToMove {
let link = FrontendServer.binURL
.appendingPathComponent(binary)
try fm.removeItem(at: link)
symlink("swift-frontend", link.path)
}
FrontendServer.loggedFrontend = nil
}
} catch {
let chmod = FrontendServer.unpatchedURL.deletingLastPathComponent()
InjectionServer.error("Patching error: \(error). " +
"Is the directory \(chmod.path) writable?")
}
updatePatchUnpatch()
}
@discardableResult
func updatePatchUnpatch() -> FrontendServer.State {
let state = FileManager.default
.fileExists(atPath: FrontendServer.patched) ?
FrontendServer.State.patched : .unpatched
DispatchQueue.main.async {
self.patchCompilerItem?.title = state.rawValue
if state == .patched {
_ = FrontendServer.startOnce
}
}
return state
}
/// Shared regular expresssions to patch .enableInjection() and @ObserveInject into a source
func prepareSwiftUI(source: String, changes: UnsafeMutablePointer? = nil) {
let fileURL = URL(fileURLWithPath: source)
guard let original = try? String(contentsOf: fileURL) else {
return
}
var patched = original, before = changes?.pointee
patched[#"""
^((\s+)(public )?(var body:|func body\([^)]*\) -\>) some View \{\n\#
(\2(?! (if|switch|ForEach) )\s+(?!\.enableInjection)\S.*\n|(\s*|#.+)\n)+)(?()
init() {
cancellable = NotificationCenter.default.publisher(for:
Notification.Name("INJECTION_BUNDLE_NOTIFICATION"))
.sink { [weak self] change in
self?.injectionNumber += 1
self?.publisher.send()
}
}
}
extension SwiftUI.View {
public func eraseToAnyView() -> some SwiftUI.View {
return AnyView(self)
}
public func enableInjection() -> some SwiftUI.View {
return eraseToAnyView()
}
public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View {
return self
.onReceive(InjectionObserver.shared.publisher, perform: bumpState)
.eraseToAnyView()
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct ObserveInjection: DynamicProperty {
@ObservedObject private var iO = InjectionObserver.shared
public init() {}
public private(set) var wrappedValue: Int {
get {0} set {}
}
}
#else
extension SwiftUI.View {
@inline(__always)
public func eraseToAnyView() -> some SwiftUI.View { return self }
@inline(__always)
public func enableInjection() -> some SwiftUI.View { return self }
@inline(__always)
public func onInjection(bumpState: @escaping () -> ()) -> some SwiftUI.View {
return self
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct ObserveInjection {
public init() {}
public private(set) var wrappedValue: Int {
get {0} set {}
}
}
#endif
#endif
"""
}
if patched != original {
do {
try patched.write(to: fileURL,
atomically: true, encoding: .utf8)
} catch {
InjectionServer.error("Could not save \(source): \(error)")
}
}
}
}
================================================
FILE: App/InjectionNext/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
App.icns
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
APPL
CFBundleShortVersionString
$(MARKETING_VERSION)
CFBundleVersion
14094
LSApplicationCategoryType
public.app-category.developer-tools
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
LSUIElement
NSHumanReadableCopyright
Copyright © 2017-20 John Holdsworth. All rights reserved.
NSMainNibFile
MainMenu
NSPrincipalClass
NSApplication
================================================
FILE: App/InjectionNext/InjectionHybrid.swift
================================================
//
// InjectionHybrid.swift
// InjectionNext
//
// Created by John Holdsworth on 09/11/2024.
// Copyright © 2024 John Holdsworth. All rights reserved.
//
// Provide file watcher/log parser fallback
// for use outside Xcode (e.g. Cursor/VSCode)
// Also uses FileWatcher for operation when
// swift-frontend has been replaced by a
// script to capture compiler invocations.
//
import Cocoa
extension AppDelegate {
static var watchers = [String: InjectionHybrid]()
static var lastWatched: String?
@IBAction func watchProject(_ sender: NSMenuItem) {
let open = NSOpenPanel()
open.prompt = "Select Project Directory"
open.canChooseDirectories = true
open.canChooseFiles = false
// open.showsHiddenFiles = TRUE;
if open.runModal() == .OK, let url = open.url {
Reloader.xcodeDev = Defaults.xcodePath+"/Contents/Developer"
watch(path: url.path)
} else {
Self.watchers.removeAll()
Self.lastWatched = nil
}
}
func watch(path: String) {
guard Self.alreadyWatching(path) == nil else { return }
GitIgnoreParser.monitor(directory: path)
Self.watchers[path] = InjectionHybrid(watching: path)
Self.lastWatched = path
watchDirectoryItem.state = Self.watchers.isEmpty ? .off : .on
}
static func alreadyWatching(_ projectRoot: String) -> String? {
return Self.watchers[projectRoot] != nil ? projectRoot :
watchers.keys.first { projectRoot.hasPrefix($0+"/") }
}
static func restartLastWatcher() {
DispatchQueue.main.async {
lastWatched.flatMap { watchers[$0]?.watcher?.restart() }
}
}
}
class InjectionHybrid: InjectionBase {
/// Last Injected for deduplication
static var lastInjected = [String: TimeInterval]()
/// Last queue of file changes
static var pendingFilesChanged = [String]()
/// Repository locked state - stops processing until app reconnects
static var isRepositoryLocked = false
/// Path to detected git lock file - used to check if git operation still active
static var gitLockPath: String?
/// InjectionNext compiler that uses InjectionLite log parser
var logParsingCompiler: NextCompiler = HybridCompiler(name: "BuildLogs")
/// Minimum seconds between injections
let minInterval = 1.0
init(watching path: String) { // FileWatcher compatibility
let watchPaths = (getenv(INJECTION_DIRECTORIES) == nil ?
NSHomeDirectory()+"/Library/Developer," : "") + path
setenv(INJECTION_DIRECTORIES, watchPaths, 1)
Reloader.injectionQueue = .main
super.init()
// Extend FileWatcher pattern to detect git lock files
FileWatcher.INJECTABLE_PATTERN = try! NSRegularExpression(
pattern: #"[^~]\.(mm?|cpp|cc|swift|lock|o)$"#)
}
/// Called from file watcher when file is edited.
override func inject(source: String) {
guard MonitorXcode.runningXcode == nil else { return }
// Detect git lock files - record path for later checking
if source.hasSuffix(".lock") &&
source.contains("/.git/") {
Self.gitLockPath = source
return
}
// Skip processing if repository is already locked
if Self.isRepositoryLocked {
log("""
File processing stopped due to git lock. \
Please relaunch your app to resume injection.
""")
return
}
// Check if source file is changing while git lock still exists
if let lockPath = Self.gitLockPath {
if FileManager.default.fileExists(atPath: lockPath) {
// Source files changing while git lock exists = branch switch/merge/rebase
Self.isRepositoryLocked = true
Self.pendingFilesChanged.removeAll()
Self.gitLockPath = nil
log("""
Git operation in progress (branch switch/merge/rebase detected). \
File processing stopped. Please relaunch your app to resume injection.
""")
return
} else {
// Lock file is gone - was probably just a commit
Self.gitLockPath = nil
}
}
let now = Date.timeIntervalSinceReferenceDate
guard !AppDelegate.watchers.isEmpty, now - (
Self.lastInjected[source] ?? 0.0) > minInterval else {
return
}
Self.lastInjected[source] = now
Self.pendingFilesChanged.append(source)
NextCompiler.compileQueue.async {
self.injectNext()
}
}
func injectNext() {
guard let source = (DispatchQueue.main.sync { () -> String? in
guard let source = Self.pendingFilesChanged.first else { return nil }
Self.pendingFilesChanged.removeAll(where: { $0 == source })
if !Self.pendingFilesChanged.isEmpty {
NextCompiler.compileQueue.async { self.injectNext() }
}
return source
}) else { return }
autoreleasepool {
var recompiler = MonitorXcode.recompiler
let platform = FrontendServer.clientPlatform
if recompiler.canCompile(source: source, for: platform),
recompiler.inject(source: source) { return }
recompiler = logParsingCompiler
if source.hasSuffix(".swift") &&
AppDelegate.ui.updatePatchUnpatch() == .patched {
let proxyCompiler = FrontendServer.frontendRecompiler(for: platform)
if proxyCompiler.canCompile(source: source) {
recompiler = proxyCompiler
}
}
if let why = GitIgnoreParser.shouldExclude(file: source) {
log("Excluded \(source) as \(why)")
} else if !recompiler.inject(source: source) {
recompiler.pendingSource = source
}
}
}
}
class HybridCompiler: NextCompiler {
/// Legacy log parsing version of recomilation
static var liteRecompiler = Recompiler()
override func recompile(source: String, platform: String) -> String? {
let oldCache = Reloader.cacheFile
Reloader.sdk = platform // Select commands cache file.
if oldCache != Reloader.cacheFile { Self.liteRecompiler = Recompiler() }
return Self.liteRecompiler.recompile(source: source, platformFilter:
"SDKs/"+platform, dylink: false)
}
override func link(object: String, dylib: String, arch: String) -> (String, Double)? {
return super.link(object: object, dylib: dylib, arch: arch) ??
Self.liteRecompiler.linkingFailed()
}
}
================================================
FILE: App/InjectionNext/InjectionNext-Bridging-Header.h
================================================
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "SimpleSocket.h"
#import "InjectionImplC.h"
#import "InjectionClient.h"
#import "/tmp/InjectionNextSalt.h"
================================================
FILE: App/InjectionNext/InjectionNext.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
com.apple.security.network.server
com.apple.security.print
com.apple.security.files.bookmarks.app-scope
================================================
FILE: App/InjectionNext/InjectionServer.swift
================================================
//
// InjectionServer.swift
// InjectionNext
//
// Created by John H on 30/05/2024.
// Copyright © 2024 John Holdsworth. All rights reserved.
//
// Subclass of SimpleSocket to receive connection from
// user apps using the InjectionNext Swift Package. An
// incoming connection will enter runInBackground() on
// a background thread. Validiates the connection and
// forwards "commands" to the client app to load dynamic
// libraries and inject them etc. Also receives feeds
// of compilation commands from swift-frontend.sh.
//
import Cocoa
import Fortify
import Popen
class InjectionServer: SimpleSocket {
/// So commands from differnt threads don't get mixed up
static let clientQueue = DispatchQueue(label: "InjectionCommand")
static private var connected = [InjectionServer]()
/// All access to `connected` serialised through clientQueue.
static var currentClients: [InjectionServer?] {
return clientQueue.sync { connected.isEmpty ? [nil] : connected }
}
/// Current connection to client app. There can be only one.
static var currentClient: InjectionServer? { currentClients.last ?? nil }
static var lastAlert: NSAlert?
/// Sorted last symbols exported by source.
var exports = [String: [String]]()
/// Keeps dynamic library file names unique.
var injectionNumber = 0
/// Some defaults
var platform = "iPhoneSimulator"
var arch = "arm64"
var tmpPath = "/unset"
class func alert(_ msg: String) {
NSLog("\(APP_PREFIX)\(APP_NAME) \(msg)")
LogBuffer.shared?.append("\(APP_NAME) \(msg)", level: "alert")
lastAlert = NSAlert()
lastAlert?.messageText = "\(self)"
lastAlert?.informativeText = msg
lastAlert?.alertStyle = .warning
lastAlert?.addButton(withTitle: "OK")
_ = lastAlert?.runModal()
}
/// Pops up an alert panel for networking
@discardableResult
override public class func error(_ message: String) -> Int32 {
let msg = String(format:message, strerror(errno))
LogBuffer.shared?.append(msg, level: "error")
DispatchQueue.main.async { alert(msg) }
return -1
}
// Send command to client app
func sendCommand(_ command: InjectionCommand, with string: String?) {
Self.clientQueue.async {
_ = self.writeCommand(command.rawValue, with: string)
}
}
// Write message into Xcode console of client app.
open func log(_ msg: String) {
NSLog("\(APP_PREFIX)\(APP_NAME) \(msg)")
LogBuffer.shared?.append(msg, level: "info")
sendCommand(.log, with: APP_PREFIX+msg)
}
open func error(_ msg: String) {
log("⚠️ "+msg)
}
lazy var copyPlugIns: () = {
let pattern = "/tmp/InjectionNext.PlugIns/*.xctest"
if platform == "iPhoneOS" && isLocalClient {
if let errors = Popen.system("""
rm -rf "\(tmpPath)"/*.xctest; \
rsync -a \(pattern) "\(tmpPath)"
""", errors: true) {
error("Copy *.xctest failed: "+errors)
}
return
}
guard let plugins = Glob(pattern: pattern) else { return }
for plugin in plugins {
writeCommand(InjectionCommand.log.rawValue, with: APP_PREFIX+"Sending "+plugin)
let url = URL(fileURLWithPath: plugin)
let dest = tmpPath+"/"+url.lastPathComponent
writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/")
writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/_CodeSignature/")
for file in [url.deletingPathExtension().lastPathComponent,
"Info.plist", "/_CodeSignature/CodeResources"] {
writeCommand(InjectionCommand.sendFile.rawValue, with: dest+"/"+file)
sendFile(url.appendingPathComponent(file).path)
}
}
}()
// Simple validation to weed out invalid connections
func validateConnection() -> Bool {
guard readInt() == INJECTION_VERSION,
let injectionKey = readString() else { return false }
guard injectionKey.hasPrefix(NSHomeDirectory()) else {
error("Invalid INJECTION_KEY: "+injectionKey)
return false
}
return true
}
// On a new connection starts executing here
override func runInBackground() {
do {
try Fortify.protect {
guard validateConnection() else {
sendCommand(.invalid, with: nil)
error("Connection did not validate.")
return
}
DispatchQueue.main.async {
InjectionHybrid.pendingFilesChanged.removeAll()
// Reset repository locked state on app reconnect (relaunch)
if InjectionHybrid.isRepositoryLocked {
InjectionHybrid.isRepositoryLocked = false
InjectionHybrid.gitLockPath = nil
self.log("Repository lock cleared - injection resumed")
}
}
AppDelegate.ui.setMenuIcon(.ok)
processResponses()
AppDelegate.ui.setMenuIcon(MonitorXcode
.runningXcode != nil ? .ready : .idle)
}
} catch {
self.error("\(self) error \(error)")
}
Self.clientQueue.sync {
Self.connected.removeAll { $0 === self }
} // flush messages and de-register
}
func processResponses() {
sendCommand(.xcodePath, with: Defaults.xcodePath)
AppDelegate.restartLastWatcher()
while true {
let responseInt = readInt()
guard let response = InjectionResponse(rawValue: responseInt) else {
error("Invalid responseInt: \(responseInt)")
break
}
switch response {
case .platform:
if let platform = readString(), let arch = readString() {
log("Platform connected: "+platform)
self.platform = platform
Reloader.arch = arch
self.arch = arch
} else {
error("**** Bad platform ****")
return
}
case .tmpPath:
if let tmpPath = readString() {
print("Tmp path: "+tmpPath)
if tmpPath.contains("/Xcode/UserData/Previews/") {
return
}
self.tmpPath = tmpPath
self.tmpPath[#"/$"#] = "" // strip trailing slash
Self.clientQueue.async {
Self.connected.append(self)
}
} else {
error("**** Bad tmp ****")
}
if MonitorXcode.runningXcode == nil &&
AppDelegate.watchers.isEmpty &&
AppDelegate.ui.updatePatchUnpatch() == .unpatched {
error("""
Xcode not launched via app. Injection will not be possible \
unless you file-watch a project and Xcode logs are available. \
You can add an env var INJECTION_PROJECT_ROOT to your scheme \
with value $(SRCROOT) to auto file-watch this project.
""")
}
if !AppDelegate.watchers.isEmpty {
log("Watching directory: " +
AppDelegate.watchers.keys.joined(separator: ", "))
}
case .projectRoot:
if let projectRoot = readString() {
log("Auto-watching project: \(projectRoot)")
DispatchQueue.main.sync {
AppDelegate.ui.watch(path: projectRoot)
Self.lastAlert?.buttons.last?.performClick(self)
Self.lastAlert = nil
}
} else {
error("**** Bad root ****")
}
case .executable:
if let executable = readString() {
Reloader.appName = URL(fileURLWithPath:
executable).lastPathComponent
}
case .detail:
if let detail = readString() {
setenv(INJECTION_DETAIL, detail, 1)
}
case .bazelTarget:
if let target = readString() {
log("Received Bazel target: \(target)")
BazelActionQueryHandler.cachedAppTarget = target
// Set environment variable so Bazel parsers can use this target
setenv(INJECTION_BAZEL_TARGET, target, 1)
} else {
error("**** Bad Bazel target ****")
}
case .injected:
AppDelegate.ui.setMenuIcon(.ok)
case .failed:
AppDelegate.ui.setMenuIcon(.error)
case .unhide:
log("Injection could not load. If this was due to a default " +
"argument. Select the app's menu item \"Unhide Symbols\".")
case .exit:
log("**** client disconnected ****")
return
@unknown default:
Self.error("**** @unknown response case \(responseInt) ****")
return
}
}
}
}
================================================
FILE: App/InjectionNext/MonitorXcode.swift
================================================
//
// RunXcode.swift
// InjectionNext
//
// Created by John H on 30/05/2024.
// Copyright © 2024 John Holdsworth. All rights reserved.
//
// Launches Xcode and monitors console output for SourceKit
// logging messages which reveal the compiler arguments to
// use for the file currently being edited (Used for real
// time syntax and error checking by the SourceKit daemon).
// Captures this information and passes it onto a Recompiler
// instance to process and inject when edited file is saved.
//
import Foundation
import SwiftRegex
import Fortify
import Popen
class MonitorXcode {
// Currently running Xcode process
static weak var runningXcode: MonitorXcode?
// The service to recompile and inject a source file.
static var recompiler = FrontendServer.frontendRecompiler(for: "Xcode")
func debug(_ what: Any..., separator: String = " ") {
#if DEBUG
print(what, separator: separator)
#endif
}
init(args: String = "") {
var args = args
#if DEBUG
args += " | tee \(Reloader.tmpbase).log"
#endif
if !FileManager.default.fileExists(atPath: Defaults.xcodePath) {
InjectionServer.error("""
No valid Xcode at path:
\(Defaults.xcodePath)
Use menu item "Select Xcode"
to select a valid path.
""")
}
else if let xcodeStdout = Popen(cmd: """
export SOURCEKIT_LOGGING=1
export RUNNING_VIA_INJECTION_NEXT=1
'\(Defaults.xcodePath)/Contents/MacOS/Xcode' 2>&1 \(args)
""") {
Self.runningXcode = self
AppDelegate.ui.launchXcodeItem.state = .on
DispatchQueue.global().async {
while true {
do {
try Fortify.protect {
AppDelegate.ui.setMenuIcon(.ready)
self.processSourceKitOutput(from: xcodeStdout)
AppDelegate.ui.setMenuIcon(.idle)
}
Self.runningXcode = nil
AppDelegate.ui.launchXcodeItem.state = .off
if !xcodeStdout.terminatedOK() && Defaults.xcodeRestart == true {
AppDelegate.ui.runXcode(self)
}
Self.recompiler.writeCache()
break // break on clean exit and EOF.
} catch {
// Continue processing on error
Self.recompiler.error(error)
}
}
}
}
}
func processSourceKitOutput(from xcodeStdout: Popen) {
var buffer = [CChar](repeating: 0, count: Popen.initialLineBufferSize)
func readQuotedString() -> String? {
var offset = 0
let doubleQuote = Int32(UInt8(ascii: "\"")), escaped = #"\""#
while let line = fgets(&buffer[offset], CInt(buffer.count-offset),
xcodeStdout.fileStream) {
offset += strlen(line)
if offset > 0 && buffer[offset-1] == UInt8(ascii: "\n") {
if let start = strchr(buffer, doubleQuote),
let end = strrchr(start+1, doubleQuote) {
end[0] = 0
var out = String(cString: start+1)
// Xcode used NSLog to log internal UTF8 strings
// using %s which uses the macOS system encoding.
// https://en.wikipedia.org/wiki/Mac_OS_Roman
// For now we need to do the following dance
// to revert scrambled non-ASCII file paths.
if out.hasPrefix("/") &&
!FileManager.default.fileExists(atPath: out),
let data = out.data(using: .macOSRoman),
let recovered = String(data: data, encoding: .utf8),
FileManager.default.fileExists(atPath: recovered) {
out = recovered
}
if strstr(start+1, escaped) != nil {
out = out.replacingOccurrences(of: escaped, with: "\"")
}
return out
}
return nil
}
var grown = [CChar](repeating: 0, count: buffer.count*2)
strcpy(&grown, buffer)
buffer = grown
}
return nil
}
let indexBuild = "/Index.noindex/Build/"
while let line = xcodeStdout.readLine() {
// debug(">>"+line+"<<")
autoreleasepool {
if line.hasPrefix(" key.request: source.request.") &&
(line == " key.request: source.request.editor.open," ||
line == " key.request: source.request.diagnostics," ||
line == " key.request: source.request.activeregions," ||
line == " key.request: source.request.relatedidents,") &&
xcodeStdout.readLine() == " key.compilerargs: [" ||
line == " key.compilerargs: [" {
var parser = FrontendServer.CompilationArgParser()
while var arg = readQuotedString() {
/// Used if injecting the Swift compiler.
let llvmIncs = "/llvm-macosx-arm64/lib"
if arg.hasPrefix("-I"), arg.contains(llvmIncs) {
arg = arg.replacingOccurrences(of: llvmIncs,
with: "/../buildbot_osx"+llvmIncs)
}
/// Arguments received from SourceKit while syntax highlighting the editor
/// have their own "Intermediates" directory. Map it back to the main one.
let alt = arg[indexBuild, "/Build/"]
if !arg.hasSuffix(".yaml"), alt != arg,
!arg.contains("/Intermediates.noindex/"),
let path: String = alt[#"[^/]*([^#]+)"#],
FileManager.default.fileExists(atPath: path) {
arg = alt
}
// SourceKit-specific args handled before the shared parser.
if arg == "-fsyntax-only" || arg == "-o" {
_ = xcodeStdout.readLine()
} else if var work: String = arg[#"-working-directory(?:=(.*))?"#] {
if work == RegexOptioned.unmatchedGroup,
let swork = readQuotedString() {
work = swork
}
parser.workingDir = work
} else if parser.args.last == "-vfsoverlay",
arg.contains(indexBuild) {
// injecting tests without having run tests
parser.args.removeLast()
} else if arg == "-Xfrontend" || arg.hasPrefix("-driver-") {
// drop silently
} else {
parser.process(arg: arg, next: readQuotedString)
}
}
guard !parser.args.isEmpty, let source =
readQuotedString() ?? readQuotedString(),
!source.contains("\\n") else {
return
}
print("Updating \(parser.args.count) args with \(parser.swiftFileCount) swift files "+source+" "+line)
let update = NextCompiler.Compilation(arguments: parser.args,
swiftFiles: parser.swiftFiles, workingDir: parser.workingDir)
NextCompiler.compileQueue.async {
Self.recompiler.store(compilation: update, for: source)
}
} else if line ==
" key.request: source.request.indexer.editor-did-save-file,",
let _ = xcodeStdout.readLine(), let source = readQuotedString() {
print("Injecting saved file "+source)
NextCompiler.compileQueue.async {
_ = Self.recompiler.inject(source: source)
}
}
}
}
}
}
================================================
FILE: App/InjectionNext/NextCompiler.swift
================================================
//
// Recompiler.swift
// InjectionNext
//
// Created by John Holdsworth on 21/06/2024.
// Copyright © 2024 John Holdsworth. All rights reserved.
//
// Server side implementation of injection.
// Recompile, link, codesign, send to client.
//
import Foundation
import Fortify
import Popen
import DLKit
/// bring in injectingXCTest()
struct Reloader {}
@discardableResult
public func log(_ what: Any..., prefix: String = APP_PREFIX, separator: String = " ") -> Bool {
var msg = what.map {"\($0)"}.joined(separator: separator)
#if INJECTION_III_APP
msg = "⏳ "+msg
#else
msg = prefix+msg
LogBuffer.shared?.append(msg, level: "info")
#endif
print(msg)
for client in InjectionServer.currentClients {
client?.sendCommand(.log, with: msg)
}
return true
}
class NextCompiler {
/// Information required to call the compiler for a file.
struct Compilation: Codable, Hashable {
/// Sundry arguments to the compiler
let arguments: [String]
/// Swift files in the target ready to be written as a -filelist
let swiftFiles: String
/// Directory to run compiler in (not important for Swift)
let workingDir: String
/// captured environment
var env: String?
}
/// Queue for one compilation at a time.
static let compileQueue = DispatchQueue(label: "InjectionCompile")
/// Last build error.
static var lastError: String?, lastSource: String?
let name: String
/// Base for temporary files
let tmpbase = "/tmp/injectionNext"
/// Injection pending if information was not available
var pendingSource: String?
/// Information for compiling a file per source file.
var compilations = [String: Compilation]()
/// Trying to avoid fragmenting memory
var lastCompilation: Compilation?
/// Previous dynamic libraries prepared by source file
var prepared = [String: String]()
/// Invalidate on three failures
var strikes = [String: Int]()
/// Default counter for Compilertron
var compileNumber = 0
init(name: String) {
self.name = name
}
func error(_ msg: String) {
let msg = "⚠️ "+msg
NSLog(msg)
log(msg)
}
func error(_ err: Error) {
error("Internal app error: \(err)")
}
var modified = false
func store(compilation: Compilation, for source: String) {
Self.lastSource = source
if lastCompilation != compilation {
lastCompilation = compilation
} //else { print("reusing") }
if compilations[source] != lastCompilation {
compilations[source] = lastCompilation
modified = true
}
if source == pendingSource {
print("Delayed injection of "+source)
if inject(source: source) {
pendingSource = nil
}
}
}
func canCompile(source: String, for platform: String? = nil) -> Bool {
if let compilation = compilations[source],
platform == nil || ("SDKs/"+platform!).withCString({ sdk in
compilation.arguments.first { strstr($0, sdk) != nil }}) != nil {
return true
} else { return false }
}
/// Main entry point called by MonitorXcode
func inject(source: String) -> Bool {
// Start tracking metrics
currentMetrics = InjectionMetricsTracker(sourcePath: source)
do {
let result = try Fortify.protect { () -> Bool in
for client in InjectionServer.currentClients.reversed() {
guard let (dylib, dylibName, platform, useFilesystem)
= try prepare(source: source, connected: client),
let data = codesign(dylib: dylib, platform: platform) else {
error("Injection failed. Was your app connected?")
AppDelegate.ui.setMenuIcon(.error)
return false
}
InjectionServer.clientQueue.sync {
guard let client = client else {
AppDelegate.ui.setMenuIcon(.ready)
return
}
// if Reloader.injectingXCTest(in: dylib) {
// _ = client.copyPlugIns
// }
if useFilesystem {
client.writeCommand(InjectionCommand
.load.rawValue, with: dylib)
} else {
client.writeCommand(InjectionCommand
.inject.rawValue, with: dylibName)
client.write(data)
}
unsupported(source: source, dylib: dylib, client: client)
}
}
Self.lastSource = source
if modified {
writeCache()
}
return true
}
// Calculate total time and send metrics
if let metrics = currentMetrics {
metrics.success = result
sendMetrics(metrics)
}
return result
} catch {
// Send failure metrics
if let metrics = currentMetrics {
metrics.success = false
sendMetrics(metrics)
}
self.error(error)
return false
}
}
/// Seek to highlight potentially unsupported injections.
func unsupported(source: String, dylib: String, client: InjectionServer) {
#if !INJECTION_III_APP
if let symbols = FileSymbols(path: dylib)?.trieSymbols()?
.filter({ entry in
lazy var symbol: String = String(cString: entry.name)
return strncmp(entry.name, "_$s", 3) == 0 &&
strstr(entry.name, "fU") == nil && // closures
!symbol.hasSuffix("MD") && !symbol.hasSuffix("Oh") &&
!symbol.hasSuffix("Wl") && !symbol.hasSuffix("WL") })
.map({ String(cString: $0.name) }).sorted() {
// print(symbols)
if let previous = client.exports[source],
previous.count != symbols.count {
log("ℹ️ Symbols altered, this may not be supported." +
" \(symbols.count) c.f. \(previous.count)")
if #available(macOS 15.0, *) {
print(symbols.difference(from: previous))
}
}
client.exports[source] = symbols
}
#endif
}
func prepare(source: String, connected: InjectionServer?) throws
-> (dylib: String, dylibName: String, platform: String, Bool)? {
AppDelegate.ui.setMenuIcon(.busy)
connected?.injectionNumber += 1
compileNumber += 1
Self.lastError = nil
// Support for https://github.com/johnno1962/Compilertron
let isCompilertron = connected == nil && source.hasSuffix(".cpp")
let compilerTmp = "/tmp/compilertron_patches"
let compilerPlatform = "MacOSX"
let compilerArch = "arm64"
let tmpPath = connected?.tmpPath ?? compilerTmp
let platform = connected?.platform ?? compilerPlatform
let sourceName = URL(fileURLWithPath: source)
.deletingPathExtension().lastPathComponent
if isCompilertron, let previous = prepared[sourceName] {
unlink(previous)
}
let dylibName = DYLIB_PREFIX + sourceName +
"_\(connected?.injectionNumber ?? compileNumber).dylib"
let useFilesystem = connected?.isLocalClient != false
#if INJECTION_III_APP
let dylibPath = (true ? tmpPath : "/tmp") + dylibName
#else
let dylibPath = (useFilesystem ? tmpPath : "/tmp") + dylibName
#endif
guard let object = try recompile(source: source, platform: platform), {
// Track compilation time
if let startTime = currentMetrics?.startTime {
currentMetrics?.compilationTimeMs =
(Date.timeIntervalSinceReferenceDate - startTime) * 1000
}
return true
}(),
tmpPath != compilerTmp || mkdir(compilerTmp, 0o777) != -999,
let (dylib, linkingTimeMs) = link(object: object, dylib: dylibPath,
arch: connected?.arch ?? compilerArch) else {
let strike = (strikes[source] ?? 0)+1
strikes[source] = strike
if strike >= 3 {
compilations.removeValue(forKey: source)
writeCache()
}
return nil
}
currentMetrics?.linkingTimeMs = linkingTimeMs
strikes[source] = 0
prepared[sourceName] = dylib
print("Prepared dylib: "+dylib)
return (dylib, dylibName, platform, useFilesystem)
}
/// Compile a source file using inforation provided by MonitorXcode
/// task and return the full path to the resulting object file.
func recompile(source: String, platform: String) throws -> String? {
guard let stored = compilations[source] else {
error("Postponing: \(source) Have you viewed it in Xcode?")
pendingSource = source
return nil
}
let uniqueObject = InjectionServer.currentClient?.injectionNumber ?? 0
let object = tmpbase+"_\(uniqueObject).o"
let isSwift = source.hasSuffix(".swift")
let filesfile = tmpbase+".filelist"
unlink(object)
unlink(filesfile)
try stored.swiftFiles.write(toFile: filesfile,
atomically: false, encoding: .utf8)
log("Recompiling: "+source)
let toolchain = Defaults.xcodePath +
"/Contents/Developer/Toolchains/XcodeDefault.xctoolchain"
let compiler = (isSwift ? FrontendServer.loggedFrontend : nil) ??
toolchain + "/usr/bin/" + (isSwift ? "swift-frontend" : "clang")
let platformUsr = Defaults.xcodePath + "/Contents/Developer/Platforms/" +
platform.replacingOccurrences(of: "Simulator", with: "OS") +
".platform/Developer/usr/"
let baseOptionsToAdd = ["-o", object, "-DDEBUG", "-DINJECTING"]
let languageSpecific = (isSwift ?
["-c", "-filelist", filesfile, "-primary-file", source,
Reloader.typeCheckLimit,
"-external-plugin-path",
platformUsr+"lib/swift/host/plugins#" +
platformUsr+"bin/swift-plugin-server",
"-external-plugin-path",
platformUsr+"local/lib/swift/host/plugins#" +
platformUsr+"bin/swift-plugin-server",
"-plugin-path", toolchain+"/usr/lib/swift/host/plugins",
"-plugin-path", toolchain+"/usr/local/lib/swift/host/plugins"] :
["-c", source, "-Xclang", "-fno-validate-pch"]) + baseOptionsToAdd
let wmoFlags: Set = [
"-whole-module-optimization",
"-internalize-at-link",
"-no-serialize-debugging-options"
]
var arguments = [String]()
var skipNext = false
for arg in stored.arguments where !wmoFlags.contains(arg) {
if skipNext { skipNext = false; continue }
if arg == "-o" { skipNext = true; continue }
arguments.append(arg)
}
if let target = InjectionServer.currentClient?.arch, target != "arm64" {
// Simulator running in Rosetta.
for i in 0.. (String, Double)? {
let linkingStartTime = Date.timeIntervalSinceReferenceDate
var linkCommand = Reloader.linkCommand + " \(object) -o \"\(dylib)\" "
if DispatchQueue.main.sync(execute: {
AppDelegate.ui.deviceTesting?.state == .on }) {
let otherOptions = DispatchQueue.main.sync { () -> String in
AppDelegate.ui.librariesField.stringValue = Defaults.deviceLibraries
return Defaults.deviceLibraries }
let platformDev = "\(Reloader.xcodeDev)/Platforms/\(Reloader.platform).platform/Developer"
linkCommand += """
-F /tmp/InjectionNext.Products \
-F "\(platformDev)/Library/Frameworks" \
-L "\(platformDev)/usr/lib" \(otherOptions)
""".replacingOccurrences(of: "__PLATFORM__", with: Reloader.sysroot)
}
if let errors = Popen.system(linkCommand, errors: true) {
error("Linking failed:\n\(linkCommand)\nerrors:\n"+errors)
Self.lastError = errors
return nil
}
let linkingTimeMs = (Date.timeIntervalSinceReferenceDate - linkingStartTime) * 1000
return (dylib, linkingTimeMs)
}
/// Codesign a dynamic library
func codesign(dylib: String, platform: String) -> Data? {
if platform != "iPhoneSimulator" {
var identity = "-"
if !platform.hasSuffix("Simulator") && platform != "MacOSX" {
identity = DispatchQueue.main.sync { AppDelegate.ui.codeSigningID }
log("Codesigning dylib with identity "+identity)
}
let codesign = """
(export CODESIGN_ALLOCATE="\(Defaults.xcodePath+"/Contents/Developer"
)/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate"; \
if /usr/bin/file \"\(dylib)\" | /usr/bin/grep ' shared library ' >/dev/null; \
then /usr/bin/codesign --force -s "\(identity)" \"\(dylib)\";\
else exit 1; fi)
"""
if let errors = Popen.system(codesign, errors: true) {
error("Codesign failed \(codesign) errors:\n"+errors)
Self.lastError = errors
}
}
return try? Data(contentsOf: URL(fileURLWithPath: dylib))
}
/// Tracks timing metrics for injection process
final class InjectionMetricsTracker: Codable {
var compilationTimeMs: Double = 0
var linkingTimeMs: Double = 0
var totalTimeMs: Double = 0
var sourcePath: String
var bazelTarget: String?
var success: Bool = false
var notificationName: String = INJECTION_METRICS_NOTIFICATION
let startTime: Double
init(sourcePath: String) {
self.sourcePath = sourcePath
self.startTime = Date.timeIntervalSinceReferenceDate
}
}
/// Current metrics being tracked
var currentMetrics: InjectionMetricsTracker?
/// Enrich metrics with Bazel target and normalize source path
#if !INJECTION_III_APP
func enrichMetrics(_ metrics: inout InjectionMetricsTracker) {
let sourcePath = metrics.sourcePath
// Find workspace root to normalize the path
if let workspaceRoot = BazelInterface.findWorkspaceRoot(containing: sourcePath) {
let workspaceRootPath = (workspaceRoot as NSString).standardizingPath
let fullPath = (sourcePath as NSString).standardizingPath
// Normalize sourcePath to workspace-relative (in place)
if fullPath.hasPrefix(workspaceRootPath + "/") {
metrics.sourcePath = String(fullPath.dropFirst(workspaceRootPath.count + 1))
} else {
metrics.sourcePath = URL(fileURLWithPath: sourcePath).lastPathComponent
}
// Try to discover the Bazel app target
do {
let queryHandler = try BazelAQueryParser(workspaceRoot: workspaceRoot)
queryHandler.autoDiscoverAppTarget(for: sourcePath)
metrics.bazelTarget = queryHandler.getAppTarget()
} catch {
// Bazel discovery failed, metrics will have nil bazelTarget
print("⚠️ Could not discover Bazel target for \(sourcePath): \(error)")
}
}
}
#endif
/// Send metrics to all connected clients
func sendMetrics(_ metrics: InjectionMetricsTracker) {
#if !INJECTION_III_APP
if AppDelegate.watchers.isEmpty &&
BazelActionQueryHandler.cachedAppTarget == nil { return }
metrics.totalTimeMs = (Date.timeIntervalSinceReferenceDate
- metrics.startTime) * 1000
var enrichedMetrics = metrics
enrichMetrics(&enrichedMetrics)
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let jsonData = try? encoder.encode(enrichedMetrics),
let jsonString = String(data: jsonData, encoding: .utf8) else {
return
}
for client in InjectionServer.currentClients {
client?.sendCommand(.metrics, with: jsonString)
}
#endif
}
}
================================================
FILE: App/InjectionNext/build_bundle.sh
================================================
#!/bin/sh -x
# build_bundle.sh
# InjectionNext
#
# Created by John Holdsworth on 22/07/2024.
# Copyright © 2024 John Holdsworth. All rights reserved.
FAMILY=$1
PLATFORM=$2
SDK="$(echo $PLATFORM | tr "[A-Z]" "[a-z]")"
sleep $3
FIXED_XCODE_DEVELOPER_PATH=/Applications/Xcode.app/Contents/Developer
export SWIFT_ACTIVE_COMPILATION_CONDITIONS=""
SWIFT_DYLIBS_PATH="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$SDK"
CONCURRENCY_DYLIBS="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/$SDK"
XCODE_PLATFORM_PATH="$FIXED_XCODE_DEVELOPER_PATH/Platforms/$PLATFORM.platform"
XCTEST_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/Frameworks"
XCTEST_SUPPORT_PATH="$XCODE_PLATFORM_PATH/Developer/usr/lib"
BUNDLE_CONFIG=Debug
if [ ! -d "$SWIFT_DYLIBS_PATH" -o ! -d "${XCTEST_FRAMEWORK_PATH}/XCTest.framework" ]; then
echo "Missing RPATH $SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH"
exit 1
fi
ADD_INSTALL_NAME=""
if [[ ${FAMILY} =~ Dev ]]; then
# real devices require a copy_bundle.sh build phase
ADD_INSTALL_NAME="LD_DYLIB_INSTALL_NAME=@rpath/lib${SDK}Injection.dylib"
fi
for i in 1 2 3; do if
"$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" $APP_SANDBOXED PRODUCT_NAME="${FAMILY}Injection" LD_RUNPATH_SEARCH_PATHS="@loader_path/Frameworks @loader_path/${FAMILY}Injection.bundle/Frameworks $SWIFT_DYLIBS_PATH $CONCURRENCY_DYLIBS $XCTEST_FRAMEWORK_PATH $XCTEST_SUPPORT_PATH" $ADD_INSTALL_NAME PLATFORM_DIR="$DEVELOPER_DIR/Platforms/$PLATFORM.platform" -sdk $SDK -config $BUNDLE_CONFIG -target InjectionBundle; then
#-archivePath "/tmp/Archive.$SDK" -derivedDataPath "/tmp/Derived.$SDK"
break
elif [ "$i" = "3" ]; then
exit 1
fi
echo "Retrying...";
done &&
rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/*.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" &&
ln -sf "${FAMILY}Injection.bundle/${FAMILY}Injection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/lib${SDK}Injection.dylib"
================================================
FILE: App/InjectionNext/build_bundles.sh
================================================
#!/bin/sh
# build_bundles.sh
# InjectionNext
#
# Created by John Holdsworth on 22/07/2024.
# Copyright © 2024 John Holdsworth. All rights reserved.
FIXED_XCODE_DEVELOPER_PATH=/Applications/Xcode.app/Contents/Developer
export SWIFT_ACTIVE_COMPILATION_CONDITIONS=""
BUILD=`/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" $CODESIGNING_FOLDER_PATH/Contents/Info.plist`
function build_bundle () {
FAMILY=$1
PLATFORM=$2
SDK=$3
SWIFT_DYLIBS_PATH="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$SDK"
CONCURRENCY_DYLIBS="$FIXED_XCODE_DEVELOPER_PATH/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/$SDK"
XCODE_PLATFORM_PATH="$FIXED_XCODE_DEVELOPER_PATH/Platforms/$PLATFORM.platform"
XCCORE_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/PrivateFrameworks"
XCTEST_FRAMEWORK_PATH="$XCODE_PLATFORM_PATH/Developer/Library/Frameworks"
XCTEST_SUPPORT_PATH="$XCODE_PLATFORM_PATH/Developer/usr/lib"
BUNDLE_CONFIG=Debug
if [ ! -d "$SWIFT_DYLIBS_PATH" -o ! -d "${XCTEST_FRAMEWORK_PATH}/XCTest.framework" ]; then
echo "Missing RPATH $SWIFT_DYLIBS_PATH $XCTEST_FRAMEWORK_PATH"
exit 1
fi
ADD_INSTALL_NAME=""
if [[ ${FAMILY} =~ Dev ]]; then
# real devices require a copy_bundle.sh build phase
ADD_INSTALL_NAME="LD_DYLIB_INSTALL_NAME=@rpath/lib${SDK}Injection.dylib"
fi
"$DEVELOPER_BIN_DIR"/xcodebuild SYMROOT=$SYMROOT ARCHS="$ARCHS" $APP_SANDBOXED PRODUCT_NAME="${FAMILY}Injection" LD_RUNPATH_SEARCH_PATHS="@executable_path/Frameworks @loader_path/Frameworks @loader_path/${FAMILY}Injection.bundle/Frameworks $SWIFT_DYLIBS_PATH $CONCURRENCY_DYLIBS $XCTEST_FRAMEWORK_PATH $XCTEST_SUPPORT_PATH $XCCORE_FRAMEWORK_PATH" $ADD_INSTALL_NAME PLATFORM_DIR="$DEVELOPER_DIR/Platforms/$PLATFORM.platform" -sdk $SDK -config $BUNDLE_CONFIG -target InjectionBundle &&
rsync -au $SYMROOT/$BUNDLE_CONFIG-$SDK/*.bundle "$CODESIGNING_FOLDER_PATH/Contents/Resources" &&
PLIST="$CODESIGNING_FOLDER_PATH/Contents/Resources/${FAMILY}Injection.bundle/Info.plist" &&
(/usr/libexec/PlistBuddy -c "Delete :CFBundleVersion" "$PLIST" || echo -n) &&
/usr/libexec/PlistBuddy -c "Add :CFBundleVersion string $BUILD" "$PLIST" &&
ln -sf "${FAMILY}Injection.bundle/${FAMILY}Injection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/lib${SDK}Injection.dylib"
}
ln -sf "macOSInjection.bundle/Contents/MacOS/macOSInjection" "$CODESIGNING_FOLDER_PATH/Contents/Resources/libmacosxInjection.dylib" &&
build_bundle iOS iPhoneSimulator iphonesimulator &&
if [[ "$ACTION" = "install" ]]; then
build_bundle tvOS AppleTVSimulator appletvsimulator &&
build_bundle xrOS XRSimulator xrsimulator &&
build_bundle iOSDev iPhoneOS iphoneos &&
build_bundle tvOSDev AppleTVOS appletvos &&
build_bundle xrOSDev XROS xros
fi &&
exit 0
================================================
FILE: App/InjectionNext/copy_bundle.sh
================================================
#!/bin/bash -x
#
# copy_bundle.sh
# InjectionIII
#
# Copies injection bundle for on-device injection.
# Thanks @oryonatan
#
# $Id: //depot/HotReloading/copy_bundle.sh#14 $
#
if [[ "$CONFIGURATION" =~ Debug ]]; then
if [ ! -w "$CODESIGNING_FOLDER_PATH" ]; then
echo '*** copy_bundle.sh unable to write to file system. ***' \
'Change build setting "User Script Sandboxing" to NO'
exit 1;
fi
# determine which prebuilt bundle to copy
RESOURCES=${RESOURCES:-"$(dirname "$0")"}
# If there are frameworks used only by tests
TESTING_FRAMEWORKS="$2"
COPY="$CODESIGNING_FOLDER_PATH/iOSInjection.bundle"
PLIST="$COPY/Info.plist"
if [ "$PLATFORM_NAME" == "macosx" ]; then
BUNDLE=${1:-macOSInjection}
COPY="$CODESIGNING_FOLDER_PATH/Contents/Resources/macOSInjection.bundle"
PLIST="$COPY/Contents/Info.plist"
elif [ "$PLATFORM_NAME" == "appletvsimulator" ]; then
BUNDLE=${1:-tvOSInjection}
elif [ "$PLATFORM_NAME" == "appletvos" ]; then
BUNDLE=${1:-tvOSDevInjection}
elif [ "$PLATFORM_NAME" == "xrsimulator" ]; then
BUNDLE=${1:-xrOSInjection}
elif [ "$PLATFORM_NAME" == "xros" ]; then
BUNDLE=${1:-xrOSDevInjection}
elif [ "$PLATFORM_NAME" == "iphoneos" ]; then
BUNDLE=${1:-iOSDevInjection}
else
BUNDLE=${1:-iOSInjection}
fi
mkdir -p "$CODESIGNING_FOLDER_PATH/Frameworks" &&
# copy frameworks used for testing into app's bundle/Frameworks
ln -sf "../iOSInjection.bundle/$BUNDLE" "$CODESIGNING_FOLDER_PATH/Frameworks/lib${PLATFORM_NAME}Injection.dylib" &&
if [[ "$BUNDLE" =~ Dev ]]; then
rsync -a "$PLATFORM_DEVELOPER_LIBRARY_DIR"/*Frameworks/{XC,StoreKit}* "$PLATFORM_DEVELOPER_USR_DIR/lib"/*.dylib "$CODESIGNING_FOLDER_PATH/Frameworks/" &&
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks"/{XC*,StoreKit*,*.dylib} ||
echo "*** You should be able to ignore the above errors ***"
else
rsync -a "$PLATFORM_DEVELOPER_LIBRARY_DIR"/*Frameworks/XC* "$PLATFORM_DEVELOPER_USR_DIR/lib"/*.dylib "$CODESIGNING_FOLDER_PATH/Frameworks/" &&
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks"/{XC*,*.dylib}
fi &&
# Copy frameworks only used in test target
PRODUCTS_DIR="$(dirname "$CODESIGNING_FOLDER_PATH")"
rm -f /tmp/InjectionNext.Products
ln -s "$PRODUCTS_DIR" /tmp/InjectionNext.Products
(cd "$PRODUCTS_DIR" && for fwork in $TESTING_FRAMEWORKS; do
if [ -f "$fwork/Info.plist" -a \
! -d "$CODESIGNING_FOLDER_PATH/Frameworks/$fwork" ]; then
rsync -a "$fwork" "$CODESIGNING_FOLDER_PATH/Frameworks" &&
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks/$fwork"
fi
done)
# Xcode 16's new SwiftTesting framework
TESTING="$PLATFORM_DEVELOPER_LIBRARY_DIR/Frameworks/Testing.Framework"
if [ -d "$TESTING" ]; then
rsync -a "$TESTING"/* "$CODESIGNING_FOLDER_PATH/Frameworks/Testing.framework/"
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/Frameworks/Testing.framework";
fi
# Make copy of "PlugIns" directory when testing
export PLUGINS="/tmp/PlugIns.$PRODUCT_NAME.$PLATFORM_NAME"
export LAST_PLUGINS="/tmp/InjectionNext.PlugIns"
rm -f $LAST_PLUGINS
if [ -d "$CODESIGNING_FOLDER_PATH/PlugIns" ]; then
(sleep 5; while
rsync -va "$CODESIGNING_FOLDER_PATH/PlugIns"/* "$PLUGINS/" |
grep -v /sec | grep /; do sleep 15; done) 1>/dev/null 2>&1 &
else bash -x <<'CAN_FAIL' 2>/dev/null
# Xcode 16 deletes PlugIns directory. copy or create link
rsync -a "$PLUGINS"/* "$CODESIGNING_FOLDER_PATH/PlugIns/" &&
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$CODESIGNING_FOLDER_PATH/PlugIns/*.xctest" ||
ln -s $PLUGINS $LAST_PLUGINS
CAN_FAIL
fi
# copy prebuilt bundle into app package and codesign
rsync -a "$RESOURCES/$BUNDLE.bundle"/* "$COPY/" &&
# See +[SimpleSocket initialize] for pre-built bundles/dylibs
/usr/libexec/PlistBuddy -c "Add :UserHome string $HOME" "$PLIST" &&
(/usr/libexec/PlistBuddy -c "Delete :InjectionUserHome" "$CODESIGNING_FOLDER_PATH/Info.plist" || echo -n) &&
/usr/libexec/PlistBuddy -c "Add :InjectionUserHome string $HOME" "$CODESIGNING_FOLDER_PATH/Info.plist" &&
codesign -f --sign "$EXPANDED_CODE_SIGN_IDENTITY" --timestamp\=none --preserve-metadata\=identifier,entitlements,flags --generate-entitlement-der "$COPY" &&
defaults write com.johnholdsworth.InjectionNext codesigningIdentity "$EXPANDED_CODE_SIGN_IDENTITY"
fi
================================================
FILE: App/InjectionNext/main.m
================================================
//
// main.m
// InjectionIII
//
// Created by John Holdsworth on 06/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
#import
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
================================================
FILE: App/InjectionNext/swift-frontend.sh
================================================
#!/bin/bash
# swift-frontend.sh
# InjectionNext
#
# Created by John Holdsworth on 23/02/2025.
# Copyright © 2025 John Holdsworth. All rights reserved.
FRONTEND="$0"
"$FRONTEND.save" "$@" &&
if [ "$2" = "-c" ]; then "/Applications/InjectionNext.app/Contents/Resources/feedcommands" \
"2.0" "$(/usr/bin/env)" "$FRONTEND.save" "$@" >>/tmp/feedcommands.log 2>&1 & fi
================================================
FILE: App/InjectionNext.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objects = {
/* Begin PBXBuildFile section */
2217BE812C0A65C20032A832 /* Fortify in Frameworks */ = {isa = PBXBuildFile; productRef = 2217BE802C0A65C20032A832 /* Fortify */; };
222BC9452C157C3200780A41 /* InjectionReady.tif in Resources */ = {isa = PBXBuildFile; fileRef = 222BC9442C157C3200780A41 /* InjectionReady.tif */; };
224E57FB2C08978F00B71C79 /* SwiftRegex in Frameworks */ = {isa = PBXBuildFile; productRef = 224E57FA2C08978F00B71C79 /* SwiftRegex */; };
224E57FE2C08BBE300B71C79 /* InjectionServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224E57FD2C08BBE300B71C79 /* InjectionServer.swift */; };
224E58002C08BC0E00B71C79 /* MonitorXcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */; };
229127F12C1EEF69005D7625 /* Experimental.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEC17029253ED117002E823F /* Experimental.swift */; };
22B645A02C18DD9D00F99B61 /* Unhider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B6459F2C18DD9D00F99B61 /* Unhider.swift */; };
22E3D98B2C19009900BB234E /* DLKit in Frameworks */ = {isa = PBXBuildFile; productRef = 22E3D98A2C19009900BB234E /* DLKit */; };
22E3D98D2C19009900BB234E /* DLKitC in Frameworks */ = {isa = PBXBuildFile; productRef = 22E3D98C2C19009900BB234E /* DLKitC */; };
BB0CECB02C73C1610000A4E6 /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = BB0CECAF2C73C1610000A4E6 /* Quick */; };
BB0CECB32C73C1890000A4E6 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = BB0CECB22C73C1890000A4E6 /* Nimble */; };
BB16653A25E9A5F2001407AE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BB16653925E9A5F0001407AE /* main.m */; };
BB2A66362E3E753B001EDD38 /* BAZEL.md in Resources */ = {isa = PBXBuildFile; fileRef = BB2A66352E3E753B001EDD38 /* BAZEL.md */; };
BB2D41AC2DAD14EA0073C203 /* Popen in Frameworks */ = {isa = PBXBuildFile; productRef = BB2D41AB2DAD14EA0073C203 /* Popen */; };
BB42F8E12EB626F100FDBBCC /* SwiftAspects.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */; };
BB42F8E22EB626F100FDBBCC /* SwiftInterpose.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */; };
BB42F8E32EB626F100FDBBCC /* SwiftTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */; };
BB42F8E42EB626F100FDBBCC /* SwiftSwizzle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */; };
BB42F8E52EB626F100FDBBCC /* SwiftArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */; };
BB42F8E62EB626F100FDBBCC /* SwiftInvoke.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */; };
BB42F8E72EB626F100FDBBCC /* EasyPointer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */; };
BB42F8E82EB626F100FDBBCC /* SwiftStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */; };
BB42F8E92EB626F100FDBBCC /* SwiftStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */; };
BB42F8EA2EB626F100FDBBCC /* StringIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D42EB626F100FDBBCC /* StringIndex.swift */; };
BB42F8EB2EB626F100FDBBCC /* SwiftLifetime.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */; };
BB42F8EC2EB626F100FDBBCC /* SwiftMeta.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */; };
BB42F9212EB639D100FDBBCC /* DLKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91B2EB639D100FDBBCC /* DLKit.swift */; };
BB42F9222EB639D100FDBBCC /* Iterators.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91F2EB639D100FDBBCC /* Iterators.swift */; };
BB42F9232EB639D100FDBBCC /* Interposing.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91E2EB639D100FDBBCC /* Interposing.swift */; };
BB42F9242EB639D100FDBBCC /* ImageSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */; };
BB42F9252EB639D100FDBBCC /* Demangling.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91A2EB639D100FDBBCC /* Demangling.swift */; };
BB42F9262EB639D100FDBBCC /* FileSymbols.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */; };
BB42F92D2EB639E500FDBBCC /* DLKitC.c in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9292EB639E500FDBBCC /* DLKitC.c */; };
BB42F92E2EB639E500FDBBCC /* trie_dladdr.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */; };
BB42F92F2EB639E500FDBBCC /* trie_dlops.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */; };
BB42F9382EB63C2900FDBBCC /* KeyPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */; };
BB42F9392EB63C2900FDBBCC /* Reloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9352EB63C2900FDBBCC /* Reloader.swift */; };
BB42F93A2EB63C2900FDBBCC /* Generics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9322EB63C2900FDBBCC /* Generics.swift */; };
BB42F93B2EB63C2900FDBBCC /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9342EB63C2900FDBBCC /* Metadata.swift */; };
BB42F93C2EB63C2900FDBBCC /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9312EB63C2900FDBBCC /* Common.swift */; };
BB42F93D2EB63C2900FDBBCC /* Sweeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9362EB63C2900FDBBCC /* Sweeper.swift */; };
BB42F93F2EB63CD800FDBBCC /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9312EB63C2900FDBBCC /* Common.swift */; };
BB42F9462EB63E3400FDBBCC /* InjectionBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */; };
BB42F9472EB63E3400FDBBCC /* Recompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9442EB63E3400FDBBCC /* Recompiler.swift */; };
BB42F9482EB63E3400FDBBCC /* InjectionLite.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */; };
BB42F9492EB63E3400FDBBCC /* LogParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9432EB63E3400FDBBCC /* LogParser.swift */; };
BB42F94A2EB63E3400FDBBCC /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */; };
BB42F94B2EB63E3F00FDBBCC /* FileWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */; };
BB42F94C2EB63E4F00FDBBCC /* LogParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9432EB63E3400FDBBCC /* LogParser.swift */; };
BB42F94D2EB63E5600FDBBCC /* Recompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9442EB63E3400FDBBCC /* Recompiler.swift */; };
BB42F94E2EB63E7700FDBBCC /* InjectionBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */; };
BB42F9552EB63EFF00FDBBCC /* BazelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */; };
BB42F9562EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */; };
BB42F9572EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */; };
BB42F9582EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */; };
BB42F9592EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */; };
BB42F95A2EB63EFF00FDBBCC /* BazelInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */; };
BB42F95B2EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */; };
BB42F95C2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */; };
BB42F95D2EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */; };
BB42F95E2EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */; };
BB4654812C257F110080EC40 /* NextCompiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4654802C257F110080EC40 /* NextCompiler.swift */; };
BB4788802EB6B6E700464AB4 /* InjectionBoot.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */; };
BB4788812EB6B71600464AB4 /* InjectionImplC.h in Resources */ = {isa = PBXBuildFile; fileRef = BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */; };
BB4788902EB6B81A00464AB4 /* fast_dladdr.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */; };
BB4788912EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */; };
BB4788922EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */; };
BB4788932EB6B81A00464AB4 /* ObjCBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */; };
BB4788942EB6B81A00464AB4 /* Trampolines.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB47888A2EB6B81A00464AB4 /* Trampolines.mm */; };
BB4788952EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */; };
BB4788962EB6B81A00464AB4 /* SwiftTrace.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */; };
BB4788972EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s in Sources */ = {isa = PBXBuildFile; fileRef = BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */; };
BB4788982EB6B81A00464AB4 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = BB4788862EB6B81A00464AB4 /* fishhook.c */; };
BB5155DA2CDED44F00704C7A /* InjectionHybrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5155D92CDED44400704C7A /* InjectionHybrid.swift */; };
AA0000012F08000000000001 /* ControlServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000002F08000000000001 /* ControlServer.swift */; };
BB52AD552F1501F500297CD9 /* Unhider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B6459F2C18DD9D00F99B61 /* Unhider.swift */; };
BB6A56F02C50E73600C92112 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6A56EF2C50E73600C92112 /* Defaults.swift */; };
BB6A56F12C50E79800C92112 /* copy_bundle.sh in Resources */ = {isa = PBXBuildFile; fileRef = BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */; };
BB85A42B2D6B9C7B00FA29D0 /* feedcommands in Resources */ = {isa = PBXBuildFile; fileRef = BB85A4242D6B9C5300FA29D0 /* feedcommands */; };
BB85A42E2D6BA13B00FA29D0 /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; };
BBA082362D6BC33500FFFB2F /* FrontendServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */; };
BBA082382D6BD6CB00FFFB2F /* swift-frontend.sh in Resources */ = {isa = PBXBuildFile; fileRef = BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */; };
BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */; };
BBB64DC51FD450AF0020BE47 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = BB037DF31FAD808B004B267C /* README.md */; };
BBB64DC61FD450B40020BE47 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = BB037DF41FAD80D0004B267C /* LICENSE */; };
BBB64DE51FD571310020BE47 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB64DE41FD571300020BE47 /* libz.tbd */; };
BBB64FEC1FD585D50020BE47 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBB64FEB1FD585D50020BE47 /* WebKit.framework */; };
BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBCA02011FB0F10300E45F0F /* AppDelegate.swift */; };
BBCA02041FB0F10300E45F0F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02031FB0F10300E45F0F /* Assets.xcassets */; };
BBCA02071FB0F10300E45F0F /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02051FB0F10300E45F0F /* MainMenu.xib */; };
BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; };
BBCA02591FB112F500E45F0F /* App.icns in Resources */ = {isa = PBXBuildFile; fileRef = BBCA02581FB112F500E45F0F /* App.icns */; };
BBCA02601FB122C300E45F0F /* InjectionIdle.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */; };
BBCA02611FB122C300E45F0F /* InjectionOK.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBCA025F1FB122C300E45F0F /* InjectionOK.tif */; };
BBCCC3B72C73B6FD00E71F81 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBBEC42D1FC56522004188B3 /* XCTest.framework */; };
BBDD843F2C4FE8D5000F3124 /* macOSInjection.bundle in Resources */ = {isa = PBXBuildFile; fileRef = BBDD84132C4FE749000F3124 /* macOSInjection.bundle */; };
BBDD84422C4FEA4E000F3124 /* InjectionNext.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */; };
BBDD84542C4FEB16000F3124 /* TupleRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84532C4FEB16000F3124 /* TupleRegex.swift */; };
BBDD84552C4FEC98000F3124 /* SimpleSocket.mm in Sources */ = {isa = PBXBuildFile; fileRef = BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */; };
BBDD845A2C4FF646000F3124 /* ClientBoot.mm in Sources */ = {isa = PBXBuildFile; fileRef = BBDD84592C4FF645000F3124 /* ClientBoot.mm */; };
BBE490DB1FB2C643003D41BB /* InjectionBusy.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBE490D91FB2C643003D41BB /* InjectionBusy.tif */; };
BBE490DC1FB2C643003D41BB /* InjectionError.tif in Resources */ = {isa = PBXBuildFile; fileRef = BBE490DA1FB2C643003D41BB /* InjectionError.tif */; };
BBEB87122C74FD930044578A /* Quick in Frameworks */ = {isa = PBXBuildFile; productRef = BBEB87112C74FD930044578A /* Quick */; };
BBF6572E2DAFF00200638170 /* NIMBLE.md in Resources */ = {isa = PBXBuildFile; fileRef = BBF6572C2DAFF00200638170 /* NIMBLE.md */; };
BBF6572F2DAFF00200638170 /* QUICK.md in Resources */ = {isa = PBXBuildFile; fileRef = BBF6572D2DAFF00200638170 /* QUICK.md */; };
BBFE1B422ED7C116004E14EE /* SwiftRefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
BB85A42C2D6B9CA200FA29D0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BB439B5D1FABA64300B4F50B /* Project object */;
proxyType = 1;
remoteGlobalIDString = BB85A4232D6B9C5300FA29D0;
remoteInfo = feedcommands;
};
BBDD843D2C4FE8C5000F3124 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BB439B5D1FABA64300B4F50B /* Project object */;
proxyType = 1;
remoteGlobalIDString = BBDD84122C4FE749000F3124;
remoteInfo = InjectionBundle;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
BB6224721FC5BA6F00AD7A3A /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = Contents/Library/LaunchServices;
dstSubfolderSpec = 1;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
BB6C87DB2520D0D3005AFCFC /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
BB85A4222D6B9C5300FA29D0 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
dstSubfolderSpec = 0;
files = (
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
222BC9442C157C3200780A41 /* InjectionReady.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionReady.tif; sourceTree = ""; };
224E57FC2C08BAE200B71C79 /* InjectionClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = InjectionClient.h; path = ../../Sources/InjectionNextC/include/InjectionClient.h; sourceTree = ""; };
224E57FD2C08BBE300B71C79 /* InjectionServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionServer.swift; sourceTree = ""; };
224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonitorXcode.swift; sourceTree = ""; };
22B6459F2C18DD9D00F99B61 /* Unhider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Unhider.swift; path = ../../InjectionLite/Sources/InjectionImpl/Unhider.swift; sourceTree = ""; };
BB037DF31FAD808B004B267C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
BB037DF41FAD80D0004B267C /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
BB16653925E9A5F0001407AE /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
BB2A66352E3E753B001EDD38 /* BAZEL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = BAZEL.md; path = ../BAZEL.md; sourceTree = SOURCE_ROOT; };
BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EasyPointer.swift; sourceTree = ""; };
BB42F8D32EB626F100FDBBCC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
BB42F8D42EB626F100FDBBCC /* StringIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringIndex.swift; sourceTree = ""; };
BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftArgs.swift; sourceTree = ""; };
BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftAspects.swift; sourceTree = ""; };
BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInterpose.swift; sourceTree = ""; };
BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInvoke.swift; sourceTree = ""; };
BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLifetime.swift; sourceTree = ""; };
BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMeta.swift; sourceTree = ""; };
BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStack.swift; sourceTree = ""; };
BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStats.swift; sourceTree = ""; };
BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSwizzle.swift; sourceTree = ""; };
BB42F8DE2EB626F100FDBBCC /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = ""; };
BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTrace.swift; sourceTree = ""; };
BB42F91A2EB639D100FDBBCC /* Demangling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Demangling.swift; sourceTree = ""; };
BB42F91B2EB639D100FDBBCC /* DLKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DLKit.swift; sourceTree = ""; };
BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSymbols.swift; sourceTree = ""; };
BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSymbols.swift; sourceTree = ""; };
BB42F91E2EB639D100FDBBCC /* Interposing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interposing.swift; sourceTree = ""; };
BB42F91F2EB639D100FDBBCC /* Iterators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Iterators.swift; sourceTree = ""; };
BB42F9272EB639E500FDBBCC /* DLKitC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DLKitC.h; sourceTree = ""; };
BB42F9292EB639E500FDBBCC /* DLKitC.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = DLKitC.c; sourceTree = ""; };
BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dladdr.mm; sourceTree = ""; };
BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trie_dlops.mm; sourceTree = ""; };
BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InjectionImplC.h; path = ../InjectionLite/Sources/InjectionImplC/include/InjectionImplC.h; sourceTree = SOURCE_ROOT; };
BB42F9312EB63C2900FDBBCC /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; };
BB42F9322EB63C2900FDBBCC /* Generics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Generics.swift; sourceTree = ""; };
BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPaths.swift; sourceTree = ""; };
BB42F9342EB63C2900FDBBCC /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = ""; };
BB42F9352EB63C2900FDBBCC /* Reloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reloader.swift; sourceTree = ""; };
BB42F9362EB63C2900FDBBCC /* Sweeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sweeper.swift; sourceTree = ""; };
BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileWatcher.swift; sourceTree = ""; };
BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionBase.swift; sourceTree = ""; };
BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionLite.swift; sourceTree = ""; };
BB42F9432EB63E3400FDBBCC /* LogParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogParser.swift; sourceTree = ""; };
BB42F9442EB63E3400FDBBCC /* Recompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Recompiler.swift; sourceTree = ""; };
BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelActionQueryHandler.swift; sourceTree = ""; };
BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelAQueryParser.swift; sourceTree = ""; };
BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BazelInterface.swift; sourceTree = ""; };
BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilenameMatcher.swift; sourceTree = ""; };
BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitIgnoreParser.swift; sourceTree = ""; };
BB4654802C257F110080EC40 /* NextCompiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextCompiler.swift; sourceTree = ""; };
BB47887C2EB6B6E700464AB4 /* InjectionImplC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InjectionImplC.h; sourceTree = ""; };
BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = InjectionBoot.mm; sourceTree = ""; };
BB4788822EB6B81A00464AB4 /* fishhook.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = fishhook.h; sourceTree = ""; };
BB4788832EB6B81A00464AB4 /* SwiftTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftTrace.h; sourceTree = ""; };
BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = fast_dladdr.mm; sourceTree = ""; };
BB4788862EB6B81A00464AB4 /* fishhook.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fishhook.c; sourceTree = ""; };
BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ObjCBridge.mm; sourceTree = ""; };
BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SwiftTrace.mm; sourceTree = ""; };
BB4788892EB6B81A00464AB4 /* SwiftTrace-Swift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftTrace-Swift.h"; sourceTree = ""; };
BB47888A2EB6B81A00464AB4 /* Trampolines.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Trampolines.mm; sourceTree = ""; };
BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_arm7.s; sourceTree = ""; };
BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_arm64.s; sourceTree = ""; };
BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_x64.s; sourceTree = ""; };
BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.asm; path = xt_forwarding_trampoline_x86.s; sourceTree = ""; };
BB5155D92CDED44400704C7A /* InjectionHybrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectionHybrid.swift; sourceTree = ""; };
AA0000002F08000000000001 /* ControlServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlServer.swift; sourceTree = ""; };
BB67DBB21FB0CDA8000EAC8A /* SimpleSocket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SimpleSocket.h; path = ../../Sources/InjectionNextC/include/SimpleSocket.h; sourceTree = ""; };
BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = SimpleSocket.mm; path = ../../Sources/InjectionNextC/SimpleSocket.mm; sourceTree = ""; };
BB6A56EF2C50E73600C92112 /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; };
BB6A56F22C51154F00C92112 /* InjectionBundle-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "InjectionBundle-Bridging-Header.h"; sourceTree = ""; };
BB85A4242D6B9C5300FA29D0 /* feedcommands */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = feedcommands; sourceTree = BUILT_PRODUCTS_DIR; };
BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrontendServer.swift; sourceTree = ""; };
BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "swift-frontend.sh"; sourceTree = ""; };
BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/ScriptingBridge.framework; sourceTree = DEVELOPER_DIR; };
BBB64DE41FD571300020BE47 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
BBB64FEB1FD585D50020BE47 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
BBBEC42D1FC56522004188B3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InjectionNext.app; sourceTree = BUILT_PRODUCTS_DIR; };
BBCA02011FB0F10300E45F0F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
BBCA02031FB0F10300E45F0F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
BBCA02061FB0F10300E45F0F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
BBCA02081FB0F10400E45F0F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
BBCA020B1FB0F10400E45F0F /* InjectionNext.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = InjectionNext.entitlements; sourceTree = ""; };
BBCA02581FB112F500E45F0F /* App.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = App.icns; sourceTree = ""; };
BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionIdle.tif; sourceTree = ""; };
BBCA025F1FB122C300E45F0F /* InjectionOK.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionOK.tif; sourceTree = ""; };
BBCCC3B02C73A6AD00E71F81 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/WatchOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
BBDD84132C4FE749000F3124 /* macOSInjection.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSInjection.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InjectionNext.swift; path = ../../Sources/InjectionNext/InjectionNext.swift; sourceTree = ""; };
BBDD843A2C4FE892000F3124 /* InjectionBoot.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = InjectionBoot.mm; path = ../../InjectionLite/Sources/InjectionImplC/InjectionBoot.mm; sourceTree = ""; };
BBDD84532C4FEB16000F3124 /* TupleRegex.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TupleRegex.swift; path = ../../SwiftRegex5/SwiftRegex5.playground/Sources/TupleRegex.swift; sourceTree = ""; };
BBDD84562C4FED60000F3124 /* build_bundles.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_bundles.sh; sourceTree = ""; };
BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = copy_bundle.sh; sourceTree = ""; };
BBDD84592C4FF645000F3124 /* ClientBoot.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ClientBoot.mm; path = ../../Sources/InjectionNextC/ClientBoot.mm; sourceTree = ""; };
BBDF943C2349277A00334E08 /* InjectionNext-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "InjectionNext-Bridging-Header.h"; sourceTree = ""; };
BBE490D91FB2C643003D41BB /* InjectionBusy.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionBusy.tif; sourceTree = ""; };
BBE490DA1FB2C643003D41BB /* InjectionError.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = InjectionError.tif; sourceTree = ""; };
BBF6572C2DAFF00200638170 /* NIMBLE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = NIMBLE.md; sourceTree = ""; };
BBF6572D2DAFF00200638170 /* QUICK.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = QUICK.md; sourceTree = ""; };
BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRefs.swift; sourceTree = ""; };
CEC17029253ED117002E823F /* Experimental.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Experimental.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
BB85A4252D6B9C5300FA29D0 /* feedcommands */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = feedcommands; sourceTree = ""; };
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
BB85A4212D6B9C5300FA29D0 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
BBCA01FB1FB0F10300E45F0F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2217BE812C0A65C20032A832 /* Fortify in Frameworks */,
22E3D98D2C19009900BB234E /* DLKitC in Frameworks */,
BBB64FEC1FD585D50020BE47 /* WebKit.framework in Frameworks */,
22E3D98B2C19009900BB234E /* DLKit in Frameworks */,
BBB040641FB17A6C007DDD0A /* ScriptingBridge.framework in Frameworks */,
224E57FB2C08978F00B71C79 /* SwiftRegex in Frameworks */,
BBB64DE51FD571310020BE47 /* libz.tbd in Frameworks */,
BB2D41AC2DAD14EA0073C203 /* Popen in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BBDD84102C4FE749000F3124 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
BB0CECB02C73C1610000A4E6 /* Quick in Frameworks */,
BBEB87122C74FD930044578A /* Quick in Frameworks */,
BB0CECB32C73C1890000A4E6 /* Nimble in Frameworks */,
BBCCC3B72C73B6FD00E71F81 /* XCTest.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
BB42F8E02EB626F100FDBBCC /* SwiftTrace */ = {
isa = PBXGroup;
children = (
BB47888F2EB6B81A00464AB4 /* SwiftTraceGuts */,
BB42F8D22EB626F100FDBBCC /* EasyPointer.swift */,
BB42F8D32EB626F100FDBBCC /* Info.plist */,
BB42F8D42EB626F100FDBBCC /* StringIndex.swift */,
BB42F8D52EB626F100FDBBCC /* SwiftArgs.swift */,
BB42F8D62EB626F100FDBBCC /* SwiftAspects.swift */,
BB42F8D72EB626F100FDBBCC /* SwiftInterpose.swift */,
BB42F8D82EB626F100FDBBCC /* SwiftInvoke.swift */,
BB42F8D92EB626F100FDBBCC /* SwiftLifetime.swift */,
BB42F8DA2EB626F100FDBBCC /* SwiftMeta.swift */,
BBFE1B412ED7C116004E14EE /* SwiftRefs.swift */,
BB42F8DB2EB626F100FDBBCC /* SwiftStack.swift */,
BB42F8DC2EB626F100FDBBCC /* SwiftStats.swift */,
BB42F8DD2EB626F100FDBBCC /* SwiftSwizzle.swift */,
BB42F8DE2EB626F100FDBBCC /* SwiftTrace.h */,
BB42F8DF2EB626F100FDBBCC /* SwiftTrace.swift */,
);
name = SwiftTrace;
path = ../SwiftTrace/SwiftTrace;
sourceTree = SOURCE_ROOT;
};
BB42F9202EB639D100FDBBCC /* DLKit */ = {
isa = PBXGroup;
children = (
BB42F92C2EB639E500FDBBCC /* DLKitC */,
BB42F91A2EB639D100FDBBCC /* Demangling.swift */,
BB42F91B2EB639D100FDBBCC /* DLKit.swift */,
BB42F91C2EB639D100FDBBCC /* FileSymbols.swift */,
BB42F91D2EB639D100FDBBCC /* ImageSymbols.swift */,
BB42F91E2EB639D100FDBBCC /* Interposing.swift */,
BB42F91F2EB639D100FDBBCC /* Iterators.swift */,
);
name = DLKit;
path = ../DLKit/Sources/DLKit;
sourceTree = SOURCE_ROOT;
};
BB42F9282EB639E500FDBBCC /* include */ = {
isa = PBXGroup;
children = (
BB42F9272EB639E500FDBBCC /* DLKitC.h */,
);
path = include;
sourceTree = "";
};
BB42F92C2EB639E500FDBBCC /* DLKitC */ = {
isa = PBXGroup;
children = (
BB42F9282EB639E500FDBBCC /* include */,
BB42F9292EB639E500FDBBCC /* DLKitC.c */,
BB42F92A2EB639E500FDBBCC /* trie_dladdr.mm */,
BB42F92B2EB639E500FDBBCC /* trie_dlops.mm */,
);
name = DLKitC;
path = ../DLKit/Sources/DLKitC;
sourceTree = SOURCE_ROOT;
};
BB42F9372EB63C2900FDBBCC /* InjectionImpl */ = {
isa = PBXGroup;
children = (
BB47887F2EB6B6E700464AB4 /* InjectionImplC */,
BB42F9312EB63C2900FDBBCC /* Common.swift */,
BB42F9322EB63C2900FDBBCC /* Generics.swift */,
BB42F9332EB63C2900FDBBCC /* KeyPaths.swift */,
BB42F9342EB63C2900FDBBCC /* Metadata.swift */,
BB42F9352EB63C2900FDBBCC /* Reloader.swift */,
BB42F9362EB63C2900FDBBCC /* Sweeper.swift */,
);
name = InjectionImpl;
path = ../InjectionLite/Sources/InjectionImpl;
sourceTree = SOURCE_ROOT;
};
BB42F9452EB63E3400FDBBCC /* InjectionLite */ = {
isa = PBXGroup;
children = (
BB42F9402EB63E3400FDBBCC /* FileWatcher.swift */,
BB42F9412EB63E3400FDBBCC /* InjectionBase.swift */,
BB42F9422EB63E3400FDBBCC /* InjectionLite.swift */,
BB42F9432EB63E3400FDBBCC /* LogParser.swift */,
BB42F9442EB63E3400FDBBCC /* Recompiler.swift */,
);
name = InjectionLite;
path = ../InjectionLite/Sources/InjectionLite;
sourceTree = SOURCE_ROOT;
};
BB42F9542EB63EFF00FDBBCC /* InjectionBazel */ = {
isa = PBXGroup;
children = (
BB42F94F2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift */,
BB42F9502EB63EFF00FDBBCC /* BazelAQueryParser.swift */,
BB42F9512EB63EFF00FDBBCC /* BazelInterface.swift */,
BB42F9522EB63EFF00FDBBCC /* FilenameMatcher.swift */,
BB42F9532EB63EFF00FDBBCC /* GitIgnoreParser.swift */,
);
name = InjectionBazel;
path = ../InjectionLite/Sources/InjectionBazel;
sourceTree = SOURCE_ROOT;
};
BB439B5C1FABA64300B4F50B = {
isa = PBXGroup;
children = (
BB037DF41FAD80D0004B267C /* LICENSE */,
BB2A66352E3E753B001EDD38 /* BAZEL.md */,
BBF6572D2DAFF00200638170 /* QUICK.md */,
BBF6572C2DAFF00200638170 /* NIMBLE.md */,
BB037DF31FAD808B004B267C /* README.md */,
BBCA01FF1FB0F10300E45F0F /* InjectionNext */,
BBDD84172C4FE79E000F3124 /* InjectionBundle */,
BB85A4252D6B9C5300FA29D0 /* feedcommands */,
BB439B661FABA64300B4F50B /* Products */,
BBB040621FB1798A007DDD0A /* Frameworks */,
);
indentWidth = 4;
sourceTree = "";
tabWidth = 4;
};
BB439B661FABA64300B4F50B /* Products */ = {
isa = PBXGroup;
children = (
BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */,
BBDD84132C4FE749000F3124 /* macOSInjection.bundle */,
BB85A4242D6B9C5300FA29D0 /* feedcommands */,
);
name = Products;
sourceTree = "";
};
BB47887D2EB6B6E700464AB4 /* include */ = {
isa = PBXGroup;
children = (
BB47887C2EB6B6E700464AB4 /* InjectionImplC.h */,
);
path = include;
sourceTree = "";
};
BB47887F2EB6B6E700464AB4 /* InjectionImplC */ = {
isa = PBXGroup;
children = (
BB47887D2EB6B6E700464AB4 /* include */,
BB47887E2EB6B6E700464AB4 /* InjectionBoot.mm */,
);
name = InjectionImplC;
path = ../InjectionLite/Sources/InjectionImplC;
sourceTree = SOURCE_ROOT;
};
BB4788842EB6B81A00464AB4 /* include */ = {
isa = PBXGroup;
children = (
BB4788822EB6B81A00464AB4 /* fishhook.h */,
BB4788832EB6B81A00464AB4 /* SwiftTrace.h */,
);
path = include;
sourceTree = "";
};
BB47888F2EB6B81A00464AB4 /* SwiftTraceGuts */ = {
isa = PBXGroup;
children = (
BB4788842EB6B81A00464AB4 /* include */,
BB4788852EB6B81A00464AB4 /* fast_dladdr.mm */,
BB4788862EB6B81A00464AB4 /* fishhook.c */,
BB4788872EB6B81A00464AB4 /* ObjCBridge.mm */,
BB4788882EB6B81A00464AB4 /* SwiftTrace.mm */,
BB4788892EB6B81A00464AB4 /* SwiftTrace-Swift.h */,
BB47888A2EB6B81A00464AB4 /* Trampolines.mm */,
BB47888B2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s */,
BB47888C2EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s */,
BB47888D2EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s */,
BB47888E2EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s */,
);
name = SwiftTraceGuts;
path = ../SwiftTrace/SwiftTraceGuts;
sourceTree = SOURCE_ROOT;
};
BBB040621FB1798A007DDD0A /* Frameworks */ = {
isa = PBXGroup;
children = (
BBCCC3B02C73A6AD00E71F81 /* XCTest.framework */,
BBB64FEB1FD585D50020BE47 /* WebKit.framework */,
BBB64DE41FD571300020BE47 /* libz.tbd */,
BBBEC42D1FC56522004188B3 /* XCTest.framework */,
BBB040631FB1798A007DDD0A /* ScriptingBridge.framework */,
);
name = Frameworks;
sourceTree = "";
};
BBCA01FF1FB0F10300E45F0F /* InjectionNext */ = {
isa = PBXGroup;
children = (
BB16653925E9A5F0001407AE /* main.m */,
BBCA02011FB0F10300E45F0F /* AppDelegate.swift */,
BB4654802C257F110080EC40 /* NextCompiler.swift */,
224E57FF2C08BC0E00B71C79 /* MonitorXcode.swift */,
224E57FD2C08BBE300B71C79 /* InjectionServer.swift */,
BB5155D92CDED44400704C7A /* InjectionHybrid.swift */,
BBA082352D6BC32D00FFFB2F /* FrontendServer.swift */,
CEC17029253ED117002E823F /* Experimental.swift */,
BB6A56EF2C50E73600C92112 /* Defaults.swift */,
22B6459F2C18DD9D00F99B61 /* Unhider.swift */,
AA0000002F08000000000001 /* ControlServer.swift */,
224E57FC2C08BAE200B71C79 /* InjectionClient.h */,
BB67DBB21FB0CDA8000EAC8A /* SimpleSocket.h */,
BB67DBB31FB0CDA8000EAC8A /* SimpleSocket.mm */,
BBCA02031FB0F10300E45F0F /* Assets.xcassets */,
BBCA025E1FB122C300E45F0F /* InjectionIdle.tif */,
222BC9442C157C3200780A41 /* InjectionReady.tif */,
BBCA025F1FB122C300E45F0F /* InjectionOK.tif */,
BBE490D91FB2C643003D41BB /* InjectionBusy.tif */,
BBE490DA1FB2C643003D41BB /* InjectionError.tif */,
BBCA02051FB0F10300E45F0F /* MainMenu.xib */,
BBCA02081FB0F10400E45F0F /* Info.plist */,
BBCA02581FB112F500E45F0F /* App.icns */,
BBCA020B1FB0F10400E45F0F /* InjectionNext.entitlements */,
BBDF943C2349277A00334E08 /* InjectionNext-Bridging-Header.h */,
BB42F9542EB63EFF00FDBBCC /* InjectionBazel */,
BBDD84562C4FED60000F3124 /* build_bundles.sh */,
BBDD84582C4FF0B9000F3124 /* copy_bundle.sh */,
BBA082372D6BD6CB00FFFB2F /* swift-frontend.sh */,
);
path = InjectionNext;
sourceTree = "";
};
BBDD84172C4FE79E000F3124 /* InjectionBundle */ = {
isa = PBXGroup;
children = (
BB42F9302EB63AAF00FDBBCC /* InjectionImplC.h */,
BBDD84592C4FF645000F3124 /* ClientBoot.mm */,
BBDD843A2C4FE892000F3124 /* InjectionBoot.mm */,
BBDD84182C4FE7E6000F3124 /* InjectionNext.swift */,
BBDD84532C4FEB16000F3124 /* TupleRegex.swift */,
BB42F9452EB63E3400FDBBCC /* InjectionLite */,
BB42F9372EB63C2900FDBBCC /* InjectionImpl */,
BB42F9202EB639D100FDBBCC /* DLKit */,
BB42F8E02EB626F100FDBBCC /* SwiftTrace */,
BB6A56F22C51154F00C92112 /* InjectionBundle-Bridging-Header.h */,
);
path = InjectionBundle;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
BB85A4232D6B9C5300FA29D0 /* feedcommands */ = {
isa = PBXNativeTarget;
buildConfigurationList = BB85A42A2D6B9C5300FA29D0 /* Build configuration list for PBXNativeTarget "feedcommands" */;
buildPhases = (
BBDD999A2F4AF61000E88DFB /* ShellScript */,
BB85A4202D6B9C5300FA29D0 /* Sources */,
BB85A4212D6B9C5300FA29D0 /* Frameworks */,
BB85A4222D6B9C5300FA29D0 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
BB85A4252D6B9C5300FA29D0 /* feedcommands */,
);
name = feedcommands;
packageProductDependencies = (
);
productName = feedcommands;
productReference = BB85A4242D6B9C5300FA29D0 /* feedcommands */;
productType = "com.apple.product-type.tool";
};
BBCA01FD1FB0F10300E45F0F /* InjectionNext */ = {
isa = PBXNativeTarget;
buildConfigurationList = BBCA020C1FB0F10400E45F0F /* Build configuration list for PBXNativeTarget "InjectionNext" */;
buildPhases = (
22240ECB2C0C7178004DCBAD /* Run Script */,
BBCA01FA1FB0F10300E45F0F /* Sources */,
BBCA01FB1FB0F10300E45F0F /* Frameworks */,
BBCA01FC1FB0F10300E45F0F /* Resources */,
BB6224721FC5BA6F00AD7A3A /* CopyFiles */,
BB6C87DB2520D0D3005AFCFC /* Embed Frameworks */,
BBDD84572C4FED87000F3124 /* ShellScript */,
);
buildRules = (
);
dependencies = (
BB85A42D2D6B9CA200FA29D0 /* PBXTargetDependency */,
BBDD843E2C4FE8C5000F3124 /* PBXTargetDependency */,
);
name = InjectionNext;
packageProductDependencies = (
224E57FA2C08978F00B71C79 /* SwiftRegex */,
2217BE802C0A65C20032A832 /* Fortify */,
22E3D98A2C19009900BB234E /* DLKit */,
22E3D98C2C19009900BB234E /* DLKitC */,
BB2D41AB2DAD14EA0073C203 /* Popen */,
);
productName = InjectionIII;
productReference = BBCA01FE1FB0F10300E45F0F /* InjectionNext.app */;
productType = "com.apple.product-type.application";
};
BBDD84122C4FE749000F3124 /* InjectionBundle */ = {
isa = PBXNativeTarget;
buildConfigurationList = BBDD84162C4FE749000F3124 /* Build configuration list for PBXNativeTarget "InjectionBundle" */;
buildPhases = (
BBDD840F2C4FE749000F3124 /* Sources */,
BBDD84102C4FE749000F3124 /* Frameworks */,
BBDD84112C4FE749000F3124 /* Resources */,
);
buildRules = (
);
dependencies = (
BB2D41AE2DAD15220073C203 /* PBXTargetDependency */,
);
name = InjectionBundle;
productName = InjectionBundle;
productReference = BBDD84132C4FE749000F3124 /* macOSInjection.bundle */;
productType = "com.apple.product-type.bundle";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
BB439B5D1FABA64300B4F50B /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1150;
LastUpgradeCheck = 1640;
ORGANIZATIONNAME = "John Holdsworth";
TargetAttributes = {
BB85A4232D6B9C5300FA29D0 = {
CreatedOnToolsVersion = 16.3;
};
BBCA01FD1FB0F10300E45F0F = {
CreatedOnToolsVersion = 9.0.1;
LastSwiftMigration = 1110;
ProvisioningStyle = Manual;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
BBDD84122C4FE749000F3124 = {
CreatedOnToolsVersion = 15.2;
};
};
};
buildConfigurationList = BB439B601FABA64300B4F50B /* Build configuration list for PBXProject "InjectionNext" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = BB439B5C1FABA64300B4F50B;
packageReferences = (
224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */,
2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */,
22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */,
BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */,
BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */,
BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */,
);
productRefGroup = BB439B661FABA64300B4F50B /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
BBCA01FD1FB0F10300E45F0F /* InjectionNext */,
BBDD84122C4FE749000F3124 /* InjectionBundle */,
BB85A4232D6B9C5300FA29D0 /* feedcommands */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
BBCA01FC1FB0F10300E45F0F /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB4788812EB6B71600464AB4 /* InjectionImplC.h in Resources */,
BB85A42B2D6B9C7B00FA29D0 /* feedcommands in Resources */,
BBDD843F2C4FE8D5000F3124 /* macOSInjection.bundle in Resources */,
BBA082382D6BD6CB00FFFB2F /* swift-frontend.sh in Resources */,
BB6A56F12C50E79800C92112 /* copy_bundle.sh in Resources */,
BBB64DC61FD450B40020BE47 /* LICENSE in Resources */,
BB2A66362E3E753B001EDD38 /* BAZEL.md in Resources */,
BBB64DC51FD450AF0020BE47 /* README.md in Resources */,
BBE490DC1FB2C643003D41BB /* InjectionError.tif in Resources */,
BBCA02591FB112F500E45F0F /* App.icns in Resources */,
BBCA02601FB122C300E45F0F /* InjectionIdle.tif in Resources */,
BBF6572E2DAFF00200638170 /* NIMBLE.md in Resources */,
BBF6572F2DAFF00200638170 /* QUICK.md in Resources */,
BBCA02611FB122C300E45F0F /* InjectionOK.tif in Resources */,
BBCA02041FB0F10300E45F0F /* Assets.xcassets in Resources */,
222BC9452C157C3200780A41 /* InjectionReady.tif in Resources */,
BBCA02071FB0F10300E45F0F /* MainMenu.xib in Resources */,
BBE490DB1FB2C643003D41BB /* InjectionBusy.tif in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BBDD84112C4FE749000F3124 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
22240ECB2C0C7178004DCBAD /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Run Script";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ \"$ACTION\" == \"install\" ]; then exit 0; fi\nbuildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\nchmod +w \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n";
};
BBDD84572C4FED87000F3124 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 12;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nInjectionNext/build_bundles.sh\n";
};
BBDD999A2F4AF61000E88DFB /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "perl -e 'print \"#define COMMANDS_VERSION @{[1_000_000 + int(rand() * ((1 << 31)) - 1_000_000)]}\\n\"' >/tmp/InjectionNextSalt.h\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
BB85A4202D6B9C5300FA29D0 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB85A42E2D6BA13B00FA29D0 /* SimpleSocket.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BBCA01FA1FB0F10300E45F0F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BB6A56F02C50E73600C92112 /* Defaults.swift in Sources */,
BB42F93F2EB63CD800FDBBCC /* Common.swift in Sources */,
BB42F94C2EB63E4F00FDBBCC /* LogParser.swift in Sources */,
224E58002C08BC0E00B71C79 /* MonitorXcode.swift in Sources */,
BB42F94D2EB63E5600FDBBCC /* Recompiler.swift in Sources */,
229127F12C1EEF69005D7625 /* Experimental.swift in Sources */,
BBA082362D6BC33500FFFB2F /* FrontendServer.swift in Sources */,
BBCA022A1FB0F64800E45F0F /* SimpleSocket.mm in Sources */,
BB42F94E2EB63E7700FDBBCC /* InjectionBase.swift in Sources */,
BB16653A25E9A5F2001407AE /* main.m in Sources */,
BBCA02021FB0F10300E45F0F /* AppDelegate.swift in Sources */,
BB5155DA2CDED44F00704C7A /* InjectionHybrid.swift in Sources */,
BB42F94B2EB63E3F00FDBBCC /* FileWatcher.swift in Sources */,
BB4654812C257F110080EC40 /* NextCompiler.swift in Sources */,
BB42F95A2EB63EFF00FDBBCC /* BazelInterface.swift in Sources */,
BB42F95B2EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */,
BB42F95C2EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */,
BB42F95D2EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */,
BB42F95E2EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */,
224E57FE2C08BBE300B71C79 /* InjectionServer.swift in Sources */,
22B645A02C18DD9D00F99B61 /* Unhider.swift in Sources */,
AA0000012F08000000000001 /* ControlServer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BBDD840F2C4FE749000F3124 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BBDD84422C4FEA4E000F3124 /* InjectionNext.swift in Sources */,
BB42F8E12EB626F100FDBBCC /* SwiftAspects.swift in Sources */,
BB42F8E22EB626F100FDBBCC /* SwiftInterpose.swift in Sources */,
BB42F8E32EB626F100FDBBCC /* SwiftTrace.swift in Sources */,
BB42F8E42EB626F100FDBBCC /* SwiftSwizzle.swift in Sources */,
BB42F8E52EB626F100FDBBCC /* SwiftArgs.swift in Sources */,
BB42F8E62EB626F100FDBBCC /* SwiftInvoke.swift in Sources */,
BB42F8E72EB626F100FDBBCC /* EasyPointer.swift in Sources */,
BB42F8E82EB626F100FDBBCC /* SwiftStack.swift in Sources */,
BB42F9552EB63EFF00FDBBCC /* BazelInterface.swift in Sources */,
BB4788902EB6B81A00464AB4 /* fast_dladdr.mm in Sources */,
BB4788912EB6B81A00464AB4 /* xt_forwarding_trampoline_arm64.s in Sources */,
BB4788922EB6B81A00464AB4 /* xt_forwarding_trampoline_x64.s in Sources */,
BB4788932EB6B81A00464AB4 /* ObjCBridge.mm in Sources */,
BB4788942EB6B81A00464AB4 /* Trampolines.mm in Sources */,
BB4788952EB6B81A00464AB4 /* xt_forwarding_trampoline_arm7.s in Sources */,
BB4788962EB6B81A00464AB4 /* SwiftTrace.mm in Sources */,
BB4788972EB6B81A00464AB4 /* xt_forwarding_trampoline_x86.s in Sources */,
BB4788982EB6B81A00464AB4 /* fishhook.c in Sources */,
BB42F9562EB63EFF00FDBBCC /* GitIgnoreParser.swift in Sources */,
BB42F9572EB63EFF00FDBBCC /* BazelActionQueryHandler.swift in Sources */,
BB42F9582EB63EFF00FDBBCC /* BazelAQueryParser.swift in Sources */,
BB42F9592EB63EFF00FDBBCC /* FilenameMatcher.swift in Sources */,
BB42F8E92EB626F100FDBBCC /* SwiftStats.swift in Sources */,
BB42F9212EB639D100FDBBCC /* DLKit.swift in Sources */,
BB42F9222EB639D100FDBBCC /* Iterators.swift in Sources */,
BB42F9232EB639D100FDBBCC /* Interposing.swift in Sources */,
BBFE1B422ED7C116004E14EE /* SwiftRefs.swift in Sources */,
BB42F9242EB639D100FDBBCC /* ImageSymbols.swift in Sources */,
BB42F9252EB639D100FDBBCC /* Demangling.swift in Sources */,
BB42F9262EB639D100FDBBCC /* FileSymbols.swift in Sources */,
BB42F8EA2EB626F100FDBBCC /* StringIndex.swift in Sources */,
BB42F8EB2EB626F100FDBBCC /* SwiftLifetime.swift in Sources */,
BB42F9462EB63E3400FDBBCC /* InjectionBase.swift in Sources */,
BB42F9472EB63E3400FDBBCC /* Recompiler.swift in Sources */,
BB42F9482EB63E3400FDBBCC /* InjectionLite.swift in Sources */,
BB42F9492EB63E3400FDBBCC /* LogParser.swift in Sources */,
BB42F94A2EB63E3400FDBBCC /* FileWatcher.swift in Sources */,
BB42F8EC2EB626F100FDBBCC /* SwiftMeta.swift in Sources */,
BB4788802EB6B6E700464AB4 /* InjectionBoot.mm in Sources */,
BBDD845A2C4FF646000F3124 /* ClientBoot.mm in Sources */,
BB42F92D2EB639E500FDBBCC /* DLKitC.c in Sources */,
BB42F92E2EB639E500FDBBCC /* trie_dladdr.mm in Sources */,
BB42F92F2EB639E500FDBBCC /* trie_dlops.mm in Sources */,
BBDD84542C4FEB16000F3124 /* TupleRegex.swift in Sources */,
BBDD84552C4FEC98000F3124 /* SimpleSocket.mm in Sources */,
BB42F9382EB63C2900FDBBCC /* KeyPaths.swift in Sources */,
BB42F9392EB63C2900FDBBCC /* Reloader.swift in Sources */,
BB52AD552F1501F500297CD9 /* Unhider.swift in Sources */,
BB42F93A2EB63C2900FDBBCC /* Generics.swift in Sources */,
BB42F93B2EB63C2900FDBBCC /* Metadata.swift in Sources */,
BB42F93C2EB63C2900FDBBCC /* Common.swift in Sources */,
BB42F93D2EB63C2900FDBBCC /* Sweeper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
BB2D41AE2DAD15220073C203 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = BB2D41AD2DAD15220073C203 /* Popen */;
};
BB85A42D2D6B9CA200FA29D0 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BB85A4232D6B9C5300FA29D0 /* feedcommands */;
targetProxy = BB85A42C2D6B9CA200FA29D0 /* PBXContainerItemProxy */;
};
BBDD843E2C4FE8C5000F3124 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BBDD84122C4FE749000F3124 /* InjectionBundle */;
targetProxy = BBDD843D2C4FE8C5000F3124 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
BBCA02051FB0F10300E45F0F /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
BBCA02061FB0F10300E45F0F /* Base */,
);
name = MainMenu.xib;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
BB439B821FABA64300B4F50B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 9V5A8WE85E;
EMIT_FRONTEND_COMMAND_LINES = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
FRAMEWORK_SEARCH_PATHS = $PLATFORM_DIR/Developer/Library/Frameworks;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = $SRCROOT/HotReloading/Sources/HotReloadingGuts/include;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)",
$PLATFORM_DIR/Developer/Library/Frameworks,
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=150";
PRODUCT_NAME = macOSInjection;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
BB439B831FABA64300B4F50B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 9V5A8WE85E;
EMIT_FRONTEND_COMMAND_LINES = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
FRAMEWORK_SEARCH_PATHS = $PLATFORM_DIR/Developer/Library/Frameworks;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
HEADER_SEARCH_PATHS = $SRCROOT/HotReloading/Sources/HotReloadingGuts/include;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(SWIFT_PLATFORM_TARGET_PREFIX)",
$PLATFORM_DIR/Developer/Library/Frameworks,
);
MTL_ENABLE_DEBUG_INFO = NO;
PRODUCT_NAME = macOSInjection;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
BB85A4282D6B9C5300FA29D0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
BB85A4292D6B9C5300FA29D0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 15.1;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Release;
};
BBCA020D1FB0F10400E45F0F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = InjectionNext/InjectionNext.entitlements;
CODE_SIGN_IDENTITY = "-";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_DEBUG_DYLIB = NO;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = InjectionNext/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionNext;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) INJECTION_NEXT_APP";
SWIFT_OBJC_BRIDGING_HEADER = "InjectionNext/InjectionNext-Bridging-Header.h";
};
name = Debug;
};
BBCA020E1FB0F10400E45F0F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = InjectionNext/InjectionNext.entitlements;
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
ENABLE_DEBUG_DYLIB = NO;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = InjectionNext/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionNext;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) INJECTION_NEXT_APP";
SWIFT_OBJC_BRIDGING_HEADER = "InjectionNext/InjectionNext-Bridging-Header.h";
};
name = Release;
};
BBDD84142C4FE749000F3124 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c17;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 John Holdsworth. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_DYLIB_INSTALL_NAME = "/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib,
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks,
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/PrivateFrameworks,
);
LIBRARY_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/usr/lib";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACH_O_TYPE = mh_dylib;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-lXCTestSwiftSupport",
"$(BUILT_PRODUCTS_DIR)/Popen.o",
);
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle;
PRODUCT_MODULE_NAME = InjectionBundle;
PRODUCT_NAME = macOSInjection;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "InjectionBundle/InjectionBundle-Bridging-Header.h";
WRAPPER_EXTENSION = bundle;
};
name = Debug;
};
BBDD84152C4FE749000F3124 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++17";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 John Holdsworth. All rights reserved.";
INFOPLIST_KEY_NSPrincipalClass = "";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
LD_DYLIB_INSTALL_NAME = "/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib,
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks,
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/PrivateFrameworks,
);
LIBRARY_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/usr/lib";
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACH_O_TYPE = mh_dylib;
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = (
"-lXCTestSwiftSupport",
"$(BUILT_PRODUCTS_DIR)/Popen.o",
);
PRODUCT_BUNDLE_IDENTIFIER = com.johnholdsworth.InjectionBundle;
PRODUCT_MODULE_NAME = InjectionBundle;
PRODUCT_NAME = macOSInjection;
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "InjectionBundle/InjectionBundle-Bridging-Header.h";
WRAPPER_EXTENSION = bundle;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
BB439B601FABA64300B4F50B /* Build configuration list for PBXProject "InjectionNext" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BB439B821FABA64300B4F50B /* Debug */,
BB439B831FABA64300B4F50B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BB85A42A2D6B9C5300FA29D0 /* Build configuration list for PBXNativeTarget "feedcommands" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BB85A4282D6B9C5300FA29D0 /* Debug */,
BB85A4292D6B9C5300FA29D0 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BBCA020C1FB0F10400E45F0F /* Build configuration list for PBXNativeTarget "InjectionNext" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BBCA020D1FB0F10400E45F0F /* Debug */,
BBCA020E1FB0F10400E45F0F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BBDD84162C4FE749000F3124 /* Build configuration list for PBXNativeTarget "InjectionBundle" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BBDD84142C4FE749000F3124 /* Debug */,
BBDD84152C4FE749000F3124 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/Fortify";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.1.12;
};
};
224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/SwiftRegex5";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 6.3.0;
};
};
22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/DLKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.5.7;
};
};
BB0CECAE2C73C1610000A4E6 /* XCRemoteSwiftPackageReference "Quick" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Quick/Quick";
requirement = {
branch = main;
kind = branch;
};
};
BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/Quick/Nimble.git";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 13.2.0;
};
};
BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/Popen";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.2.1;
};
};
BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/johnno1962/Quick";
requirement = {
branch = uncached;
kind = branch;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
2217BE802C0A65C20032A832 /* Fortify */ = {
isa = XCSwiftPackageProductDependency;
package = 2217BE7F2C0A65C20032A832 /* XCRemoteSwiftPackageReference "Fortify" */;
productName = Fortify;
};
224E57FA2C08978F00B71C79 /* SwiftRegex */ = {
isa = XCSwiftPackageProductDependency;
package = 224E57F92C08978F00B71C79 /* XCRemoteSwiftPackageReference "SwiftRegex5" */;
productName = SwiftRegex;
};
22E3D98A2C19009900BB234E /* DLKit */ = {
isa = XCSwiftPackageProductDependency;
package = 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */;
productName = DLKit;
};
22E3D98C2C19009900BB234E /* DLKitC */ = {
isa = XCSwiftPackageProductDependency;
package = 22E3D9892C19009900BB234E /* XCRemoteSwiftPackageReference "DLKit" */;
productName = DLKitC;
};
BB0CECAF2C73C1610000A4E6 /* Quick */ = {
isa = XCSwiftPackageProductDependency;
package = BB0CECAE2C73C1610000A4E6 /* XCRemoteSwiftPackageReference "Quick" */;
productName = Quick;
};
BB0CECB22C73C1890000A4E6 /* Nimble */ = {
isa = XCSwiftPackageProductDependency;
package = BB0CECB12C73C1890000A4E6 /* XCRemoteSwiftPackageReference "Nimble" */;
productName = Nimble;
};
BB2D41AB2DAD14EA0073C203 /* Popen */ = {
isa = XCSwiftPackageProductDependency;
package = BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */;
productName = Popen;
};
BB2D41AD2DAD15220073C203 /* Popen */ = {
isa = XCSwiftPackageProductDependency;
package = BB2D41A42DAD10110073C203 /* XCRemoteSwiftPackageReference "Popen" */;
productName = Popen;
};
BBEB87112C74FD930044578A /* Quick */ = {
isa = XCSwiftPackageProductDependency;
package = BBEB87102C74FD930044578A /* XCRemoteSwiftPackageReference "Quick" */;
productName = Quick;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = BB439B5D1FABA64300B4F50B /* Project object */;
}
================================================
FILE: App/InjectionNext.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
IDEDidComputeMac32BitWarning
================================================
FILE: App/InjectionNext.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
================================================
================================================
FILE: App/InjectionNext.xcodeproj/xcshareddata/xcschemes/InjectionNext.xcscheme
================================================
================================================
FILE: App/LICENSE
================================================
MIT License
Copyright (c) 2024 John Holdsworth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: App/NIMBLE.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016 Quick Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: App/QUICK.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014, Quick Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: App/feedcommands/main.mm
================================================
//
// main.mm
// feedcommands
//
// Created by John Holdsworth on 23/02/2025.
// Copyright © 2025 John Holdsworth. All rights reserved.
//
#import
#import "/tmp/InjectionNextSalt.h"
#import "../../../InjectionNext/Sources/InjectionNextC/include/SimpleSocket.h"
#import "../../../InjectionNext/Sources/InjectionNextC/include/InjectionClient.h"
@interface FeedSocket: SimpleSocket
@end
@implementation FeedSocket {
FILE *out;
}
+ (int)error:(NSString *)message {
if (isatty(STDIN_FILENO))
printf([NSString stringWithFormat:@"%@/%@\n",
self, message].UTF8String, strerror(errno));
return 1;
}
- (BOOL)writeBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd {
if (!out)
out = fdopen(clientSocket, "w");
return fwrite(buffer, 1, length, out) == length;
}
- (void)dealloc {
if (out)
fclose(out);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
if (SimpleSocket *app = [FeedSocket connectTo:@COMMANDS_PORT]) {
[app writeInt:COMMANDS_VERSION];
[app writeString:NSHomeDirectory()];
for (int i=1; i/rules_xcodeproj.noindex/build_output_base/` rather than the main Bazel output base. InjectionNext automatically detects this and:
- Resolves `bazel-out/` paths to the rules_xcodeproj output base
- Maps aquery configuration hashes (e.g. `ios_sim_arm64-fastbuild-*`) to the corresponding rules_xcodeproj configs (e.g. `ios_sim_arm64-dbg-*`)
- Uses the rules_xcodeproj exec root as the working directory for recompilation
This is transparent — hot reloading works the same whether you build via `bazel run` or from Xcode with a rules_xcodeproj-generated project.
================================================
FILE: INTRO.md
================================================
## A lightning introduction to Injection
As it says in the README.md, using a feature of Apple's linker Code Injection
allows you to update the implementation (i.e. body) of functions in your
app without having to relauch it. There are three parts to implementing
this functionality. Recompiling your source file and preparing a dynamic
library, loading the dynamic library and stitching the new implementations
into your running program and arranging for the new implementations to be
called to you can see on the screen that your code changes have taken effect.
The first part is implemented by the InjectionNext.app that runs on the menu
bar, the second and third by code in the InjectionNext Swift package which
you add to the project you want to be able to inject. On startup the Swift
package creates a socket connection to the InjectionNext.app so it can
be controlled by it and instructed to load dynamic libaries either shared
with it on the filesystem or received through the socket connection for
real devices.
### Preparing the dynamic library
InjectionNext comes with a new solution to this problem. It integrates with
Xcode which if you launch it using the app (with SourceKit logging enabled)
provides all the information you need to recompile a file with the user's
changes which is the first step. The object file created by the recompilation
is then linked in a reasonably generic way to create a dyanmic library which
can be loaded into your program at run time using dlopen().
### Loading and binding the dynamic library
When a new dynamic library is available, the InjectionNext.app messages the
code in the InjectionNext swift package to load it and "bind" it. Binding is
the means by which a call site in your program is dispatched to the implementing
function and when you use the "Other Linker Flags", "-Xlinker -interposable"
this disptach is indirect through a writable section of memory. Change the
function pointer in this memory and your new version of code is called instead
of the old. This is done by a small piece of code from facebook named "fishhook"
and all that remains is to know the new functions just loaded in the dynamic
library. To do this you scan the "symbol table" data structures included in
the dynamic library to find the "mangled" function names. The mangled names
of Swift functions all start with "$s" and generally end in "F" or something
very similar and most of injection is simply extracting these names and the
implementation they point to and calling fishhook to rebind them. Classes
are a ittle more complicated in that they have "vtables" in the meta-data
describing the class but we don't need to explain that here. For Objective-C
methods there way always a public runtime API to rebind them.
### Forcing redisplay
This is the more difficult part of injection to realise. You can have the
new fuction implementtions loaded and rebound but you won't see any change in
the appearance on the screen until that new code is called. With SwiftUI the
esiest way to do this is to have an observed instance variable on your View
struct that changes when an injection has occured. There is code to do this in
the HotSwiftUI and Inject packages if you add an @ObserveInjection property
wrapper to the View struct. It's a little more complicated for legacy
UIViewController subclasses and what needs to happen generally is
viewDidLoad() needs to be called again each time you inject but how to arrange
this for all currently displayed view controllers? The solution is
InjectionNext performs a sweep all known instances of classes in the app
and if they have been injected in the last loaded dynamic library and have an
"@objc func injected()" method, call it. This method can call viewDidLoad()
or "configureView()" or perform any other work required to update the display.
Another approach you can use is is to "host" the view controller using the
Inject package which reinstantiates the view controller on injection.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 John Holdsworth
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version: 5.4
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "InjectionNext",
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "InjectionNext",
targets: ["InjectionBundle"]),
// To avoid duplicate symbols if other
// packages use e.g. DLKit or fishhook
// .library(
// name: "InjectionNextDynamic",
// type: .dynamic,
// targets: ["InjectionNext"]),
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "InjectionBundle", dependencies: ["InjectionNextC"],
path: "Sources/InjectionNext", swiftSettings: [.define("DEBUG_ONLY")]),
.target(
name: "InjectionNextC",
cSettings: [.define("DEBUG_ONLY"), .define("FISHHOOK_EXPORT")]),
.testTarget(
name: "InjectionNextTests",
dependencies: ["InjectionBundle"]),
]
)
================================================
FILE: README.md
================================================
# InjectionNext
### The fourth evolution of Code Injection for Xcode
Using a feature of Apple's linker this implementation of Code Injection
allows you to update the implementation (i.e. body) of functions in your
app without having to relaunch it. This can save a developer a significant
amount of time tweaking code or iterating over a design. This repo is a
refresh of the [InjectionIII](https://github.com/johnno1962/InjectionIII)
app that uses different techniques to determine how to rebuild source files
that should be faster and more reliable for very large projects.
The basic M.O. is to download one of the binary releases in this repo (or build
the app in the `App` directory), move it to /Applications, quit Xcode and run the
`InjectionNext.app` and use that to re-launch Xcode using the menu item
`Launch Xcode` from the status bar. You then add this repo as a Swift package
dependency of your project and that should be all that is required for injection
in the simulator, injection on devices and injection of a MacOS app. No more
code changes required to load binary code bundles etc and you can leave
the InjectionNext package configured into your project permanently as
its code is only included for a DEBUG build. Your code changes take effect
when you save a source for an app that has this package as a dependency
and has connected to the InjectIonNext app which has launched Xcode.
As ever, it is important to add the options `-Xlinker` and `-interposable`
(without double quotes and on separate lines) to the "Other Linker Flags" of
the targets of your project (for the `Debug` configuration only) to enable
function "interposing". Otherwise, you will only be able to inject non-final
class methods.

If your app contains local Swift packages, add the following to their targets
in Package.swift to be able to inject functions inside the package.
linkerSettings: [
.unsafeFlags(["-Xlinker", "-interposable"],
.when(configuration: .debug))
]
**Please note:** Due to an Xcode bug (as of version 16.2), `.when(configuration: .debug)`
is ignored for local package linker settings. Until it's fixed, you might find this
workaround helpful:
```swift
var linkerSettings: [LinkerSetting] {
let isInjectionRunning = ProcessInfo.processInfo.environment["RUNNING_VIA_INJECTION_NEXT"] != nil
return isInjectionRunning ? [.unsafeFlags(["-Xlinker", "-interposable"])] : []
}
```
**Please note:** you can only inject changes to code inside a function body
and you cannot add/remove or rename properties with storage or add or
reorder methods in a non final class or change function signatures.
To inject SwiftUI sucessfully a couple of minor code changes to each View are
required. Consult the README of repo https://github.com/johnno1962/HotSwiftUI
or you can make the required changes automatically using the menu item
"Prepare SwiftUI/...". For SwiftUI you would generally also integrate
either the [Inject](https://github.com/krzysztofzablocki/Inject) or
[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) package into your project.
If you'd rather not be adding a SPM dependency to your project, the app's
resources contains pre-built bundles which you can copy into your app during
the build by using a "Run Script/Build Phase" (while disabling the "user
script sandboxing" build setting) such as the following:
```
export RESOURCES="/Applications/InjectionNext.app/Contents/Resources"
if [ -f "$RESOURCES/copy_bundle.sh" ]; then
"$RESOURCES/copy_bundle.sh"
fi
```
These bundles should load automatically if you've integrated the
[Inject](https://github.com/krzysztofzablocki/Inject) or
[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) packages into your project.
Otherwise, you can add the following code to run at startup of your app:
```
#if DEBUG
if let path = Bundle.main.path(forResource:
"iOSInjection", ofType: "bundle") ??
Bundle.main.path(forResource:
"macOSInjection", ofType: "bundle") {
Bundle(path: path)!.load()
}
#endif
```
The binary bundles also integrate [Nimble](https://github.com/Quick/Nimble)
and a slightly modified version of the [Quick](https://github.com/Quick/Quick)
testing framework to inhibit spec caching under their respective Apache licences.
An alternative to loading a bundle is to add the following additional "Other Linker Flag":
`/Applications/InjectionNext.app/Contents/Resources/lib$(PLATFORM_NAME)Injection.dylib`
When your app runs it should connect to the `InjectionNext.app` and it's icon
change to orange. After that, by parsing the messages from the "supervised"
launch of Xcode it is possible to know when files are saved and exactly how
to recompile them for injection. Injection on a device uses the same
configuration but is opt-in through the menu item "Enable Devices"
(as it needs to open a network port). You also need to select the
project's "expanded codesigning identity" from the codesigning
phase of your build logs in the window that pops up. Sometimes a
device will not connect to the app first time after unlocking it.
If at first it doesn't succeed, try again.
The colours of the menu bar icon bar correspond to:
* Blue when you first run the InjectionNext app.
* Purple when you have launched Xcode using the app.
* Orange when your client app has connected to it.
* Green while it is recompiling a saved source.
* Yellow if the source has failed to compile.
To inject tests on a device: when enabling the "Enable Devices"
menu item, if you select "Enable testing on device", the arguments
shown will be added to the link of each dynamic library. As you
do this, the command mentioned above will be inserted into the clipboard
which you should paste into your project as a "Run Script" "Build Phase"
of the main target to copy the required libraries into the app bundle.
### Cursor/VSCode/File-watcher mode.
If you would like to use InjectionNext with the Cursor code editor,
you can have it fall back to InjectionIII-style log parsing using
the "...or Watch Project" menu item to select the project root
you will be working under (or use the new "Proxy" mode below
for Swift projects.) In this case, you shouldn't launch
Xcode from inside the InjectionNext.app but you'll need to have
built your app in Xcode at some point in the past for the logs
to be available. You should build using the same version as that
selected by `xcode-select`. With Xcode 16.3+, for this log parsing
mode to continue working you'll need to add a custom build setting
EMIT_FRONTEND_COMMAND_LINES. If you'd like the InjectionNext.app to
automatically file watch your project, add the following environment
variable to your scheme: `INJECTION_PROJECT_ROOT=$(SRCROOT)`. Also,
Injection doesn't work with setting COMPILATION_CACHE_ENABLE_CACHING.
### Scheme environment variable INJECTION_TRACE
If you add an environment variable INJECTION_TRACE to your scheme,
logging aspects will be added to all functions in a file when you inject
it so you can see they are being called and as an aid to debugging. This
means you can turn on detailed logging for a file just by injecting it.
For a full list of all environment variable that can be specified consult
[this source file](https://github.com/johnno1962/InjectionLite/blob/main/Sources/InjectionImplC/include/InjectionImplC.h#L45).
YMMV with global tracing but on a device it will likely be more reliable
if you use the precompiled bundles with a copy_bundle.sh build phase.
If you still see crashes, this can occur when an argument's type has
been miscategorised. Add a INJECTION_TRACE_REPAIR schema variable and
failing methods will be added to a list excluding them from logging
when you re-run your app.
### Fallback compiler "proxy" mode (Not normally used).
It is also possible to intercept swift compilation commands as a new proof of
concept for when at some point in the future these are no longer captured in
the Xcode logs (as was the case with Xcode 16.3 beta1). In this case, select
"Intercept compiler" to patch the current toolchain slightly to capture all
compilations using a script and send them to the InjectionNext.app. Once this
patch has been applied you don't need to launch Xcode from the app and you can
inject by starting a file watcher using the "...or Watch Project" menu item
(though this should happen automatically when you recompile Swift sources).
For proxy mode to work the app must be installed in /Applications.
So, InjectionNext now has three ways which it can operate. The original mode
of operation launching Xcode inside the app takes precedence and, if you have
selected a file watcher and are intercepting the compiler commands this "proxy
mode" is the next preference followed by the log parsing fallback using
the [InjectionLite](https://github.com/johnno1962/InjectionLite) package
which essentially works as InjectionIII did when the logs are available.
For more information consult the [original InjectionIII README](https://github.com/johnno1962/InjectionIII)
or for the bigger picture see [this swift evolution post](https://forums.swift.org/t/weve-been-doing-it-wrong-all-this-time/72015).
You can run InjectionNext from the command line and have it open
your project in Xcode automatically using the -projectPath option.
open -a InjectionNext --args -projectPath /path/to/project
Set a user default with the same name if you want to always open
this project inside the selected Xcode on launching the app.
After 100 injections you'll be reminded you can sponsor this project on GitHub.
The fabulous app icon is thanks to Katya of [pixel-mixer.com](http://pixel-mixer.com/).
================================================
FILE: Sources/InjectionNext/InjectionNext.swift
================================================
//
// InjectionNext.swift
// InjectionNext Package
//
// Created by John Holdsworth on 30/05/2024.
//
// Client app side of injection using implementation of InjectionLite.
//
#if DEBUG || !SWIFT_PACKAGE
import Foundation
#if canImport(InjectionImpl)
import InjectionImpl
#endif
#if canImport(InjectionNextC)
@_exported import InjectionNextC
#endif
@objc(InjectionNext)
open class InjectionNext: SimpleSocket {
override class open func error(_ message: String) -> Int32 {
let msg = String(format: message, strerror(errno))
print(APP_PREFIX+APP_NAME+": "+msg)
if errno == EHOSTUNREACH { // No route to host
print("ℹ️ "+APP_NAME+": Accept permission prompt on device.")
}
return errno
}
func log(_ msg: String) {
print(APP_PREFIX+APP_NAME+": "+msg)
}
func error(_ msg: String) {
log("⚠️ "+msg)
}
/// Connection from client app opened in ClientBoot.mm arrives here
open override func runInBackground() {
super.write(INJECTION_VERSION)
#if targetEnvironment(simulator) || os(macOS)
super.write(NSHomeDirectory())
#else
if let bazelWorkspace = getenv(BUILD_WORKSPACE_DIRECTORY) {
super.write(String(cString: bazelWorkspace))
} else {
super.write(INJECTION_KEY)
}
#endif
// Find client platform
#if os(macOS) || targetEnvironment(macCatalyst)
var platform = "Mac"
#elseif os(tvOS)
var platform = "AppleTV"
#elseif os(visionOS)
var platform = "XR"
#elseif os(watchOS)
var platform = "Watch"
#else
var platform = "iPhone"
#endif
#if targetEnvironment(simulator)
platform += "Simulator"
#else
platform += "OS"
#endif
#if os(macOS)
platform += "X"
#endif
Reloader.platform = platform
#if arch(x86_64)
let arch = "x86_64"
#else
let arch = "arm64"
#endif
// Let server side know the platform and architecture
writeCommand(InjectionResponse.platform.rawValue, with: platform)
super.write(arch)
if let projectRoot = getenv(INJECTION_PROJECT_ROOT) ??
getenv(BUILD_WORKSPACE_DIRECTORY) {
writeCommand(InjectionResponse.projectRoot.rawValue,
with: String(cString: projectRoot))
}
writeCommand(InjectionResponse.tmpPath.rawValue, with: NSTemporaryDirectory())
if let detail = getenv(INJECTION_DETAIL) {
writeCommand(InjectionResponse.detail.rawValue,
with: String(cString: detail))
}
if let bazelTarget = getenv(INJECTION_BAZEL_TARGET) {
writeCommand(InjectionResponse.bazelTarget.rawValue,
with: String(cString: bazelTarget))
}
if let executable = Bundle.main.executablePath {
writeCommand(InjectionResponse.executable.rawValue,
with: executable)
}
Reloader.injectionQueue.sync {
tracingOptions()
}
log("\(arch) \(platform) connected to app, waiting for commands.")
#if !SWIFT_PACKAGE
if let build = Bundle(for: Self.self)
.infoDictionary?["CFBundleVersion"] as? String {
detail("Bundle build #"+build)
}
#endif
processCommandsFromApp()
log("Connection lost, disconnecting.")
}
func tracingOptions() {
SwiftTrace.injectableSymbol = Reloader.injectableSymbol
SwiftTrace.defaultMethodExclusions += // CoreFoundation
#"|\[NS(Method|Tagged|Array|\w*Dict|Date|Data|Timer)|allocWithZone:|__unurl|_trueSelf"#
+ #"|InjectionBundle.|fast_dl"#
for name in [INJECTION_TRACE_LOOKUP, INJECTION_TRACE_FILTER, INJECTION_TRACE_ALL,
INJECTION_TRACE_FRAMEWORKS, INJECTION_TRACE_UIKIT, INJECTION_TRACE] {
if let value = getenv(name) {
setVariable(name: name, to: String(cString: value), first: true)
}
}
}
func setVariable(name: String, to value: String, first: Bool) {
let wasSet = getenv(name) != nil
if name == INJECTION_DLOPEN_MODE, let mode = Int32(value) {
DLKit.dlOpenMode = mode
} else if name == INJECTION_TRACE_FILTER {
if value == UNSETENV_VALUE {
if wasSet {
SwiftTrace.traceFilterInclude = "."
}
} else {
SwiftTrace.traceFilterInclude = value
}
}
if value == UNSETENV_VALUE {
unsetenv(name)
return
}
setenv(name, value, 1)
if !first && wasSet {
return
}
switch name {
/// Custom type lookup on tracing.
case INJECTION_TRACE_LOOKUP:
if value.hasPrefix("|") {
SwiftTrace.defaultLookupExclusions += value
}
SwiftTrace.typeLookup = true
/// Entire App bundle tracing.
case INJECTION_TRACE_ALL:
if value.hasPrefix("|") {
SwiftTrace.defaultMethodExclusions += value
}
SwiftTrace.interposeEclusions = SwiftTrace.exclusionRegexp
appBundleImages { imageName, _, _ in
if SwiftTrace.interposeMethods(inBundlePath: imageName) == 0,
strstr(imageName, "XCT") == nil {
self.error("""
Unable to interpose to trace image \
\(String(cString: imageName)), have you added \
"Other Linker Flags" -Xlinker -interposable
""")
}
SwiftTrace.trace(bundlePath: imageName)
}
/// Trace calls to framework e.g. SwiftUI,SwiftUICore
case INJECTION_TRACE_FRAMEWORKS:
var frmwks = value
if frmwks == "" || frmwks == "1" { frmwks = "SwiftUI,SwiftUICore" }
for frmwk in frmwks.components(separatedBy: ",") {
if let dylib = DLKit.imageMap[frmwk] {
Self.target = dylib
appBundleImages { path, header, slide in
rebind_symbols_trace(autoBitCast(header), slide, Self.tracer)
}
} else {
error("Invalid trace framework \(frmwk)")
}
}
/// Trace UIKit internals using swizzling
case INJECTION_TRACE_UIKIT:
var frmwks = value
if frmwks == "" || frmwks == "1" { frmwks = "UIKitCore" }
for frmwk in frmwks.components(separatedBy: ",") {
if let bundle = DLKit.imageMap[frmwk]?.imageName {
SwiftTrace.trace(bundlePath: bundle)
} else {
error("Invalid swizzle framework \(frmwk)")
}
}
/// Function and class method tracing on injection.
case INJECTION_TRACE:
Reloader.traceHook = { (injected, name) in
let name = SwiftMeta.demangle(symbol: name) ?? String(cString: name)
detail("SwiftTracing \(name)")
return autoBitCast(SwiftTrace.trace(name: name, original: injected)) ?? injected
}
default:
break
}
}
static var target: ImageSymbols?
static var tracer: STTracer = { existing, symname in
var traced = existing
if SwiftTrace.injectableSymbol(symname),
let info = trie_iterator(existing),
target?.imageHeader == info.pointee.header,
let name = SwiftMeta.demangle(symbol: symname) {
detail("Tracing \(name) \(existing)")
traced = autoBitCast(SwiftTrace
.trace(name: " "+name, original: existing)) ?? existing
}
return traced
}
func processCommandsFromApp() {
var loader = Reloader() // InjectionLite injection implementation
func injectAndSweep(_ dylib: String) {
Reloader.injectionNumber += 1
var succeeded = false
if let (image, classes) = Reloader.injectionQueue
.sync(execute: { loader.loadAndPatch(in: dylib) }) {
loader.sweeper.sweepAndRunTests(image: image, classes: classes)
succeeded = true
let countKey = "__injectionsPerformed", howOften = 100
let count = UserDefaults.standard.integer(forKey: countKey)+1
UserDefaults.standard.set(count, forKey: countKey)
if count % howOften == 0 && getenv("INJECTION_SPONSOR") == nil {
log("""
ℹ️ Seems like you're using injection quite a bit. \
Have you considered sponsoring the project at \
https://github.com/johnno1962/\(APP_NAME) or \
asking your boss if they should? (This message \
prints every \(howOften) injections.)
""")
}
} else {
writeCommand(InjectionResponse.unhide.rawValue, with: nil)
}
writeCommand(succeeded ? InjectionResponse.injected.rawValue :
InjectionResponse.failed.rawValue, with: nil)
}
while true {
let commandInt = readInt()
guard let command = InjectionCommand(rawValue: commandInt) else {
error("Invalid command rawValue: \(commandInt)")
break
}
switch command {
case .invalid:
return error("Connection did not validate. Have you upgraded?")
case .log:
if let msg = readString() {
print(msg)
}
case .xcodePath:
if let xcodePath = readString() {
log("Xcode path: "+xcodePath)
Reloader.xcodeDev = xcodePath+"/Contents/Developer"
}
case .sendFile:
guard let path = readString() else {
return error("Unable to read path")
}
if path.hasSuffix("/") {
mkdir(path, 0o755)
continue
}
recvFile(path)
case .load:
guard let dylib = readString() else {
return error("Unable to read path")
}
injectAndSweep(dylib)
case .inject:
guard let dylibName = readString(), let data = readData() else {
return error("Unable to read dylib")
}
let dylib = NSTemporaryDirectory() + dylibName
try! data.write(to: URL(fileURLWithPath: dylib))
injectAndSweep(dylib)
case .metrics:
guard let metricsJSON = readString() else {
return error("Unable to read metrics JSON")
}
if let data = metricsJSON.data(using: .utf8),
let metricsDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let notificationName = metricsDict["notification_name"] as? String {
NotificationCenter.default.post(
name: NSNotification.Name(notificationName),
object: nil,
userInfo: metricsDict
)
}
case .setenv:
while let name = readString(), let value = readString() {
setVariable(name: name, to: value, first: false)
if readInt() != commandInt {
break
}
}
case .EOF:
return
default:
return error("**** @unknown case \(commandInt) **** " +
"Do you need to update the InjectionNext package?")
}
}
}
}
#endif
================================================
FILE: Sources/InjectionNextC/ClientBoot.mm
================================================
//
// ClientBoot.m
//
//
// Created by John H on 31/05/2024.
//
#if DEBUG || !SWIFT_PACKAGE
#import
#import
#import "InjectionImplC.h"
#import "InjectionClient.h"
#import "SimpleSocket.h"
@interface InjectionNext : SimpleSocket
@end
@implementation NSObject(InjectionNext)
static SimpleSocket *injectionClient;
static dispatch_once_t onlyOneClient;
/// Called on load of image containing this code
+ (void)load {
if ([InjectionNext InjectionBoot_inPreview]) return;
#if !TARGET_OS_MAC
[self performSelectorOnMainThread:@selector(connectInBackground)
withObject:nil waitUntilDone:NO];
}
+ (void)connectInBackground {
#endif
[self performSelectorInBackground:@selector(connectToInjection:)
withObject:[InjectionNext self]];
}
/// Attempt to connect to InjectionNext.app
+ (void)connectToInjection:(Class)clientClass {
const char *hostip = getenv(INJECTION_HOST) ?: "127.0.0.1";
// Do we need to use broadcasts to find devlepers Mac on the network
#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_OSX
if (@available(iOS 14.0, *)) if (![NSProcessInfo processInfo].isiOSAppOnMac) {
printf(APP_PREFIX APP_NAME": Locating developer's Mac. Have you selected \"Enable Devices\"?\n");
hostip = [SimpleSocket getMulticastService:HOTRELOADING_MULTICAST port:HOTRELOADING_PORT
message:APP_PREFIX"Connecting to %s (%s)...\n"].UTF8String;
}
#endif
// Have the address to connect to, connect and start local thread.
NSString *socketAddr = [NSString stringWithFormat:@"%s%s", hostip, INJECTION_ADDRESS];
for (int retry=0, retrys=1; retry
#include
#include
#include
#include
#include
#if 0
#define SLog NSLog
#else
#define SLog while(0) NSLog
#endif
#define MAX_PACKET 16384
#define EOS ~0
NSString *INJECTION_KEY = @__FILE__;
typedef union {
struct {
__uint8_t sa_len; /* total length */
sa_family_t sa_family; /* [XSI] address family */
};
struct sockaddr_storage any;
struct sockaddr_in ip4;
struct sockaddr addr;
} sockaddr_union;
@implementation SimpleSocket
#if !SWIFT_PACKAGE
+ (void)initialize { // Pre-built bundles (+InjectionNext.app)
INJECTION_KEY = [NSBundle bundleForClass:self]
.infoDictionary[@"UserHome"] ?: [NSBundle mainBundle]
.infoDictionary[@"InjectionUserHome"] ?: NSHomeDirectory();
}
#endif
+ (int)error:(NSString *)message {
NSLog([@"%@/" stringByAppendingString:message],
self, strerror(errno));
return -1;
}
+ (void)startServer:(NSString *)address {
[self performSelectorInBackground:@selector(runServer:) withObject:address];
}
+ (void)forEachInterface:(void (^)(ifaddrs *ifa, in_addr_t addr, in_addr_t mask))handler {
ifaddrs *addrs;
if (getifaddrs(&addrs) < 0) {
[self error:@"Could not getifaddrs: %s"];
return;
}
for (ifaddrs *ifa = addrs; ifa; ifa = ifa->ifa_next)
if (ifa->ifa_addr->sa_family == AF_INET)
handler(ifa, ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr,
((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr);
freeifaddrs(addrs);
}
static int lastServerSocket;
+ (void)runServer:(NSString *)address {
sockaddr_union serverAddr;
[self parseV4Address:address into:&serverAddr.any];
int serverSocket = [self newSocket:serverAddr.sa_family];
if (serverSocket < 0)
return;
lastServerSocket = serverSocket;
if (bind(serverSocket, &serverAddr.addr, serverAddr.sa_len) < 0)
[self error:@"Could not bind service socket: %s"];
else if (listen(serverSocket, 50) < 0)
[self error:@"Service socket would not listen: %s"];
else
while (serverSocket) {
sockaddr_union clientAddr;
socklen_t addrLen = sizeof clientAddr;
int clientSocket = accept(serverSocket, &clientAddr.addr, &addrLen);
if (clientSocket > 0) {
int yes = 1;
if (setsockopt(clientSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0)
[self error:@"Could not set SO_NOSIGPIPE: %s"];
@autoreleasepool {
struct sockaddr_in *v4Addr = &clientAddr.ip4;
printf("%s: Connection from %s:%d\n", object_getClassName(self),
inet_ntoa(v4Addr->sin_addr), ntohs(v4Addr->sin_port));
SimpleSocket *client = [[self alloc] initSocket:clientSocket];
client.isLocalClient =
v4Addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK);
[self forEachInterface:^(ifaddrs *ifa, in_addr_t addr, in_addr_t mask) {
if (v4Addr->sin_addr.s_addr == addr)
client.isLocalClient = TRUE;
}];
[client run];
}
}
else if (lastServerSocket)
[NSThread sleepForTimeInterval:.5];
else
break;
}
close(serverSocket);
}
+ (void)stopLastServer {
if (lastServerSocket)
close(lastServerSocket);
lastServerSocket = 0;
[NSThread sleepForTimeInterval:.5];
}
+ (instancetype)connectTo:(NSString *)address {
sockaddr_union serverAddr;
[self parseV4Address:address into:&serverAddr.any];
int clientSocket = [self newSocket:serverAddr.sa_family];
if (clientSocket < 0)
return nil;
if (connect(clientSocket, &serverAddr.addr, serverAddr.sa_len) < 0) {
[self error:@"Could not connect: %s"];
return nil;
}
return [[self alloc] initSocket:clientSocket];
}
+ (int)newSocket:(sa_family_t)addressFamily {
int newSocket, yes = 1;
if ((newSocket = socket(addressFamily, SOCK_STREAM, 0)) < 0)
[self error:@"Could not open service socket: %s"];
else if (setsockopt(newSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0)
[self error:@"Could not set SO_REUSEADDR: %s"];
else if (setsockopt(newSocket, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof yes) < 0)
[self error:@"Could not set SO_NOSIGPIPE: %s"];
else if (setsockopt(newSocket, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof yes) < 0)
[self error:@"Could not set TCP_NODELAY: %s"];
else if (fcntl(newSocket, F_SETFD, FD_CLOEXEC) < 0)
[self error:@"Could not set FD_CLOEXEC: %s"];
else
return newSocket;
return -1;
}
/**
* Available formats
* @"[:]"
* where can be NNN.NNN.NNN.NNN or hostname, empty for localhost or * for all interfaces
* The default port is 80 or a specific number to bind or an empty string to allocate any port
*/
+ (BOOL)parseV4Address:(NSString *)address into:(struct sockaddr_storage *)serverAddr {
NSArray *parts = [address componentsSeparatedByString:@":"];
struct sockaddr_in *v4Addr = (struct sockaddr_in *)serverAddr;
bzero(v4Addr, sizeof *v4Addr);
v4Addr->sin_family = AF_INET;
v4Addr->sin_len = sizeof *v4Addr;
v4Addr->sin_port = htons(parts.count > 1 ? parts[1].intValue : 80);
const char *host = parts[0].UTF8String;
if (!host[0])
v4Addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
else if (host[0] == '*')
v4Addr->sin_addr.s_addr = htonl(INADDR_ANY);
else if (isdigit(host[0]))
v4Addr->sin_addr.s_addr = inet_addr(host);
else if (struct hostent *hp = gethostbyname2(host, v4Addr->sin_family))
memcpy(&v4Addr->sin_addr, hp->h_addr, hp->h_length);
else {
[self error:[NSString stringWithFormat:@"Unable to look up host for %@", address]];
return FALSE;
}
return TRUE;
}
- (instancetype)initSocket:(int)socket {
if ((self = [super init])) {
clientSocket = socket;
}
return self;
}
- (void)run {
[self performSelectorInBackground:@selector(runInBackground) withObject:nil];
}
- (void)runInBackground {
[[self class] error:@"-[SimpleSocket runInBackground] not implemented in subclass"];
}
typedef ssize_t (*io_func)(int, void *, size_t);
- (BOOL)perform:(io_func)io ofBytes:(const void *)buffer
length:(size_t)length cmd:(SEL)cmd {
size_t bytes, ptr = 0;
SLog(@"#%d %s %lu [%p] %s", clientSocket, io == read ?
"<-" : "->", length, buffer, sel_getName(cmd));
while (ptr < length && (bytes = io(clientSocket,
(char *)buffer+ptr, MIN(length-ptr, MAX_PACKET))) > 0)
ptr += bytes;
if (ptr < length) {
if (errno)
NSLog(@"[%@ %s:%p length:%lu] error: %lu %s",
self, sel_getName(cmd), buffer, length, ptr, strerror(errno));
return FALSE;
}
return TRUE;
}
- (BOOL)readBytes:(void *)buffer length:(size_t)length cmd:(SEL)cmd {
return [self perform:read ofBytes:buffer length:length cmd:cmd];
}
- (int)readInt {
int32_t anint = EOS;
if (![self readBytes:&anint length:sizeof anint cmd:_cmd])
return EOS;
SLog(@"#%d <- %d", clientSocket, anint);
return anint;
}
- (void *)readPointer {
void *aptr = (void *)EOS;
if (![self readBytes:&aptr length:sizeof aptr cmd:_cmd])
return aptr;
SLog(@"#%d <- %p", clientSocket, aptr);
return aptr;
}
- (NSData *)readData {
size_t length = [self readInt];
if (length == EOS) return nil;
void *bytes = malloc(length);
if (!bytes || ![self readBytes:bytes length:length cmd:_cmd])
return nil;
return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:YES];
}
- (NSString *)readString {
size_t length = [self readInt];
if (length == EOS) return nil;
void *bytes = malloc(length+1);
if (!bytes || ![self readBytes:bytes length:length cmd:_cmd])
return nil;
((char *)bytes)[length] = 0;
NSString *str = [[NSString alloc] initWithBytesNoCopy:bytes length:length
encoding:NSUTF8StringEncoding freeWhenDone:YES];
SLog(@"#%d <- %d '%@'", clientSocket, (int)str.length, str);
return str;
}
- (BOOL)writeBytes:(const void *)buffer length:(size_t)length cmd:(SEL)cmd {
return [self perform:(io_func)write ofBytes:buffer length:length cmd:cmd];
}
- (BOOL)writeInt:(int)length {
SLog(@"#%d %d ->", clientSocket, length);
return [self writeBytes:&length length:sizeof length cmd:_cmd];
}
- (BOOL)writeCStr:(const char *)string {
uint32_t len = (uint32_t)strlen(string);
SLog(@"#%d %d '%s' ->", clientSocket, (int)len, string);
return [self writeInt:len] && [self writeBytes:string length:len cmd:_cmd];
}
- (BOOL)writePointer:(void *)ptr {
SLog(@"#%d %p ->", clientSocket, ptr);
return [self writeBytes:&ptr length:sizeof ptr cmd:_cmd];
}
- (BOOL)writeData:(NSData *)data {
uint32_t length = (uint32_t)data.length;
SLog(@"#%d [%d] ->", clientSocket, length);
return [self writeInt:length] &&
[self writeBytes:data.bytes length:length cmd:_cmd];
}
- (BOOL)writeString:(NSString *)string {
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
SLog(@"#%d %d '%@' ->", clientSocket, (int)data.length, string);
return [self writeData:data];
}
- (BOOL)writeCommand:(int)command withString:(NSString *)string {
return [self writeInt:command] &&
(!string || [self writeString:string]);
}
- (BOOL)failed:(SEL)sel file:(id)file {
NSLog(@"-[%@ %s \"%@\"]: Could not fopen(), %s",
self, sel_getName(sel), file, strerror(errno));
return FALSE;
}
- (BOOL)sendFile:(NSString *)path {
struct stat st;
FILE *input = fopen(path.UTF8String, "r");
if (!input || fstat(fileno(input), &st))
return [self writeInt:INT_MAX] &&
[self writeString:[NSString stringWithFormat:
@"-[%@ %s \"%@\"]: Could not open to send, %s",
self, sel_getName(_cmd), path, strerror(errno)]] &&
[self failed:_cmd file:path];
[self writeInt:(int)st.st_size];
off_t pos = 0, chunk;
char buffer[MAX_PACKET];
while ((chunk = MIN(st.st_size-pos, sizeof buffer)) > 0 &&
(chunk = fread(buffer, 1, chunk, input)) > 0 &&
[self writeBytes:buffer length:chunk cmd:_cmd])
pos += chunk;
fclose(input);
return pos == st.st_size;
}
- (BOOL)recvFile:(NSString *)path {
FILE *output = fopen(path.UTF8String, "w");
if (!output) return [self failed:_cmd file:path];
off_t sz = [self readInt], pos = 0, chunk;
if (sz == INT_MAX)
return [self failed:_cmd file:[self readString]];
char buffer[MAX_PACKET];
while ((chunk = MIN(sz-pos, sizeof buffer)) > 0 &&
[self readBytes:buffer length:chunk cmd:_cmd] &&
(chunk = fwrite(buffer, 1, chunk, output)) > 0)
pos += chunk;
fclose(output);
return pos == sz;
}
- (void)dealloc {
close(clientSocket);
}
/// Hash used to differentiate InjectionNext users broadcasting.
/// Hash derived from path to user's home directory determined
/// from path to this source file in project's DerivedData or for
/// pre-built bundles, home directory taken from a value patched
/// into the bundle's Info.plist as it is copied into client app.
+ (int)multicastHash {
#if SWIFT_PACKAGE
NSString *file = @__FILE__;
INJECTION_KEY = [file
stringByReplacingOccurrencesOfString: @"(/Users/[^/]+).*"
withString: @"$1" options: NSRegularExpressionSearch
range: NSMakeRange(0, file.length)];
#endif
const char *key = INJECTION_KEY.UTF8String;
int hash = 0;
for (size_t i=0, len = strlen(key); i> 24) {
case 10: // mobile network
// case 172: // hotspot
case 127: // loopback
return;
}
int idx = if_nametoindex(ifa->ifa_name);
setsockopt(multicastSocket, IPPROTO_IP, IP_BOUND_IF, &idx, sizeof idx);
addr.sin_addr.s_addr = laddr | ~nmask;
printf("Broadcasting to %s#%d:%s to locate Injection host...\n",
ifa->ifa_name, idx, inet_ntoa(addr.sin_addr));
if (sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0,
(struct sockaddr *)&addr, sizeof addr) < 0)
[self error:@"Could not send broadcast ping: %s"];
if (sendto(multicastSocket, &msgbuf, sizeof msgbuf, 0,
(struct sockaddr *)&addr, sizeof addr) < 0)
[self error:@"Could not send broadcast ping: %s"];
}];
socklen_t addrlen = sizeof addr;
while (recvfrom(multicastSocket, &msgbuf, sizeof msgbuf, 0,
(struct sockaddr *)&addr, &addrlen) < sizeof msgbuf) {
[self error:@"%s: Error receiving from broadcast: %s"];
sleep(1);
}
const char *ipaddr = inet_ntoa(addr.sin_addr);
printf(format, msgbuf.host, ipaddr);
close(multicastSocket);
return [NSString stringWithUTF8String:ipaddr];
}
@end
@implementation SimpleService
static NSMutableDictionary *callbacks;
+ (void)startServer:(NSString *_Nonnull)address callback:(SimpleCallback)callback {
if (!callbacks)
callbacks = [NSMutableDictionary new];
callbacks[address] = callback;
[super startServer:address];
}
- (void)runInBackground {
sockaddr_union addr;
socklen_t len = sizeof addr;
if (getsockname(clientSocket, &addr.addr, &len) < 0)
[[self class] error:@"getsockname failed %s"];
NSString *address = [NSString stringWithFormat:@"*:%d", ntohs(addr.ip4.sin_port)];
if (SimpleCallback callback = callbacks[address])
callback(clientSocket);
else
[[self class] error:@"Missing callback"];
}
@end
@implementation SimpleHTTP
+ (void)startServer:(NSString *_Nonnull)address callback:(NSData *_Nullable (^_Nonnull)(char *_Nonnull header))callback {
[super startServer:address callback:^(int socket) {
FILE *stream = fdopen(socket, "r+");
size_t bsize = 1000, length;
char *header = (char *)malloc(bsize), *hptr = header;
while ((length = strlen(fgets(hptr, (int)(bsize - (hptr-header)),
stream)?:"")) > 2 || (length && strcmp(hptr-2, "\r\n\r\n")))
if ((hptr += length)-header == bsize-1) {
char *newbuff = (char *)realloc(header, bsize *= 2);
hptr = newbuff + (hptr - header);
header = newbuff;
}
NSData *body = callback(header) ?: [NSData new];
fprintf(stream, "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n"
"Content-Length: %ld\r\n\r\n", body.length);
fwrite(body.bytes, body.length, 1, stream);
fclose(stream);
free(header);
}];
}
@end
#endif
================================================
FILE: Sources/InjectionNextC/include/InjectionClient.h
================================================
//
// InjectionClient.h
// InjectionBundle
//
// Created by John Holdsworth on 06/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
// $Id: //depot/InjectionNext/Sources/InjectionNextC/include/InjectionClient.h#60 $
//
// Shared definitions between server and client.
//
#import
#define HOTRELOADING_PORT ":8887"
#define HOTRELOADING_MULTICAST "239.255.255.239"
#define INJECTION_VERSION 4001
#define COMMANDS_PORT ":8896"
#define INJECTION_ADDRESS HOTRELOADING_PORT
extern NSString *INJECTION_KEY;
#undef APP_NAME
#define APP_NAME "InjectionNext"
#define APP_PREFIX "🔥 "
#define DYLIB_PREFIX "/eval_injection_" // Expected by DLKit.appImages
#define INJECTION_APP_VERSION "INJECTION_APP_VERSION"
#define INJECTION_DLOPEN_MODE "INJECTION_DLOPEN_MODE"
#define UNSETENV_VALUE "__NULL__"
@interface NSObject(HotReloading)
+ (void)runXCTestCase:(Class)aTestCase;
@end
@interface NSProcessInfo(iOSAppOnMac)
@property BOOL isiOSAppOnMac;
@end
typedef NS_ENUM(int, InjectionCommand) {
// commands to InjectionNext package
InjectionLog,
InjectionLoad,
InjectionInject,
InjectionXcodePath,
InjectionSendFile,
InjectionMetrics,
InjectionSetenv,
InjectionEndenv,
InjectionInvalid = 1000,
InjectionEOF = ~0
};
typedef NS_ENUM(int, InjectionResponse) {
// responses from InjectionNext package
InjectionPlatform,
InjectionInjected,
InjectionFailed,
InjectionTmpPath,
InjectionUnhide,
InjectionProjectRoot,
InjectionDetail,
InjectionBazelTarget,
InjectionExecutable,
InjectionExit = ~0
};
================================================
FILE: Sources/InjectionNextC/include/SimpleSocket.h
================================================
//
// SimpleSocket.h
// InjectionIII
//
// Created by John Holdsworth on 06/11/2017.
// Copyright © 2017 John Holdsworth. All rights reserved.
//
// $Id: //depot/HotReloading/Sources/HotReloadingGuts/include/SimpleSocket.h#16 $
//
#import
#import
@interface SimpleSocket : NSObject {
@protected
int clientSocket;
}
@property BOOL isLocalClient;
+ (void)startServer:(NSString *_Nonnull)address;
+ (void)runServer:(NSString *_Nonnull)address;
+ (int)error:(NSString *_Nonnull)message;
+ (void)stopLastServer;
+ (instancetype _Nullable)connectTo:(NSString *_Nonnull)address;
+ (BOOL)parseV4Address:(NSString *_Nonnull)address into:(struct sockaddr_storage *_Nonnull)serverAddr;
+ (void)multicastServe:(const char *_Nonnull)multicast port:(const char *_Nonnull)port;
+ (NSString *_Nonnull)getMulticastService:(const char *_Nonnull)multicast
port:(const char *_Nonnull)port
message:(const char *_Nonnull)format;
- (instancetype _Nonnull)initSocket:(int)socket;
- (void)run;
- (void)runInBackground;
- (int)readInt;
- (void * _Nullable)readPointer;
- (NSData *_Nullable)readData;
- (NSString *_Nullable)readString;
- (BOOL)readBytes:(void * _Nonnull)buffer length:(size_t)length cmd:(SEL _Nonnull)cmd;
- (BOOL)writeInt:(int)length;
- (BOOL)writeCStr:(const char *_Nonnull)string;
- (BOOL)writePointer:(void * _Nullable)pointer;
- (BOOL)writeData:(NSData *_Nonnull)data;
- (BOOL)writeString:(NSString *_Nonnull)string;
- (BOOL)writeCommand:(int)command withString:(NSString *_Nullable)string;
- (BOOL)sendFile:(NSString *_Nonnull)path;
- (BOOL)recvFile:(NSString *_Nonnull)path;
@end
@interface SimpleService: SimpleSocket
typedef void (^_Nonnull SimpleCallback)(int socket);
+ (void)startServer:(NSString *_Nonnull)address callback:(SimpleCallback)callback;
@end
@interface SimpleHTTP: SimpleService
+ (void)startServer:(NSString *_Nonnull)address callback:(NSData *_Nullable (^_Nonnull)(char *_Nonnull header))callback;
@end
================================================
FILE: Tests/InjectionNextTests/InjectionNextTests.swift
================================================
import XCTest
@testable import InjectionBundle
final class InjectionNextTests: XCTestCase {
func testExample() throws {
// XCTest Documentation
// https://developer.apple.com/documentation/xctest
// Defining Test Cases and Test Methods
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
}
}
================================================
FILE: mcp-server/.gitignore
================================================
node_modules/
================================================
FILE: mcp-server/README.md
================================================
# InjectionNext MCP Server
MCP (Model Context Protocol) server that lets AI agents control InjectionNext — start file watching, check status, read debug logs, toggle device injection, and more.
## Prerequisites
- **macOS** with Xcode installed
- **Node.js** 18.14.1+
- **InjectionNext.app** built from this repo (with ControlServer support)
## Step 1: Build InjectionNext with ControlServer
```bash
# Clone and init submodules
git clone
cd InjectionNext
git submodule update --init --recursive
# Build the app (ad-hoc signing for local dev)
cd App
xcodebuild -project InjectionNext.xcodeproj \
-scheme InjectionNext \
-configuration Debug build \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO
# The built app is at:
# ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app
```
Optionally copy it to `/Applications`:
```bash
cp -R ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app /Applications/
```
## Step 2: Enable the ControlServer
The TCP control server is **opt-in**. Enable it via a UserDefault before launching the app:
```bash
defaults write com.johnholdsworth.InjectionNext mcpServer -bool true
```
To disable it later:
```bash
defaults delete com.johnholdsworth.InjectionNext mcpServer
```
## Step 3: Install the MCP server
```bash
cd mcp-server
npm install
```
## Step 4: Configure Cursor
Add to your Cursor MCP config at `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per-project):
```json
{
"mcpServers": {
"injection-next": {
"command": "node",
"args": ["/absolute/path/to/InjectionNext/mcp-server/index.js"]
}
}
}
```
Replace `/absolute/path/to` with the actual path to this repo.
## Step 5: Launch InjectionNext
Start the app before using the MCP tools:
```bash
open /Applications/InjectionNext.app
# or from DerivedData:
open ~/Library/Developer/Xcode/DerivedData/InjectionNext-*/Build/Products/Debug/InjectionNext.app
```
You should see the InjectionNext icon in the menu bar.
## Step 6: Test it
### Quick test from terminal (no MCP needed)
```bash
# Check status
echo '{"action":"status"}' | nc -w 3 localhost 8919
# Start watching a project
echo '{"action":"watch_project","path":"/path/to/your/project"}' | nc -w 3 localhost 8919
# Read debug logs
echo '{"action":"get_logs"}' | nc -w 3 localhost 8919
# Stop watching
echo '{"action":"stop_watching"}' | nc -w 3 localhost 8919
```
### Test from Cursor
After configuring the MCP server, open Cursor and ask the AI:
> "Use the injection-next MCP to check the status of InjectionNext"
or:
> "Watch my project at /path/to/project for hot reloading"
or:
> "Show me the InjectionNext debug logs"
## Available Tools
| Tool | Description |
|------|-------------|
| `get_status` | Full app status: Xcode, watched dirs, compiler, clients |
| `watch_project` | Start file-watching a directory for hot-reloading |
| `stop_watching` | Stop all file watchers |
| `launch_xcode` | Launch Xcode via InjectionNext with SourceKit logging |
| `get_compiler_state` | Check if Swift compiler is intercepted |
| `enable_devices` | Toggle device/simulator injection support |
| `unhide_symbols` | Fix default-argument symbol visibility issues |
| `get_last_error` | Get last compilation error |
| `prepare_swiftui_source` | Add injection annotations to current SwiftUI file |
| `prepare_swiftui_project` | Prepare all SwiftUI files in target |
| `set_xcode_path` | Point to a different Xcode.app |
| `get_logs` | Read debug console (supports `since` for polling) |
| `clear_logs` | Clear the log buffer |
## Architecture
```
┌─────────────┐ TCP :8919 ┌──────────────────────┐
│ MCP Server │◄───────────────────────►│ InjectionNext.app │
│ (Node.js) │ JSON commands/resp │ ┌─────────────────┐ │
│ │ │ │ ControlServer │ │
│ stdio ↕ │ │ │ (TCP listener) │ │
│ │ │ └────────┬────────┘ │
│ Cursor / │ │ │ │
│ AI Agent │ │ ┌────────▼────────┐ │
└─────────────┘ │ │ AppDelegate │ │
│ │ IBActions │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ LogBuffer │ │
│ │ (ring buffer) │ │
│ └─────────────────┘ │
└──────────────────────┘
```
## Troubleshooting
**"Cannot connect to InjectionNext on port 8919"**
- Make sure InjectionNext.app is running (check menu bar icon)
- Make sure you built the version with ControlServer support (from this repo)
- Check: `lsof -i :8919` should show InjectionNext listening
**Port already in use**
- Kill any stale InjectionNext processes: `pkill -f InjectionNext`
- Re-launch the app
**Logs are empty**
- Logs only capture events that happen while the app is running
- Try `watch_project` to generate some activity, then `get_logs`
================================================
FILE: mcp-server/index.js
================================================
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import net from "node:net";
const CONTROL_PORT = 8919;
const CONTROL_HOST = "127.0.0.1";
function sendCommand(action, params = {}) {
return new Promise((resolve, reject) => {
const client = new net.Socket();
const timeout = setTimeout(() => {
client.destroy();
reject(new Error("Connection timed out. Is InjectionNext running with ControlServer?"));
}, 5000);
client.connect(CONTROL_PORT, CONTROL_HOST, () => {
const payload = JSON.stringify({ action, ...params }) + "\n";
client.write(payload);
});
let data = "";
client.on("data", (chunk) => {
data += chunk.toString();
if (data.includes("\n")) {
clearTimeout(timeout);
client.destroy();
try {
resolve(JSON.parse(data.trim()));
} catch {
reject(new Error("Invalid JSON response from InjectionNext"));
}
}
});
client.on("error", (err) => {
clearTimeout(timeout);
if (err.code === "ECONNREFUSED") {
reject(new Error(
"Cannot connect to InjectionNext on port 8919. " +
"Make sure InjectionNext.app is running (build with ControlServer support)."
));
} else {
reject(err);
}
});
});
}
function formatResponse(result) {
if (!result.success) {
return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
}
const text = result.data
? JSON.stringify(result.data, null, 2)
: "OK";
return { content: [{ type: "text", text }] };
}
const server = new McpServer({
name: "injection-next",
version: "1.0.0",
});
server.tool(
"get_status",
"Get the current status of InjectionNext: Xcode state, watched directories, compiler interception, connected clients, and last error",
{},
async () => {
const result = await sendCommand("status");
return formatResponse(result);
}
);
server.tool(
"watch_project",
"Start file-watching a project directory for hot-reloading. This enables injection for Cursor/VS Code workflows without launching Xcode through the app.",
{ path: z.string().describe("Absolute path to the project directory to watch") },
async ({ path }) => {
const result = await sendCommand("watch_project", { path });
return formatResponse(result);
}
);
server.tool(
"stop_watching",
"Stop watching all project directories",
{},
async () => {
const result = await sendCommand("stop_watching");
return formatResponse(result);
}
);
server.tool(
"launch_xcode",
"Launch Xcode through InjectionNext with SOURCEKIT_LOGGING enabled for full injection support",
{},
async () => {
const result = await sendCommand("launch_xcode");
return formatResponse(result);
}
);
server.tool(
"get_compiler_state",
"Check whether the Swift compiler is currently intercepted (patched) by InjectionNext",
{},
async () => {
const result = await sendCommand("intercept_compiler");
return formatResponse(result);
}
);
server.tool(
"enable_devices",
"Enable or disable device injection support (opens TCP port for device/simulator connections)",
{ enable: z.boolean().describe("true to enable, false to disable") },
async ({ enable }) => {
const result = await sendCommand("enable_devices", { enable });
return formatResponse(result);
}
);
server.tool(
"unhide_symbols",
"Unhide default-argument symbols in the current build to fix injection loading failures",
{},
async () => {
const result = await sendCommand("unhide_symbols");
return formatResponse(result);
}
);
server.tool(
"get_last_error",
"Get the last compilation error from InjectionNext",
{},
async () => {
const result = await sendCommand("get_last_error");
return formatResponse(result);
}
);
server.tool(
"prepare_swiftui_source",
"Automatically add .enableInjection() and @ObserveInjection to the currently edited SwiftUI source file",
{},
async () => {
const result = await sendCommand("prepare_swiftui_source");
return formatResponse(result);
}
);
server.tool(
"prepare_swiftui_project",
"Prepare all SwiftUI source files in the current target for hot-reloading injection",
{},
async () => {
const result = await sendCommand("prepare_swiftui_project");
return formatResponse(result);
}
);
server.tool(
"set_xcode_path",
"Set the path to the Xcode installation to use",
{ path: z.string().describe("Absolute path to Xcode.app (e.g. /Applications/Xcode.app)") },
async ({ path }) => {
const result = await sendCommand("set_xcode_path", { path });
return formatResponse(result);
}
);
server.tool(
"get_logs",
"Read the InjectionNext debug console — returns recent log entries including injection events, compilation output, errors, file watcher activity, and client connections. Use 'since' (unix timestamp) to get only new logs since your last read.",
{
since: z.number().optional().describe("Unix timestamp — only return logs after this time. Omit to get all recent logs."),
limit: z.number().optional().describe("Max entries to return (default 200, max 500)"),
},
async ({ since, limit }) => {
const params = {};
if (since !== undefined) params.since = since;
if (limit !== undefined) params.limit = limit;
const result = await sendCommand("get_logs", params);
if (!result.success) {
return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
}
const logs = result.data?.logs ?? [];
if (logs.length === 0) {
return { content: [{ type: "text", text: "No new log entries." }] };
}
const lines = logs.map((e) => {
const ts = new Date(e.timestamp * 1000).toISOString().slice(11, 23);
const tag = e.level !== "info" ? ` [${e.level.toUpperCase()}]` : "";
return `${ts}${tag} ${e.message}`;
});
const header = `--- ${logs.length} log entries (${result.data.count} total buffered) ---`;
return { content: [{ type: "text", text: header + "\n" + lines.join("\n") }] };
}
);
server.tool(
"clear_logs",
"Clear the InjectionNext log buffer",
{},
async () => {
const result = await sendCommand("clear_logs");
return formatResponse(result);
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
================================================
FILE: mcp-server/package.json
================================================
{
"name": "injection-next-mcp",
"version": "1.0.0",
"description": "MCP server for controlling InjectionNext hot-reloading app",
"type": "module",
"main": "index.js",
"engines": {
"node": ">=18.14.1"
},
"bin": {
"injection-next-mcp": "./index.js"
},
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1",
"zod": "^3.23.8"
}
}