├── .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 |
--------------------------------------------------------------------------------