├── .gitignore ├── .swiftformat ├── .swiftlint.yml ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── xcgrapher.xcscheme ├── Makefile ├── Marketting └── xcgrapher.png ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── XCGrapherLib │ ├── DependencyManagers │ │ ├── CocoapodsManager.swift │ │ ├── DependencyManager.swift │ │ ├── NativeDependencyManager.swift │ │ ├── SwiftPackageManager.swift │ │ └── UnmanagedDependencyManager.swift │ ├── Digraph.swift │ ├── Extensions │ │ ├── Array.swift │ │ ├── FileManager.swift │ │ ├── Scanner.swift │ │ └── String.swift │ ├── Helpers │ │ ├── General.swift │ │ └── ShellTask.swift │ ├── ImportFinder.swift │ ├── Models │ │ └── PackageDescription.swift │ ├── PluginSupport │ │ ├── PluginLoader.swift │ │ └── PluginSupport.swift │ ├── ShellTasks │ │ ├── Graphviz.swift │ │ ├── SwiftBuild.swift │ │ ├── SwiftPackage.swift │ │ ├── SwiftPackageDependencySource.swift │ │ ├── Xcodebuild.swift │ │ └── Xcodeproj.swift │ ├── XCGrapher.swift │ └── XCGrapherOptions.swift ├── XCGrapherModuleImportPlugin │ ├── Extensions.swift │ ├── Models.swift │ └── Plugin.swift └── xcgrapher │ ├── XCGrapherArguments.swift │ └── main.swift └── Tests ├── SampleProjects ├── SomeApp │ ├── Podfile │ ├── Podfile.lock │ ├── SomeApp.xcodeproj │ │ ├── project.pbxproj │ │ └── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ ├── SomeApp.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── SomeApp │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── FileNotInTargetMembership.swift │ │ ├── Imports │ │ ├── Apple │ │ │ ├── AVFoundationImports.swift │ │ │ ├── FoundationImportrs.swift │ │ │ └── UIKitImports.swift │ │ ├── Pods │ │ │ ├── Auth0Imports.swift │ │ │ ├── MoyaImports.swift │ │ │ └── RxImports.swift │ │ └── SPM │ │ │ ├── ChartsImports.swift │ │ │ ├── LottieImports.swift │ │ │ └── RealmImports.swift │ │ ├── Info.plist │ │ └── SceneDelegate.swift └── SomePackage │ ├── .gitignore │ ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── contents.xcworkspacedata │ ├── Package.resolved │ ├── Package.swift │ ├── README.md │ ├── Sources │ └── SomePackage │ │ ├── AppleImports.swift │ │ ├── DependencyImports.swift │ │ └── SomePackage.swift │ └── Tests │ ├── LinuxMain.swift │ └── SomePackageTests │ ├── SomePackageTests.swift │ └── XCTestManifests.swift └── XCGrapherLibTests ├── ArrayExtensionTests.swift ├── FileManagerExtensionTests.swift ├── Helpers.swift ├── ScannerTests.swift ├── ShellTaskTests.swift ├── StringExtensionTests.swift ├── XCGrapherTests+SPM.swift └── XCGrapherTests+Xcodeproject.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | Tests/SampleProjects/SomeApp/Pods 7 | Tests/SampleProjects/SomePackage/.build 8 | Sources/xcgrapher/Generated.swift 9 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --swiftversion 5 2 | 3 | # Format 4 | --allman false 5 | --elseposition same-line 6 | --guardelse same-line 7 | --ifdef no-indent 8 | --wraparguments before-first 9 | --wrapcollections before-first 10 | --wrapparameters before-first 11 | 12 | # File Options 13 | --exclude "**/.build/**" 14 | --exclude "**/Pods/**" 15 | 16 | # Rules 17 | --enable andOperator 18 | --enable AnyObjectProtocol 19 | --enable blankLinesAroundMark 20 | --enable blankLinesBetweenScopes 21 | --enable consecutiveBlankLines 22 | --enable consecutiveSpaces 23 | --enable duplicateImports 24 | --enable emptyBraces 25 | --enable hoistPatternLet 26 | --enable initCoderUnavailable 27 | --enable isEmpty 28 | --enable leadingDelimiters 29 | --enable linebreakAtEndOfFile 30 | --enable linebreaks 31 | --enable modifierOrder 32 | --enable preferKeyPath 33 | --enable redundantBackticks 34 | --enable redundantBreak 35 | --enable redundantExtensionACL 36 | --enable redundantFileprivate 37 | --enable redundantGet 38 | --enable redundantInit 39 | --enable redundantLet 40 | --enable redundantLetError 41 | --enable redundantNilInit 42 | --enable redundantObjc 43 | --enable redundantParens 44 | --enable redundantPattern 45 | --enable redundantSelf 46 | --enable redundantVoidReturnType 47 | --enable semicolons 48 | --enable sortedImports 49 | --enable spaceAroundBraces 50 | --enable spaceAroundBrackets 51 | --enable spaceAroundComments 52 | --enable spaceAroundGenerics 53 | --enable spaceAroundOperators 54 | --enable spaceAroundParens 55 | --enable spaceInsideBraces 56 | --enable spaceInsideBrackets 57 | --enable spaceInsideComments 58 | --enable spaceInsideGenerics 59 | --enable spaceInsideParens 60 | --enable strongifiedSelf 61 | --enable strongOutlets 62 | --enable todos 63 | --enable trailingSpace 64 | --enable typeSugar 65 | --enable void 66 | --enable wrap 67 | --enable wrapAttributes 68 | 69 | --disable blankLinesAtEndOfScope 70 | --disable blankLinesAtStartOfScope 71 | --disable numberFormatting 72 | --disable redundantReturn 73 | --disable unusedArguments 74 | --disable wrapMultilineStatementBraces 75 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | opt_in_rules: 2 | - closure_spacing 3 | - overridden_super_call 4 | - redundant_nil_coalescing 5 | - prohibited_super_call 6 | - explicit_init 7 | - unused_import 8 | - empty_parameters 9 | - attributes 10 | - closure_end_indentation 11 | - collection_alignment 12 | - trailing_comma 13 | - convenience_type 14 | - empty_collection_literal 15 | - empty_count 16 | - empty_enum_arguments 17 | - empty_string 18 | - extension_access_modifier 19 | - file_name_no_space 20 | - implicit_getter 21 | - implicit_return 22 | - joined_default_parameter 23 | - modifier_order 24 | - multiline_arguments 25 | - multiline_literal_brackets 26 | - multiple_closures_with_trailing_closure 27 | - operator_usage_whitespace 28 | - pattern_matching_keywords 29 | - prefer_self_type_over_type_of_self 30 | - unneeded_parentheses_in_closure_argument 31 | - multiline_arguments_brackets 32 | - multiline_function_chains 33 | - for_where 34 | - trailing_whitespace 35 | 36 | disabled_rules: 37 | - file_length 38 | - line_length 39 | - type_body_length 40 | - identifier_name 41 | - type_name 42 | - todo 43 | - nesting 44 | - duplicate_enum_cases 45 | - force_unwrapping 46 | - force_cast 47 | - function_body_length 48 | - cyclomatic_complexity 49 | - weak_delegate 50 | 51 | custom_rules: 52 | 53 | customrule_case_let: 54 | regex: "case +\\.[A-Za-z0-9]*\\([^\n]*?(let )" 55 | message: "Prefer `case let .something(varname):` instead of `case .something(let varname):`" 56 | capture_group: 1 57 | 58 | customrule_force_vertical_alignment_with_whitespace: 59 | regex: "[^ ]( {2,20})[:=]" 60 | message: "Unexpected whitespace. Use just a single space between elements" 61 | capture_group: 1 62 | 63 | customrule_string_contentsof: 64 | regex: "String\\(contentsOf:" 65 | message: "This could make a synchronous API call - reconsider or use String(contentsOfFile:) for loading local files" 66 | 67 | customrule_init_nscoder: 68 | regex: "required init[^\n]*: NSCoder\\) \\{(\n +[fp]| \\S+\"| precon)" 69 | message: "Simplify to 'required init?(coder: NSCoder) { fatalError() }'" 70 | capture_group: 1 71 | 72 | customrule_blank_line_opening_class: 73 | regex: "(class|extension) [A-Z][^{\n]*\\{[^\n}]*\n^[^\n]" 74 | message: "Insert blank line after class/extension opening '{'" 75 | 76 | # This is a bit of a cheeky one as it's only checking top-level classes.. but should still work as a teaching mechanism 77 | customrule_blank_line_closing_class: 78 | regex: " *(class|extension) [A-Z][^\n]*\n(\n [^\n]*|\n)+?\\}\n(\\})" 79 | message: "Insert blank line before class/extension closing '}'" 80 | capture_group: 3 81 | 82 | included: # paths to include during linting. `--path` is ignored if present. 83 | 84 | excluded: # paths to ignore during linting. Takes precedence over `included`. 85 | - ".build" 86 | - "Tests/SampleProjects/SomePackage/.build" 87 | - "Tests/SampleProjects/SomeApp/Pods" 88 | 89 | large_tuple: 90 | warning: 5 91 | 92 | trailing_comma: 93 | mandatory_comma: true 94 | 95 | multiline_arguments: 96 | first_argument_location: next_line 97 | 98 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/xcgrapher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 77 | 78 | 79 | 80 | 81 | 91 | 93 | 99 | 100 | 101 | 102 | 108 | 110 | 116 | 117 | 118 | 119 | 121 | 122 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix ?= /usr/local 2 | bindir = $(prefix)/bin 3 | libdir = $(prefix)/lib 4 | buildroot = $(shell swift build -c release --show-bin-path) 5 | 6 | configure: 7 | echo "let DEFAULT_PLUGIN_LOCATION=\"$(libdir)/libXCGrapherModuleImportPlugin.dylib\"" > Sources/xcgrapher/Generated.swift 8 | 9 | build: configure 10 | xcrun swift build -c release --disable-sandbox 11 | 12 | install: build 13 | # Seems like brew hasn't created this yet and it confuses 'install' so... 14 | mkdir -p "$(bindir)" 15 | mkdir -p "$(libdir)" 16 | # Install the binary 17 | install "$(buildroot)/xcgrapher" "$(bindir)" 18 | # Install the libs 19 | install "$(buildroot)/libXCGrapherPluginSupport.dylib" "$(libdir)" 20 | install "$(buildroot)/libXCGrapherModuleImportPlugin.dylib" "$(libdir)" 21 | install_name_tool -change "$(buildroot)/libXCGrapherPluginSupport.dylib" "$(libdir)/libXCGrapherPluginSupport.dylib" "$(bindir)/xcgrapher" 22 | install_name_tool -change "@rpath/libXCGrapherPluginSupport.dylib" "$(libdir)/libXCGrapherPluginSupport.dylib" "$(bindir)/xcgrapher" 23 | 24 | uninstall: 25 | rm -rf "$(bindir)/xcgrapher" 26 | rm -rf "$(libdir)/libXCGrapherPluginSupport.dylib" 27 | rm -rf "$(libdir)/libXCGrapherModuleImportPlugin.dylib" 28 | 29 | lint: 30 | swiftlint --autocorrect . 31 | swiftlint . 32 | swiftformat . 33 | 34 | clean: 35 | rm -rf .build 36 | rm Sources/xcgrapher/Generated.swift 37 | 38 | .PHONY: build install uninstall clean configure 39 | -------------------------------------------------------------------------------- /Marketting/xcgrapher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxchuquimia/xcgrapher/1af334762393165b438860ca73039dd46b3ebfe0/Marketting/xcgrapher.png -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "swift-argument-parser", 6 | "repositoryURL": "https://github.com/apple/swift-argument-parser", 7 | "state": { 8 | "branch": null, 9 | "revision": "831ed5e860a70e745bc1337830af4786b2576881", 10 | "version": "0.4.1" 11 | } 12 | }, 13 | { 14 | "package": "XCGrapherPluginSupport", 15 | "repositoryURL": "https://github.com/maxchuquimia/XCGrapherPluginSupport", 16 | "state": { 17 | "branch": null, 18 | "revision": "207d864c14b0cf6ed54b9f892817984f9acf03dc", 19 | "version": "0.0.6" 20 | } 21 | } 22 | ] 23 | }, 24 | "version": 1 25 | } 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "xcgrapher", 7 | platforms: [ 8 | .macOS(.v10_15), 9 | ], 10 | products: [ 11 | .executable(name: "xcgrapher", targets: ["xcgrapher"]), 12 | .library(name: "XCGrapherModuleImportPlugin", type: .dynamic, targets: ["XCGrapherModuleImportPlugin"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "0.4.0")), 16 | .package(url: "https://github.com/maxchuquimia/XCGrapherPluginSupport", .upToNextMinor(from: "0.0.6")), 17 | ], 18 | targets: [ 19 | .target( 20 | name: "xcgrapher", 21 | dependencies: [ 22 | "XCGrapherLib", 23 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 24 | ] 25 | ), 26 | .target( 27 | name: "XCGrapherLib", // Main source added to a separate framework for testability reasons 28 | dependencies: [ 29 | "XCGrapherPluginSupport", 30 | ] 31 | ), 32 | .target( 33 | name: "XCGrapherModuleImportPlugin", 34 | dependencies: [ 35 | "XCGrapherPluginSupport", 36 | ] 37 | ), 38 | .testTarget( 39 | name: "XCGrapherLibTests", 40 | dependencies: ["XCGrapherLib"] 41 | ), 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XCGrapher 2 | `xcgrapher` is, by default, a framework-level dependency graph generator for Xcode projects. 3 | It works by reading local clones of the source, so it's not a problem if your project relies on internal/unpublished frameworks. 4 | 5 | However, it is so much more than just that. `xcgrapher` supports custom (Swift) plugins so you can easily parse your source code and quickly create graphs that are meaningful to you and your team! 6 | 7 | ## Basic Usage 8 | To produce a graph of imported Cocoapods and Swift Package Manager modules for the target `SomeApp` in the project `SomeApp.xcodeproj`: 9 | ```sh 10 | xcgrapher --project SomeApp.xcodeproj --target SomeApp --pods --spm 11 | ``` 12 | This produces the following image: 13 | 14 | 15 | 16 | You could also pass `--apple` to include native frameworks in the graph. See `xcgrapher --help` for more options. 17 | 18 | ### Installation 19 | 20 | ```sh 21 | brew tap maxchuquimia/scripts 22 | brew install xcgrapher 23 | gem install xcodeproj # If you use Cocoapods you probably don't need to do this 24 | ``` 25 | 26 | Or, just clone the project and `make install`. 27 | 28 | ## Custom Graphs 29 | What if (for example) your team has it's own property wrappers for dependency injection? You can graph it's usage that too! 30 | 31 | Create yourself a new Swift Package and subclass `XCGrapherPlugin` from the package [maxchuquimia/XCGrapherPluginSupport](https://github.com/maxchuquimia/XCGrapherPluginSupport). You can override a function that will be called once with every source file in your project and it's (SPM) dependencies. Then you can parse each file as needed and generate an array of arrows that will be drawn. 32 | 33 | In fact, `xcgrapher`'s default behaviour is [implemented as a plugin](https://github.com/maxchuquimia/xcgrapher/tree/master/Sources/XCGrapherModuleImportPlugin) too! 34 | 35 | For full documentation take a look at the [XCGrapherPluginSupport](https://github.com/maxchuquimia/XCGrapherPluginSupport) repo. 36 | 37 | ## How it works 38 | 39 | #### Main Project Target 40 | `xcgrapher` uses `xcodeproj` (a Cocoapods gem) to find all the source files of the given target. It then reads them and creates a list of `import`s to know which `--pods`, `--spm` and/or `--apple` modules are part of the target. 41 | 42 | #### Swift Package Manager 43 | `xcgrapher` builds the `--project` so that all it's SPM dependencies are cloned. It parses the build output to find the location of these clones and calls `swift package describe` on each. Then it iterates through all the source files of each package to find their `import` lines and repeats. 44 | 45 | #### Cocoapods 46 | `xcgrapher` uses the _Podfile.lock_ to discover what each pod's dependencies are. Change the location of the lockfile with the `--podlock` option if needed. Cocoapods source files are not currently searched so graphing links to imported Apple frameworks from a pod is unsupported, as is file-by-file processing in a custom plugin. `xcgrapher` is really geared towards Xcode projects and Swift Packages. 47 | 48 | #### Apple 49 | `xcgrapher` assumes `/System/Library/Frameworks` and another path (see _NativeDependencyManager.swift_) contains a finite list of frameworks belonging to Apple. This probably isn't ideal for some cases, so open a PR if you know a better way! 50 | 51 | #### Carthage 52 | Carthage dependencies are currently unsupported. Need it? Add it! Conform to the `DependencyManager` protocol and have a look at the usage of `SwiftPackageManager`. 53 | 54 | ## Notes on Development 55 | Here's a few things to bear in mind if you're adding a feature: 56 | 57 | - Run `make configure` before trying to build or run tests from Xcode (this is done automatically before `make build` or `make install`) 58 | - Tests pass in Xcode, however you can't run the project from within Xcode (due to enivironment variables missing). When developing I like to just `make install` after unit tests are passing and then do my manual testing from the command line. 59 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/DependencyManagers/CocoapodsManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | struct CocoapodsManager { 5 | /// Contains something like: 6 | /// ``` 7 | /// PODS: 8 | /// - Auth0 (1.32.0): 9 | /// - JWTDecode 10 | /// - SimpleKeychain 11 | /// - JWTDecode (2.6.0) 12 | /// - NSObject_Rx (5.2.0): 13 | /// - RxSwift (~> 6.0.0) 14 | /// ... etc 15 | /// ``` 16 | let lockfilePodList: String 17 | 18 | init(lockFile: FileManager.Path) throws { 19 | lockfilePodList = 20 | try String(contentsOfFile: lockFile) 21 | .scan { 22 | $0.scanUpTo(string: "PODS:") 23 | $0.scanAndStoreUpTo(string: "\n\n") 24 | } 25 | // Account for NSObject+Rx being quoted and actually being imported with a _ instead of + 26 | .replacingOccurrences(of: "+", with: "_") 27 | .replacingOccurrences(of: "\"", with: "") 28 | } 29 | } 30 | 31 | extension CocoapodsManager: DependencyManager { 32 | 33 | var pluginModuleType: XCGrapherImport.ModuleType { 34 | .cocoapods 35 | } 36 | 37 | func isManaging(module: String) -> Bool { 38 | let podlockEntry = "\n - ".appending(module).appending(" ") 39 | return lockfilePodList.contains(podlockEntry) 40 | } 41 | 42 | func dependencies(of module: String) -> [String] { 43 | // Parse the lockfile, looking for entries indented underneath `module` 44 | lockfilePodList 45 | .scan { 46 | $0.scanUpToAndIncluding(string: "\n - ".appending(module).appending(" ")) 47 | $0.scanAndStoreUpTo(string: "\n - ") 48 | } 49 | .breakIntoLines() 50 | .dropFirst() 51 | .map { $0.trimmingCharacters(in: CharacterSet(charactersIn: " -:\n")) } 52 | .filter { !$0.isEmpty } 53 | .map { $0.components(separatedBy: " ")[0] } 54 | .map { $0.replacingOccurrences(of: "\"", with: "") } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/DependencyManagers/DependencyManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | protocol DependencyManager { 5 | 6 | /// Asks the dependency manager if it is responsible for managing the framework named `module`. 7 | func isManaging(module: String) -> Bool 8 | 9 | /// Produces a list of the direct dependencies of `module`. Call it again with a single element 10 | /// from the returned list to discover it's dependencies ad infinitum. 11 | func dependencies(of module: String) -> [String] 12 | 13 | /// The type of dependencies this DependencyManager managers 14 | var pluginModuleType: XCGrapherImport.ModuleType { get } 15 | 16 | } 17 | 18 | extension Array where Element == DependencyManager { 19 | 20 | func manager(of module: String) -> DependencyManager? { 21 | first { $0.isManaging(module: module) } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/DependencyManagers/NativeDependencyManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | struct NativeDependencyManager { 5 | 6 | let allNativeFrameworks: [String] 7 | 8 | init() throws { 9 | let standardList = try FileManager.default 10 | .contentsOfDirectory(atPath: "/System/Library/Frameworks") 11 | 12 | // It seems the above does not contain UIKit.framework though... so let's use another list that does 13 | let backupList = try FileManager.default 14 | .contentsOfDirectory(atPath: "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/iOSSupport/System/Library/Frameworks") 15 | 16 | allNativeFrameworks = (standardList + backupList) 17 | .map { $0.replacingOccurrences(of: ".framework", with: "") } 18 | .unique() 19 | } 20 | 21 | } 22 | 23 | extension NativeDependencyManager: DependencyManager { 24 | 25 | var pluginModuleType: XCGrapherImport.ModuleType { 26 | .apple 27 | } 28 | 29 | func isManaging(module: String) -> Bool { 30 | allNativeFrameworks.contains(module) 31 | } 32 | 33 | func dependencies(of module: String) -> [String] { 34 | [] // Obviously we don't know how Apple's frameworks work internally 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/DependencyManagers/SwiftPackageManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | struct SwiftPackageManager { 5 | 6 | let knownSPMTargets: [PackageDescription.Target] 7 | 8 | /// - Parameter packageClones: A list of directories, each a cloned SPM dependency. 9 | init(packageClones: [FileManager.Path]) throws { 10 | knownSPMTargets = try packageClones.flatMap { 11 | try SwiftPackage(clone: $0).targets() 12 | } 13 | } 14 | 15 | } 16 | 17 | extension SwiftPackageManager: DependencyManager { 18 | 19 | var pluginModuleType: XCGrapherImport.ModuleType { 20 | .spm 21 | } 22 | 23 | func isManaging(module: String) -> Bool { 24 | knownSPMTargets.contains { $0.name == module } 25 | } 26 | 27 | func dependencies(of module: String) -> [String] { 28 | guard let target = knownSPMTargets.first(where: { $0.name == module }) else { return [] } 29 | return ImportFinder(fileList: target.sources).allImportedModules() 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/DependencyManagers/UnmanagedDependencyManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | /// A dependency manager that always claims to be managing modules passed into it 5 | /// but never knows what their dependencies are. 6 | struct UnmanagedDependencyManager {} 7 | 8 | extension UnmanagedDependencyManager: DependencyManager { 9 | 10 | var pluginModuleType: XCGrapherImport.ModuleType { 11 | .other 12 | } 13 | 14 | func isManaging(module: String) -> Bool { 15 | true 16 | } 17 | 18 | func dependencies(of module: String) -> [String] { 19 | [] 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Digraph.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A class for generating a Digraph string file - e.g. 4 | /// ``` 5 | /// digraph SomeApp { 6 | /// "a" -> "b" 7 | /// "b" -> "c" 8 | /// } 9 | /// ``` 10 | class Digraph { 11 | 12 | /// The name of the digraph structure 13 | let name: String 14 | 15 | private var edges: [Edge] = [] 16 | 17 | init(name: String) { 18 | self.name = name 19 | } 20 | 21 | /// Adds an arrow line from `a` to `b` in the graph. 22 | /// - Parameters: 23 | /// - a: The element the arrow should originate from 24 | /// - b: The element the arrow should point to 25 | /// - color: The color of the line, e.g. `#FF0000` 26 | func addEdge(from a: String, to b: String, color: String? = nil) { 27 | edges.append(Edge(a: a, b: b, color: color)) 28 | } 29 | 30 | /// Removes any edge with the name `a` 31 | func removeEdges(referencing a: String) { 32 | edges.removeAll { 33 | $0.a == a || $0.b == a 34 | } 35 | } 36 | 37 | func build() -> String { 38 | var lines = ["digraph \(name) {"] 39 | lines.append("") 40 | lines.append(" graph [ nodesep = 0.5, ranksep = 4, overlap = false, splines = true ]") // splines=ortho, 41 | lines.append(" node [ shape = box ]") 42 | lines.append("") 43 | lines.append(contentsOf: indentedEdgeStrings) 44 | lines.append("") 45 | lines.append("}") 46 | return lines.joined(separator: "\n") 47 | } 48 | 49 | } 50 | 51 | private extension Digraph { 52 | 53 | struct Edge { 54 | let a: String 55 | let b: String 56 | let color: String? 57 | 58 | var string: String { 59 | let base = "\"\(a)\" -> \"\(b)\"" 60 | var attributes: [String: String] = [:] 61 | if let color = color { 62 | attributes["color"] = "\"\(color)\"" 63 | } 64 | return base + (attributes.isEmpty ? "" : " [ \(attributes.map { $0 + "=" + $1 }.joined(separator: ", ")) ]") 65 | } 66 | } 67 | 68 | var indentedEdgeStrings: [String] { 69 | edges 70 | .map { " ".appending($0.string) } 71 | .sortedAscendingCaseInsensitively() 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Extensions/Array.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array { 4 | 5 | func appending(_ element: Element) -> [Element] { 6 | self + [element] 7 | } 8 | 9 | } 10 | 11 | extension Array where Element: Hashable { 12 | 13 | func unique() -> [Element] { 14 | Array(Set(self)) 15 | } 16 | 17 | } 18 | 19 | extension Array where Element == String { 20 | 21 | func sortedAscendingCaseInsensitively() -> [String] { 22 | sorted { a, b -> Bool in 23 | let _a = a.lowercased() 24 | let _b = b.lowercased() 25 | return _a == _b ? a < b : _a < _b 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Extensions/FileManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension FileManager { 4 | 5 | typealias Path = String 6 | 7 | func directoryExists(atPath path: Path) -> Bool { 8 | var isDirectory: ObjCBool = false 9 | fileExists(atPath: path, isDirectory: &isDirectory) 10 | return isDirectory.boolValue 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Extensions/Scanner.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Scanner { 4 | 5 | class Builder { 6 | 7 | private var operations: [(Scanner) -> String?] = [] 8 | 9 | func scanUpTo(string: String) { 10 | operations.append { scanner -> String? in 11 | _ = scanner.scanUpToString(string) 12 | return nil 13 | } 14 | } 15 | 16 | func scanUpToAndIncluding(string: String) { 17 | operations.append { scanner -> String? in 18 | _ = scanner.scanUpToString(string) 19 | _ = scanner.scanString(string) 20 | return nil 21 | } 22 | } 23 | 24 | func scanAndStoreUpTo(string: String) { 25 | operations.append { scanner -> String? in 26 | scanner.scanUpToString(string) 27 | } 28 | } 29 | 30 | func scanAndStoreUpToCharacters(from set: CharacterSet) { 31 | operations.append { scanner -> String? in 32 | scanner.scanUpToCharacters(from: set) 33 | } 34 | } 35 | 36 | func scanAndStoreUpToAndIncluding(string: String) { 37 | operations.append { scanner -> String? in 38 | guard let part1 = scanner.scanUpToString(string) else { return nil } 39 | guard let part2 = scanner.scanString(string) else { return nil } 40 | return part1 + part2 41 | } 42 | } 43 | 44 | func execute(on string: String) -> String { 45 | var finalOutput = "" 46 | let scanner = Scanner(string: string) 47 | scanner.charactersToBeSkipped = nil // WHY does this have a default value!? 48 | for operation in operations { 49 | if let operationOutput = operation(scanner) { 50 | finalOutput.append(operationOutput) 51 | } 52 | } 53 | return finalOutput 54 | } 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension String { 4 | 5 | func appendingPathComponent(_ component: String) -> String { 6 | var result = self 7 | let delimiter = "/" 8 | if !result.hasSuffix(delimiter), !component.hasPrefix(delimiter) { 9 | result.append(delimiter) 10 | } else if result.hasSuffix(delimiter), component.hasPrefix(delimiter) { 11 | result.removeLast() 12 | } 13 | result.append(component) 14 | return result 15 | } 16 | 17 | } 18 | 19 | extension String { 20 | 21 | func scan(buildOperations: (Scanner.Builder) -> Void) -> String { 22 | let builder = Scanner.Builder() 23 | buildOperations(builder) 24 | return builder.execute(on: self) 25 | } 26 | 27 | func lastPathComponent() -> String { 28 | components(separatedBy: "/").last ?? "" 29 | } 30 | 31 | func breakIntoLines() -> [String] { 32 | components(separatedBy: .newlines) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Helpers/General.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func die(_ message: String? = nil, file: String = #file, function: String = #function, line: Int = #line) -> Never { 4 | if let message = message { 5 | LogError(message, file: file) 6 | } else { 7 | LogError("Fatal error: \(file):\(line) \(function)") 8 | } 9 | exit(1) 10 | } 11 | 12 | func Log(_ items: Any..., dim: Bool = false, file: String = #file) { 13 | let color = dim ? Colors.dim : "" 14 | print(items.reduce(color + logPrefix(file: file)) { $0 + " \($1)" } + Colors.reset) 15 | } 16 | 17 | func LogError(_ items: Any..., file: String = #file) { 18 | print(items.reduce(Colors.red + logPrefix(file: file)) { $0 + " \($1)" } + Colors.reset) 19 | } 20 | 21 | func failWithContext(attempt: @autoclosure () throws -> T, context: Any, file: String = #file) throws -> T { 22 | do { 23 | return try attempt() 24 | } catch { 25 | LogError("Error context:", String(describing: context), file: file) 26 | throw error 27 | } 28 | } 29 | 30 | private func logPrefix(file: String) -> String { 31 | let name = file.components(separatedBy: "/").last?.replacingOccurrences(of: ".swift", with: "") ?? "???" 32 | return "[\(name)]" 33 | } 34 | 35 | private enum Colors { 36 | static let red = "\u{001B}[0;31m" 37 | static let dim = "\u{001B}[2m" 38 | static let reset = "\u{001B}[0;0m" 39 | } 40 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Helpers/ShellTask.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol ShellTask { 4 | 5 | /// The raw shell representation of the command. 6 | var stringRepresentation: String { get } 7 | 8 | /// A localised string to be displayed when the command cannot be found. 9 | var commandNotFoundInstructions: String { get } 10 | 11 | } 12 | 13 | extension ShellTask { 14 | 15 | @discardableResult 16 | func execute() throws -> String { 17 | let task = Process() 18 | let stdout = Pipe() 19 | let stderr = Pipe() 20 | 21 | task.standardOutput = stdout 22 | task.standardError = stderr 23 | task.arguments = ["-c", stringRepresentation] 24 | task.launchPath = "/bin/bash" 25 | task.launch() 26 | 27 | Log(stringRepresentation, dim: true) 28 | 29 | let output = stdout.fileHandleForReading.readDataToEndOfFile() 30 | let errorOutput = stderr.fileHandleForReading.readDataToEndOfFile() 31 | 32 | task.waitUntilExit() 33 | 34 | if task.terminationStatus == 0 { 35 | return String(data: output, encoding: .utf8)! 36 | } else if task.terminationStatus == 127 { 37 | LogError(commandNotFoundInstructions) 38 | throw CommandError.commandNotFound(message: commandNotFoundInstructions) 39 | } else { 40 | LogError("The command failed with exit code \(task.terminationStatus)") 41 | throw CommandError.failure(stderr: String(data: errorOutput, encoding: .utf8)!) 42 | } 43 | } 44 | 45 | } 46 | 47 | enum CommandError: LocalizedError { 48 | 49 | case failure(stderr: String) 50 | case commandNotFound(message: String) 51 | 52 | var errorDescription: String? { 53 | switch self { 54 | case let .failure(stderr): return stderr 55 | case let .commandNotFound(message): return message 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ImportFinder.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Must also handle `@testable import X`, `import class X.Y` etc 4 | struct ImportFinder { 5 | 6 | let fileList: [FileManager.Path] 7 | 8 | /// Read each file in `fileList` and search for `import X`, `@testable import X`, `import class X.Y` etc. 9 | /// - Returns: A list of frameworks being imported by every file in `fileList`. 10 | func allImportedModules() -> [String] { 11 | fileList 12 | // swiftlint:disable force_try 13 | .map { try! String(contentsOfFile: $0) } 14 | .flatMap { $0.breakIntoLines() } 15 | .map { $0.trimmingCharacters(in: .whitespaces) } 16 | .filter { $0.hasPrefix("import ") || $0.hasPrefix("@testable import ") } 17 | .map { 18 | $0 19 | .replacingOccurrences(of: "@testable ", with: "") 20 | .replacingOccurrences(of: "@_exported ", with: "") 21 | .replacingOccurrences(of: " class ", with: " ") 22 | .replacingOccurrences(of: " struct ", with: " ") 23 | .replacingOccurrences(of: " enum ", with: " ") 24 | .replacingOccurrences(of: " protocol ", with: " ") 25 | .replacingOccurrences(of: " var ", with: " ") 26 | .replacingOccurrences(of: " let ", with: " ") 27 | .replacingOccurrences(of: " func ", with: " ") 28 | .replacingOccurrences(of: " typealias ", with: " ") 29 | } 30 | .filter { $0 != "import Swift" && !$0.hasPrefix("import Swift.") } // We should ignore "import Swift.Y" and "import Swift" - we can assume the project is dependent on Swift 31 | .map { 32 | $0.scan { 33 | $0.scanUpToAndIncluding(string: "import ") 34 | $0.scanAndStoreUpToCharacters(from: CharacterSet(charactersIn: " .")) 35 | } 36 | } 37 | .unique() 38 | .sortedAscendingCaseInsensitively() 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/Models/PackageDescription.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct PackageDescription: Decodable { 4 | let name: String 5 | let path: String 6 | let targets: [Target] 7 | 8 | struct Target: Decodable { 9 | let name: String 10 | let path: String 11 | let sources: [String] 12 | let type: String 13 | } 14 | 15 | init(from decoder: Decoder) throws { 16 | let values = try decoder.container(keyedBy: CodingKeys.self) 17 | name = try values.decode(String.self, forKey: .name) 18 | path = try values.decode(String.self, forKey: .path) 19 | // Map all target-related paths to be absolute. 20 | targets = try values.decode([Target].self, forKey: .targets).map { [path] target -> Target in 21 | #if swift(>=5.4) 22 | let path = path.appendingPathComponent(target.path) 23 | let sources = target.sources.map { path.appendingPathComponent($0) } 24 | #else 25 | let path = target.path 26 | let sources = target.sources.map { target.path.appendingPathComponent($0) } 27 | #endif 28 | return Target( 29 | name: target.name, 30 | path: path, 31 | sources: sources, 32 | type: target.type 33 | ) 34 | } 35 | 36 | } 37 | 38 | enum CodingKeys: String, CodingKey { 39 | case name, path, targets 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/PluginSupport/PluginLoader.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | // Adapted from https://theswiftdev.com/building-and-loading-dynamic-libraries-at-runtime-in-swift/ (removed need for the builder pattern / simplified what is needed within the plugin itself) 5 | enum PluginLoader { 6 | enum LoadError: LocalizedError { 7 | case libraryNotFound(path: FileManager.Path) 8 | case unableToOpen(reason: String) 9 | case symbolNotFound(symbol: String) 10 | 11 | var errorDescription: String? { 12 | let prefix = "Error opening lib: " 13 | switch self { 14 | case let .libraryNotFound(path): return prefix + "\(path) does not exist." 15 | case let .unableToOpen(reason): return prefix + reason 16 | case let .symbolNotFound(symbol): return prefix + "symbol \(symbol) not found." 17 | } 18 | } 19 | } 20 | 21 | private typealias MakeFunction = @convention(c) () -> UnsafeMutableRawPointer 22 | 23 | static func plugin(at path: FileManager.Path) throws -> XCGrapherPlugin { 24 | guard FileManager.default.fileExists(atPath: path) else { throw LoadError.libraryNotFound(path: path) } 25 | 26 | let openResult = dlopen(path, RTLD_NOW | RTLD_LOCAL) 27 | 28 | guard openResult != nil else { throw LoadError.unableToOpen(reason: String(format: "%s", dlerror() ?? "??")) } 29 | 30 | defer { dlclose(openResult) } 31 | 32 | let symbolName = "makeXCGrapherPlugin" 33 | let sym = dlsym(openResult, symbolName) 34 | 35 | guard sym != nil else { throw LoadError.symbolNotFound(symbol: symbolName) } 36 | 37 | let makeXCGrapherPlugin: MakeFunction = unsafeBitCast(sym, to: MakeFunction.self) 38 | let pluginPointer = makeXCGrapherPlugin() 39 | let plugin = Unmanaged.fromOpaque(pluginPointer).takeRetainedValue() 40 | return plugin // This is the custom subclass of XCGrapherPlugin 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/PluginSupport/PluginSupport.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | class PluginSupport { 5 | 6 | let plugin: XCGrapherPlugin 7 | 8 | var swiftPackageManager: SwiftPackageManager? 9 | var cocoapodsManager: CocoapodsManager? 10 | var nativeManager: NativeDependencyManager? 11 | var unknownManager: UnmanagedDependencyManager? 12 | 13 | init(pluginPath: FileManager.Path) throws { 14 | plugin = try PluginLoader.plugin(at: pluginPath) 15 | } 16 | 17 | init(plugin: XCGrapherPlugin) { 18 | self.plugin = plugin 19 | } 20 | 21 | func generateDigraph(target: String, projectSourceFiles: [FileManager.Path]) throws -> Digraph { 22 | let digraph = Digraph(name: "XCGrapher") 23 | var nodes: [Any] = [] 24 | 25 | // MARK: - Main Target 26 | 27 | let targetImports = ImportFinder(fileList: projectSourceFiles).allImportedModules() 28 | 29 | for file in projectSourceFiles { 30 | let pluginFile = XCGrapherFile( 31 | filename: file.lastPathComponent(), 32 | filepath: file, 33 | fileContents: try failWithContext(attempt: String(contentsOfFile: file), context: (target: target, file: file)), 34 | origin: .target(name: target) 35 | ) 36 | 37 | let _nodes = try plugin_process(file: pluginFile) 38 | nodes.append(contentsOf: _nodes) 39 | } 40 | 41 | for module in targetImports { 42 | // MARK: - Swift Package Manager 43 | 44 | // Also handles Apple frameworks imported by Swift Packages 45 | if swiftPackageManager?.isManaging(module: module) == true { 46 | var previouslyEncounteredModules: Set = [] 47 | try recurseSwiftPackages(from: module, importedBy: target, importerType: .target, building: &nodes, skipping: &previouslyEncounteredModules) 48 | } 49 | 50 | // MARK: - Cocoapods 51 | 52 | else if cocoapodsManager?.isManaging(module: module) == true { 53 | var previouslyEncounteredModules: Set = [] 54 | try recurseCocoapods(from: module, importedBy: target, importerType: .target, building: &nodes, skipping: &previouslyEncounteredModules) 55 | } 56 | 57 | // MARK: - Apple 58 | 59 | // (only Apple frameworks imported by the main --target) 60 | else if nativeManager?.isManaging(module: module) == true { 61 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: target, moduleType: .apple, importerType: .target)) 62 | nodes.append(contentsOf: _nodes) 63 | } 64 | 65 | // Weird unknown cases 66 | else if unknownManager?.isManaging(module: module) == true { 67 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: target, moduleType: .other, importerType: .target)) 68 | nodes.append(contentsOf: _nodes) 69 | } 70 | } 71 | 72 | // MARK: - Finish up 73 | 74 | let edges = try plugin_makeArrows(from: nodes) 75 | for edge in Set(edges) { 76 | digraph.addEdge(from: edge.origin, to: edge.destination, color: edge.color) 77 | } 78 | 79 | return digraph 80 | } 81 | 82 | } 83 | 84 | // MARK: - Recursive Functions 85 | 86 | private extension PluginSupport { 87 | 88 | func recurseSwiftPackages(from module: String, importedBy importer: String, importerType: XCGrapherImport.ModuleType, building nodeList: inout [Any], skipping modulesToSkip: inout Set) throws { 89 | if swiftPackageManager?.isManaging(module: module) == true { 90 | // `module` is a Swift Package and `importer` is either a Swift Package or the main --target 91 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: importer, moduleType: .spm, importerType: importerType)) 92 | nodeList.append(contentsOf: _nodes) 93 | 94 | guard !modulesToSkip.contains(module) else { return } 95 | modulesToSkip.insert(module) 96 | 97 | guard let package = swiftPackageManager?.knownSPMTargets.first(where: { $0.name == module }) else { return } 98 | 99 | // Give the plugin the opportunity to read the package's source files 100 | for file in package.sources { 101 | let pluginFile = XCGrapherFile( 102 | filename: file.lastPathComponent(), 103 | filepath: file, 104 | fileContents: try failWithContext(attempt: String(contentsOfFile: file), context: (package: module, file: file)), 105 | origin: .spm(importName: module) 106 | ) 107 | 108 | let _nodes = try plugin_process(file: pluginFile) 109 | nodeList.append(contentsOf: _nodes) 110 | } 111 | 112 | // Now recurse 113 | let packageImports = ImportFinder(fileList: package.sources).allImportedModules() 114 | for _module in packageImports { 115 | try recurseSwiftPackages(from: _module, importedBy: module, importerType: .spm, building: &nodeList, skipping: &modulesToSkip) 116 | } 117 | } else if nativeManager?.isManaging(module: module) == true { 118 | modulesToSkip.insert(module) 119 | 120 | // `module` is an Apple framework and `importer` is a Swift Package 121 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: importer, moduleType: .apple, importerType: importerType)) 122 | nodeList.append(contentsOf: _nodes) 123 | } else if unknownManager?.isManaging(module: module) == true { 124 | modulesToSkip.insert(module) 125 | 126 | // Weird case 127 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: importer, moduleType: .other, importerType: importerType)) 128 | nodeList.append(contentsOf: _nodes) 129 | } 130 | } 131 | 132 | func recurseCocoapods(from module: String, importedBy importer: String, importerType: XCGrapherImport.ModuleType, building nodeList: inout [Any], skipping modulesToSkip: inout Set) throws { 133 | let _nodes = try plugin_process(library: XCGrapherImport(moduleName: module, importerName: importer, moduleType: .cocoapods, importerType: importerType)) 134 | nodeList.append(contentsOf: _nodes) 135 | 136 | guard !modulesToSkip.contains(module) else { return } 137 | modulesToSkip.insert(module) 138 | 139 | guard cocoapodsManager?.isManaging(module: module) == true else { return } 140 | 141 | for podImport in cocoapodsManager?.dependencies(of: module) ?? [] { 142 | try recurseCocoapods(from: podImport, importedBy: module, importerType: .cocoapods, building: &nodeList, skipping: &modulesToSkip) 143 | } 144 | } 145 | 146 | } 147 | 148 | // MARK: - Plugin Caller Proxies 149 | 150 | private extension PluginSupport { 151 | 152 | func plugin_process(library: XCGrapherImport) throws -> [Any] { 153 | guard library.importerName != library.moduleName else { return [] } // Filter when the library imports itself 154 | 155 | do { 156 | return try plugin.process(library: library) 157 | } catch { 158 | LogError("The plugin function \(type(of: plugin)).process(library:) threw an error: \(error)") 159 | throw error 160 | } 161 | } 162 | 163 | func plugin_process(file: XCGrapherFile) throws -> [Any] { 164 | do { 165 | return try plugin.process(file: file) 166 | } catch { 167 | LogError("The plugin function \(type(of: plugin)).process(file:) threw an error: \(error)") 168 | throw error 169 | } 170 | } 171 | 172 | func plugin_makeArrows(from processingResults: [Any]) throws -> [XCGrapherArrow] { 173 | do { 174 | return try plugin.makeArrows(from: processingResults) 175 | } catch { 176 | LogError("The plugin function \(type(of: plugin)).makeEdges(from:) threw an error: \(error)") 177 | throw error 178 | } 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/Graphviz.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Graphviz { 4 | 5 | let input: FileManager.Path 6 | let output: FileManager.Path 7 | 8 | } 9 | 10 | extension Graphviz: ShellTask { 11 | 12 | var stringRepresentation: String { 13 | "dot -T png -o \"\(output)\" \"\(input)\" " 14 | } 15 | 16 | var commandNotFoundInstructions: String { 17 | "Missing command 'dot' - install it with `brew install graphviz` or see https://graphviz.org/download/" 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/SwiftBuild.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct SwiftBuild: SwiftPackageDependencySource { 4 | 5 | let packagePath: FileManager.Path 6 | let product: String 7 | 8 | func computeCheckoutsDirectory() throws -> String { 9 | // Clone all the packages into the default checkout directory for Swift Packages 10 | let buildDirectory = try execute() 11 | .breakIntoLines() 12 | .dropLast() 13 | .last! 14 | // `URL(fileURLWithPath:)` adds a `file://` prefix, which the system can't locate for some reason. 15 | let checkoutsDirectory = URL(string: buildDirectory)! 16 | .deletingLastPathComponent() 17 | .deletingLastPathComponent() 18 | .appendingPathComponent("checkouts") 19 | return checkoutsDirectory.absoluteString 20 | } 21 | 22 | } 23 | 24 | extension SwiftBuild: ShellTask { 25 | 26 | var stringRepresentation: String { 27 | // We need to first build the package (which implicitly resolves its dependencies), and then print the binary path via `--show-bin-path` 28 | "swift build --package-path \"\(packagePath)\" --product \"\(product)\" && swift build --package-path \"\(packagePath)\" --product \"\(product)\" --show-bin-path" 29 | } 30 | 31 | var commandNotFoundInstructions: String { 32 | "Missing command 'swift'" 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/SwiftPackage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct SwiftPackage { 4 | 5 | let clone: FileManager.Path 6 | 7 | func targets() throws -> [PackageDescription.Target] { 8 | let json = try execute() 9 | let jsonData = json.data(using: .utf8)! 10 | let description = try JSONDecoder().decode(PackageDescription.self, from: jsonData) 11 | return description.targets 12 | } 13 | 14 | } 15 | 16 | extension SwiftPackage: ShellTask { 17 | 18 | var stringRepresentation: String { 19 | "swift package --package-path \"\(clone)\" describe --type json" 20 | } 21 | 22 | var commandNotFoundInstructions: String { 23 | "Missing command 'swift'" 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/SwiftPackageDependencySource.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | protocol SwiftPackageDependencySource { 4 | 5 | func computeCheckoutsDirectory() throws -> String 6 | func swiftPackageDependencies() throws -> [FileManager.Path] 7 | 8 | } 9 | 10 | extension SwiftPackageDependencySource { 11 | 12 | func swiftPackageDependencies() throws -> [FileManager.Path] { 13 | let checkoutsDirectory = try computeCheckoutsDirectory() 14 | // Return the paths to every package clone 15 | return try FileManager.default.contentsOfDirectory(atPath: checkoutsDirectory) 16 | .map { checkoutsDirectory.appendingPathComponent($0) } 17 | .filter { FileManager.default.directoryExists(atPath: $0) } 18 | .appending(checkoutsDirectory) // We also need to check the checkouts directory itself - it seems Realm unpacks itself weirdly and puts it's Package.swift in the checkouts folder :eye_roll: 19 | .filter { FileManager.default.fileExists(atPath: $0.appendingPathComponent("Package.swift")) } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/Xcodebuild.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Xcodebuild: SwiftPackageDependencySource { 4 | let projectFile: FileManager.Path 5 | let target: String 6 | 7 | func computeCheckoutsDirectory() throws -> String { 8 | // Clone all the packages into $DERIVED_DATA/SourcePackages/checkouts 9 | let output = try execute() 10 | .breakIntoLines() 11 | 12 | // Find the $DERIVED_DATA path 13 | let derivedDataDir = output 14 | .first(where: { $0.contains(" BUILD_DIR = ") })! 15 | .replacingOccurrences(of: "BUILD_DIR =", with: "") 16 | .trimmingCharacters(in: .whitespacesAndNewlines) 17 | .scan { 18 | $0.scanAndStoreUpToAndIncluding(string: "DerivedData/") 19 | $0.scanAndStoreUpTo(string: "/") 20 | } 21 | return derivedDataDir.appending("/SourcePackages/checkouts") 22 | } 23 | 24 | } 25 | 26 | extension Xcodebuild: ShellTask { 27 | 28 | var stringRepresentation: String { 29 | "xcodebuild -project \"\(projectFile)\" -target \"\(target)\" -showBuildSettings" 30 | } 31 | 32 | var commandNotFoundInstructions: String { 33 | "Missing command 'xcodebuild'" 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/ShellTasks/Xcodeproj.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Xcodeproj { 4 | 5 | let projectFile: FileManager.Path 6 | let target: String 7 | 8 | func compileSourcesList() throws -> [FileManager.Path] { 9 | try execute() 10 | .breakIntoLines() 11 | .filter { $0.hasSuffix(".swift") } 12 | .filter { FileManager.default.fileExists(atPath: $0) } 13 | } 14 | 15 | } 16 | 17 | extension Xcodeproj: ShellTask { 18 | 19 | var stringRepresentation: String { 20 | "ruby -r xcodeproj -e 'Xcodeproj::Project.open(\"\(projectFile)\").targets.filter do |t| t.name == \"\(target)\" end.first.source_build_phase.files.to_a.reject do |f| f.file_ref.nil? end.each do |f| puts f.file_ref.real_path.to_s end'" 21 | } 22 | 23 | var commandNotFoundInstructions: String { 24 | "Missing command 'xcodeproj' - install it with `gem install xcodeproj` or see https://github.com/CocoaPods/Xcodeproj" 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/XCGrapher.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum XCGrapher { 4 | 5 | public static func run(with options: XCGrapherOptions) throws { 6 | // MARK: - Load the plugin 7 | 8 | Log("Loading plugin \(options.plugin)") 9 | let pluginHandler = try PluginSupport(pluginPath: options.plugin) 10 | 11 | // MARK: - Prepare the --target source file list 12 | 13 | Log("Generating list of source files in \(options.startingPoint.localisedName)") 14 | var sources: [FileManager.Path] = [] 15 | switch options.startingPoint { 16 | case let .xcodeProject(project): 17 | let xcodeproj = Xcodeproj(projectFile: project, target: options.target) 18 | sources = try xcodeproj.compileSourcesList() 19 | case let .swiftPackage(packagePath): 20 | let package = SwiftPackage(clone: packagePath) 21 | guard let target = try package.targets().first(where: { $0.name == options.target }) else { die("Could not locate target '\(options.target)'") } 22 | sources = target.sources 23 | } 24 | 25 | // MARK: - Create dependency manager lookups 26 | 27 | if options.spm || options.startingPoint.isSPM { 28 | Log("Building Swift Package list") 29 | let swiftPackageDependencySource: SwiftPackageDependencySource 30 | switch options.startingPoint { 31 | case .xcodeProject: swiftPackageDependencySource = Xcodebuild(projectFile: options.startingPoint.path, target: options.target) 32 | case .swiftPackage: swiftPackageDependencySource = SwiftBuild(packagePath: options.startingPoint.path, product: options.target) 33 | } 34 | let swiftPackageClones = try swiftPackageDependencySource.swiftPackageDependencies() 35 | let swiftPackageManager = try SwiftPackageManager(packageClones: swiftPackageClones) 36 | pluginHandler.swiftPackageManager = swiftPackageManager 37 | } 38 | 39 | if options.pods { 40 | Log("Building Cocoapod list") 41 | let cocoapodsManager = try CocoapodsManager(lockFile: options.podlock) 42 | pluginHandler.cocoapodsManager = cocoapodsManager 43 | } 44 | 45 | if options.apple { 46 | Log("Building Apple framework list") 47 | let nativeManager = try NativeDependencyManager() 48 | pluginHandler.nativeManager = nativeManager 49 | } 50 | 51 | if options.force { 52 | Log("Ensuring all additional modules are graphed") 53 | // Don't ignore unknown dependencies - add a manager that claims it is reponsible for them being there. 54 | // MUST be last in `allDependencyManagers`. 55 | let unknownManager = UnmanagedDependencyManager() 56 | pluginHandler.unknownManager = unknownManager 57 | } 58 | 59 | // MARK: - Graphing 60 | 61 | Log("Graphing...") 62 | 63 | let digraph = try pluginHandler.generateDigraph( 64 | target: options.target, 65 | projectSourceFiles: sources 66 | ) 67 | 68 | // MARK: - Writing 69 | 70 | let digraphOutput = "/tmp/xcgrapher.dot" 71 | 72 | try digraph.build() 73 | .data(using: .utf8)! 74 | .write(to: URL(fileURLWithPath: digraphOutput)) 75 | 76 | try Graphviz(input: digraphOutput, output: options.output).execute() 77 | 78 | Log("Result written to", options.output) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Sources/XCGrapherLib/XCGrapherOptions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol XCGrapherOptions { 4 | var startingPoint: StartingPoint { get } 5 | var target: String { get } 6 | var podlock: String { get } 7 | var output: String { get } 8 | var apple: Bool { get } 9 | var spm: Bool { get } 10 | var pods: Bool { get } 11 | var force: Bool { get } 12 | var plugin: String { get } 13 | } 14 | 15 | public enum StartingPoint { 16 | 17 | case xcodeProject(String) 18 | case swiftPackage(String) 19 | 20 | var localisedName: String { 21 | switch self { 22 | case let .xcodeProject(project): return "Xcode project at path '\(project)'" 23 | case let .swiftPackage(packagePath): return "Swift Package at path '\(packagePath)'" 24 | } 25 | } 26 | 27 | var isSPM: Bool { 28 | switch self { 29 | case .xcodeProject: return false 30 | case .swiftPackage: return true 31 | } 32 | } 33 | 34 | var path: String { 35 | switch self { 36 | case let .xcodeProject(projectPath): return projectPath 37 | case let .swiftPackage(packagePath): return packagePath 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/XCGrapherModuleImportPlugin/Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | extension XCGrapherImport.ModuleType { 5 | 6 | var customColor: String { 7 | switch self { 8 | case .target: return "#000000" // Black 9 | case .apple: return "#0071E3" // That classic Apple blue colour we all know 10 | case .spm: return "#F05138" // The orange of the Swift logo 11 | case .cocoapods: return "#380200" // The banner color from Cocoapods.org 12 | case .other: return "#FF0000" // Red (something went wrong) 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Sources/XCGrapherModuleImportPlugin/Models.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ImportInfo { 4 | let importedModuleName: String 5 | let importerModuleName: String 6 | let color: String 7 | } 8 | -------------------------------------------------------------------------------- /Sources/XCGrapherModuleImportPlugin/Plugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherPluginSupport 3 | 4 | // MARK: - Dylib makeXCGrapherPlugin exporting 5 | 6 | @_cdecl("makeXCGrapherPlugin") 7 | public func makeXCGrapherPlugin() -> UnsafeMutableRawPointer { 8 | Unmanaged.passRetained(XCGrapherModuleImportPlugin()).toOpaque() 9 | } 10 | 11 | // MARK: - Custom Plugin 12 | 13 | public class XCGrapherModuleImportPlugin: XCGrapherPlugin { 14 | 15 | override public func process(file: XCGrapherFile) throws -> [Any] { 16 | [] // We don't care about reading file info manually for this particular plugin 17 | } 18 | 19 | override public func process(library: XCGrapherImport) throws -> [Any] { 20 | // We want to store: 21 | // - Who is being imported (library.moduleName) 22 | // - Who is doing the importing (library.importerName) 23 | // - A custom color we can use for rendering the arrow (see Extensions.swift) 24 | let importInfo = ImportInfo( 25 | importedModuleName: library.moduleName, 26 | importerModuleName: library.importerName, 27 | color: library.moduleType.customColor 28 | ) 29 | 30 | return [importInfo] 31 | } 32 | 33 | override public func makeArrows(from processingResults: [Any]) throws -> [XCGrapherArrow] { 34 | processingResults 35 | // This is safe because we only ever returned an `ImportInfo` from the process(x:) functions above 36 | .map { $0 as! ImportInfo } 37 | 38 | // For every item returned from a process(x:) function, make an edge (arrow) from the importing module to the imported module. 39 | .map { 40 | XCGrapherArrow( 41 | origin: $0.importerModuleName, 42 | destination: $0.importedModuleName, 43 | color: $0.color 44 | ) 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Sources/xcgrapher/XCGrapherArguments.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import Foundation 3 | import XCGrapherLib 4 | 5 | typealias XCGrapherArguments = xcgrapher 6 | 7 | /// Needs this name for `ParsableArguments`'s help text to be correct 8 | struct xcgrapher: ParsableArguments { 9 | 10 | @Option(name: .long, help: "The path to the .xcodeproj") 11 | public var project: String? 12 | 13 | @Option(name: .long, help: "The name of the Xcode project target (or Swift Package product) to use as a starting point") 14 | public var target: String 15 | 16 | @Option(name: .long, help: "The path to a Swift Package directory") 17 | public var package: String? 18 | 19 | @Option(name: .long, help: "The path to the projects Podfile.lock") 20 | public var podlock: String = "./Podfile.lock" 21 | 22 | @Option(name: .shortAndLong, help: "The path to which the output PNG should be written") 23 | public var output: String = "/tmp/xcgrapher.png" 24 | 25 | @Option(name: .long, help: "The path to an XCGrapherPlugin-conforming dylib. Passing this option will override xcgrapher's default behaviour and use the plugin for consolidating the node tree instead.") 26 | public var plugin: String = DEFAULT_PLUGIN_LOCATION // If you're getting an error here run `make configure` to generate DEFAULT_PLUGIN_LOCATION 27 | 28 | @Flag(name: .long, help: "Include Apple frameworks in the graph (for --target and readable-source --spm packages)") 29 | public var apple: Bool = false 30 | 31 | @Flag(name: .long, help: "Include Swift Package Manager frameworks in the graph") 32 | public var spm: Bool = false 33 | 34 | @Flag(name: .long, help: "Include Cocoapods frameworks in the graph") 35 | public var pods: Bool = false 36 | 37 | @Flag(name: .long, help: "Show frameworks that no dependency manager claims to be managing (perhaps there are name discrepancies?). Using this option doesn't make sense unless you are also using all the other include flags relevant to your project.") 38 | public var force: Bool = false 39 | 40 | var startingPoint: StartingPoint { 41 | if let project = project { 42 | return .xcodeProject(project) 43 | } else { 44 | // Should be safe due to the implementation of validate() below 45 | return .swiftPackage(package!) 46 | } 47 | } 48 | 49 | public func validate() throws { 50 | var isRunningForXcodeProject = false 51 | 52 | if let project = project { 53 | isRunningForXcodeProject = true 54 | guard FileManager.default.directoryExists(atPath: project) else { die("'\(project)' is not a valid xcode project.") } 55 | } 56 | 57 | if !isRunningForXcodeProject { 58 | guard let package = package else { die("--project or --package must be provided.") } 59 | guard !package.isEmpty else { die("--package is invalid") } 60 | guard FileManager.default.fileExists(atPath: package.appendingPathComponent("Package.swift")) else { die("'\(package)' is not a valid Swift Package directory") } 61 | } 62 | 63 | guard !target.isEmpty else { die("--target must not be empty.") } 64 | 65 | if isRunningForXcodeProject { 66 | guard spm || apple || pods else { die("Must include at least one of --apple, --spm or --pods") } 67 | } 68 | } 69 | 70 | } 71 | 72 | extension XCGrapherArguments: XCGrapherOptions {} 73 | -------------------------------------------------------------------------------- /Sources/xcgrapher/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import XCGrapherLib 3 | 4 | let options = XCGrapherArguments.parseOrExit() 5 | 6 | do { 7 | try XCGrapher.run(with: options) 8 | } catch { 9 | die(error.localizedDescription) 10 | } 11 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/Podfile: -------------------------------------------------------------------------------- 1 | target 'SomeApp' do 2 | platform :ios, '13.0' 3 | use_frameworks! 4 | # These exist only for tests, so they need to be exact 5 | pod 'RxSwift', '6.0.0' 6 | pod 'RxCocoa', '6.0.0' 7 | pod 'NSObject+Rx', '5.2.0' 8 | pod 'Auth0', '1.32.0' 9 | pod 'Moya', '14.0.0' 10 | end 11 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (5.4.1) 3 | - Auth0 (1.32.0): 4 | - JWTDecode 5 | - SimpleKeychain 6 | - JWTDecode (2.6.0) 7 | - Moya (14.0.0): 8 | - Moya/Core (= 14.0.0) 9 | - Moya/Core (14.0.0): 10 | - Alamofire (~> 5.0) 11 | - "NSObject+Rx (5.2.0)": 12 | - RxSwift (~> 6.0.0) 13 | - RxCocoa (6.0.0): 14 | - RxRelay (= 6.0.0) 15 | - RxSwift (= 6.0.0) 16 | - RxRelay (6.0.0): 17 | - RxSwift (= 6.0.0) 18 | - RxSwift (6.0.0) 19 | - SimpleKeychain (0.12.2) 20 | 21 | DEPENDENCIES: 22 | - Auth0 (= 1.32.0) 23 | - Moya (= 14.0.0) 24 | - "NSObject+Rx (= 5.2.0)" 25 | - RxCocoa (= 6.0.0) 26 | - RxSwift (= 6.0.0) 27 | 28 | SPEC REPOS: 29 | trunk: 30 | - Alamofire 31 | - Auth0 32 | - JWTDecode 33 | - Moya 34 | - "NSObject+Rx" 35 | - RxCocoa 36 | - RxRelay 37 | - RxSwift 38 | - SimpleKeychain 39 | 40 | SPEC CHECKSUMS: 41 | Alamofire: 2291f7d21ca607c491dd17642e5d40fdcda0e65c 42 | Auth0: 24f8ade897f19bfaca637464b0dc6e9bb7fd70f9 43 | JWTDecode: 480ca441ddbd5fbfb2abf17870af62f86436be58 44 | Moya: 5b45dacb75adb009f97fde91c204c1e565d31916 45 | "NSObject+Rx": 488b91a66043cc47de9cd5de9264f2ae1e2fcb13 46 | RxCocoa: 3f79328fafa3645b34600f37c31e64c73ae3a80e 47 | RxRelay: 8d593be109c06ea850df027351beba614b012ffb 48 | RxSwift: c14e798c59b9f6e9a2df8fd235602e85cc044295 49 | SimpleKeychain: d6a74781e20ebac8cd89deebf593587913aeed07 50 | 51 | PODFILE CHECKSUM: e033541c49b88eaa58a7099228a70f228ec244a2 52 | 53 | COCOAPODS: 1.10.1 54 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 51A170EA26169BC000A7C0FD /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 51A170E926169BC000A7C0FD /* Lottie */; }; 11 | 51A1710226169BDA00A7C0FD /* LottieImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1710126169BDA00A7C0FD /* LottieImports.swift */; }; 12 | 51A1710526169BF200A7C0FD /* AVFoundationImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1710426169BF200A7C0FD /* AVFoundationImports.swift */; }; 13 | 51A1710926169C2500A7C0FD /* FoundationImportrs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1710826169C2500A7C0FD /* FoundationImportrs.swift */; }; 14 | 51A1711326169D3300A7C0FD /* ChartsDynamic in Frameworks */ = {isa = PBXBuildFile; productRef = 51A1711226169D3300A7C0FD /* ChartsDynamic */; }; 15 | 51A1711426169D3300A7C0FD /* ChartsDynamic in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51A1711226169D3300A7C0FD /* ChartsDynamic */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 16 | 51A1711826169DB800A7C0FD /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 51A1711726169DB800A7C0FD /* RealmSwift */; }; 17 | 51A1711A26169DB800A7C0FD /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 51A1711926169DB800A7C0FD /* Realm */; }; 18 | 51A1711C26169DC900A7C0FD /* ChartsImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1711B26169DC900A7C0FD /* ChartsImports.swift */; }; 19 | 51A1711E26169DD700A7C0FD /* RealmImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1711D26169DD700A7C0FD /* RealmImports.swift */; }; 20 | 55CB73262612D090000FB0B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB73252612D090000FB0B3 /* AppDelegate.swift */; }; 21 | 55CB73282612D090000FB0B3 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB73272612D090000FB0B3 /* SceneDelegate.swift */; }; 22 | 55CB732A2612D090000FB0B3 /* UIKitImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB73292612D090000FB0B3 /* UIKitImports.swift */; }; 23 | 55CB732D2612D090000FB0B3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55CB732B2612D090000FB0B3 /* Main.storyboard */; }; 24 | 55CB732F2612D092000FB0B3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 55CB732E2612D092000FB0B3 /* Assets.xcassets */; }; 25 | 55CB73322612D092000FB0B3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 55CB73302612D092000FB0B3 /* LaunchScreen.storyboard */; }; 26 | 55CB734A2612D44B000FB0B3 /* MoyaImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB73492612D44B000FB0B3 /* MoyaImports.swift */; }; 27 | 55CB73502612D462000FB0B3 /* RxImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB734F2612D462000FB0B3 /* RxImports.swift */; }; 28 | 55CB73552612D4AF000FB0B3 /* Auth0Imports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CB73542612D4AF000FB0B3 /* Auth0Imports.swift */; }; 29 | A7F769F045BDE665C9D46F0B /* Pods_SomeApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F337E1111B60AEE48675B94C /* Pods_SomeApp.framework */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXCopyFilesBuildPhase section */ 33 | 51A1711526169D3300A7C0FD /* Embed Frameworks */ = { 34 | isa = PBXCopyFilesBuildPhase; 35 | buildActionMask = 2147483647; 36 | dstPath = ""; 37 | dstSubfolderSpec = 10; 38 | files = ( 39 | 51A1711426169D3300A7C0FD /* ChartsDynamic in Embed Frameworks */, 40 | ); 41 | name = "Embed Frameworks"; 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXCopyFilesBuildPhase section */ 45 | 46 | /* Begin PBXFileReference section */ 47 | 51A1710126169BDA00A7C0FD /* LottieImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LottieImports.swift; sourceTree = ""; }; 48 | 51A1710426169BF200A7C0FD /* AVFoundationImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVFoundationImports.swift; sourceTree = ""; }; 49 | 51A1710826169C2500A7C0FD /* FoundationImportrs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationImportrs.swift; sourceTree = ""; }; 50 | 51A1711B26169DC900A7C0FD /* ChartsImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartsImports.swift; sourceTree = ""; }; 51 | 51A1711D26169DD700A7C0FD /* RealmImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmImports.swift; sourceTree = ""; }; 52 | 51C68AB62619C32000B0EFE3 /* FileNotInTargetMembership.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileNotInTargetMembership.swift; sourceTree = ""; }; 53 | 55CB73222612D090000FB0B3 /* SomeApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SomeApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 55CB73252612D090000FB0B3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 55 | 55CB73272612D090000FB0B3 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 56 | 55CB73292612D090000FB0B3 /* UIKitImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitImports.swift; sourceTree = ""; }; 57 | 55CB732C2612D090000FB0B3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 58 | 55CB732E2612D092000FB0B3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 55CB73312612D092000FB0B3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 60 | 55CB73332612D092000FB0B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 55CB73492612D44B000FB0B3 /* MoyaImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoyaImports.swift; sourceTree = ""; }; 62 | 55CB734F2612D462000FB0B3 /* RxImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxImports.swift; sourceTree = ""; }; 63 | 55CB73542612D4AF000FB0B3 /* Auth0Imports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Auth0Imports.swift; sourceTree = ""; }; 64 | 6E2EFEEB4F7DAAEFF24DEA64 /* Pods-SomeApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SomeApp.debug.xcconfig"; path = "Target Support Files/Pods-SomeApp/Pods-SomeApp.debug.xcconfig"; sourceTree = ""; }; 65 | AD971342226A177C5E24001D /* Pods-SomeApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SomeApp.release.xcconfig"; path = "Target Support Files/Pods-SomeApp/Pods-SomeApp.release.xcconfig"; sourceTree = ""; }; 66 | F337E1111B60AEE48675B94C /* Pods_SomeApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SomeApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 55CB731F2612D090000FB0B3 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 51A1711826169DB800A7C0FD /* RealmSwift in Frameworks */, 75 | 51A1711326169D3300A7C0FD /* ChartsDynamic in Frameworks */, 76 | A7F769F045BDE665C9D46F0B /* Pods_SomeApp.framework in Frameworks */, 77 | 51A170EA26169BC000A7C0FD /* Lottie in Frameworks */, 78 | 51A1711A26169DB800A7C0FD /* Realm in Frameworks */, 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | /* End PBXFrameworksBuildPhase section */ 83 | 84 | /* Begin PBXGroup section */ 85 | 51A1710B26169C3100A7C0FD /* Apple */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 51A1710426169BF200A7C0FD /* AVFoundationImports.swift */, 89 | 55CB73292612D090000FB0B3 /* UIKitImports.swift */, 90 | 51A1710826169C2500A7C0FD /* FoundationImportrs.swift */, 91 | ); 92 | path = Apple; 93 | sourceTree = ""; 94 | }; 95 | 51A1710D26169C3800A7C0FD /* SPM */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 51A1710126169BDA00A7C0FD /* LottieImports.swift */, 99 | 51A1711B26169DC900A7C0FD /* ChartsImports.swift */, 100 | 51A1711D26169DD700A7C0FD /* RealmImports.swift */, 101 | ); 102 | path = SPM; 103 | sourceTree = ""; 104 | }; 105 | 51A1710F26169C3E00A7C0FD /* Pods */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 55CB73492612D44B000FB0B3 /* MoyaImports.swift */, 109 | 55CB734F2612D462000FB0B3 /* RxImports.swift */, 110 | 55CB73542612D4AF000FB0B3 /* Auth0Imports.swift */, 111 | ); 112 | path = Pods; 113 | sourceTree = ""; 114 | }; 115 | 537B80F642424D669D2FA993 /* Frameworks */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | F337E1111B60AEE48675B94C /* Pods_SomeApp.framework */, 119 | ); 120 | name = Frameworks; 121 | sourceTree = ""; 122 | }; 123 | 55CB73192612D090000FB0B3 = { 124 | isa = PBXGroup; 125 | children = ( 126 | 55CB73242612D090000FB0B3 /* SomeApp */, 127 | 55CB73232612D090000FB0B3 /* Products */, 128 | F72A94DD378EA162FE435312 /* Pods */, 129 | 537B80F642424D669D2FA993 /* Frameworks */, 130 | ); 131 | sourceTree = ""; 132 | }; 133 | 55CB73232612D090000FB0B3 /* Products */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 55CB73222612D090000FB0B3 /* SomeApp.app */, 137 | ); 138 | name = Products; 139 | sourceTree = ""; 140 | }; 141 | 55CB73242612D090000FB0B3 /* SomeApp */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 55CB73252612D090000FB0B3 /* AppDelegate.swift */, 145 | 55CB73272612D090000FB0B3 /* SceneDelegate.swift */, 146 | 51C68AB62619C32000B0EFE3 /* FileNotInTargetMembership.swift */, 147 | 55CB734C2612D455000FB0B3 /* Imports */, 148 | 55CB732B2612D090000FB0B3 /* Main.storyboard */, 149 | 55CB732E2612D092000FB0B3 /* Assets.xcassets */, 150 | 55CB73302612D092000FB0B3 /* LaunchScreen.storyboard */, 151 | 55CB73332612D092000FB0B3 /* Info.plist */, 152 | ); 153 | path = SomeApp; 154 | sourceTree = ""; 155 | }; 156 | 55CB734C2612D455000FB0B3 /* Imports */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 51A1710F26169C3E00A7C0FD /* Pods */, 160 | 51A1710D26169C3800A7C0FD /* SPM */, 161 | 51A1710B26169C3100A7C0FD /* Apple */, 162 | ); 163 | path = Imports; 164 | sourceTree = ""; 165 | }; 166 | F72A94DD378EA162FE435312 /* Pods */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 6E2EFEEB4F7DAAEFF24DEA64 /* Pods-SomeApp.debug.xcconfig */, 170 | AD971342226A177C5E24001D /* Pods-SomeApp.release.xcconfig */, 171 | ); 172 | path = Pods; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXGroup section */ 176 | 177 | /* Begin PBXNativeTarget section */ 178 | 55CB73212612D090000FB0B3 /* SomeApp */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 55CB73362612D092000FB0B3 /* Build configuration list for PBXNativeTarget "SomeApp" */; 181 | buildPhases = ( 182 | 32B0AD25FB60CD3898679A73 /* [CP] Check Pods Manifest.lock */, 183 | 55CB731E2612D090000FB0B3 /* Sources */, 184 | 55CB731F2612D090000FB0B3 /* Frameworks */, 185 | 55CB73202612D090000FB0B3 /* Resources */, 186 | 6F54B8343B475B0E53DEC1DD /* [CP] Embed Pods Frameworks */, 187 | 51A1711526169D3300A7C0FD /* Embed Frameworks */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = SomeApp; 194 | packageProductDependencies = ( 195 | 51A170E926169BC000A7C0FD /* Lottie */, 196 | 51A1711226169D3300A7C0FD /* ChartsDynamic */, 197 | 51A1711726169DB800A7C0FD /* RealmSwift */, 198 | 51A1711926169DB800A7C0FD /* Realm */, 199 | ); 200 | productName = SomeApp; 201 | productReference = 55CB73222612D090000FB0B3 /* SomeApp.app */; 202 | productType = "com.apple.product-type.application"; 203 | }; 204 | /* End PBXNativeTarget section */ 205 | 206 | /* Begin PBXProject section */ 207 | 55CB731A2612D090000FB0B3 /* Project object */ = { 208 | isa = PBXProject; 209 | attributes = { 210 | LastSwiftUpdateCheck = 1240; 211 | LastUpgradeCheck = 1240; 212 | TargetAttributes = { 213 | 55CB73212612D090000FB0B3 = { 214 | CreatedOnToolsVersion = 12.4; 215 | }; 216 | }; 217 | }; 218 | buildConfigurationList = 55CB731D2612D090000FB0B3 /* Build configuration list for PBXProject "SomeApp" */; 219 | compatibilityVersion = "Xcode 9.3"; 220 | developmentRegion = en; 221 | hasScannedForEncodings = 0; 222 | knownRegions = ( 223 | en, 224 | Base, 225 | ); 226 | mainGroup = 55CB73192612D090000FB0B3; 227 | packageReferences = ( 228 | 51A170E826169BC000A7C0FD /* XCRemoteSwiftPackageReference "lottie-ios" */, 229 | 51A1711126169D3300A7C0FD /* XCRemoteSwiftPackageReference "Charts" */, 230 | 51A1711626169DB800A7C0FD /* XCRemoteSwiftPackageReference "realm-cocoa" */, 231 | ); 232 | productRefGroup = 55CB73232612D090000FB0B3 /* Products */; 233 | projectDirPath = ""; 234 | projectRoot = ""; 235 | targets = ( 236 | 55CB73212612D090000FB0B3 /* SomeApp */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 55CB73202612D090000FB0B3 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | 55CB73322612D092000FB0B3 /* LaunchScreen.storyboard in Resources */, 247 | 55CB732F2612D092000FB0B3 /* Assets.xcassets in Resources */, 248 | 55CB732D2612D090000FB0B3 /* Main.storyboard in Resources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXResourcesBuildPhase section */ 253 | 254 | /* Begin PBXShellScriptBuildPhase section */ 255 | 32B0AD25FB60CD3898679A73 /* [CP] Check Pods Manifest.lock */ = { 256 | isa = PBXShellScriptBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | inputFileListPaths = ( 261 | ); 262 | inputPaths = ( 263 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 264 | "${PODS_ROOT}/Manifest.lock", 265 | ); 266 | name = "[CP] Check Pods Manifest.lock"; 267 | outputFileListPaths = ( 268 | ); 269 | outputPaths = ( 270 | "$(DERIVED_FILE_DIR)/Pods-SomeApp-checkManifestLockResult.txt", 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | shellPath = /bin/sh; 274 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 275 | showEnvVarsInLog = 0; 276 | }; 277 | 6F54B8343B475B0E53DEC1DD /* [CP] Embed Pods Frameworks */ = { 278 | isa = PBXShellScriptBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | inputFileListPaths = ( 283 | "${PODS_ROOT}/Target Support Files/Pods-SomeApp/Pods-SomeApp-frameworks-${CONFIGURATION}-input-files.xcfilelist", 284 | ); 285 | name = "[CP] Embed Pods Frameworks"; 286 | outputFileListPaths = ( 287 | "${PODS_ROOT}/Target Support Files/Pods-SomeApp/Pods-SomeApp-frameworks-${CONFIGURATION}-output-files.xcfilelist", 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | shellPath = /bin/sh; 291 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SomeApp/Pods-SomeApp-frameworks.sh\"\n"; 292 | showEnvVarsInLog = 0; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 55CB731E2612D090000FB0B3 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 51A1710526169BF200A7C0FD /* AVFoundationImports.swift in Sources */, 302 | 51A1710226169BDA00A7C0FD /* LottieImports.swift in Sources */, 303 | 51A1711E26169DD700A7C0FD /* RealmImports.swift in Sources */, 304 | 55CB734A2612D44B000FB0B3 /* MoyaImports.swift in Sources */, 305 | 55CB732A2612D090000FB0B3 /* UIKitImports.swift in Sources */, 306 | 55CB73262612D090000FB0B3 /* AppDelegate.swift in Sources */, 307 | 51A1711C26169DC900A7C0FD /* ChartsImports.swift in Sources */, 308 | 55CB73502612D462000FB0B3 /* RxImports.swift in Sources */, 309 | 51A1710926169C2500A7C0FD /* FoundationImportrs.swift in Sources */, 310 | 55CB73282612D090000FB0B3 /* SceneDelegate.swift in Sources */, 311 | 55CB73552612D4AF000FB0B3 /* Auth0Imports.swift in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | /* End PBXSourcesBuildPhase section */ 316 | 317 | /* Begin PBXVariantGroup section */ 318 | 55CB732B2612D090000FB0B3 /* Main.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 55CB732C2612D090000FB0B3 /* Base */, 322 | ); 323 | name = Main.storyboard; 324 | sourceTree = ""; 325 | }; 326 | 55CB73302612D092000FB0B3 /* LaunchScreen.storyboard */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 55CB73312612D092000FB0B3 /* Base */, 330 | ); 331 | name = LaunchScreen.storyboard; 332 | sourceTree = ""; 333 | }; 334 | /* End PBXVariantGroup section */ 335 | 336 | /* Begin XCBuildConfiguration section */ 337 | 55CB73342612D092000FB0B3 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_ANALYZER_NONNULL = YES; 342 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 343 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 344 | CLANG_CXX_LIBRARY = "libc++"; 345 | CLANG_ENABLE_MODULES = YES; 346 | CLANG_ENABLE_OBJC_ARC = YES; 347 | CLANG_ENABLE_OBJC_WEAK = YES; 348 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_COMMA = YES; 351 | CLANG_WARN_CONSTANT_CONVERSION = YES; 352 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 360 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 361 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 362 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 363 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 365 | CLANG_WARN_STRICT_PROTOTYPES = YES; 366 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 367 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | COPY_PHASE_STRIP = NO; 371 | DEBUG_INFORMATION_FORMAT = dwarf; 372 | ENABLE_STRICT_OBJC_MSGSEND = YES; 373 | ENABLE_TESTABILITY = YES; 374 | GCC_C_LANGUAGE_STANDARD = gnu11; 375 | GCC_DYNAMIC_NO_PIC = NO; 376 | GCC_NO_COMMON_BLOCKS = YES; 377 | GCC_OPTIMIZATION_LEVEL = 0; 378 | GCC_PREPROCESSOR_DEFINITIONS = ( 379 | "DEBUG=1", 380 | "$(inherited)", 381 | ); 382 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 383 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 384 | GCC_WARN_UNDECLARED_SELECTOR = YES; 385 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 386 | GCC_WARN_UNUSED_FUNCTION = YES; 387 | GCC_WARN_UNUSED_VARIABLE = YES; 388 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 389 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 390 | MTL_FAST_MATH = YES; 391 | ONLY_ACTIVE_ARCH = YES; 392 | SDKROOT = iphoneos; 393 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 394 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 395 | }; 396 | name = Debug; 397 | }; 398 | 55CB73352612D092000FB0B3 /* Release */ = { 399 | isa = XCBuildConfiguration; 400 | buildSettings = { 401 | ALWAYS_SEARCH_USER_PATHS = NO; 402 | CLANG_ANALYZER_NONNULL = YES; 403 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 405 | CLANG_CXX_LIBRARY = "libc++"; 406 | CLANG_ENABLE_MODULES = YES; 407 | CLANG_ENABLE_OBJC_ARC = YES; 408 | CLANG_ENABLE_OBJC_WEAK = YES; 409 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 410 | CLANG_WARN_BOOL_CONVERSION = YES; 411 | CLANG_WARN_COMMA = YES; 412 | CLANG_WARN_CONSTANT_CONVERSION = YES; 413 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 414 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 415 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 416 | CLANG_WARN_EMPTY_BODY = YES; 417 | CLANG_WARN_ENUM_CONVERSION = YES; 418 | CLANG_WARN_INFINITE_RECURSION = YES; 419 | CLANG_WARN_INT_CONVERSION = YES; 420 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 422 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 423 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 424 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 425 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 426 | CLANG_WARN_STRICT_PROTOTYPES = YES; 427 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 428 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | COPY_PHASE_STRIP = NO; 432 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 433 | ENABLE_NS_ASSERTIONS = NO; 434 | ENABLE_STRICT_OBJC_MSGSEND = YES; 435 | GCC_C_LANGUAGE_STANDARD = gnu11; 436 | GCC_NO_COMMON_BLOCKS = YES; 437 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 438 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 439 | GCC_WARN_UNDECLARED_SELECTOR = YES; 440 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 441 | GCC_WARN_UNUSED_FUNCTION = YES; 442 | GCC_WARN_UNUSED_VARIABLE = YES; 443 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 444 | MTL_ENABLE_DEBUG_INFO = NO; 445 | MTL_FAST_MATH = YES; 446 | SDKROOT = iphoneos; 447 | SWIFT_COMPILATION_MODE = wholemodule; 448 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 449 | VALIDATE_PRODUCT = YES; 450 | }; 451 | name = Release; 452 | }; 453 | 55CB73372612D092000FB0B3 /* Debug */ = { 454 | isa = XCBuildConfiguration; 455 | baseConfigurationReference = 6E2EFEEB4F7DAAEFF24DEA64 /* Pods-SomeApp.debug.xcconfig */; 456 | buildSettings = { 457 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 458 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 459 | CODE_SIGN_STYLE = Automatic; 460 | INFOPLIST_FILE = SomeApp/Info.plist; 461 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 462 | LD_RUNPATH_SEARCH_PATHS = ( 463 | "$(inherited)", 464 | "@executable_path/Frameworks", 465 | ); 466 | PRODUCT_BUNDLE_IDENTIFIER = com.test.SomeApp; 467 | PRODUCT_NAME = "$(TARGET_NAME)"; 468 | SWIFT_VERSION = 5.0; 469 | TARGETED_DEVICE_FAMILY = "1,2"; 470 | }; 471 | name = Debug; 472 | }; 473 | 55CB73382612D092000FB0B3 /* Release */ = { 474 | isa = XCBuildConfiguration; 475 | baseConfigurationReference = AD971342226A177C5E24001D /* Pods-SomeApp.release.xcconfig */; 476 | buildSettings = { 477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 478 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 479 | CODE_SIGN_STYLE = Automatic; 480 | INFOPLIST_FILE = SomeApp/Info.plist; 481 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 482 | LD_RUNPATH_SEARCH_PATHS = ( 483 | "$(inherited)", 484 | "@executable_path/Frameworks", 485 | ); 486 | PRODUCT_BUNDLE_IDENTIFIER = com.test.SomeApp; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_VERSION = 5.0; 489 | TARGETED_DEVICE_FAMILY = "1,2"; 490 | }; 491 | name = Release; 492 | }; 493 | /* End XCBuildConfiguration section */ 494 | 495 | /* Begin XCConfigurationList section */ 496 | 55CB731D2612D090000FB0B3 /* Build configuration list for PBXProject "SomeApp" */ = { 497 | isa = XCConfigurationList; 498 | buildConfigurations = ( 499 | 55CB73342612D092000FB0B3 /* Debug */, 500 | 55CB73352612D092000FB0B3 /* Release */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 55CB73362612D092000FB0B3 /* Build configuration list for PBXNativeTarget "SomeApp" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 55CB73372612D092000FB0B3 /* Debug */, 509 | 55CB73382612D092000FB0B3 /* Release */, 510 | ); 511 | defaultConfigurationIsVisible = 0; 512 | defaultConfigurationName = Release; 513 | }; 514 | /* End XCConfigurationList section */ 515 | 516 | /* Begin XCRemoteSwiftPackageReference section */ 517 | 51A170E826169BC000A7C0FD /* XCRemoteSwiftPackageReference "lottie-ios" */ = { 518 | isa = XCRemoteSwiftPackageReference; 519 | repositoryURL = "https://github.com/airbnb/lottie-ios.git"; 520 | requirement = { 521 | kind = upToNextMajorVersion; 522 | minimumVersion = 3.2.1; 523 | }; 524 | }; 525 | 51A1711126169D3300A7C0FD /* XCRemoteSwiftPackageReference "Charts" */ = { 526 | isa = XCRemoteSwiftPackageReference; 527 | repositoryURL = "https://github.com/danielgindi/Charts"; 528 | requirement = { 529 | kind = exactVersion; 530 | version = 4.0.1; 531 | }; 532 | }; 533 | 51A1711626169DB800A7C0FD /* XCRemoteSwiftPackageReference "realm-cocoa" */ = { 534 | isa = XCRemoteSwiftPackageReference; 535 | repositoryURL = "https://github.com/realm/realm-cocoa/"; 536 | requirement = { 537 | kind = exactVersion; 538 | version = 10.7.2; 539 | }; 540 | }; 541 | /* End XCRemoteSwiftPackageReference section */ 542 | 543 | /* Begin XCSwiftPackageProductDependency section */ 544 | 51A170E926169BC000A7C0FD /* Lottie */ = { 545 | isa = XCSwiftPackageProductDependency; 546 | package = 51A170E826169BC000A7C0FD /* XCRemoteSwiftPackageReference "lottie-ios" */; 547 | productName = Lottie; 548 | }; 549 | 51A1711226169D3300A7C0FD /* ChartsDynamic */ = { 550 | isa = XCSwiftPackageProductDependency; 551 | package = 51A1711126169D3300A7C0FD /* XCRemoteSwiftPackageReference "Charts" */; 552 | productName = ChartsDynamic; 553 | }; 554 | 51A1711726169DB800A7C0FD /* RealmSwift */ = { 555 | isa = XCSwiftPackageProductDependency; 556 | package = 51A1711626169DB800A7C0FD /* XCRemoteSwiftPackageReference "realm-cocoa" */; 557 | productName = RealmSwift; 558 | }; 559 | 51A1711926169DB800A7C0FD /* Realm */ = { 560 | isa = XCSwiftPackageProductDependency; 561 | package = 51A1711626169DB800A7C0FD /* XCRemoteSwiftPackageReference "realm-cocoa" */; 562 | productName = Realm; 563 | }; 564 | /* End XCSwiftPackageProductDependency section */ 565 | }; 566 | rootObject = 55CB731A2612D090000FB0B3 /* Project object */; 567 | } 568 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Charts", 6 | "repositoryURL": "https://github.com/danielgindi/Charts", 7 | "state": { 8 | "branch": null, 9 | "revision": "8d134a78bb2cb9e7a0f05ecbd5880bb05cd02863", 10 | "version": "4.0.1" 11 | } 12 | }, 13 | { 14 | "package": "Lottie", 15 | "repositoryURL": "https://github.com/airbnb/lottie-ios.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "b3c60ba1716baab6faeeb217d8ce1da336f92718", 19 | "version": "3.2.1" 20 | } 21 | }, 22 | { 23 | "package": "Realm", 24 | "repositoryURL": "https://github.com/realm/realm-cocoa/", 25 | "state": { 26 | "branch": null, 27 | "revision": "ae8e646590396dfc13c1abbf8aa2e48c43766dce", 28 | "version": "10.7.2" 29 | } 30 | }, 31 | { 32 | "package": "RealmDatabase", 33 | "repositoryURL": "https://github.com/realm/realm-core", 34 | "state": { 35 | "branch": null, 36 | "revision": "bab46acdca91c417a0d4849b8f4992a3c17e29a5", 37 | "version": "10.5.5" 38 | } 39 | }, 40 | { 41 | "package": "swift-algorithms", 42 | "repositoryURL": "https://github.com/apple/swift-algorithms", 43 | "state": { 44 | "branch": null, 45 | "revision": "4cca4895cc65743c32ddd9a59d7663f8b058ac16", 46 | "version": "0.0.4" 47 | } 48 | }, 49 | { 50 | "package": "swift-numerics", 51 | "repositoryURL": "https://github.com/apple/swift-numerics", 52 | "state": { 53 | "branch": null, 54 | "revision": "6583ac70c326c3ee080c1d42d9ca3361dca816cd", 55 | "version": "0.1.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Charts", 6 | "repositoryURL": "https://github.com/danielgindi/Charts", 7 | "state": { 8 | "branch": null, 9 | "revision": "8d134a78bb2cb9e7a0f05ecbd5880bb05cd02863", 10 | "version": "4.0.1" 11 | } 12 | }, 13 | { 14 | "package": "Lottie", 15 | "repositoryURL": "https://github.com/airbnb/lottie-ios.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "b3c60ba1716baab6faeeb217d8ce1da336f92718", 19 | "version": "3.2.1" 20 | } 21 | }, 22 | { 23 | "package": "Realm", 24 | "repositoryURL": "https://github.com/realm/realm-cocoa/", 25 | "state": { 26 | "branch": null, 27 | "revision": "ae8e646590396dfc13c1abbf8aa2e48c43766dce", 28 | "version": "10.7.2" 29 | } 30 | }, 31 | { 32 | "package": "RealmDatabase", 33 | "repositoryURL": "https://github.com/realm/realm-core", 34 | "state": { 35 | "branch": null, 36 | "revision": "bab46acdca91c417a0d4849b8f4992a3c17e29a5", 37 | "version": "10.5.5" 38 | } 39 | }, 40 | { 41 | "package": "swift-algorithms", 42 | "repositoryURL": "https://github.com/apple/swift-algorithms", 43 | "state": { 44 | "branch": null, 45 | "revision": "4cca4895cc65743c32ddd9a59d7663f8b058ac16", 46 | "version": "0.0.4" 47 | } 48 | }, 49 | { 50 | "package": "swift-numerics", 51 | "repositoryURL": "https://github.com/apple/swift-numerics", 52 | "state": { 53 | "branch": null, 54 | "revision": "6583ac70c326c3ee080c1d42d9ca3361dca816cd", 55 | "version": "0.1.0" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @main 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 7 | // Override point for customization after application launch. 8 | return true 9 | } 10 | 11 | // MARK: UISceneSession Lifecycle 12 | 13 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 14 | // Called when a new scene session is being created. 15 | // Use this method to select a configuration to create the new scene with. 16 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/FileNotInTargetMembership.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Apple/AVFoundationImports.swift: -------------------------------------------------------------------------------- 1 | import class AVFoundation.AVAsset 2 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Apple/FoundationImportrs.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Apple/UIKitImports.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ViewController: UIViewController {} 4 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Pods/Auth0Imports.swift: -------------------------------------------------------------------------------- 1 | import Auth0 2 | import Foundation 3 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Pods/MoyaImports.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Moya 3 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/Pods/RxImports.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RxSwift 3 | import NSObject_Rx 4 | import RxCocoa 5 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/SPM/ChartsImports.swift: -------------------------------------------------------------------------------- 1 | import Charts 2 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/SPM/LottieImports.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Lottie 3 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Imports/SPM/RealmImports.swift: -------------------------------------------------------------------------------- 1 | import RealmSwift 2 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UIApplicationSupportsIndirectInputEvents 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomeApp/SomeApp/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 | 5 | var window: UIWindow? 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Alamofire", 6 | "repositoryURL": "https://github.com/Alamofire/Alamofire.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "f96b619bcb2383b43d898402283924b80e2c4bae", 10 | "version": "5.4.3" 11 | } 12 | }, 13 | { 14 | "package": "Kingfisher", 15 | "repositoryURL": "https://github.com/onevcat/Kingfisher.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "44450a8f564d7c0165f736ba2250649ff8d3e556", 19 | "version": "6.3.0" 20 | } 21 | }, 22 | { 23 | "package": "Moya", 24 | "repositoryURL": "https://github.com/Moya/Moya.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "b3e5a233e0d85fd4d69f561c80988590859c7dee", 28 | "version": "14.0.0" 29 | } 30 | }, 31 | { 32 | "package": "ReactiveSwift", 33 | "repositoryURL": "https://github.com/Moya/ReactiveSwift.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "f195d82bb30e412e70446e2b4a77e1b514099e88", 37 | "version": "6.1.0" 38 | } 39 | }, 40 | { 41 | "package": "RxSwift", 42 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "254617dd7fae0c45319ba5fbea435bf4d0e15b5d", 46 | "version": "5.1.2" 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SomePackage", 7 | platforms: [ 8 | .macOS(.v10_15), 9 | ], 10 | products: [ 11 | .library( 12 | name: "SomePackage", 13 | targets: ["SomePackage"] 14 | ), 15 | ], 16 | dependencies: [ 17 | .package(name: "Kingfisher", url: "https://github.com/onevcat/Kingfisher.git", .upToNextMajor(from: "6.0.0")), 18 | .package(name: "Moya", url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "14.0.0")), 19 | .package(name: "Alamofire", url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.4.3")), 20 | ], 21 | targets: [ 22 | .target( 23 | name: "SomePackage", 24 | dependencies: [ 25 | "Kingfisher", 26 | "Moya", 27 | "Alamofire", 28 | ] 29 | ), 30 | .testTarget( 31 | name: "SomePackageTests", 32 | dependencies: ["SomePackage"] 33 | ), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/README.md: -------------------------------------------------------------------------------- 1 | # SomePackage 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Sources/SomePackage/AppleImports.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleImports.swift 3 | // 4 | // 5 | // Created by Max Chuquimia on 4/5/21. 6 | // 7 | 8 | import CoreGraphics 9 | import Foundation 10 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Sources/SomePackage/DependencyImports.swift: -------------------------------------------------------------------------------- 1 | import Alamofire 2 | import Foundation 3 | import Kingfisher 4 | import Moya 5 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Sources/SomePackage/SomePackage.swift: -------------------------------------------------------------------------------- 1 | struct SomePackage { 2 | var text = "Hello, World!" 3 | } 4 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SomePackageTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SomePackageTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Tests/SomePackageTests/SomePackageTests.swift: -------------------------------------------------------------------------------- 1 | @testable import SomePackage 2 | import XCTest 3 | 4 | final class SomePackageTests: XCTestCase { 5 | 6 | func testExample() { 7 | // This is an example of a functional test case. 8 | // Use XCTAssert and related functions to verify your tests produce the correct 9 | // results. 10 | XCTAssertEqual(SomePackage().text, "Hello, World!") 11 | } 12 | 13 | static var allTests = [ 14 | ("testExample", testExample), 15 | ] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Tests/SampleProjects/SomePackage/Tests/SomePackageTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | [ 6 | testCase(SomePackageTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/ArrayExtensionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import XCGrapherLib 2 | import XCTest 3 | 4 | final class ArrayExtensionTests: XCTestCase { 5 | 6 | func testUnique() { 7 | let sut = Array.unique 8 | 9 | let output1 = sut(["a", "a", "b", "c", "b", "d"])() 10 | XCTAssertEqual(output1.count, 4) 11 | XCTAssertTrue(output1.contains("a")) 12 | XCTAssertTrue(output1.contains("b")) 13 | XCTAssertTrue(output1.contains("c")) 14 | XCTAssertTrue(output1.contains("d")) 15 | 16 | let output2 = sut(["1", "2", "1", "4", "4", "4"])() 17 | XCTAssertEqual(output2.count, 3) 18 | XCTAssertTrue(output2.contains("1")) 19 | XCTAssertTrue(output2.contains("2")) 20 | XCTAssertTrue(output2.contains("4")) 21 | } 22 | 23 | func testSortedAscendingCaseInsensitively() { 24 | let sut = Array.sortedAscendingCaseInsensitively 25 | 26 | let output1 = sut(["baa", "dab", "Baa", "DAA"])() 27 | XCTAssertEqual(output1, ["Baa", "baa", "DAA", "dab"]) 28 | 29 | let output2 = sut(["ABC", "ABX", "abx", "abc"])() 30 | XCTAssertEqual(output2, ["ABC", "abc", "ABX", "abx"]) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/FileManagerExtensionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import XCGrapherLib 2 | import XCTest 3 | 4 | final class FileManagerExtensionTests: XCTestCase { 5 | 6 | func testDirectoryExists() { 7 | let sut = FileManager.default.directoryExists(atPath:) 8 | 9 | // This path always exists and is a directory 10 | XCTAssertTrue(sut("/Users")) 11 | 12 | // This path does not exist at all 13 | XCTAssertFalse(sut("/NotADirectoryOrAFile12345")) 14 | 15 | // This path does exist but it's not a directory 16 | XCTAssertFalse(sut("/bin/bash")) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/Helpers.swift: -------------------------------------------------------------------------------- 1 | import class Foundation.Bundle 2 | @testable import XCGrapherLib 3 | import XCTest 4 | 5 | /// Asserts that the `digraph` is made up of **ONLY** the `edges` and nothing more 6 | func XCGrapherAssertDigraphIsMadeFromEdges(_ digraph: String, _ edges: [(String, String)], file: StaticString = #file, line: UInt = #line) { 7 | var digraphEdgeStrings = digraph 8 | .breakIntoLines() 9 | .filter { $0.contains("\" -> \"") } // We only care about the lines that contain `"X" -> "Y"` 10 | 11 | for (edgeFrom, edgeTo) in edges { 12 | let expectedEdge = "\"\(edgeFrom)\" -> \"\(edgeTo)\"" 13 | guard let lineWithExpectedEdge = digraphEdgeStrings.firstIndex(where: { $0.contains(expectedEdge) }) else { 14 | XCTFail("The digraph does not contain the edge \(expectedEdge)", file: file, line: line) 15 | continue 16 | } 17 | digraphEdgeStrings.remove(at: lineWithExpectedEdge) 18 | } 19 | 20 | if !digraphEdgeStrings.isEmpty { 21 | XCTFail("The digraph contains unexpected edges: \(digraphEdgeStrings)") 22 | } 23 | } 24 | 25 | /// Finds the location of the products that were built in order for the test to run 26 | func productsDirectory() -> String { 27 | Bundle.allBundles 28 | .first { $0.bundlePath.hasSuffix(".xctest") }! 29 | .bundleURL 30 | .deletingLastPathComponent() 31 | .path 32 | } 33 | 34 | func defaultXCGrapherPluginLocation() -> String { 35 | productsDirectory() 36 | .appendingPathComponent("PackageFrameworks") 37 | .appendingPathComponent("XCGrapherModuleImportPlugin.framework") 38 | .appendingPathComponent("XCGrapherModuleImportPlugin") 39 | } 40 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/ScannerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import XCGrapherLib 2 | import XCTest 3 | 4 | final class ScannerTests: XCTestCase { 5 | 6 | var sut: Scanner.Builder! 7 | 8 | override func setUpWithError() throws { 9 | try super.setUpWithError() 10 | sut = Scanner.Builder() 11 | } 12 | 13 | func testScanAndStoreUpTo() { 14 | let given = "abc 123 567" 15 | 16 | sut.scanUpTo(string: " ") 17 | sut.scanAndStoreUpTo(string: " 5") 18 | 19 | XCTAssertEqual(sut.execute(on: given), " 123") 20 | } 21 | 22 | func testScanAndStoreUpToMultiple() { 23 | let given = "abc 123 567 xyz" 24 | 25 | sut.scanUpTo(string: "1") 26 | XCTAssertEqual(sut.execute(on: given), "") 27 | 28 | sut.scanAndStoreUpTo(string: " ") 29 | XCTAssertEqual(sut.execute(on: given), "123") 30 | 31 | sut.scanAndStoreUpTo(string: " xyz") 32 | XCTAssertEqual(sut.execute(on: given), "123 567") 33 | } 34 | 35 | func testScanAndStoreUpToAndIncluding() { 36 | let given = "abc 123 567" 37 | 38 | sut.scanUpToAndIncluding(string: " ") 39 | XCTAssertEqual(sut.execute(on: given), "") 40 | 41 | sut.scanAndStoreUpToAndIncluding(string: " ") 42 | XCTAssertEqual(sut.execute(on: given), "123 ") 43 | } 44 | 45 | func testScanAndStoreUpToAndIncludingMultiple() { 46 | let given = "abc 123 567" 47 | 48 | sut.scanAndStoreUpToAndIncluding(string: " 1") 49 | XCTAssertEqual(sut.execute(on: given), "abc 1") 50 | 51 | sut.scanUpToAndIncluding(string: "5") 52 | XCTAssertEqual(sut.execute(on: given), "abc 1") 53 | 54 | sut.scanAndStoreUpToAndIncluding(string: "7") 55 | XCTAssertEqual(sut.execute(on: given), "abc 167") 56 | } 57 | 58 | func testScanAndStoreUpToCharacters() { 59 | let given = "?!@#abc@?@!" 60 | 61 | sut.scanAndStoreUpToCharacters(from: CharacterSet.alphanumerics) 62 | XCTAssertEqual(sut.execute(on: given), "?!@#") 63 | 64 | sut.scanAndStoreUpToCharacters(from: CharacterSet.alphanumerics.inverted) 65 | XCTAssertEqual(sut.execute(on: given), "?!@#abc") 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/ShellTaskTests.swift: -------------------------------------------------------------------------------- 1 | @testable import XCGrapherLib 2 | import XCTest 3 | 4 | final class ShellTaskTests: XCTestCase { 5 | 6 | private var sut: ShellTask! { mock } 7 | private var mock: ConcreteShellTask! 8 | 9 | override func setUpWithError() throws { 10 | try super.setUpWithError() 11 | mock = ConcreteShellTask() 12 | } 13 | 14 | func testCommandNotFound() { 15 | mock.stringRepresentation = "not_a_command123" 16 | mock.commandNotFoundInstructions = UUID().uuidString 17 | 18 | do { 19 | try sut.execute() 20 | XCTFail("sut.execute() should throw and never reach here") 21 | } catch let CommandError.commandNotFound(message) { 22 | XCTAssertEqual(message, mock.commandNotFoundInstructions) 23 | } catch { 24 | XCTFail("Unexpected error \(error)") 25 | } 26 | } 27 | 28 | func testCommandFailed() { 29 | mock.stringRepresentation = "which" 30 | do { 31 | try sut.execute() 32 | XCTFail("sut.execute() should throw and never reach here") 33 | } catch let CommandError.failure(stderr) { 34 | XCTAssertEqual(stderr, "usage: which [-as] program ...\n") 35 | } catch { 36 | XCTFail("Unexpected error \(error)") 37 | } 38 | } 39 | 40 | func testCommandSuccess() { 41 | mock.stringRepresentation = "which bash" 42 | do { 43 | let output = try sut.execute() 44 | XCTAssertEqual(output, "/bin/bash\n") 45 | } catch { 46 | XCTFail("Unexpected error \(error)") 47 | } 48 | } 49 | 50 | } 51 | 52 | private class ConcreteShellTask: ShellTask { 53 | 54 | var stringRepresentation: String = "" 55 | var commandNotFoundInstructions: String = "" 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/StringExtensionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import XCGrapherLib 2 | import XCTest 3 | 4 | final class StringExtensionTests: XCTestCase { 5 | 6 | func testScanBuilder() { 7 | let given = "abc" 8 | let sut = given.scan 9 | 10 | let output = sut { 11 | $0.scanAndStoreUpToAndIncluding(string: "b") 12 | } 13 | 14 | XCTAssertEqual(output, "ab") 15 | } 16 | 17 | func testAppendingPathComponent() { 18 | let sut = String.appendingPathComponent 19 | 20 | XCTAssertEqual(sut("/a/b")("c"), "/a/b/c") 21 | XCTAssertEqual(sut("/a/b")("/c"), "/a/b/c") 22 | XCTAssertEqual(sut("/a/b/")("c"), "/a/b/c") 23 | XCTAssertEqual(sut("/a/b/")("/c"), "/a/b/c") 24 | } 25 | 26 | func testBreakIntoLines() { 27 | let sut = String.breakIntoLines 28 | 29 | XCTAssertEqual(sut("a\nb\nc")(), ["a", "b", "c"]) 30 | XCTAssertEqual(sut("abc")(), ["abc"]) 31 | } 32 | 33 | func testLastPathComponent() { 34 | let sut = String.lastPathComponent 35 | 36 | XCTAssertEqual(sut("a")(), "a") 37 | XCTAssertEqual(sut("a/b/c")(), "c") 38 | XCTAssertEqual(sut("a/b.swift")(), "b.swift") 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/XCGrapherTests+SPM.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import XCGrapherLib 4 | 5 | /// `sut` fail to execute `dot`, however we don't care as we are just reading the output text file 6 | final class XCGrapherSPMTests: XCTestCase { 7 | 8 | private var sut: ((XCGrapherOptions) throws -> Void)! 9 | private var options: ConcreteGrapherOptions! 10 | let dotfile = "/tmp/xcgrapher.dot" 11 | 12 | override func setUpWithError() throws { 13 | try super.setUpWithError() 14 | sut = XCGrapher.run 15 | options = ConcreteGrapherOptions() 16 | 17 | try? FileManager.default.removeItem(atPath: dotfile) // Remove if needed only 18 | } 19 | 20 | func testSomeAppSPM() throws { 21 | // GIVEN we only pass --spm to xcgrapher 22 | options.spm = true 23 | 24 | // WHEN we generate a digraph 25 | try? sut(options) 26 | let digraph = try String(contentsOfFile: dotfile) 27 | 28 | // THEN the digraph only contains these edges 29 | let expectedEdges = KnownEdges.spm 30 | 31 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 32 | } 33 | 34 | func testSomeAppAppleAndSPM() throws { 35 | // GIVEN we pass --apple and --spm to xcgrapher 36 | options.apple = true 37 | options.spm = true 38 | 39 | // WHEN we generate a digraph 40 | try? sut(options) 41 | let digraph = try String(contentsOfFile: dotfile) 42 | 43 | // THEN the digraph only contains these edges 44 | let expectedEdges = KnownEdges.apple + KnownEdges.spm + KnownEdges.appleFromSPM 45 | 46 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 47 | } 48 | 49 | } 50 | 51 | private struct ConcreteGrapherOptions: XCGrapherOptions { 52 | 53 | static let somePackageRoot = URL(fileURLWithPath: #file) 54 | .deletingLastPathComponent() 55 | .deletingLastPathComponent() 56 | .appendingPathComponent("SampleProjects") 57 | .appendingPathComponent("SomePackage") 58 | .path 59 | 60 | var startingPoint: StartingPoint = .swiftPackage(somePackageRoot) 61 | var target: String = "SomePackage" 62 | var podlock: String = "" 63 | var output: String = "/tmp/xcgraphertests.png" 64 | var apple: Bool = false 65 | var spm: Bool = false 66 | var pods: Bool = false 67 | var force: Bool = false 68 | var plugin: String = defaultXCGrapherPluginLocation() 69 | 70 | } 71 | 72 | private enum KnownEdges { 73 | 74 | static let spm = [ 75 | ("SomePackage", "Kingfisher"), 76 | ("SomePackage", "Moya"), 77 | ("Moya", "Alamofire"), 78 | ("SomePackage", "Alamofire"), 79 | ] 80 | 81 | static let apple = [ 82 | ("SomePackage", "Foundation"), 83 | ("SomePackage", "CoreGraphics"), 84 | ] 85 | 86 | static let appleFromSPM: [(String, String)] = [ 87 | ("Alamofire", "Combine"), 88 | ("Alamofire", "CoreServices"), 89 | ("Alamofire", "Foundation"), 90 | ("Alamofire", "MobileCoreServices"), 91 | ("Alamofire", "SystemConfiguration"), 92 | ("Kingfisher", "Accelerate"), 93 | ("Kingfisher", "AppKit"), 94 | ("Kingfisher", "AVKit"), 95 | ("Kingfisher", "Combine"), 96 | ("Kingfisher", "CoreGraphics"), 97 | ("Kingfisher", "CoreImage"), 98 | ("Kingfisher", "CoreServices"), 99 | ("Kingfisher", "Foundation"), 100 | ("Kingfisher", "ImageIO"), 101 | ("Kingfisher", "MobileCoreServices"), 102 | ("Kingfisher", "SwiftUI"), 103 | ("Kingfisher", "UIKit"), 104 | ("Moya", "AppKit"), 105 | ("Moya", "Foundation"), 106 | ("Moya", "UIKit"), 107 | ] 108 | 109 | } 110 | -------------------------------------------------------------------------------- /Tests/XCGrapherLibTests/XCGrapherTests+Xcodeproject.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | @testable import XCGrapherLib 4 | 5 | /// `sut` fail to execute `dot`, however we don't care as we are just reading the output text file 6 | final class XCGrapherXcodeprojectTests: XCTestCase { 7 | 8 | private var sut: ((XCGrapherOptions) throws -> Void)! 9 | private var options: ConcreteGrapherOptions! 10 | let dotfile = "/tmp/xcgrapher.dot" 11 | 12 | override class func setUp() { 13 | super.setUp() 14 | 15 | let someAppRoot = ConcreteGrapherOptions.someAppRoot 16 | if !FileManager.default.directoryExists(atPath: someAppRoot.appendingPathComponent("Pods")) { 17 | XCTFail("Run `pod install` in \(someAppRoot) before running these tests.") 18 | } 19 | } 20 | 21 | override func setUpWithError() throws { 22 | try super.setUpWithError() 23 | sut = XCGrapher.run 24 | options = ConcreteGrapherOptions() 25 | 26 | try? FileManager.default.removeItem(atPath: dotfile) 27 | } 28 | 29 | func testSomeAppPods() throws { 30 | // GIVEN we only pass --pods to xcgrapher 31 | options.pods = true 32 | 33 | // WHEN we generate a digraph 34 | try? sut(options) 35 | let digraph = try String(contentsOfFile: dotfile) 36 | 37 | // THEN the digraph only contains these edges 38 | let expectedEdges = KnownEdges.pods 39 | 40 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 41 | } 42 | 43 | func testSomeAppSPM() throws { 44 | // GIVEN we only pass --spm to xcgrapher 45 | options.spm = true 46 | 47 | // WHEN we generate a digraph 48 | try? sut(options) 49 | let digraph = try String(contentsOfFile: dotfile) 50 | 51 | // THEN the digraph only contains these edges 52 | let expectedEdges = KnownEdges.spm 53 | 54 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 55 | } 56 | 57 | func testSomeAppPodsAndSPM() throws { 58 | // GIVEN we pass both --spm and --pods to xcgrapher 59 | options.spm = true 60 | options.pods = true 61 | 62 | // WHEN we generate a digraph 63 | try? sut(options) 64 | let digraph = try String(contentsOfFile: dotfile) 65 | 66 | // THEN the digraph only contains these edges 67 | let expectedEdges = KnownEdges.spm + KnownEdges.pods 68 | 69 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 70 | } 71 | 72 | func testSomeAppApple() throws { 73 | // GIVEN we only pass --apple to xcgrapher 74 | options.apple = true 75 | 76 | // WHEN we generate a digraph 77 | try? sut(options) 78 | let digraph = try String(contentsOfFile: dotfile) 79 | 80 | // THEN the digraph only contains these edges 81 | let expectedEdges = KnownEdges.apple 82 | 83 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 84 | } 85 | 86 | func testSomeAppAppleAndSPM() throws { 87 | // GIVEN we pass --apple and --spm to xcgrapher 88 | options.apple = true 89 | options.spm = true 90 | 91 | // WHEN we generate a digraph 92 | try? sut(options) 93 | let digraph = try String(contentsOfFile: dotfile) 94 | 95 | // THEN the digraph only contains these edges 96 | let expectedEdges = KnownEdges.apple + KnownEdges.spm + KnownEdges.appleFromSPM 97 | 98 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 99 | } 100 | 101 | func testSomeAppAppleAndSPMAndPods() throws { 102 | // GIVEN we pass --apple and --spm and --pods to xcgrapher 103 | options.apple = true 104 | options.spm = true 105 | options.pods = true 106 | 107 | // WHEN we generate a digraph 108 | try? sut(options) 109 | let digraph = try String(contentsOfFile: dotfile) 110 | 111 | // THEN the digraph only contains these edges 112 | let expectedEdges = KnownEdges.apple + KnownEdges.spm + KnownEdges.appleFromSPM + KnownEdges.pods + KnownEdges.appleFromPods 113 | 114 | XCGrapherAssertDigraphIsMadeFromEdges(digraph, expectedEdges) 115 | } 116 | 117 | } 118 | 119 | private struct ConcreteGrapherOptions: XCGrapherOptions { 120 | 121 | static let someAppRoot = URL(fileURLWithPath: #file) 122 | .deletingLastPathComponent() 123 | .deletingLastPathComponent() 124 | .appendingPathComponent("SampleProjects") 125 | .appendingPathComponent("SomeApp") 126 | .path 127 | 128 | var startingPoint: StartingPoint = .xcodeProject(someAppRoot.appendingPathComponent("SomeApp.xcodeproj")) 129 | var target: String = "SomeApp" 130 | var podlock: String = someAppRoot.appendingPathComponent("Podfile.lock") 131 | var output: String = "/tmp/xcgraphertests.png" 132 | var apple: Bool = false 133 | var spm: Bool = false 134 | var pods: Bool = false 135 | var force: Bool = false 136 | var plugin: String = defaultXCGrapherPluginLocation() 137 | 138 | } 139 | 140 | private enum KnownEdges { 141 | 142 | static let pods = [ 143 | ("SomeApp", "RxSwift"), 144 | ("SomeApp", "RxCocoa"), 145 | ("SomeApp", "Auth0"), 146 | ("SomeApp", "Moya"), 147 | ("SomeApp", "NSObject_Rx"), 148 | ("NSObject_Rx", "RxSwift"), 149 | ("RxCocoa", "RxSwift"), 150 | ("RxCocoa", "RxRelay"), 151 | ("RxRelay", "RxSwift"), 152 | ("Moya", "Moya/Core"), 153 | ("Moya/Core", "Alamofire"), 154 | ("Auth0", "JWTDecode"), 155 | ("Auth0", "SimpleKeychain"), 156 | ] 157 | 158 | static let spm = [ 159 | ("SomeApp", "Charts"), 160 | ("SomeApp", "RealmSwift"), 161 | ("SomeApp", "Lottie"), 162 | ("RealmSwift", "Realm"), 163 | ("Charts", "Algorithms"), 164 | ("Algorithms", "RealModule"), 165 | ("RealModule", "_NumericsShims"), 166 | ] 167 | 168 | static let apple = [ 169 | ("SomeApp", "Foundation"), 170 | ("SomeApp", "AVFoundation"), 171 | ("SomeApp", "UIKit"), 172 | ] 173 | 174 | static let appleFromSPM = [ 175 | ("RealmSwift", "Combine"), 176 | ("RealmSwift", "SwiftUI"), 177 | ("RealmSwift", "Foundation"), 178 | ("Lottie", "Foundation"), 179 | ("Lottie", "AppKit"), 180 | ("Lottie", "CoreGraphics"), 181 | ("Lottie", "CoreText"), 182 | ("Lottie", "QuartzCore"), 183 | ("Lottie", "UIKit"), 184 | ("Charts", "Foundation"), 185 | ("Charts", "AppKit"), 186 | ("Charts", "CoreGraphics"), 187 | ("Charts", "QuartzCore"), 188 | ("Charts", "Cocoa"), 189 | ("Charts", "Quartz"), 190 | ("Charts", "UIKit"), 191 | ] 192 | 193 | static let appleFromPods: [(String, String)] = [ 194 | // Unsupported at this time 195 | ] 196 | 197 | } 198 | --------------------------------------------------------------------------------