From 99806e9c0ff6b6c06667f351f90cd7fd2f9fef06 Mon Sep 17 00:00:00 2001 From: Julien Lengrand-Lambert Date: Wed, 3 Jul 2024 13:28:55 +0200 Subject: [PATCH] my stuff --- 5GUIs.xcodeproj/project.pbxproj | 164 ++++++++- 5GUIs/BundleFeatureDetectionOperation.swift | 328 ++++++++++++++++++ .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 58 ++++ test-nate/Assets.xcassets/Contents.json | 6 + test-nate/ContentView.swift | 24 ++ .../Preview Assets.xcassets/Contents.json | 6 + test-nate/test_nate.entitlements | 10 + test-nate/test_nateApp.swift | 17 + 9 files changed, 617 insertions(+), 7 deletions(-) create mode 100644 5GUIs/BundleFeatureDetectionOperation.swift create mode 100644 test-nate/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 test-nate/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 test-nate/Assets.xcassets/Contents.json create mode 100644 test-nate/ContentView.swift create mode 100644 test-nate/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 test-nate/test_nate.entitlements create mode 100644 test-nate/test_nateApp.swift diff --git a/5GUIs.xcodeproj/project.pbxproj b/5GUIs.xcodeproj/project.pbxproj index 101737f..01ba705 100644 --- a/5GUIs.xcodeproj/project.pbxproj +++ b/5GUIs.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 6B1A06FB2C3566F4009A4C2D /* BundleFeatureDetectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A06FA2C3566F4009A4C2D /* BundleFeatureDetectionOperation.swift */; }; + 6B1A07032C356845009A4C2D /* test_nateApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A07022C356845009A4C2D /* test_nateApp.swift */; }; + 6B1A07052C356845009A4C2D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B1A07042C356845009A4C2D /* ContentView.swift */; }; + 6B1A07072C356846009A4C2D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B1A07062C356846009A4C2D /* Assets.xcassets */; }; + 6B1A070A2C356846009A4C2D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B1A07092C356846009A4C2D /* Preview Assets.xcassets */; }; E83E32BF25220277009BE980 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E83E32BE25220277009BE980 /* Assets.xcassets */; }; E83E32C225220277009BE980 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E83E32C125220277009BE980 /* Preview Assets.xcassets */; }; E83E32C525220277009BE980 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E83E32C325220277009BE980 /* Main.storyboard */; }; @@ -28,7 +33,6 @@ E8F1C42825260051000B517B /* DetailsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C40F25260051000B517B /* DetailsPopover.swift */; }; E8F1C42925260051000B517B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41025260051000B517B /* ContentView.swift */; }; E8F1C42A25260051000B517B /* MainFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41125260051000B517B /* MainFileView.swift */; }; - E8F1C42B25260051000B517B /* BundleFeatureDetectionOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41325260051000B517B /* BundleFeatureDetectionOperation.swift */; }; E8F1C42C25260051000B517B /* InfoDict.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41425260051000B517B /* InfoDict.swift */; }; E8F1C42D25260051000B517B /* FakeDetectionStepper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41525260051000B517B /* FakeDetectionStepper.swift */; }; E8F1C42E25260051000B517B /* FakeStepConfigs.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F1C41625260051000B517B /* FakeStepConfigs.swift */; }; @@ -40,6 +44,13 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 6B1A06FA2C3566F4009A4C2D /* BundleFeatureDetectionOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleFeatureDetectionOperation.swift; sourceTree = ""; }; + 6B1A07002C356845009A4C2D /* test-nate.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "test-nate.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B1A07022C356845009A4C2D /* test_nateApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = test_nateApp.swift; sourceTree = ""; }; + 6B1A07042C356845009A4C2D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 6B1A07062C356846009A4C2D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 6B1A07092C356846009A4C2D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 6B1A070B2C356846009A4C2D /* test_nate.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = test_nate.entitlements; sourceTree = ""; }; E83E32B725220275009BE980 /* 5 GUIs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "5 GUIs.app"; sourceTree = BUILT_PRODUCTS_DIR; }; E83E32BE25220277009BE980 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; E83E32C125220277009BE980 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; @@ -66,7 +77,6 @@ E8F1C40F25260051000B517B /* DetailsPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsPopover.swift; sourceTree = ""; }; E8F1C41025260051000B517B /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; E8F1C41125260051000B517B /* MainFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainFileView.swift; sourceTree = ""; }; - E8F1C41325260051000B517B /* BundleFeatureDetectionOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BundleFeatureDetectionOperation.swift; path = ../Sources/5GUIs/BundleFeatureDetectionOperation.swift; sourceTree = ""; }; E8F1C41425260051000B517B /* InfoDict.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoDict.swift; sourceTree = ""; }; E8F1C41525260051000B517B /* FakeDetectionStepper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeDetectionStepper.swift; sourceTree = ""; }; E8F1C41625260051000B517B /* FakeStepConfigs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeStepConfigs.swift; sourceTree = ""; }; @@ -80,6 +90,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6B1A06FD2C356845009A4C2D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; E83E32B425220275009BE980 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -90,11 +107,32 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6B1A07012C356845009A4C2D /* test-nate */ = { + isa = PBXGroup; + children = ( + 6B1A07022C356845009A4C2D /* test_nateApp.swift */, + 6B1A07042C356845009A4C2D /* ContentView.swift */, + 6B1A07062C356846009A4C2D /* Assets.xcassets */, + 6B1A070B2C356846009A4C2D /* test_nate.entitlements */, + 6B1A07082C356846009A4C2D /* Preview Content */, + ); + path = "test-nate"; + sourceTree = ""; + }; + 6B1A07082C356846009A4C2D /* Preview Content */ = { + isa = PBXGroup; + children = ( + 6B1A07092C356846009A4C2D /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; E83E32AE25220275009BE980 = { isa = PBXGroup; children = ( E8F1C437252600A9000B517B /* README.md */, E83E32B925220275009BE980 /* 5GUIs */, + 6B1A07012C356845009A4C2D /* test-nate */, E83E32B825220275009BE980 /* Products */, ); sourceTree = ""; @@ -103,6 +141,7 @@ isa = PBXGroup; children = ( E83E32B725220275009BE980 /* 5 GUIs.app */, + 6B1A07002C356845009A4C2D /* test-nate.app */, ); name = Products; sourceTree = ""; @@ -115,7 +154,6 @@ E8F1C41B25260051000B517B /* Style.swift */, E8F1C41A25260051000B517B /* WindowState.swift */, E8F1C41925260051000B517B /* Windows.swift */, - E8F1C41325260051000B517B /* BundleFeatureDetectionOperation.swift */, E8F1C41225260051000B517B /* Model */, E8F1C40725260051000B517B /* Views */, E8F1C40125260051000B517B /* Utilities */, @@ -125,6 +163,7 @@ E83E32C625220277009BE980 /* Info.plist */, E83E32C725220277009BE980 /* _GUIs.entitlements */, E83E32C025220277009BE980 /* Preview Content */, + 6B1A06FA2C3566F4009A4C2D /* BundleFeatureDetectionOperation.swift */, ); path = 5GUIs; sourceTree = ""; @@ -236,6 +275,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 6B1A06FF2C356845009A4C2D /* test-nate */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6B1A070C2C356846009A4C2D /* Build configuration list for PBXNativeTarget "test-nate" */; + buildPhases = ( + 6B1A06FC2C356845009A4C2D /* Sources */, + 6B1A06FD2C356845009A4C2D /* Frameworks */, + 6B1A06FE2C356845009A4C2D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "test-nate"; + productName = "test-nate"; + productReference = 6B1A07002C356845009A4C2D /* test-nate.app */; + productType = "com.apple.product-type.application"; + }; E83E32B625220275009BE980 /* 5GUIs */ = { isa = PBXNativeTarget; buildConfigurationList = E83E32CA25220277009BE980 /* Build configuration list for PBXNativeTarget "5GUIs" */; @@ -260,9 +316,12 @@ E83E32AF25220275009BE980 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1200; + LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1200; TargetAttributes = { + 6B1A06FF2C356845009A4C2D = { + CreatedOnToolsVersion = 15.4; + }; E83E32B625220275009BE980 = { CreatedOnToolsVersion = 12.0; LastSwiftMigration = 1200; @@ -283,11 +342,21 @@ projectRoot = ""; targets = ( E83E32B625220275009BE980 /* 5GUIs */, + 6B1A06FF2C356845009A4C2D /* test-nate */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 6B1A06FE2C356845009A4C2D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6B1A070A2C356846009A4C2D /* Preview Assets.xcassets in Resources */, + 6B1A07072C356846009A4C2D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E83E32B525220275009BE980 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -325,6 +394,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 6B1A06FC2C356845009A4C2D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6B1A07052C356845009A4C2D /* ContentView.swift in Sources */, + 6B1A07032C356845009A4C2D /* test_nateApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; E83E32B325220275009BE980 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -339,7 +417,6 @@ E8F1C42C25260051000B517B /* InfoDict.swift in Sources */, E8F1C43325260051000B517B /* Style.swift in Sources */, E8F1C42F25260051000B517B /* LoadBundleImage.swift in Sources */, - E8F1C42B25260051000B517B /* BundleFeatureDetectionOperation.swift in Sources */, E8F1C42825260051000B517B /* DetailsPopover.swift in Sources */, E8F1C42725260051000B517B /* InfoPanel.swift in Sources */, E8F1C41E25260051000B517B /* URLItems.swift in Sources */, @@ -354,6 +431,7 @@ E8F1C42A25260051000B517B /* MainFileView.swift in Sources */, E8F1C42225260051000B517B /* SpinnerView.swift in Sources */, E8F1C42325260051000B517B /* PleaseDropAFileView.swift in Sources */, + 6B1A06FB2C3566F4009A4C2D /* BundleFeatureDetectionOperation.swift in Sources */, E8F1C42525260051000B517B /* SorryNotAnExecutableView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -372,6 +450,69 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 6B1A070D2C356846009A4C2D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test-nate/test_nate.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"test-nate/Preview Content\""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "nl.lengrand.test-nate"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 6B1A070E2C356846009A4C2D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_ENTITLEMENTS = "test-nate/test_nate.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"test-nate/Preview Content\""; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.5; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "nl.lengrand.test-nate"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; E83E32C825220277009BE980 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -499,7 +640,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"5GUIs/Preview Content\""; - DEVELOPMENT_TEAM = 4GXF3JAMM4; + DEVELOPMENT_TEAM = 9W4T5R5M9Y; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = 5GUIs/Info.plist; @@ -529,7 +670,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 7; DEVELOPMENT_ASSET_PATHS = "\"5GUIs/Preview Content\""; - DEVELOPMENT_TEAM = 4GXF3JAMM4; + DEVELOPMENT_TEAM = 9W4T5R5M9Y; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = 5GUIs/Info.plist; @@ -549,6 +690,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 6B1A070C2C356846009A4C2D /* Build configuration list for PBXNativeTarget "test-nate" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6B1A070D2C356846009A4C2D /* Debug */, + 6B1A070E2C356846009A4C2D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; E83E32B225220275009BE980 /* Build configuration list for PBXProject "5GUIs" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/5GUIs/BundleFeatureDetectionOperation.swift b/5GUIs/BundleFeatureDetectionOperation.swift new file mode 100644 index 0000000..b0c0a41 --- /dev/null +++ b/5GUIs/BundleFeatureDetectionOperation.swift @@ -0,0 +1,328 @@ +// +// BundleFeatureDetectionOperation.swift +// 5 GUIs +// +// Created by Helge Heß on 28.09.20. +// + +import SwiftUI + +protocol BundleFeatureDetectionOperationDelegate: AnyObject { + func detectionStateDidChange(_ state: BundleFeatureDetectionOperation) +} + +/** + * This is the main "operation" object which runs from a background queue and + * collects all the info we want. + */ +final class BundleFeatureDetectionOperation: ObservableObject { + // FIXME: this is more like a 'load operation'. But we can't use plain + // Operation, because we are async (would need to do that boilerplate). + + weak var delegate : BundleFeatureDetectionOperationDelegate? + + enum State: Equatable { + case processing + case failedToOpen(Swift.Error?) + case notAnApplication + case finished + static func == (lhs: State, rhs: State) -> Bool { + switch ( lhs, rhs ) { + case ( .processing , .processing ): return true + case ( .failedToOpen , .failedToOpen ): return true + case ( .notAnApplication , .notAnApplication ): return true + case ( .finished , .finished ): return true + default: return false + } + } + } + + let fm = FileManager.default + + @Published var state = State.processing { + didSet { + assert(_dispatchPreconditionTest(.onQueue(.main))) + delegate?.detectionStateDidChange(self) + } + } + @Published var info : ExecutableFileTechnologyInfo { + // Careful w/ accessing this within the operation, you can't!!! Threads. + didSet { + assert(_dispatchPreconditionTest(.onQueue(.main))) + delegate?.detectionStateDidChange(self) + } + } + @Published var otoolAvailable = true + + let url : URL + + private let nesting : Int + + init(_ url: URL, nesting: Int = 1) { + self.url = url + self.info = ExecutableFileTechnologyInfo(fileURL: url) + self.nesting = nesting + } + func resume() { + DispatchQueue.global().async { + self.startWork() + } + } + + + // MARK: - Just some threadsafe helpers ... + + private func apply(_ block: @escaping () -> Void) { + RunLoop.main.perform(block) + } + private func apply(_ keyPath: + ReferenceWritableKeyPath, + _ value: V) + { + apply { + self[keyPath: keyPath] = value + } + } + private func applyState(_ state: State) { // Q: Any + apply(\.state, state) + } + + + // MARK: - Main Entry + + private func startWork() { // Q: background + + var isDir : ObjCBool = false + guard fm.fileExists(atPath: url.path, isDirectory: &isDir) else { + return applyState(.failedToOpen(nil)) + } + + if isDir.boolValue { + processWrapper(url) + } + else { + processFile(url) + } + } + + + // MARK: - Workers + + private func processFile(_ url: URL) { + guard fm.isExecutableFile(atPath: url.path) else { + return applyState(.notAnApplication) + } + + // TODO: grab Info.plist embedded in Mach-O + + apply(\.info.executableURL, url) + + processExecutable(url) + + applyState(.finished) + } + + /** + * Processes an app bundle (on a background queue). + * + * Steps + * 1. load and parse info dictionary + * 2. load image + * 3. run otool on the main executable and its dependencies + * 4. look for files in the bundle hierarchy + */ + private func processWrapper(_ url: URL) { // Q: Any + guard let bundle = Bundle(url: url) else { + print("could not open bundle:", url) + return applyState(.failedToOpen(nil)) + } + + let info = InfoDict(bundle.infoDictionary ?? [:]) + + guard let executableURL = bundle.executableURL else { + print("no executable in bundle:", bundle) + return applyState(.notAnApplication) + } + let receiptURL = bundle.appStoreReceiptURL + + apply { + self.info.executableURL = executableURL + self.info.receiptURL = receiptURL + self.info.infoDictionary = info + } + + let image = loadImage(in: info, bundle: bundle) + apply(\.info.appImage, image) + + processExecutable(executableURL) + + processDirectoryContents(url) + + if nesting < 2 { + processNestedApplications(ownExecutable: info.executable + ?? executableURL.lastPathComponent) + } + + // DONE + self.applyState(.finished) + } + + + // MARK: - Individual Workers + + private func processNestedApplications(ownExecutable: String) { + let contents = url.appendingPathComponent("Contents") + + func scan(_ directory: URL) { + let apps = fm.ls(directory, suffix: ".app").lazy + .filter { $0 != ownExecutable } + .map { directory.appendingPathComponent($0) } + for app in apps { + // We could actually async-nest the operations, but all things are + // currently synchronous anyways. + let op = BundleFeatureDetectionOperation(app, nesting: nesting + 1) + op.startWork() // same thread (vs resume), no delegate + + if op.info.executableURL != nil && + op.info.infoDictionary != nil && + !op.info.detectedTechnologies.isEmpty + { + apply { + self.info.embeddedExecutables.append(op.info) + } + } + } + } + + scan(contents.appendingPathComponent("MacOS")) + scan(contents.appendingPathComponent("Frameworks")) + } + + /// This runs objdump on the executable (w/ traversal of dependencies) + private func processExecutable(_ executableURL: URL) { // Q: Any + do { + let dependencies = try otool(executableURL) + + // scan in this bg thread + var detectedFeatures = DetectedTechnologies() + detectedFeatures.scanDependencies(dependencies) + + apply { + self.otoolAvailable = true + self.info.dependencies = dependencies + self.info.detectedTechnologies.formUnion(detectedFeatures) + } + } + catch { + print("Could not invoke OTool:", error) + apply(\.otoolAvailable, false) + } + } + + /// Looks at the directory hierarchy of the bundle + private func processDirectoryContents(_ url: URL) { // Q: Any + var detectedFeatures = DetectedTechnologies() + let contents = url.appendingPathComponent("Contents") + + // Charles & Eclipse + for pc in [ "Java", "Eclipse" ] { + let suburl = contents.appendingPathComponent(pc) + if fm.fileExists(atPath: suburl.path) { + detectedFeatures.insert(.java) + break + } + } + + // JD-GUI + do { + if info.infoDictionary?.JavaX ?? false { + detectedFeatures.insert(.java) + } + } + + do { // Electron apps seem to have this ... + let suburl = contents.appendingPathComponent("Resources/app.asar") + if fm.fileExists(atPath: suburl.path) { + detectedFeatures.insert(.electron) + } + } + + // Check for AppleScript scripts contained (app or not) + do { + let suburl = contents.appendingPathComponent("Resources/Scripts") + if !fm.ls(suburl, suffix: ".scpt").isEmpty { + detectedFeatures.insert(.applescript) + } + } + + // scan for Platypus + do { + let suburl1 = + contents.appendingPathComponent("Resources/AppSettings.plist") + let suburl2 = contents.appendingPathComponent("Resources/script") + if fm.fileExists(atPath: suburl1.path) && + fm.fileExists(atPath: suburl2.path) + { + detectedFeatures.insert(.platypus) + } + } + + // scan the Frameworks directory + do { + let suburl = contents.appendingPathComponent("Frameworks") + for filename in fm.ls(suburl) { + if filename.hasPrefix("libwx_") { + detectedFeatures.insert(.wxWidgets) + } + else if filename == "python-extensions" { + detectedFeatures.insert(.python) + } + } + } + + if !detectedFeatures.isEmpty { + apply { + self.info.detectedTechnologies.formUnion(detectedFeatures) + } + } + } +} + +fileprivate extension FileManager { + + func ls(_ url: URL, suffix: String = "") -> [ String ] { + (try? contentsOfDirectory(at: url, includingPropertiesForKeys: nil, + options: .skipsSubdirectoryDescendants) + .map { $0.lastPathComponent } + .filter { $0.hasSuffix(suffix) } + .sorted() + ) ?? [] + } +} + +extension DetectedTechnologies { + + mutating func scanDependencies(_ dependencies: [ String ]) { + for dep in dependencies { + func check(_ option: DetectedTechnologies, _ needle: String) -> Bool + { + guard !contains(option) else { return false } // scanned already + guard dep.contains(needle) else { return false } + self.insert(option) + return true + } + + if check(.electron, "Electron") { continue } + if check(.catalyst, "UIKitMacHelper") { continue } + if check(.appkit, "AppKit.framework") { continue } + if check(.swiftui, "SwiftUI.framework") { continue } + if check(.uikit, "UIKit.framework") { continue } + if check(.qt, "QtCore.framework") { continue } + if check(.webkit, "WebKit.framework") { continue } + + if check(.cplusplus, "libc++") { continue } + if check(.objc, "libobjc") { continue } + if check(.swift, "libswiftCore") { continue } + } + } +} diff --git a/test-nate/Assets.xcassets/AccentColor.colorset/Contents.json b/test-nate/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/test-nate/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-nate/Assets.xcassets/AppIcon.appiconset/Contents.json b/test-nate/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/test-nate/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-nate/Assets.xcassets/Contents.json b/test-nate/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/test-nate/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-nate/ContentView.swift b/test-nate/ContentView.swift new file mode 100644 index 0000000..81a7157 --- /dev/null +++ b/test-nate/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// test-nate +// +// Created by julien Lengrand-Lambert on 03/07/2024. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/test-nate/Preview Content/Preview Assets.xcassets/Contents.json b/test-nate/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/test-nate/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/test-nate/test_nate.entitlements b/test-nate/test_nate.entitlements new file mode 100644 index 0000000..18aff0c --- /dev/null +++ b/test-nate/test_nate.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/test-nate/test_nateApp.swift b/test-nate/test_nateApp.swift new file mode 100644 index 0000000..19ecd1a --- /dev/null +++ b/test-nate/test_nateApp.swift @@ -0,0 +1,17 @@ +// +// test_nateApp.swift +// test-nate +// +// Created by julien Lengrand-Lambert on 03/07/2024. +// + +import SwiftUI + +@main +struct test_nateApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +}