Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hacking Xcode Behaviors / macOS native symposium #08

Hacking Xcode Behaviors / macOS native symposium #08

プロジェクトのBuild PhasesにRun Scriptを追加することでターゲットのビルド時に特定の処理を挟み込むことができますが、Xcode Behaviorsはプロジェクト単位ではなくXcode全般でのイベントに処理を挟み込むことができます。そんなBehaviorsをハックして、ビルドやテストのイベントを別アプリから監視する仕組みを実現するまでの試行錯誤について紹介します。

Kyome (Takuto Nakamura)

August 27, 2022
Tweet

More Decks by Kyome (Takuto Nakamura)

Other Decks in Programming

Transcript

  1. Xcode Behaviors Swift Package Managerʹؔ͢Δ
 Πϕϯτ͸αϙʔτ͞Ε͍ͯͳ͍
 ͷ͕࢒೦ Reset Package Caches

    Resolve Package Versions Update to Latest Package Versions
 Behaviors 🫤
  2. XCHook & XCMonitor XCHook͸Xcode BehaviorsͷBuildͱTestingΛϑοΫͯ͠ɺ
 ֎෦ΞϓϦʹ௨஌Λૹ৴Ͱ͖ΔΑ͏ʹ͢ΔSwift Package੡ϥΠϒϥϦ Swift Packageͱͯ͠ར༻Մೳɿhttps://github.com/kyome22/XCHook 􀐛

    XcodeͷPreferencesΛॻ͖׵͑ͯɺBehaviorsΠϕϯτʹScriptΛࢦఆ ΠϕϯτΛղऍͯ͠৘ใ੔ܗˍ֎෦ΞϓϦʹ޲͚ͯ௨஌ૹ৴ ௨஌ͷड৴पΓΛ؆୯ʹ͢Δ֎෦ΞϓϦ޲͚ͷAPI
  3. XCHook & XCMonitor BehaviorsͷΠϕϯτൃՐ 􀐛 XCHook XCMonitor Xcode Script͕ΠϕϯτΛղऍͯ͠৘ใΛ੔ܗ ৘ใΛ֎෦ΞϓϦʹ௨஌ͱͯ͠ૹ৴

    Xcodeͷىಈঢ়ଶΛ؂ࢹͭͭ͠ɺ௨஌Λৗ࣌଴ػ ड৴࣍ୈɺදࣔΛߋ৽ˍΠϕϯτͷϩάΛஷΊΔ ར༻ฤ
  4. vs App Sandboxͷน XCMonitor Xcode 􀙦 App Sandbox App Sandbox͸ɺΞϓϦ͕ΞΫηεՄೳͳྖҬΛ੍ݶ͢Δ͜ͱͰɺ


    γεςϜͷϦιʔεͱϢʔβʔͷσʔλΛอޢ͢ΔͨΊͷ΋ͷ ֎෦ΞϓϦΛૢ࡞ͨ͠Γঢ়ଶΛऔಘ͢Δ͜ͱ͸௨ৗෆՄೳ ࣗ࡞ΞϓϦͷ৔߹ɺApp Sandboxͷ༗ޮ/ແޮ͸։ൃऀʹҕͶΒΕ͍ͯΔ Xcode͸App Sandboxʹอޢ͞Ε͍ͯΔͨΊɺ֎෦͔ΒΞΫηεͰ͖ͳ͍
  5. vs App Sandboxͷน XCMonitor Xcode 􀙦 App Sandbox 􀐕 Behaviors

    >_ Shell Script Swift Script 􀫊 ~/.xchook BehaviorsͷRun ScriptͰεΫϦϓτΛಈ͔͢৔߹ɺ
 Shell ScriptͱSwiftͷεΫϦϓτʹ͸࣮ߦݖݶΛ෇༩͢Δ chmod 744 ͜ΕΒͷεΫϦϓτ͸ϗʔϜσΟϨΫτϦ௚Լʹ .xchook σΟϨΫτϦΛ
 ࡞੒ͯͦ͜͠ʹ഑ஔ
  6. vs App Sandboxͷน XCMonitor Xcode 􀙦 App Sandbox Plist App

    Sandbox 􀞡 ಋೖϋʔυϧΛԼ͛ΔͨΊɺBehaviorsͷRun ScriptΛࣗಈͰࢦఆ͢Δ
 ࢓૊Έ͕ཉ͍͠ Preferencesͷ಺༰͸ ~/Library/Preferences/com.apple.dt.Xcode.plist 
 ʹอଘ͞ΕΔ App SandboxΛແޮʹ͢Ε͹ɺ֎෦ΞϓϦ͔ΒϑΝΠϧΛಡΈॻ͖Մೳ
  7. vs App Sandboxͷน ಋೖϋʔυϧΛԼ͛ΔͨΊɺBehaviorsͷRun ScriptΛࣗಈͰࢦఆ͢Δ
 ࢓૊Έ͕ཉ͍͠ Preferencesͷ಺༰͸ ~/Library/Preferences/com.apple.dt.Xcode.plist 
 ʹอଘ͞ΕΔ

    App SandboxΛແޮʹ͢Ε͹ɺ֎෦ΞϓϦ͔ΒϑΝΠϧΛಡΈॻ͖Մೳ XCMonitor Xcode 􀙦 App Sandbox Plist App Sandbox 􀞡 Full Access
  8. ϓϩδΣΫτύεͷऔಘ Run ScriptΛ࣮ߦͨ͠ࡍͷ؀ڥม਺ Xcode Projectͷ৔߹ ؀ڥม਺ ஋ XcodeProject PROJECT_NAME.xcodeproj XcodeProjectPath

    PROJECT_ROOT/PROJECT_NAME.xcodeproj XcodeWorkspacePath PROJECT_ROOT/PROJECT_NAME.xcodeproj/project.xcworkspace PWD PROJECT_ROOT * PROJECT_ROOT = ϓϩδΣΫτͷϧʔτͷઈରύε * PROJECT_NAME = ϓϩδΣΫτ໊
  9. ϓϩδΣΫτύεͷऔಘ Run ScriptΛ࣮ߦͨ͠ࡍͷ؀ڥม਺ Xcode Workspaceͷ৔߹ʢCocoaPodsΛ࢖͍ͬͯΔͳͲʣ ؀ڥม਺ ஋ XcodeWorkspace PROJECT_NAME.xcworkspace XcodeWorkspacePath

    PROJECT_ROOT/PROJECT_NAME.xcworkspace PWD PROJECT_ROOT * PROJECT_ROOT = ϓϩδΣΫτͷϧʔτͷઈରύε * PROJECT_NAME = ϓϩδΣΫτ໊
  10. ϓϩδΣΫτύεͷऔಘ Run ScriptΛ࣮ߦͨ͠ࡍͷ؀ڥม਺ Swift Packageܗࣜͷ৔߹ ؀ڥม਺ ஋ XcodeWorkspace package.xcworkspace XcodeWorkspacePath

    PROJECT_ROOT/.swiftpm/xcode/package.xcworkspace PWD PROJECT_ROOT/.swiftpm/xcode * PROJECT_ROOT = ϓϩδΣΫτͷϧʔτͷઈରύε
  11. ϓϩδΣΫτύεͷऔಘ if (؀ڥม਺XcodeProject͕औಘՄೳ) { Xcode Project } else if (xcworkspaceϑΝΠϧͷ໊લ͕package.xcworkspace)

    { Swift Package } else { Xcode Workspace } Run ScriptΛ࣮ߦͨ͠ࡍͷ؀ڥม਺ͷಛ௃ ؀ڥม਺ XcodeProject ͕औಘͰ͖Δͷ͸Xcode Projectܗࣜͷ৔߹ͷΈ Swift Packageܗࣜͷ৔߹ɺxcworkspaceϑΝΠϧͷ໊લ͸
 package.xcworkspace Ͱݻఆ
  12. ϓϩδΣΫτύεͷऔಘ ҎԼͷShell ScriptͰϓϩδΣΫτ໊ͱύε͕औಘͰ͖Δ #!/bin/sh - if [ "$XcodeProject" ]; then

    # Normal Xcode Project PROJECT_NAME=`basename "$XcodeProject" .xcodeproj` PROJECT_PATH=$XcodeProjectPath elif [ "$XcodeWorkspace" = "package.xcworkspace" ]; then # Swift Package Library PROJECT_NAME=`basename $(dirname "$XcodeWorkspacePath" | sed -r "s/\.swiftpm.+//")` PROJECT_PATH=$XcodeWorkspacePath else # XCWorkspace (Probably using CocoaPods.) PROJECT_NAME=`basename "$XcodeWorkspace" .xcworkspace` PROJECT_PATH=$XcodeWorkspacePath fi ࢀরɿhttps://github.com/Kyome22/XCHook/blob/main/Sources/XCHook/run_scripts/build_start.sh
  13. XcodeͷPreferencesΛॻ͖׵͑Δ { "Xcode.AlertEvents.4_1": { "Xcode.AlertEvents.BuildStart": { "Xcode.Alert.RunScript": { "enabled": boolean,

    "path": "Shell Scriptͷύε" } }, "Xcode.AlertEvents.TestingSucceeds": { "Xcode.Alert.RunScript": { "enabled": boolean, "path": "Shell Scriptͷύε" } } } } Plist ͷ಺༰ΛJSONܗࣜͰൈਮͨ͠΋ͷ BehaviorsʹRun ScriptΛઃఆ͢Δʹ͸ɺ Xcode.AlertEvents ͱ Xcode.AlertEvents.4_1 ͷ಺༰Λॻ͖׵͑Ε͹ྑ͍ ྫ͑͹ɺXcode.AlertEvent.BuildStart ͷத ͷ Xcode.Alert.RunScript ʹ࣮ߦ͍ͨ͠
 Shell ScriptͷύεΛॻ͖ࠐΉ
  14. XcodeͷPreferencesΛॻ͖׵͑Δ { "Xcode.AlertEvents.4_1": { "Xcode.AlertEvents.BuildStart": { "Xcode.Alert.RunScript": { "enabled": boolean,

    "path": "Shell Scriptͷύε" } }, "Xcode.AlertEvents.TestingSucceeds": { "Xcode.Alert.RunScript": { "enabled": boolean, "path": "Shell Scriptͷύε" } } } } Plist ͷ಺༰ΛJSONܗࣜͰൈਮͨ͠΋ͷ BehaviorsʹRun ScriptΛઃఆ͢Δʹ͸ɺ Xcode.AlertEvents ͱ Xcode.AlertEvents.4_1 ͷ಺༰Λॻ͖׵͑Ε͹ྑ͍ ྫ͑͹ɺXcode.AlertEvent.BuildStart ͷத ͷ Xcode.Alert.RunScript ʹ࣮ߦ͍ͨ͠
 Shell ScriptͷύεΛॻ͖ࠐΉ ֊૚ߏ଄ͷਂ͍෦෼ͷॻ͖׵͕͑ඞཁ ଞͷ෦෼͸ͦͷ··อ͍ͪͨ
  15. XcodeͷPreferencesΛॻ͖׵͑Δ 4_1 ͬͯͳΜͩʁ 🧐 Xcode.AlertEvents ͱ Xcode.AlertEvents.4_1 ͸΄΅ಉ͡σʔλߏ଄͕ͩɺ
 ͓ͦΒ͘໾ׂ͕ҧ͏ Xcode.AlertEvents.4_1

    ͸Ϣʔβʔ͕ࢦఆͨ͠શͯͷઃఆ͕ೖΔͷͰຊମ Xcode.AlertEvents ͸4_1ͷόοΫΞοϓʁʢҰ෦ͷઃఆ͚͕ͩೖΔʣ
  16. XcodeͷPreferencesΛॻ͖׵͑Δ Command LineͰProperty ListΛॻ͖׵͑Δ৔߹ɺplutil ΍ PlistBuddy Λ
 ࢖༻͢Δ͜ͱ͕ଟ͍ plutil ͸̍֊૚໨·Ͱ͔͠௚઀ૢ࡞Ͱ͖ͳ͍

    😩 → ࿦֎ PlistBuddy ͸̎֊૚Ҏ্͋ͬͯ΋ૢ࡞͕Մೳ 😃 อଘܗ͕ࣜXMLʹͳͬͯ͠·͏ ☹ ্ॻ͖͍ͨ͠ՕॴͷΈΛॻ͖׵͑ΔΑ͏ͳϚʔδ͕Ͱ͖ͳ͍ 🤯
 ʢ߲̍໨ͣͭͷॻ͖׵͑ͱ·ͩଘࡏ͍ͯ͠ͳ͍σʔλͷ௥Ճ͸Ͱ͖Δʣ
  17. ্ॻ͖͍ͨ͠ՕॴͷΈΛॻ͖׵͑ΔΑ͏ͳϚʔδ͕Ͱ͖ͳ͍ͷҙຯ XcodeͷPreferencesΛॻ͖׵͑Δ { "Apple": { "Price": 100, "Color": "Green" },

    "Banana": { "Price": 100, "Color": "Yellow" }, "Cherry": { "Price": 100, "Color": "Red" } } { "Apple": { "Price": 150 }, "Banana": { "Price": 150 }, "Cherry": { "Price": 150 } } { "Apple": { "Price": 150, "Color": "Green" }, "Banana": { "Price": 150, "Color": "Yellow" }, "Cherry": { "Price": 150, "Color": "Red" } } ΦϦδφϧ ॻ͖׵͍͑ͨ಺༰ ཧ૝ͷϚʔδ݁Ռ
  18. ্ॻ͖͍ͨ͠ՕॴͷΈΛॻ͖׵͑ΔΑ͏ͳϚʔδ͕Ͱ͖ͳ͍ͷҙຯ XcodeͷPreferencesΛॻ͖׵͑Δ { "Apple": { "Price": 100, "Color": "Green" },

    "Banana": { "Price": 100, "Color": "Yellow" }, "Cherry": { "Price": 100, "Color": "Red" } } { "Apple": { "Price": 150 }, "Banana": { "Price": 150 }, "Cherry": { "Price": 150 } } ΦϦδφϧ ॻ͖׵͍͑ͨ಺༰ طʹಉ໊ͷΩʔ͕ଘࡏͯ͠
 ͍Δ৔߹Ϛʔδ͕Ͱ͖ͳ͍ PlistBuddyͰͷϚʔδ݁Ռ
  19. XcodeͷPreferencesΛॻ͖׵͑Δ let saveURL = URL(fileURLWithPath: "sample.plist") var dict: [String: Any]

    = [:] // dictΛฤूʢதུʣ let data = try! PropertyListSerialization.data(fromPropertyList: dict, format: .binary, options: .zero) try! data.write(to: saveURL, options: .atomic) طଘͷCommand LineπʔϧΛ࢖ΘͣSwiftͰࣗલ࣮૷͢Δ SwiftͰProperty ListΛॻ͖׵͑Δ৔߹ɺPropertyListSerialization Λ࢖͑͹
 BinaryܗࣜͷProperty ListΛฤूՄೳ
  20. ΠϕϯτͷώετϦʔΛਖ਼͍͠ॱংͰऔಘ͢Δ func run(_ args: String...) { let task = Process()

    task.launchPath = "/bin/sh" task.arguments = ["-c", args.joined(separator: " ")] task.launch() task.waitUntilExit() } run("gcc", "timestamp.c", "-o", "ts") CݴޠͰϛϦඵϨϕϧͷλΠϜελϯϓΛऔಘ͢ΔʢϏϧυฤʣ Process() Λ࢖ͬͯSwift͔ΒShellΛୟ͘ ༻ҙ͓͍ͯͨ͠CݴޠͷίʔυΛ gcc ͰίϯύΠϧ͢Δ