├── .github └── workflows │ └── tag.yml ├── .gitignore ├── .swiftlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── RELEASING.md ├── Sources ├── XCUtilityFramework │ ├── Extensions │ │ ├── Path+Extension.swift │ │ └── String+Extension.swift │ ├── Log │ │ ├── Log.swift │ │ └── PrintLogAdapter.swift │ ├── Model │ │ ├── Configuration.swift │ │ ├── File.swift │ │ ├── Files.swift │ │ ├── Version.swift │ │ ├── XcodeFile.swift │ │ └── XcodeProject.swift │ ├── Pipeline │ │ ├── Pipeline.swift │ │ ├── PipelineState.swift │ │ ├── StepFactory │ │ │ ├── FindAllStepFactory.swift │ │ │ ├── FindUnbuiltStepFactory.swift │ │ │ ├── FindUnreferencedStepFactory.swift │ │ │ └── StepFactory.swift │ │ └── Steps │ │ │ ├── DeleteUnbuiltFiles.swift │ │ │ ├── DeleteUnreferencedFiles.swift │ │ │ ├── FindFiles.swift │ │ │ ├── FindUnbuiltFiles.swift │ │ │ ├── FindUnreferencedFiles.swift │ │ │ ├── FindXcodeFiles.swift │ │ │ ├── FindXcodeProjects.swift │ │ │ ├── FindXcodeReferences.swift │ │ │ └── FindXcodeSourceBuildFiles.swift │ └── Yaml │ │ └── YamlParser.swift └── xcutility │ ├── Commands │ ├── FindAllCommand.swift │ ├── FindUnbuiltCommand.swift │ ├── FindUnreferencedCommand.swift │ └── VersionCommand.swift │ ├── Extensions │ └── CommandProtocol+Extension.swift │ └── main.swift ├── Tests ├── Fixtures │ ├── CaseSensitivity │ │ ├── TestApp.xcodeproj │ │ │ └── project.pbxproj │ │ └── TestApp │ │ │ ├── APPDELEGATE.swift │ │ │ ├── Info.plist │ │ │ └── viewcontroller.swift │ ├── Configs │ │ ├── empty.yml │ │ ├── invalid.yml │ │ └── simple.yml │ ├── InvalidProject │ │ └── InvalidProject.xcodeproj │ │ │ └── project.pbxproj │ └── SimpleProject │ │ ├── SimpleProject.xcodeproj │ │ └── project.pbxproj │ │ └── SimpleProject │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── ViewController.swift └── XCUtilityFrameworkTests │ ├── Extensions │ ├── Path+ExtensionTests.swift │ └── String+ExtensionTests.swift │ ├── Fixtures.swift │ ├── Model │ ├── ConfigurationTests.swift │ ├── FileTests.swift │ └── XcodeProjectTests.swift │ ├── Pipeline │ ├── PipelineTests.swift │ ├── StepFactory │ │ ├── FindAllStepFactoryTests.swift │ │ ├── FindUnbuiltStepFactoryTests.swift │ │ ├── FindUnreferencedStepFactoryTests.swift │ │ └── StepFactoryTests.swift │ └── Steps │ │ ├── DeleteUnbuiltFilesTests.swift │ │ ├── DeleteUnreferencedFilesTests.swift │ │ ├── FindFilesTests.swift │ │ ├── FindUnbuiltFilesTests.swift │ │ ├── FindUnreferencedFilesTests.swift │ │ ├── FindXcodeFilesTests.swift │ │ ├── FindXcodeProjectsTests.swift │ │ ├── FindXcodeReferencesTests.swift │ │ └── FindXcodeSourceBuildFilesTests.swift │ └── Yaml │ └── YamlParserTests.swift └── xcutility.xcodeproj └── xcshareddata └── xcschemes └── XCUtilityFramework.xcscheme /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: tag 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release-xcframework: 9 | runs-on: macos-latest 10 | env: 11 | PROJECT_NAME: xcutility 12 | FRAMEWORK_NAME: XCUtilityFramework 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: create xcode project 16 | run: | 17 | swift package generate-xcodeproj 18 | 19 | - name: Create xcframework 20 | id: create-xcframework 21 | uses: jeffctown/create-xcframework@0.1.0-beta5 22 | with: 23 | project: ${{ env.PROJECT_NAME }}.xcodeproj 24 | scheme: ${{ env.FRAMEWORK_NAME }} 25 | framework: ${{ env.FRAMEWORK_NAME }} 26 | 27 | - name: Create GitHub Release 28 | id: create_release 29 | uses: actions/create-release@v1 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | with: 33 | tag_name: ${{ github.ref }} 34 | release_name: Release ${{ github.ref }} 35 | body: | 36 | Release ${{ github.ref }} 37 | Checksum ${{ steps.create-xcframework.outputs.checksum }} 38 | draft: true 39 | prerelease: true 40 | 41 | - name: Add xcframework to Release 42 | id: upload-release-asset 43 | uses: actions/upload-release-asset@v1 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | with: 47 | upload_url: ${{ steps.create_release.outputs.upload_url }} 48 | asset_path: ${{ steps.create-xcframework.outputs.xcframework }} 49 | asset_name: ${{ env.FRAMEWORK_NAME }}.xcframework.zip 50 | asset_content_type: application/zip 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | xcshareddata 7 | xcuserdata 8 | project.xcworkspace 9 | /Carthage 10 | *.pkg 11 | *.zip 12 | .idea/ 13 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Sources 3 | - Tests 4 | excluded: 5 | - Tests/Fixtures 6 | 7 | opt_in_rules: 8 | - anyobject_protocol 9 | - array_init 10 | - attributes 11 | - closure_end_indentation 12 | - closure_spacing 13 | - collection_alignment 14 | - conditional_returns_on_newline 15 | - contains_over_first_not_nil 16 | - discouraged_object_literal 17 | - discouraged_optional_boolean 18 | - discouraged_optional_collection 19 | - empty_count 20 | - empty_string 21 | - empty_xctest_method 22 | - explicit_init 23 | - fallthrough 24 | - fatal_error_message 25 | - file_header 26 | - file_name 27 | - first_where 28 | - identical_operands 29 | - implicit_return 30 | - implicitly_unwrapped_optional 31 | - joined_default_parameter 32 | - let_var_whitespace 33 | - last_where 34 | - literal_expression_end_indentation 35 | - lower_acl_than_parent 36 | - multiline_arguments 37 | - multiline_parameters 38 | - nimble_operator 39 | - number_separator 40 | - object_literal 41 | - operator_usage_whitespace 42 | - overridden_super_call 43 | - override_in_extension 44 | - pattern_matching_keywords 45 | - private_action 46 | - private_outlet 47 | - prohibited_interface_builder 48 | - prohibited_super_call 49 | - quick_discouraged_call 50 | - quick_discouraged_focused_test 51 | - quick_discouraged_pending_test 52 | - redundant_nil_coalescing 53 | - redundant_type_annotation 54 | - required_enum_case 55 | - single_test_class 56 | - sorted_first_last 57 | - sorted_imports 58 | - static_operator 59 | - strict_fileprivate 60 | - switch_case_on_newline 61 | - trailing_closure 62 | - unavailable_function 63 | - unneeded_parentheses_in_closure_argument 64 | - untyped_error_in_catch 65 | - vertical_parameter_alignment_on_call 66 | - vertical_whitespace_closing_braces 67 | - vertical_whitespace_opening_braces 68 | - xct_specific_matcher 69 | - yoda_condition 70 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Master 2 | 3 | #### Breaking 4 | 5 | * None. 6 | 7 | #### Experimental 8 | 9 | * None. 10 | 11 | #### Enhancements 12 | 13 | * None. 14 | 15 | #### Bug Fixes 16 | 17 | * None. 18 | 19 | ## 0.2.0 (03/24/2019) 20 | 21 | #### Breaking 22 | 23 | * Renamed the tool `xcutility` 24 | * Renamed commands 25 | 26 | ## 0.1.2 (02/17/2019) 27 | 28 | #### Enhancements 29 | 30 | * Improved the output from deleting files. 31 | 32 | ## 0.1.1 (02/16/2019) 33 | 34 | #### Bug Fixes 35 | 36 | * Fixed an issue with false positives caused by Xcode References and the File System having different casing (#9). 37 | 38 | 39 | ## 0.1.0 (02/15/2019) 40 | 41 | #### Initial Release 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Pull Requests 2 | 3 | All changes, no matter how trivial, must be done via pull request. Commits 4 | should never be made directly on the `master` branch. Prefer rebasing over 5 | merging `master` into your PR branch to update it and resolve conflicts. 6 | 7 | _If you have commit access to XCUtility and believe your change to be trivial 8 | and not worth waiting for review, you may open a pull request and merge 9 | immediately, but this should be the exception, not the norm._ 10 | 11 | ### Tests 12 | 13 | XCUtility supports building via Xcode and Swift Package Manager on macOS. When contributing code changes, please 14 | ensure that both supported build methods continue to work and pass tests. 15 | 16 | ```shell 17 | $ make test 18 | $ make xcodetest 19 | ``` 20 | 21 | ## Tracking changes 22 | 23 | All changes should be made via pull requests on GitHub. 24 | 25 | When issuing a pull request, please add a summary of your changes to 26 | the `CHANGELOG.md` file. 27 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * xcutility install method: [ ] .pkg, [ ] source 2 | * `which xcutilty`: 3 | * `xcutility version`: 4 | * `xcodebuild -version`: 5 | 6 | **xcutilty Output** 7 | ``` 8 | 9 | ``` 10 | 11 | **Actual outcome** 12 | xcutilty did or did not ... 13 | 14 | **Expected outcome** 15 | xcutilty should ... 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jeff Lett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/xcrun make -f 2 | 3 | XCUTILITY_TEMPORARY_FOLDER?=/tmp/XCUtility.dst 4 | PREFIX?=/usr/local 5 | 6 | OUTPUT_PACKAGE=XCUtility.pkg 7 | FRAMEWORK_NAME=XCUtilityFramework 8 | 9 | XCUTILITY_EXECUTABLE=./.build/release/xcutility 10 | BINARIES_FOLDER=/usr/local/bin 11 | 12 | # ZSH_COMMAND · run single command in `zsh` shell, ignoring most `zsh` startup files. 13 | ZSH_COMMAND := ZDOTDIR='/var/empty' zsh -o NO_GLOBAL_RCS -c 14 | # RM_SAFELY · `rm -rf` ensuring first and only parameter is non-null, contains more than whitespace, non-root if resolving absolutely. 15 | RM_SAFELY := $(ZSH_COMMAND) '[[ ! $${1:?} =~ "^[[:space:]]+\$$" ]] && [[ $${1:A} != "/" ]] && [[ $${\#} == "1" ]] && noglob rm -rf $${1:A}' -- 16 | 17 | VERSION_STRING=$(shell git describe --abbrev=0 --tags) 18 | 19 | RM=rm -f 20 | MKDIR=mkdir -p 21 | SUDO=sudo 22 | CP=cp 23 | 24 | .PHONY: all clean lint test installables package install uninstall xcodeproj xcodetest codecoverage archive release 25 | 26 | all: installables 27 | 28 | clean: 29 | swift package clean 30 | 31 | lint: 32 | swiftlint --strict 33 | 34 | test: 35 | swift test 36 | 37 | installables: 38 | swift build -c release 39 | 40 | package: installables archive 41 | $(MKDIR) "$(XCUTILITY_TEMPORARY_FOLDER)$(BINARIES_FOLDER)" 42 | $(CP) "$(XCUTILITY_EXECUTABLE)" "$(XCUTILITY_TEMPORARY_FOLDER)$(BINARIES_FOLDER)" 43 | 44 | pkgbuild \ 45 | --identifier "com.jefflett.xcutility" \ 46 | --install-location "/" \ 47 | --root "$(XCUTILITY_TEMPORARY_FOLDER)" \ 48 | --version "$(VERSION_STRING)" \ 49 | "$(OUTPUT_PACKAGE)" 50 | 51 | install: installables 52 | $(SUDO) $(CP) -f "$(XCUTILITY_EXECUTABLE)" "$(BINARIES_FOLDER)" 53 | 54 | uninstall: 55 | $(RM) "$(BINARIES_FOLDER)/xcutility" 56 | 57 | xcodeproj: 58 | swift package generate-xcodeproj 59 | 60 | xcodetest: xcodeproj 61 | xcodebuild -scheme xcutility build test 62 | 63 | codecoverage: xcodeproj 64 | xcodebuild -scheme xcutility -enableCodeCoverage YES build test -quiet 65 | 66 | archive: 67 | carthage build --no-skip-current --platform mac 68 | carthage archive $(FRAMEWORK_NAME) 69 | 70 | release: | lint test xcodetest archive package install 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "AEXML", 6 | "repositoryURL": "https://github.com/tadija/AEXML", 7 | "state": { 8 | "branch": null, 9 | "revision": "54bb8ea6fb693dd3f92a89e5fcc19e199fdeedd0", 10 | "version": "4.3.3" 11 | } 12 | }, 13 | { 14 | "package": "Commandant", 15 | "repositoryURL": "https://github.com/Carthage/Commandant.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "07cad52573bad19d95844035bf0b25acddf6b0f6", 19 | "version": "0.15.0" 20 | } 21 | }, 22 | { 23 | "package": "Nimble", 24 | "repositoryURL": "https://github.com/Quick/Nimble.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "e9d769113660769a4d9dd3afb855562c0b7ae7b0", 28 | "version": "7.3.4" 29 | } 30 | }, 31 | { 32 | "package": "PathKit", 33 | "repositoryURL": "https://github.com/kylef/PathKit", 34 | "state": { 35 | "branch": null, 36 | "revision": "e2f5be30e4c8f531c9c1e8765aa7b71c0a45d7a0", 37 | "version": "0.9.2" 38 | } 39 | }, 40 | { 41 | "package": "Quick", 42 | "repositoryURL": "https://github.com/Quick/Quick.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "f2b5a06440ea87eba1a167cab37bf6496646c52e", 46 | "version": "1.3.4" 47 | } 48 | }, 49 | { 50 | "package": "Result", 51 | "repositoryURL": "https://github.com/antitypical/Result.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "2ca499ba456795616fbc471561ff1d963e6ae160", 55 | "version": "4.1.0" 56 | } 57 | }, 58 | { 59 | "package": "Spectre", 60 | "repositoryURL": "https://github.com/kylef/Spectre.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", 64 | "version": "0.9.0" 65 | } 66 | }, 67 | { 68 | "package": "SwiftShell", 69 | "repositoryURL": "https://github.com/kareman/SwiftShell", 70 | "state": { 71 | "branch": null, 72 | "revision": "beebe43c986d89ea5359ac3adcb42dac94e5e08a", 73 | "version": "4.1.2" 74 | } 75 | }, 76 | { 77 | "package": "xcodeproj", 78 | "repositoryURL": "https://github.com/tuist/xcodeproj.git", 79 | "state": { 80 | "branch": null, 81 | "revision": "4d31d2be2532e9213d58cd4e0b15588c5dfae42d", 82 | "version": "6.5.0" 83 | } 84 | }, 85 | { 86 | "package": "Yams", 87 | "repositoryURL": "https://github.com/jpsim/Yams.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "26ab35f50ea891e8edefcc9d975db2f6b67e1d68", 91 | "version": "1.0.1" 92 | } 93 | } 94 | ] 95 | }, 96 | "version": 1 97 | } 98 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "xcutility", 8 | platforms: [ 9 | .iOS(.v11) 10 | ], 11 | products: [ 12 | .library(name: "XCUtilityFramework", targets: ["XCUtilityFramework"]), 13 | .executable(name: "xcutility", targets: ["xcutility"]) 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/tuist/xcodeproj.git", from: "6.5.0"), 17 | .package(url: "https://github.com/Carthage/Commandant.git", from: "0.15.0"), 18 | .package(url: "https://github.com/jpsim/Yams.git", from: "1.0.1") 19 | ], 20 | targets: [ 21 | .target(name: "xcutility", dependencies: ["XCUtilityFramework","Commandant"]), 22 | .target(name: "XCUtilityFramework", dependencies: ["xcodeproj","Yams"]), 23 | .testTarget(name: "XCUtilityFrameworkTests", dependencies: ["XCUtilityFramework"]) 24 | ], 25 | swiftLanguageVersions: [.v5] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xcutility 2 | 3 | [![Swift Version](https://img.shields.io/badge/Swift-5.0-orange.svg?style=for-the-badge)](https://swift.org) 4 | [![GitHub release](https://img.shields.io/github/release/jeffctown/xcutility.svg?style=for-the-badge)](https://github.com/jeffctown/xcutility/releases) 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=for-the-badge)](https://raw.githubusercontent.com/jeffctown/xcutility/master/LICENSE.md) 6 | 7 | [![Build Status](https://travis-ci.org/jeffctown/xcutility.svg?&branch=master)](https://travis-ci.org/jeffctown/xcutility)[![codecov.io](https://codecov.io/github/jeffctown/xcutility/coverage.svg?branch=master)](https://codecov.io/github/jeffctown/xcutility?branch=master) 8 | 9 | xcutility is a tool to find and delete unused files from Xcode projects. 10 | 11 | It recursively searches through a path to find all of the path's Xcode projects and files, and will tell you which files are not referenced or built in any of your Xcode projects. 12 | 13 | ## Installation 14 | 15 | ### Using a pre-built package: 16 | 17 | You can install xcutility by downloading `xcutility.pkg` from the 18 | [latest GitHub release](https://github.com/jeffctown/xcutility/releases/latest) and 19 | running it. 20 | 21 | ### Compiling from source: 22 | 23 | You can also build from source by cloning this project and running 24 | `make install` (Xcode 10.2 or later). 25 | 26 | ## Quick Start 27 | 28 | * Find Swift files not referenced in any Xcode projects: 29 | ```xcutility norefs --extensions .swift``` 30 | 31 | * Find Objective C files not referenced in any Xcode projects: 32 | ```xcutility norefs --extensions .m,.h``` 33 | 34 | * Find Swift & Objective C files not referenced in any Xcode projects: 35 | ```xcutility norefs --extensions .m,.h,.swift``` 36 | 37 | * Find Swift files not compiled in any Xcode projects: 38 | ```xcutility unbuilt --extensions .swift``` 39 | 40 | * Find Objective C files not compiled in any Xcode projects: 41 | ```xcutility unbuilt --extensions .m``` 42 | 43 | * Find Swift & Objective C files not compiled in any Xcode projects: 44 | ```xcutility unbuilt --extensions .m,.swift``` 45 | 46 | 47 | ## Usage 48 | 49 | 50 | ### Help 51 | 52 | ``` 53 | $ xcutility help 54 | Available commands: 55 | 56 | all Find files that are not built or referenced in any Xcode projects 57 | help Display general or command-specific help 58 | norefs Find files that are not referenced in any Xcode projects 59 | unbuilt Find files that are referenced in Xcode but not built in any build phases 60 | version Display the current version of xcutility 61 | ``` 62 | 63 | ### Unreferenced Files 64 | 65 | 66 | #### Find Files that are Unreferenced by any Xcode Projects in the Current Directory 67 | 68 | ```bash 69 | xcutility norefs 70 | ``` 71 | 72 | #### Find Files that are Unreferenced by any Xcode Projects In a Specific Path 73 | 74 | ```bash 75 | xcutility norefs --path ~/MySweetSweetApp/ 76 | ``` 77 | 78 | #### Find Files With a Certain Extension that are Unreferenced by any Xcode Projects 79 | 80 | ```bash 81 | xcutility norefs --extensions .swift 82 | ``` 83 | 84 | #### Find Only Files With Multiple Extensions that are Unreferenced by any Xcode Projects 85 | 86 | ```bash 87 | xcutility norefs --extensions .swift,.h,.m 88 | ``` 89 | 90 | #### Find And Delete Files With Multiple Extensions that are Unreferenced by any Xcode Projects 91 | 92 | ```bash 93 | xcutility norefs --extensions .swift,.h,.m --delete 94 | ``` 95 | 96 | #### Find Unreferenced Files Verbosely 97 | 98 | ```bash 99 | xcutility norefs --verbose 100 | ``` 101 | 102 | ### Find Unreferenced Files Combined Usage 103 | 104 | ```bash 105 | xcutility norefs --path ~/MySweetSweetApp/ --extension .swift,.h,.m --verbose --delete 106 | ``` 107 | 108 | ### Unbuilt Files 109 | 110 | 111 | #### Find Files that are not built by any Xcode Project Build Phases in the Current Directory 112 | 113 | ```bash 114 | xcutility unbuilt 115 | ``` 116 | 117 | #### Find Files that are not built by any Xcode Project Build Phases in a specific path 118 | 119 | ```bash 120 | xcutility unbuilt --path ~/MySweetSweetApp/ 121 | ``` 122 | 123 | #### Find Files With a Certain Extension that are not built by any Xcode Projects Build Phases 124 | 125 | ```bash 126 | xcutility unbuilt --extensions .swift 127 | ``` 128 | 129 | #### Find Only Files With Multiple Extensions that are not built by any Xcode Projects Build Phases 130 | 131 | ```bash 132 | xcutility unbuilt --extensions .swift,.h,.m 133 | ``` 134 | 135 | #### Find And Delete Files With Multiple Extensions that are not built by any Xcode Projects Build Phases 136 | 137 | ```bash 138 | xcutility unbuilt --extensions .swift,.m --delete 139 | ``` 140 | 141 | #### Find Unbuilt Files Verbosely 142 | 143 | ```bash 144 | xcutility unbuilt --verbose 145 | ``` 146 | 147 | ### Find Unbuilt Files Combined Usage 148 | 149 | ```bash 150 | xcutility unbuilt --path ~/MySweetSweetApp/ --extension .swift,.m --verbose --delete 151 | ``` 152 | 153 | 154 | ## License 155 | 156 | xcutility is released under the [MIT license](LICENSE.md). -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing a New Version of xcutility 2 | ## 1. Create the Release Notes 3 | Document what’s changed, keeping an eye out for breaking changes that will affect the version number. 4 | 5 | Release notes typically follow this pattern: 6 | 7 | > **Fixed** 8 | > * Something was fixed (#pr-number). Thanks @pr-author! 9 | > 10 | > **Added** 11 | > * Something was added (#pr-number). Thanks @pr-author! 12 | > 13 | > **Improved** 14 | > * Something was improved (#pr-number). Thanks @pr-author! 15 | > 16 | > Thank you to {authors of PRs that improved code quality} for improvements to the code base! Thank you to {reviewers of PRs} for reviewing pull requests! 17 | 18 | Try to call out more important items earlier in the list. 19 | 20 | You can use the [compare](https://github.com/jeffctown/xcutility/compare) page on GitHub to see what’s changed since the previous release. Typically only the merge commits (those of the form _Merge pull request #2342 from branch-name_) are needed. Click the link to the PR to see what changed. 21 | 22 | ## 2. Update Version Number 23 | If the changes in this release are breaking, then increment the minor number (`0.26.0` to `0.27.0`). Otherwise increment the patch number (`0.26.0` to `0.26.1`). 24 | 25 | The version number needs to be set in the `Version` struct. 26 | 27 | ## 3. Create Draft Release 28 | Create a [new release](https://github.com/jeffctown/xcutility/releases/new) on GitHub. The tag version should always be 3 numbers (i.e. `0.26.0`, not `0.26`). Check _This is a pre-release_ for now. 29 | 30 | ## 4. Create Installer 31 | 32 | This step will require [Carthage](https://github.com/Carthage/Carthage) to be installed. 33 | 34 | Now that you’ve created the release, do a `git pull`: you need the tag locally when you create the installer. 35 | 36 | Run `make release` to run most of the release process and install the latest version. It’s probably a good idea to run `xcutility version` to make sure that you’ve picked up the changes. 37 | 38 | ## 5. Publish Release 39 | Edit the release you created. Add the `XCUtility.pkg` and `XCUtilityFramework.framework.zip` that were created, uncheck _This in a pre-release_, and publish! Congratulations, you’ve published the release! 👏 40 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Extensions/Path+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Path+Extension.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/11/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | 11 | public extension Path { 12 | var isXcodeProject: Bool { 13 | return self.absolute().string.hasSuffix(".xcodeproj") 14 | } 15 | 16 | func recursiveFilter(isIncluded: (Path) -> Bool) throws -> [Path] { 17 | return try self.recursiveChildren().filter(isIncluded) 18 | } 19 | 20 | func isValid(context: PipelineState) -> Bool { 21 | let isFile = self.isFile 22 | let isValidPath = self.isValid(config: context.config) 23 | let isValidFileExtension = self.isValidExtension(context: context) 24 | return isFile && isValidPath && isValidFileExtension 25 | } 26 | 27 | func isValid(config: Configuration) -> Bool { 28 | for excluded in config.excluded { 29 | if self.string.contains(excluded) { 30 | return false 31 | } 32 | } 33 | return true 34 | } 35 | 36 | func isValidExtension(context: PipelineState) -> Bool { 37 | let string = self.string 38 | var isValidExtension = context.config.extensions.isEmpty ? true : false 39 | for ext in context.config.extensions { 40 | if string.hasSuffix(ext) { 41 | isValidExtension = true 42 | } 43 | } 44 | return isValidExtension 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Extensions/String+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extension.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | 11 | public extension String { 12 | var path: Path { 13 | return Path(self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Log/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol LogAdapter { 11 | func log(_ item: Any) 12 | } 13 | 14 | struct Log { 15 | static let shared = Log() 16 | 17 | private init() { 18 | self.adapters.append(PrintLogAdapter()) 19 | } 20 | 21 | var adapters = [LogAdapter]() 22 | 23 | func header(_ title: String) { 24 | print(""" 25 | 26 | ************ \(title) ************ 27 | 28 | """) 29 | } 30 | 31 | func summary(_ title: String) { 32 | log(""" 33 | 34 | ****** \(title) ****** 35 | 36 | """) 37 | } 38 | 39 | func log(_ message: Any) { 40 | adapters.forEach { $0.log(message) } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Log/PrintLogAdapter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrintLogAdapter.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PrintLogAdapter: LogAdapter { 11 | func log(_ item: Any) { 12 | print(item) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol ConfigurationOptions { 11 | var config: String { get } 12 | var extensions: [String] { get } 13 | var verbose: Bool { get } 14 | var path: String { get } 15 | var delete: Bool { get } 16 | } 17 | 18 | public struct Configuration { 19 | public static let fileName = ".xcutility.yml" 20 | let excluded: [String] 21 | let extensions: [String] 22 | let verbose: Bool 23 | let path: String 24 | 25 | init(excluded: [String], extensions: [String], verbose: Bool, path: String) { 26 | self.excluded = excluded 27 | self.verbose = verbose 28 | self.path = path 29 | self.extensions = extensions 30 | } 31 | } 32 | 33 | extension Configuration { 34 | private enum Key: String { 35 | case excluded 36 | } 37 | 38 | public init(options: ConfigurationOptions) throws { 39 | try self.init(configPath: options.config, 40 | extensions: options.extensions, 41 | verbose: options.verbose, 42 | path: options.path) 43 | } 44 | 45 | init(configPath: String, extensions: [String], verbose: Bool, path: String) throws { 46 | if verbose { 47 | print("Loading configuration from '\(configPath)'") 48 | } 49 | do { 50 | let dict = try YamlParser.parse(configPath) 51 | if verbose { 52 | print("Loaded configuration successfully.") 53 | } 54 | self.init(dict: dict, extensions: extensions, verbose: verbose, path: path) 55 | return 56 | } catch YamlParser.YamlParserError.fileNotFound { 57 | if verbose { 58 | print("Loading configuration failed. Using default.") 59 | } 60 | self.init(excluded: [], 61 | extensions: extensions, 62 | verbose: verbose, 63 | path: path) 64 | return 65 | } 66 | } 67 | 68 | init(dict: [String: Any], extensions: [String], verbose: Bool, path: String) { 69 | let excluded = dict[Configuration.Key.excluded.rawValue] as? [String] ?? [] 70 | self.init(excluded: excluded, 71 | extensions: extensions, 72 | verbose: verbose, 73 | path: path) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/15/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | 11 | struct File: CustomStringConvertible { 12 | let path: Path 13 | private let queue: DispatchQueue 14 | private var projectReferences: Int 15 | 16 | init(filename: String, referenceCount: Int = 0) { 17 | self.init(path: Path(filename), referenceCount: referenceCount) 18 | } 19 | 20 | init(path: Path, referenceCount: Int = 0) { 21 | self.path = path 22 | self.projectReferences = referenceCount 23 | self.queue = DispatchQueue(label: path.string) 24 | } 25 | 26 | mutating func incrementReferenceCount() { 27 | queue.sync { self.projectReferences += 1 } 28 | } 29 | 30 | var references: Int { 31 | return self.projectReferences 32 | } 33 | 34 | var hasProjectReferences: Bool { 35 | var hasProjectRefs: Bool = false 36 | queue.sync { hasProjectRefs = self.projectReferences > 0 } 37 | return hasProjectRefs 38 | } 39 | 40 | var description: String { 41 | return "\(path.string) \(projectReferences)" 42 | } 43 | } 44 | 45 | extension File: Comparable { 46 | static func < (lhs: File, rhs: File) -> Bool { 47 | return lhs.path < rhs.path 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/Files.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Files.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/15/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | 11 | struct Files { 12 | private var files = [String: File]() 13 | 14 | init(array: [File] = []) { 15 | array.forEach { add($0) } 16 | } 17 | 18 | func file(for path: Path) -> File? { 19 | // these are lowercased for a reason. 20 | // the file system in MacOS is case insensitive, but case preserving. 21 | return files[path.string.lowercased()] 22 | } 23 | 24 | mutating func add(_ file: File) { 25 | files[file.path.string.lowercased()] = file 26 | } 27 | 28 | var all: [File] { 29 | return Array(files.values) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/Version.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Version.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/15/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct Version { 11 | public let value: String 12 | 13 | public static let current = Version(value: "0.2.0") 14 | } 15 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/XcodeFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeFile.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | import xcodeproj 11 | 12 | struct XcodeFile { 13 | let fileReference: PBXFileReference? 14 | let path: Path 15 | } 16 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Model/XcodeProject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeProject.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | import xcodeproj 11 | 12 | class XcodeProject: CustomStringConvertible { 13 | private let xcodeProj: XcodeProj 14 | private let path: Path 15 | private let directory: Path 16 | var files: [XcodeFile] 17 | var sourceBuildFiles: [String] 18 | var unbuiltFiles: [XcodeFile] 19 | 20 | init?(path: String) { 21 | do { 22 | self.path = Path(path) 23 | self.xcodeProj = try XcodeProj(path: self.path) 24 | self.sourceBuildFiles = [String]() 25 | self.unbuiltFiles = [XcodeFile]() 26 | self.files = [XcodeFile]() 27 | self.directory = Path(self.path.string.replacingOccurrences(of: self.path.lastComponent, with: "")) 28 | } catch { 29 | Log.shared.log("\(path) : \(error.localizedDescription)") 30 | return nil 31 | } 32 | } 33 | 34 | func loadFiles(context: PipelineState) { 35 | for xcodeFileReference in xcodeProj.pbxproj.fileReferences { 36 | do { 37 | if let path = try xcodeFileReference.fullPath(sourceRoot: directory), 38 | Path(path.string).isValid(context: context) { 39 | if context.config.verbose { 40 | Log.shared.log("Found Xcode File: \(path)") 41 | } 42 | 43 | files.append(XcodeFile(fileReference: xcodeFileReference, path: path)) 44 | } 45 | } catch { 46 | Log.shared.log(error.localizedDescription) 47 | } 48 | } 49 | } 50 | 51 | func loadSourceBuildFiles(context: PipelineState) { 52 | for sourceBuildPhase in xcodeProj.pbxproj.sourcesBuildPhases { 53 | for fileReference in sourceBuildPhase.files { 54 | do { 55 | if let file = fileReference.file, 56 | let fullPath = try file.fullPath(sourceRoot: directory) { 57 | sourceBuildFiles.append(fullPath.string) 58 | } 59 | } catch { 60 | Log.shared.log(error.localizedDescription) 61 | } 62 | } 63 | } 64 | } 65 | 66 | func remove(sourceBuildfile: PBXFileReference) { 67 | self.xcodeProj.pbxproj.delete(object: sourceBuildfile) 68 | } 69 | 70 | func save() throws { 71 | let path = Path(self.path.string + "/project.pbxproj") 72 | try self.xcodeProj.pbxproj.write(path: path, override: true) 73 | } 74 | 75 | // MARK: - Protocol CustomStringConvertible 76 | var description: String { 77 | return "\(self.path.string) \(self.files.count) files \(self.sourceBuildFiles.count) source build files" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Pipeline.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepPipeline.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol Step { 11 | mutating func run(context: PipelineState) throws 12 | } 13 | 14 | public struct Pipeline { 15 | let steps: [Step] 16 | let context: PipelineState 17 | 18 | public init(steps: [Step], context: PipelineState) { 19 | self.steps = steps 20 | self.context = context 21 | if context.config.verbose { 22 | Log.shared.log("Verbose Logging Enabled.") 23 | } 24 | } 25 | 26 | public mutating func run() throws { 27 | for var step in steps { 28 | try step.run(context: context) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/PipelineState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepPipelineContext.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public class PipelineState { 11 | var files = Files() 12 | var xcodeProjects = [XcodeProject]() 13 | var unreferencedFiles = Files() 14 | let config: Configuration 15 | 16 | public init(config: Configuration) { 17 | self.config = config 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/StepFactory/FindAllStepFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindAllStepFactory.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FindAllStepFactory: StepFactory { 11 | func steps(delete: Bool) -> [Step] { 12 | var steps: [Step] = [ 13 | FindFiles(), 14 | FindXcodeProjects(), 15 | FindXcodeSourceBuildFiles(), 16 | FindXcodeFiles(), 17 | FindXcodeReferences(), 18 | FindUnreferencedFiles(), 19 | FindUnbuiltFiles() 20 | ] 21 | 22 | if delete { 23 | steps.append(DeleteUnreferencedFiles()) 24 | steps.append(DeleteUnbuiltFiles()) 25 | } 26 | return steps 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/StepFactory/FindUnbuiltStepFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnbuiltStepFactory.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FindUnbuiltStepFactory: StepFactory { 11 | func steps(delete: Bool) -> [Step] { 12 | var steps: [Step] = [ 13 | FindXcodeProjects(), 14 | FindXcodeSourceBuildFiles(), 15 | FindXcodeFiles(), 16 | FindUnbuiltFiles() 17 | ] 18 | if delete { 19 | steps.append(DeleteUnbuiltFiles()) 20 | } 21 | 22 | return steps 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/StepFactory/FindUnreferencedStepFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnreferencedStepFactory.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FindUnreferencedStepFactory: StepFactory { 11 | func steps(delete: Bool) -> [Step] { 12 | var steps: [Step] = [ 13 | FindFiles(), 14 | FindXcodeProjects(), 15 | FindXcodeFiles(), 16 | FindXcodeReferences(), 17 | FindUnreferencedFiles() 18 | ] 19 | 20 | if delete { 21 | steps.append(DeleteUnreferencedFiles()) 22 | } 23 | 24 | return steps 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/StepFactory/StepFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepFactory.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/14/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol StepFactory { 11 | func steps(delete: Bool) -> [Step] 12 | } 13 | 14 | public struct AbstractStepFactory { 15 | public enum Command { 16 | case findAll, findUnbuilt, findUnreferenced 17 | } 18 | 19 | public static func stepFactory(for command: Command) -> StepFactory { 20 | switch command { 21 | case .findAll: 22 | return FindAllStepFactory() 23 | case .findUnbuilt: 24 | return FindUnbuiltStepFactory() 25 | case .findUnreferenced: 26 | return FindUnreferencedStepFactory() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/DeleteUnbuiltFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteUnbuiltFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/9/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct DeleteUnbuiltFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | var count = 0 15 | for xcodeProject in context.xcodeProjects { 16 | for unbuiltFile in xcodeProject.unbuiltFiles { 17 | Log.shared.log("Deleting Unbuilt File: \(unbuiltFile.path.string)") 18 | if let fileReference = unbuiltFile.fileReference { 19 | xcodeProject.remove(sourceBuildfile: fileReference) 20 | try unbuiltFile.path.delete() 21 | count += 1 22 | } 23 | try xcodeProject.save() 24 | } 25 | } 26 | 27 | Log.shared.summary("Deleted \(count) Unbuilt Files") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/DeleteUnreferencedFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteUnreferencedFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct DeleteUnreferencedFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | for unreferencedFile in context.unreferencedFiles.all { 15 | Log.shared.log("Deleting Unreferenced File: \(unreferencedFile.path.string)") 16 | try unreferencedFile.path.delete() 17 | } 18 | 19 | Log.shared.summary("Deleted \(context.unreferencedFiles.all.count) Unreferenced Files") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | let files = try context.config.path.path.recursiveFilter { $0.isValid(context: context) } 15 | for file in files { 16 | if context.config.verbose { 17 | Log.shared.log("Found File: \(file.string)") 18 | } 19 | context.files.add(File(path: file.absolute())) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindUnbuiltFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnbuiltFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindUnbuiltFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | Log.shared.header("Unbuilt Files") 15 | 16 | var count = 0 17 | for xcodeProject in context.xcodeProjects { 18 | for xcodeFile in xcodeProject.files { 19 | guard !xcodeFile.path.string.hasSuffix(".h") else { 20 | continue 21 | } 22 | 23 | if !xcodeProject.sourceBuildFiles.contains(xcodeFile.path.string) { 24 | if xcodeProject.unbuiltFiles.isEmpty { 25 | Log.shared.log(xcodeProject) 26 | } 27 | xcodeProject.unbuiltFiles.append(xcodeFile) 28 | Log.shared.log(" \(xcodeFile.path.string)") 29 | } 30 | } 31 | count += xcodeProject.unbuiltFiles.count 32 | } 33 | 34 | Log.shared.summary("Total \(count) Unbuilt Files") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindUnreferencedFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnreferencedFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindUnreferencedFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) { 14 | context.unreferencedFiles = Files(array: context.files.all.filter { !$0.hasProjectReferences }) 15 | Log.shared.header("Unreferenced Files") 16 | 17 | for unreferencedFile in context.unreferencedFiles.all.sorted() { 18 | Log.shared.log(unreferencedFile.path.string) 19 | } 20 | 21 | Log.shared.summary("Total \(context.unreferencedFiles.all.count) Unreferenced Files") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindXcodeFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindXcodeFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | context.xcodeProjects = context.xcodeProjects.map { 15 | $0.loadFiles(context: context) 16 | return $0 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindXcodeProjects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeProjects.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindXcodeProjects: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | let xcodeProjectPaths = try context.config.path.path.recursiveFilter { 15 | $0.isXcodeProject && $0.isValid(config: context.config) 16 | } 17 | for xcodeProjectPath in xcodeProjectPaths { 18 | if context.config.verbose { 19 | Log.shared.log("Found Xcode Project: \(xcodeProjectPath)") 20 | } 21 | 22 | guard let xcodeProject = XcodeProject(path: xcodeProjectPath.string) else { 23 | continue 24 | } 25 | 26 | context.xcodeProjects.append(xcodeProject) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindXcodeReferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeReferences.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindXcodeReferences: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | for xcodeProject in context.xcodeProjects { 15 | for xcodeFile in xcodeProject.files { 16 | if context.config.verbose { 17 | Log.shared.log(" Found Xcode File Reference: \(xcodeFile)") 18 | } 19 | 20 | if var file = context.files.file(for: xcodeFile.path) { 21 | file.incrementReferenceCount() 22 | context.files.add(file) 23 | if context.config.verbose { 24 | Log.shared.log(" Incremented Reference Count: \(file)") 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Pipeline/Steps/FindXcodeSourceBuildFiles.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GatherXcodeSourceBuildFiles.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct FindXcodeSourceBuildFiles: Step { 11 | public init() {} 12 | 13 | public func run(context: PipelineState) throws { 14 | for xcodeProject in context.xcodeProjects { 15 | xcodeProject.loadSourceBuildFiles(context: context) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/XCUtilityFramework/Yaml/YamlParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YamlParser.swift 3 | // XCUtilityFramework 4 | // 5 | // Created by Jeff Lett on 3/10/19. 6 | // 7 | 8 | import Foundation 9 | import Yams 10 | 11 | public struct YamlParser { 12 | enum YamlParserError: Error { 13 | case yamlParsing(String) 14 | case fileNotFound(String) 15 | } 16 | 17 | public static func parse(_ yamlPath: String) throws -> [String: Any] { 18 | guard !yamlPath.isEmpty && FileManager.default.fileExists(atPath: yamlPath) else { 19 | throw YamlParser.YamlParserError.fileNotFound(yamlPath) 20 | } 21 | 22 | do { 23 | let yaml = try String(contentsOfFile: yamlPath, encoding: .utf8) 24 | return try Yams.load(yaml: yaml, .default) as? [String: Any] ?? [:] 25 | } catch { 26 | throw YamlParserError.yamlParsing("\(error)") 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/xcutility/Commands/FindAllCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindAllCommand.swift 3 | // xcutility 4 | // 5 | // Created by Jeff Lett on 3/8/19. 6 | // 7 | 8 | import Commandant 9 | import Foundation 10 | import Result 11 | import XCUtilityFramework 12 | 13 | struct FindAllCommand: CommandProtocol, CommandProvider { 14 | // MARK: - CommandProvider 15 | 16 | let command: AbstractStepFactory.Command = .findAll 17 | 18 | func beginMessage(path: String) -> String { 19 | return "Finding files in: \(path)" 20 | } 21 | 22 | // MARK: - CommandProtocol 23 | 24 | let verb = "all" 25 | let function = "Find files that are not built or referenced in any Xcode projects" 26 | 27 | // MARK: - OptionsProtocol 28 | 29 | struct Options: OptionsProtocol, ConfigurationOptions { 30 | let path: String 31 | let extensions: [String] 32 | let verbose: Bool 33 | let delete: Bool 34 | let config: String 35 | 36 | static func create(_ path: String) -> ([String]) -> (Bool) -> (Bool) -> (String) -> Options { 37 | return { extensions in { verbose in { delete in { config in Options(path: path, 38 | extensions: extensions, 39 | verbose: verbose, 40 | delete: delete, 41 | config: config) } } } } 42 | } 43 | 44 | static func evaluate(_ mode: CommandMode) -> Result>> { 45 | return create 46 | <*> mode <| optionPath 47 | <*> mode <| optionExtensions 48 | <*> mode <| switchVerbose 49 | <*> mode <| switchDelete 50 | <*> mode <| optionConfig 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/xcutility/Commands/FindUnbuiltCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnbuilt.swift 3 | // xcutility 4 | // 5 | // Created by Jeff Lett on 3/8/19. 6 | // 7 | 8 | import Commandant 9 | import Foundation 10 | import Result 11 | import XCUtilityFramework 12 | 13 | struct FindUnbuiltCommand: CommandProtocol, CommandProvider { 14 | // MARK: - CommandProvider 15 | 16 | let command: AbstractStepFactory.Command = .findUnbuilt 17 | 18 | func beginMessage(path: String) -> String { 19 | return "Finding unbuilt files in: \(path)" 20 | } 21 | 22 | // MARK: - CommandProtocol 23 | 24 | let verb = "unbuilt" 25 | let function = "Find files that are referenced in Xcode but not built in any build phases" 26 | 27 | // MARK: - OptionsProtocol 28 | 29 | struct Options: OptionsProtocol, ConfigurationOptions { 30 | let path: String 31 | let extensions: [String] 32 | let verbose: Bool 33 | let delete: Bool 34 | let config: String 35 | 36 | static func create(_ path: String) -> ([String]) -> (Bool) -> (Bool) -> (String) -> Options { 37 | return { extensions in { verbose in { delete in { config in Options(path: path, 38 | extensions: extensions, 39 | verbose: verbose, 40 | delete: delete, 41 | config: config) } } } } 42 | } 43 | 44 | static func evaluate(_ mode: CommandMode) -> Result>> { 45 | return create 46 | <*> mode <| optionPath 47 | <*> mode <| optionExtensions 48 | <*> mode <| switchVerbose 49 | <*> mode <| switchDelete 50 | <*> mode <| optionConfig 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/xcutility/Commands/FindUnreferencedCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnreferencedCommand.swift 3 | // xcutility 4 | // 5 | // Created by Jeff Lett on 2/11/19. 6 | // 7 | 8 | import Commandant 9 | import Foundation 10 | import Result 11 | import XCUtilityFramework 12 | 13 | class FindUnreferencedCommand: CommandProtocol, CommandProvider { 14 | // MARK: - CommandProvider 15 | 16 | let command: AbstractStepFactory.Command = .findUnreferenced 17 | 18 | func beginMessage(path: String) -> String { 19 | return "Finding unreferenced files in: \(path)" 20 | } 21 | 22 | // MARK: - CommandProtocol 23 | 24 | let verb = "norefs" 25 | let function = "Find files that are not referenced in any Xcode projects" 26 | 27 | // MARK: - OptionsProtocol 28 | 29 | struct Options: OptionsProtocol, ConfigurationOptions { 30 | let path: String 31 | let extensions: [String] 32 | let verbose: Bool 33 | let delete: Bool 34 | let config: String 35 | 36 | static func create(_ path: String) -> ([String]) -> (Bool) -> (Bool) -> (String) -> Options { 37 | return { extensions in { verbose in { delete in { config in Options(path: path, 38 | extensions: extensions, 39 | verbose: verbose, 40 | delete: delete, 41 | config: config) } } } } 42 | } 43 | 44 | static func evaluate(_ mode: CommandMode) -> Result>> { 45 | return create 46 | <*> mode <| optionPath 47 | <*> mode <| optionExtensions 48 | <*> mode <| switchVerbose 49 | <*> mode <| switchDelete 50 | <*> mode <| optionConfig 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/xcutility/Commands/VersionCommand.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VersionCommand.swift 3 | // xcutility 4 | // 5 | // Created by Jeff Lett on 2/15/19. 6 | // 7 | 8 | import Commandant 9 | import Foundation 10 | import Result 11 | import XCUtilityFramework 12 | 13 | struct VersionCommand: CommandProtocol { 14 | // MARK: - CommandProtocol 15 | 16 | let verb = "version" 17 | let function = "Display the current version of xcutility" 18 | 19 | func run(_ options: NoOptions>) -> Result<(), CommandantError<()>> { 20 | print(Version.current.value) 21 | return .success(()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/xcutility/Extensions/CommandProtocol+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandProtocol+Extension.swift 3 | // xcutility 4 | // 5 | // Created by Jeff Lett on 3/8/19. 6 | // 7 | 8 | import Commandant 9 | import Foundation 10 | import Result 11 | import XCUtilityFramework 12 | 13 | protocol CommandProvider { 14 | var command: AbstractStepFactory.Command { get } 15 | 16 | func beginMessage(path: String) -> String 17 | } 18 | 19 | extension CommandProtocol where Self: CommandProvider { 20 | func run(_ options: T) -> Result<(), CommandantError<()>> { 21 | let beginMessage = self.beginMessage(path: options.path) 22 | print(beginMessage) 23 | let stepFactory = AbstractStepFactory.stepFactory(for: self.command) 24 | let steps = stepFactory.steps(delete: options.delete) 25 | do { 26 | let config = try Configuration(options: options) 27 | let context = PipelineState(config: config) 28 | var pipeline = Pipeline(steps: steps, context: context) 29 | try pipeline.run() 30 | return .success(()) 31 | } catch { 32 | return .failure(.usageError(description: error.localizedDescription)) 33 | } 34 | } 35 | } 36 | 37 | extension CommandProtocol { 38 | static var switchVerbose: Switch { 39 | return Switch(flag: "v", key: "verbose", usage: "enable verbose logging") 40 | } 41 | 42 | static var switchDelete: Switch { 43 | return Switch(flag: "d", key: "delete", usage: "delete files") 44 | } 45 | 46 | static var optionPath: Option { 47 | return Option(key: "path", defaultValue: FileManager.default.currentDirectoryPath, usage: "the path to search") 48 | } 49 | 50 | static var optionExtensions: Option<[String]> { 51 | return Option(key: "extensions", 52 | defaultValue: [], 53 | usage: "limit the search to files having specific file extensions") 54 | } 55 | 56 | static var optionConfig: Option { 57 | return Option(key: "config", 58 | defaultValue: Configuration.fileName, 59 | usage: "the path to XcodeTools configuration file") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/xcutility/main.swift: -------------------------------------------------------------------------------- 1 | import Commandant 2 | 3 | let registry = CommandRegistry>() 4 | 5 | registry.register(FindAllCommand()) 6 | registry.register(FindUnbuiltCommand()) 7 | registry.register(FindUnreferencedCommand()) 8 | registry.register(VersionCommand()) 9 | 10 | let helpCommand = HelpCommand(registry: registry) 11 | 12 | registry.register(helpCommand) 13 | 14 | registry.main(defaultVerb: helpCommand.verb) { error in 15 | print(error) 16 | } 17 | -------------------------------------------------------------------------------- /Tests/Fixtures/CaseSensitivity/TestApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1F24411E2217203900AA9906 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F24411D2217203900AA9906 /* AppDelegate.swift */; }; 11 | 1F2441202217203900AA9906 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F24411F2217203900AA9906 /* ViewController.swift */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 1F24411A2217203900AA9906 /* TestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | 1F24411D2217203900AA9906 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 17 | 1F24411F2217203900AA9906 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 18 | 1F2441292217203900AA9906 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFrameworksBuildPhase section */ 22 | 1F2441172217203900AA9906 /* Frameworks */ = { 23 | isa = PBXFrameworksBuildPhase; 24 | buildActionMask = 2147483647; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXFrameworksBuildPhase section */ 30 | 31 | /* Begin PBXGroup section */ 32 | 1F2441112217203800AA9906 = { 33 | isa = PBXGroup; 34 | children = ( 35 | 1F24411C2217203900AA9906 /* TestApp */, 36 | 1F24411B2217203900AA9906 /* Products */, 37 | ); 38 | sourceTree = ""; 39 | }; 40 | 1F24411B2217203900AA9906 /* Products */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | 1F24411A2217203900AA9906 /* TestApp.app */, 44 | ); 45 | name = Products; 46 | sourceTree = ""; 47 | }; 48 | 1F24411C2217203900AA9906 /* TestApp */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 1F24411D2217203900AA9906 /* AppDelegate.swift */, 52 | 1F24411F2217203900AA9906 /* ViewController.swift */, 53 | 1F2441292217203900AA9906 /* Info.plist */, 54 | ); 55 | path = TestApp; 56 | sourceTree = ""; 57 | }; 58 | /* End PBXGroup section */ 59 | 60 | /* Begin PBXNativeTarget section */ 61 | 1F2441192217203900AA9906 /* TestApp */ = { 62 | isa = PBXNativeTarget; 63 | buildConfigurationList = 1F24412C2217203900AA9906 /* Build configuration list for PBXNativeTarget "TestApp" */; 64 | buildPhases = ( 65 | 1F2441162217203900AA9906 /* Sources */, 66 | 1F2441172217203900AA9906 /* Frameworks */, 67 | 1F2441182217203900AA9906 /* Resources */, 68 | ); 69 | buildRules = ( 70 | ); 71 | dependencies = ( 72 | ); 73 | name = TestApp; 74 | productName = TestApp; 75 | productReference = 1F24411A2217203900AA9906 /* TestApp.app */; 76 | productType = "com.apple.product-type.application"; 77 | }; 78 | /* End PBXNativeTarget section */ 79 | 80 | /* Begin PBXProject section */ 81 | 1F2441122217203800AA9906 /* Project object */ = { 82 | isa = PBXProject; 83 | attributes = { 84 | LastSwiftUpdateCheck = 1020; 85 | LastUpgradeCheck = 1020; 86 | ORGANIZATIONNAME = "Jeff Lett"; 87 | TargetAttributes = { 88 | 1F2441192217203900AA9906 = { 89 | CreatedOnToolsVersion = 10.2; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 1F2441152217203800AA9906 /* Build configuration list for PBXProject "TestApp" */; 94 | compatibilityVersion = "Xcode 9.3"; 95 | developmentRegion = en; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | Base, 100 | ); 101 | mainGroup = 1F2441112217203800AA9906; 102 | productRefGroup = 1F24411B2217203900AA9906 /* Products */; 103 | projectDirPath = ""; 104 | projectRoot = ""; 105 | targets = ( 106 | 1F2441192217203900AA9906 /* TestApp */, 107 | ); 108 | }; 109 | /* End PBXProject section */ 110 | 111 | /* Begin PBXResourcesBuildPhase section */ 112 | 1F2441182217203900AA9906 /* Resources */ = { 113 | isa = PBXResourcesBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXResourcesBuildPhase section */ 120 | 121 | /* Begin PBXSourcesBuildPhase section */ 122 | 1F2441162217203900AA9906 /* Sources */ = { 123 | isa = PBXSourcesBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | 1F2441202217203900AA9906 /* ViewController.swift in Sources */, 127 | 1F24411E2217203900AA9906 /* AppDelegate.swift in Sources */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXSourcesBuildPhase section */ 132 | 133 | /* Begin XCBuildConfiguration section */ 134 | 1F24412A2217203900AA9906 /* Debug */ = { 135 | isa = XCBuildConfiguration; 136 | buildSettings = { 137 | ALWAYS_SEARCH_USER_PATHS = NO; 138 | CLANG_ANALYZER_NONNULL = YES; 139 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 140 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 141 | CLANG_CXX_LIBRARY = "libc++"; 142 | CLANG_ENABLE_MODULES = YES; 143 | CLANG_ENABLE_OBJC_ARC = YES; 144 | CLANG_ENABLE_OBJC_WEAK = YES; 145 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 146 | CLANG_WARN_BOOL_CONVERSION = YES; 147 | CLANG_WARN_COMMA = YES; 148 | CLANG_WARN_CONSTANT_CONVERSION = YES; 149 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 150 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 151 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 152 | CLANG_WARN_EMPTY_BODY = YES; 153 | CLANG_WARN_ENUM_CONVERSION = YES; 154 | CLANG_WARN_INFINITE_RECURSION = YES; 155 | CLANG_WARN_INT_CONVERSION = YES; 156 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 157 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 158 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 159 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 160 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 161 | CLANG_WARN_STRICT_PROTOTYPES = YES; 162 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 163 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 164 | CLANG_WARN_UNREACHABLE_CODE = YES; 165 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 166 | CODE_SIGN_IDENTITY = "iPhone Developer"; 167 | COPY_PHASE_STRIP = NO; 168 | DEBUG_INFORMATION_FORMAT = dwarf; 169 | ENABLE_STRICT_OBJC_MSGSEND = YES; 170 | ENABLE_TESTABILITY = YES; 171 | GCC_C_LANGUAGE_STANDARD = gnu11; 172 | GCC_DYNAMIC_NO_PIC = NO; 173 | GCC_NO_COMMON_BLOCKS = YES; 174 | GCC_OPTIMIZATION_LEVEL = 0; 175 | GCC_PREPROCESSOR_DEFINITIONS = ( 176 | "DEBUG=1", 177 | "$(inherited)", 178 | ); 179 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 180 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 181 | GCC_WARN_UNDECLARED_SELECTOR = YES; 182 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 183 | GCC_WARN_UNUSED_FUNCTION = YES; 184 | GCC_WARN_UNUSED_VARIABLE = YES; 185 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 186 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 187 | MTL_FAST_MATH = YES; 188 | ONLY_ACTIVE_ARCH = YES; 189 | SDKROOT = iphoneos; 190 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 191 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 192 | }; 193 | name = Debug; 194 | }; 195 | 1F24412B2217203900AA9906 /* Release */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | CLANG_ANALYZER_NONNULL = YES; 200 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 202 | CLANG_CXX_LIBRARY = "libc++"; 203 | CLANG_ENABLE_MODULES = YES; 204 | CLANG_ENABLE_OBJC_ARC = YES; 205 | CLANG_ENABLE_OBJC_WEAK = YES; 206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 207 | CLANG_WARN_BOOL_CONVERSION = YES; 208 | CLANG_WARN_COMMA = YES; 209 | CLANG_WARN_CONSTANT_CONVERSION = YES; 210 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 212 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 213 | CLANG_WARN_EMPTY_BODY = YES; 214 | CLANG_WARN_ENUM_CONVERSION = YES; 215 | CLANG_WARN_INFINITE_RECURSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 222 | CLANG_WARN_STRICT_PROTOTYPES = YES; 223 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | CODE_SIGN_IDENTITY = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 230 | ENABLE_NS_ASSERTIONS = NO; 231 | ENABLE_STRICT_OBJC_MSGSEND = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu11; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 235 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 236 | GCC_WARN_UNDECLARED_SELECTOR = YES; 237 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 238 | GCC_WARN_UNUSED_FUNCTION = YES; 239 | GCC_WARN_UNUSED_VARIABLE = YES; 240 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 241 | MTL_ENABLE_DEBUG_INFO = NO; 242 | MTL_FAST_MATH = YES; 243 | SDKROOT = iphoneos; 244 | SWIFT_COMPILATION_MODE = wholemodule; 245 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 246 | VALIDATE_PRODUCT = YES; 247 | }; 248 | name = Release; 249 | }; 250 | 1F24412D2217203900AA9906 /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 254 | CODE_SIGN_STYLE = Automatic; 255 | DEVELOPMENT_TEAM = NTX63W5EU6; 256 | INFOPLIST_FILE = TestApp/Info.plist; 257 | LD_RUNPATH_SEARCH_PATHS = ( 258 | "$(inherited)", 259 | "@executable_path/Frameworks", 260 | ); 261 | PRODUCT_BUNDLE_IDENTIFIER = com.jefflett.TestApp; 262 | PRODUCT_NAME = "$(TARGET_NAME)"; 263 | SWIFT_VERSION = 5.0; 264 | TARGETED_DEVICE_FAMILY = "1,2"; 265 | }; 266 | name = Debug; 267 | }; 268 | 1F24412E2217203900AA9906 /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 272 | CODE_SIGN_STYLE = Automatic; 273 | DEVELOPMENT_TEAM = NTX63W5EU6; 274 | INFOPLIST_FILE = TestApp/Info.plist; 275 | LD_RUNPATH_SEARCH_PATHS = ( 276 | "$(inherited)", 277 | "@executable_path/Frameworks", 278 | ); 279 | PRODUCT_BUNDLE_IDENTIFIER = com.jefflett.TestApp; 280 | PRODUCT_NAME = "$(TARGET_NAME)"; 281 | SWIFT_VERSION = 5.0; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | }; 284 | name = Release; 285 | }; 286 | /* End XCBuildConfiguration section */ 287 | 288 | /* Begin XCConfigurationList section */ 289 | 1F2441152217203800AA9906 /* Build configuration list for PBXProject "TestApp" */ = { 290 | isa = XCConfigurationList; 291 | buildConfigurations = ( 292 | 1F24412A2217203900AA9906 /* Debug */, 293 | 1F24412B2217203900AA9906 /* Release */, 294 | ); 295 | defaultConfigurationIsVisible = 0; 296 | defaultConfigurationName = Release; 297 | }; 298 | 1F24412C2217203900AA9906 /* Build configuration list for PBXNativeTarget "TestApp" */ = { 299 | isa = XCConfigurationList; 300 | buildConfigurations = ( 301 | 1F24412D2217203900AA9906 /* Debug */, 302 | 1F24412E2217203900AA9906 /* Release */, 303 | ); 304 | defaultConfigurationIsVisible = 0; 305 | defaultConfigurationName = Release; 306 | }; 307 | /* End XCConfigurationList section */ 308 | }; 309 | rootObject = 1F2441122217203800AA9906 /* Project object */; 310 | } 311 | -------------------------------------------------------------------------------- /Tests/Fixtures/CaseSensitivity/TestApp/APPDELEGATE.swift: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /Tests/Fixtures/CaseSensitivity/TestApp/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tests/Fixtures/CaseSensitivity/TestApp/viewcontroller.swift: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /Tests/Fixtures/Configs/empty.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffctown/xcutility/f236f89e1620dc2ac5837110445941630deae6e8/Tests/Fixtures/Configs/empty.yml -------------------------------------------------------------------------------- /Tests/Fixtures/Configs/invalid.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - 3 | 12897A 4 | xols_version: 0.2.0 5 | /.,1#@ 6 | -------------------------------------------------------------------------------- /Tests/Fixtures/Configs/simple.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Pods 3 | - .gitignore 4 | - Gemfile 5 | - Podfile 6 | - Carthage 7 | - .build 8 | - .DS_Store 9 | - .framework 10 | - .playground 11 | - .idea/ 12 | - .podspec 13 | - fastlane 14 | 15 | -------------------------------------------------------------------------------- /Tests/Fixtures/InvalidProject/InvalidProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffctown/xcutility/f236f89e1620dc2ac5837110445941630deae6e8/Tests/Fixtures/InvalidProject/InvalidProject.xcodeproj/project.pbxproj -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1F1AD888221592D700513B1C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1AD887221592D700513B1C /* AppDelegate.swift */; }; 11 | 1F1AD88A221592D700513B1C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1AD889221592D700513B1C /* ViewController.swift */; }; 12 | 1F1AD88D221592D700513B1C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F1AD88B221592D700513B1C /* Main.storyboard */; }; 13 | 1F1AD88F221592D800513B1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1F1AD88E221592D800513B1C /* Assets.xcassets */; }; 14 | 1F1AD892221592D800513B1C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1F1AD890221592D800513B1C /* LaunchScreen.storyboard */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 1F1AD884221592D700513B1C /* SimpleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 1F1AD887221592D700513B1C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 20 | 1F1AD889221592D700513B1C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 21 | 1F1AD88C221592D700513B1C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 22 | 1F1AD88E221592D800513B1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 1F1AD891221592D800513B1C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | 1F1AD893221592D800513B1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | 1F1AD881221592D700513B1C /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXFrameworksBuildPhase section */ 36 | 37 | /* Begin PBXGroup section */ 38 | 1F1AD87B221592D700513B1C = { 39 | isa = PBXGroup; 40 | children = ( 41 | 1F1AD886221592D700513B1C /* SimpleProject */, 42 | 1F1AD885221592D700513B1C /* Products */, 43 | ); 44 | sourceTree = ""; 45 | }; 46 | 1F1AD885221592D700513B1C /* Products */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | 1F1AD884221592D700513B1C /* SimpleProject.app */, 50 | ); 51 | name = Products; 52 | sourceTree = ""; 53 | }; 54 | 1F1AD886221592D700513B1C /* SimpleProject */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 1F1AD887221592D700513B1C /* AppDelegate.swift */, 58 | 1F1AD889221592D700513B1C /* ViewController.swift */, 59 | 1F1AD88B221592D700513B1C /* Main.storyboard */, 60 | 1F1AD88E221592D800513B1C /* Assets.xcassets */, 61 | 1F1AD890221592D800513B1C /* LaunchScreen.storyboard */, 62 | 1F1AD893221592D800513B1C /* Info.plist */, 63 | ); 64 | path = SimpleProject; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | 1F1AD883221592D700513B1C /* SimpleProject */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = 1F1AD896221592D800513B1C /* Build configuration list for PBXNativeTarget "SimpleProject" */; 73 | buildPhases = ( 74 | 1F1AD880221592D700513B1C /* Sources */, 75 | 1F1AD881221592D700513B1C /* Frameworks */, 76 | 1F1AD882221592D700513B1C /* Resources */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = SimpleProject; 83 | productName = SimpleProject; 84 | productReference = 1F1AD884221592D700513B1C /* SimpleProject.app */; 85 | productType = "com.apple.product-type.application"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | 1F1AD87C221592D700513B1C /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastSwiftUpdateCheck = 1020; 94 | LastUpgradeCheck = 1020; 95 | ORGANIZATIONNAME = "Jeff Lett"; 96 | TargetAttributes = { 97 | 1F1AD883221592D700513B1C = { 98 | CreatedOnToolsVersion = 10.2; 99 | }; 100 | }; 101 | }; 102 | buildConfigurationList = 1F1AD87F221592D700513B1C /* Build configuration list for PBXProject "SimpleProject" */; 103 | compatibilityVersion = "Xcode 9.3"; 104 | developmentRegion = en; 105 | hasScannedForEncodings = 0; 106 | knownRegions = ( 107 | en, 108 | Base, 109 | ); 110 | mainGroup = 1F1AD87B221592D700513B1C; 111 | productRefGroup = 1F1AD885221592D700513B1C /* Products */; 112 | projectDirPath = ""; 113 | projectRoot = ""; 114 | targets = ( 115 | 1F1AD883221592D700513B1C /* SimpleProject */, 116 | ); 117 | }; 118 | /* End PBXProject section */ 119 | 120 | /* Begin PBXResourcesBuildPhase section */ 121 | 1F1AD882221592D700513B1C /* Resources */ = { 122 | isa = PBXResourcesBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | 1F1AD892221592D800513B1C /* LaunchScreen.storyboard in Resources */, 126 | 1F1AD88F221592D800513B1C /* Assets.xcassets in Resources */, 127 | 1F1AD88D221592D700513B1C /* Main.storyboard in Resources */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXResourcesBuildPhase section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | 1F1AD880221592D700513B1C /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 1F1AD88A221592D700513B1C /* ViewController.swift in Sources */, 139 | 1F1AD888221592D700513B1C /* AppDelegate.swift in Sources */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | /* End PBXSourcesBuildPhase section */ 144 | 145 | /* Begin PBXVariantGroup section */ 146 | 1F1AD88B221592D700513B1C /* Main.storyboard */ = { 147 | isa = PBXVariantGroup; 148 | children = ( 149 | 1F1AD88C221592D700513B1C /* Base */, 150 | ); 151 | name = Main.storyboard; 152 | sourceTree = ""; 153 | }; 154 | 1F1AD890221592D800513B1C /* LaunchScreen.storyboard */ = { 155 | isa = PBXVariantGroup; 156 | children = ( 157 | 1F1AD891221592D800513B1C /* Base */, 158 | ); 159 | name = LaunchScreen.storyboard; 160 | sourceTree = ""; 161 | }; 162 | /* End PBXVariantGroup section */ 163 | 164 | /* Begin XCBuildConfiguration section */ 165 | 1F1AD894221592D800513B1C /* Debug */ = { 166 | isa = XCBuildConfiguration; 167 | buildSettings = { 168 | ALWAYS_SEARCH_USER_PATHS = NO; 169 | CLANG_ANALYZER_NONNULL = YES; 170 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 171 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 172 | CLANG_CXX_LIBRARY = "libc++"; 173 | CLANG_ENABLE_MODULES = YES; 174 | CLANG_ENABLE_OBJC_ARC = YES; 175 | CLANG_ENABLE_OBJC_WEAK = YES; 176 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 177 | CLANG_WARN_BOOL_CONVERSION = YES; 178 | CLANG_WARN_COMMA = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 181 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 182 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 183 | CLANG_WARN_EMPTY_BODY = YES; 184 | CLANG_WARN_ENUM_CONVERSION = YES; 185 | CLANG_WARN_INFINITE_RECURSION = YES; 186 | CLANG_WARN_INT_CONVERSION = YES; 187 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 188 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 189 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 192 | CLANG_WARN_STRICT_PROTOTYPES = YES; 193 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 194 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | CODE_SIGN_IDENTITY = "iPhone Developer"; 198 | COPY_PHASE_STRIP = NO; 199 | DEBUG_INFORMATION_FORMAT = dwarf; 200 | ENABLE_STRICT_OBJC_MSGSEND = YES; 201 | ENABLE_TESTABILITY = YES; 202 | GCC_C_LANGUAGE_STANDARD = gnu11; 203 | GCC_DYNAMIC_NO_PIC = NO; 204 | GCC_NO_COMMON_BLOCKS = YES; 205 | GCC_OPTIMIZATION_LEVEL = 0; 206 | GCC_PREPROCESSOR_DEFINITIONS = ( 207 | "DEBUG=1", 208 | "$(inherited)", 209 | ); 210 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 211 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 212 | GCC_WARN_UNDECLARED_SELECTOR = YES; 213 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 214 | GCC_WARN_UNUSED_FUNCTION = YES; 215 | GCC_WARN_UNUSED_VARIABLE = YES; 216 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 217 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 218 | MTL_FAST_MATH = YES; 219 | ONLY_ACTIVE_ARCH = YES; 220 | SDKROOT = iphoneos; 221 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 222 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 223 | }; 224 | name = Debug; 225 | }; 226 | 1F1AD895221592D800513B1C /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_ENABLE_OBJC_WEAK = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | CODE_SIGN_IDENTITY = "iPhone Developer"; 259 | COPY_PHASE_STRIP = NO; 260 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 261 | ENABLE_NS_ASSERTIONS = NO; 262 | ENABLE_STRICT_OBJC_MSGSEND = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu11; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 266 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 267 | GCC_WARN_UNDECLARED_SELECTOR = YES; 268 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 269 | GCC_WARN_UNUSED_FUNCTION = YES; 270 | GCC_WARN_UNUSED_VARIABLE = YES; 271 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 272 | MTL_ENABLE_DEBUG_INFO = NO; 273 | MTL_FAST_MATH = YES; 274 | SDKROOT = iphoneos; 275 | SWIFT_COMPILATION_MODE = wholemodule; 276 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 277 | VALIDATE_PRODUCT = YES; 278 | }; 279 | name = Release; 280 | }; 281 | 1F1AD897221592D800513B1C /* Debug */ = { 282 | isa = XCBuildConfiguration; 283 | buildSettings = { 284 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 285 | CODE_SIGN_STYLE = Automatic; 286 | DEVELOPMENT_TEAM = NTX63W5EU6; 287 | INFOPLIST_FILE = SimpleProject/Info.plist; 288 | LD_RUNPATH_SEARCH_PATHS = ( 289 | "$(inherited)", 290 | "@executable_path/Frameworks", 291 | ); 292 | PRODUCT_BUNDLE_IDENTIFIER = com.jefflett.SimpleProject; 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | SWIFT_VERSION = 5.0; 295 | TARGETED_DEVICE_FAMILY = "1,2"; 296 | }; 297 | name = Debug; 298 | }; 299 | 1F1AD898221592D800513B1C /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | CODE_SIGN_STYLE = Automatic; 304 | DEVELOPMENT_TEAM = NTX63W5EU6; 305 | INFOPLIST_FILE = SimpleProject/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = ( 307 | "$(inherited)", 308 | "@executable_path/Frameworks", 309 | ); 310 | PRODUCT_BUNDLE_IDENTIFIER = com.jefflett.SimpleProject; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_VERSION = 5.0; 313 | TARGETED_DEVICE_FAMILY = "1,2"; 314 | }; 315 | name = Release; 316 | }; 317 | /* End XCBuildConfiguration section */ 318 | 319 | /* Begin XCConfigurationList section */ 320 | 1F1AD87F221592D700513B1C /* Build configuration list for PBXProject "SimpleProject" */ = { 321 | isa = XCConfigurationList; 322 | buildConfigurations = ( 323 | 1F1AD894221592D800513B1C /* Debug */, 324 | 1F1AD895221592D800513B1C /* Release */, 325 | ); 326 | defaultConfigurationIsVisible = 0; 327 | defaultConfigurationName = Release; 328 | }; 329 | 1F1AD896221592D800513B1C /* Build configuration list for PBXNativeTarget "SimpleProject" */ = { 330 | isa = XCConfigurationList; 331 | buildConfigurations = ( 332 | 1F1AD897221592D800513B1C /* Debug */, 333 | 1F1AD898221592D800513B1C /* Release */, 334 | ); 335 | defaultConfigurationIsVisible = 0; 336 | defaultConfigurationName = Release; 337 | }; 338 | /* End XCConfigurationList section */ 339 | }; 340 | rootObject = 1F1AD87C221592D700513B1C /* Project object */; 341 | } 342 | -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject/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/Fixtures/SimpleProject/SimpleProject/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/Fixtures/SimpleProject/SimpleProject/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Tests/Fixtures/SimpleProject/SimpleProject/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Extensions/Path+ExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable:this file_name 2 | // PathExtensionTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import PathKit 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | final class PathExtensionsTests: XCTestCase { 13 | func testThatPathKnowsWhenItIsAnXcodeProject() { 14 | let xcodeprojPath = Path("/Users/user/repo/project.xcodeproj") 15 | XCTAssert(xcodeprojPath.isXcodeProject) 16 | 17 | let relativeXcodeprojPath = Path("./Sweetapp.xcodeproj") 18 | XCTAssert(relativeXcodeprojPath.isXcodeProject) 19 | 20 | let insideXcodeprojPath = Path("/Users/user/repo/project.xcodeproj/project.pbxproj") 21 | XCTAssertFalse(insideXcodeprojPath.isXcodeProject) 22 | } 23 | 24 | func testThatRecursivelyFilterFilters() { 25 | let filtered = try? Path(FileManager.default.currentDirectoryPath).recursiveFilter { _ -> Bool in 26 | false 27 | } 28 | XCTAssert(filtered != nil) 29 | XCTAssert(filtered!.isEmpty) 30 | } 31 | 32 | func testThatInvalidPathsAreInvalid() { 33 | let config = Configuration(excluded: [".xcodeproj", 34 | ".xcworkspace", 35 | "png", 36 | ".podspec", 37 | ".gitignore", 38 | ".DS_Store", 39 | "fastlane", 40 | ".git", 41 | "Carthage", 42 | ".framework", 43 | "Podfile", 44 | "Gemfile", 45 | ".playground", 46 | ".idea"], 47 | extensions: [], 48 | verbose: false, 49 | path: "") 50 | let context = PipelineState(config: config) 51 | XCTAssertFalse(Path("/Users/user/repo/project.xcodeproj").isValid(context: context)) 52 | XCTAssertFalse(Path("/Users/user/repo/project.xcworkspace").isValid(context: context)) 53 | XCTAssertFalse(Path("/Users/user/repo/Images.xcassets/image.png").isValid(context: context)) 54 | XCTAssertFalse(Path("/Users/user/repo/BadassPod.podspec").isValid(context: context)) 55 | XCTAssertFalse(Path("/Users/user/repo/.gitignore").isValid(context: context)) 56 | XCTAssertFalse(Path("/Users/user/repo/.DS_Store").isValid(context: context)) 57 | XCTAssertFalse(Path("/Users/user/repo/fastlane").isValid(context: context)) 58 | XCTAssertFalse(Path("/Users/user/repo/.git").isValid(context: context)) 59 | XCTAssertFalse(Path("/Users/user/repo/Carthage").isValid(context: context)) 60 | XCTAssertFalse(Path("/Users/user/repo/AFNetworking.framework").isValid(context: context)) 61 | XCTAssertFalse(Path("/Users/user/repo/Podfile").isValid(context: context)) 62 | XCTAssertFalse(Path("/Users/user/repo/Podfile.lock").isValid(context: context)) 63 | XCTAssertFalse(Path("/Users/user/repo/Gemfile").isValid(context: context)) 64 | XCTAssertFalse(Path("/Users/user/repo/thing.playground").isValid(context: context)) 65 | XCTAssertFalse(Path("/Users/user/repo/.idea/").isValid(context: context)) 66 | } 67 | 68 | func testThatValidExtensionIsWorkingWithNoOption() { 69 | let config = Configuration(excluded: [], 70 | extensions: [], 71 | verbose: false, 72 | path: "") 73 | let context = PipelineState(config: config) 74 | XCTAssert(Path("/Users/user/repo/File.swift").isValidExtension(context: context)) 75 | XCTAssert(Path("/Users/user/repo/File.h").isValidExtension(context: context)) 76 | XCTAssert(Path("/Users/user/repo/File.m").isValidExtension(context: context)) 77 | } 78 | 79 | func testThatValidExtensionsIsWorkingWithExtensionsOption() { 80 | let config = Configuration(excluded: [], 81 | extensions: [".h", ".m", ".swift"], 82 | verbose: false, 83 | path: "") 84 | let context = PipelineState(config: config) 85 | XCTAssert(Path("/Users/user/repo/File.swift").isValidExtension(context: context)) 86 | XCTAssert(Path("/Users/user/repo/File.h").isValidExtension(context: context)) 87 | XCTAssert(Path("/Users/user/repo/File.m").isValidExtension(context: context)) 88 | XCTAssertFalse(Path("/Users/user/repo/File").isValidExtension(context: context)) 89 | XCTAssertFalse(Path("/Users/user/repo/File.m.swif").isValidExtension(context: context)) 90 | XCTAssertFalse(Path("/Users/user/repo/File.md").isValidExtension(context: context)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Extensions/String+ExtensionTests.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable:this file_name 2 | // StringExtensionTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/12/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | import XCUtilityFramework 11 | 12 | final class StringExtensionsTests: XCTestCase { 13 | func testPathReturnsAPath() { 14 | let valid = "/Users/hefe/boom.xcodeproj" 15 | let path = valid.path 16 | XCTAssertNotNil(path) 17 | XCTAssertEqual(path.string, valid) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Fixtures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Fixtures.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/4/19. 6 | // 7 | 8 | import Foundation 9 | 10 | class Fixtures { 11 | static let fixturesFolder = "/" + #file.split(separator: "/").dropLast(2).joined(separator: "/") + "/Fixtures/" 12 | static let fixtureSimpleProjectPath = fixturesFolder.appending("SimpleProject/") 13 | static let fixtureSimpleProject = fixtureSimpleProjectPath + "SimpleProject.xcodeproj" 14 | static let fixtureCaseSensitivityPath = fixturesFolder.appending("CaseSensitivity/") 15 | static let fixtureInvalidProjectPath = fixturesFolder.appending("InvalidProject/") 16 | static let fixtureConfigPath = fixturesFolder.appending("Configs/") 17 | static let fixtureConfigSimplePath = fixtureConfigPath.appending("simple.yml") 18 | static let fixtureConfigEmptyPath = fixtureConfigPath.appending("empty.yml") 19 | static let fixtureConfigInvalidPath = fixtureConfigPath.appending("invalid.yml") 20 | } 21 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Model/ConfigurationTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurationTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/11/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | // swiftlint:disable force_try 13 | class ConfigurationTests: XCTestCase { 14 | func testSimpleConfigurationFileIsLoaded() { 15 | let configPath = Fixtures.fixtureConfigSimplePath 16 | let config = try! Configuration(configPath: configPath, extensions: [], verbose: true, path: "") 17 | XCTAssertEqual(config.excluded.count, 12) 18 | } 19 | 20 | func testConfigurationIsDefaultWhenGivenBadPath() { 21 | do { 22 | let config = try Configuration(configPath: "ksj", extensions: [], verbose: false, path: "") 23 | XCTAssertEqual(config.excluded, []) 24 | XCTAssertEqual(config.path, "") 25 | XCTAssertFalse(config.verbose) 26 | } catch { 27 | XCTFail("This should return a default configuration, not throw.") 28 | } 29 | } 30 | 31 | func testConfigurationLoadsAnEmptyFile() { 32 | let configPath = Fixtures.fixtureConfigEmptyPath 33 | let config = try! Configuration(configPath: configPath, extensions: [], verbose: true, path: "") 34 | XCTAssertNotNil(config) 35 | } 36 | 37 | func testConfigurationLoadsAnInvalidFile() { 38 | do { 39 | let configPath = Fixtures.fixtureConfigInvalidPath 40 | _ = try Configuration(configPath: configPath, extensions: [], verbose: false, path: "") 41 | XCTFail("Exception Expected") 42 | } catch { 43 | XCTAssertNotNil(error) 44 | } 45 | } 46 | 47 | struct MockOptions: ConfigurationOptions { 48 | var extensions: [String] 49 | var verbose: Bool 50 | var path: String 51 | var config: String 52 | var delete: Bool 53 | } 54 | 55 | func testConfiguration() { 56 | let expectedExtensions = [".swift"] 57 | let expectedVerbose = true 58 | let expectedPath = "Path" 59 | let options = MockOptions(extensions: expectedExtensions, 60 | verbose: expectedVerbose, 61 | path: expectedPath, 62 | config: "Config", 63 | delete: false) 64 | let config = try! Configuration(options: options) 65 | XCTAssertEqual(config.extensions, expectedExtensions) 66 | XCTAssertEqual(config.verbose, expectedVerbose) 67 | XCTAssertEqual(config.path, expectedPath) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Model/FileTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileTests.swift 3 | // AEXML 4 | // 5 | // Created by Jeff Lett on 2/15/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | import XCTest 11 | @testable import XCUtilityFramework 12 | 13 | class FileTests: XCTestCase { 14 | func testComparable() { 15 | let path1 = Path("a") 16 | let path2 = Path("b") 17 | let file1 = File(path: path1) 18 | let file2 = File(path: path2) 19 | XCTAssert(file1 < file2) 20 | } 21 | 22 | func testDescription() { 23 | let path = Path("imapath") 24 | var file = File(path: path) 25 | XCTAssertEqual(file.description, "imapath 0") 26 | file.incrementReferenceCount() 27 | XCTAssertEqual(file.description, "imapath 1") 28 | } 29 | 30 | func testConcurrencyOfFileReferences() { 31 | let num = 10_000 32 | let path = Path("imapath") 33 | var file = File(path: path) 34 | DispatchQueue.concurrentPerform(iterations: num) { _ in 35 | file.incrementReferenceCount() 36 | } 37 | XCTAssertEqual(file.references, num) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Model/XcodeProjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeProjectTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/11/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | class XcodeProjectTests: XCTestCase { 13 | } 14 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/PipelineTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/13/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable implicitly_unwrapped_optional 12 | class PipelineTests: XCTestCase { 13 | var step: MockStep! 14 | var config: Configuration! 15 | var context: PipelineState! 16 | 17 | class MockStep: Step { 18 | var numRuns = 0 19 | 20 | func run(context: PipelineState) throws { 21 | numRuns += 1 22 | } 23 | } 24 | 25 | enum MockError: Error { 26 | case mockError 27 | } 28 | 29 | class MockThrowingStep: Step { 30 | func run(context: PipelineState) throws { 31 | throw MockError.mockError 32 | } 33 | } 34 | 35 | override func setUp() { 36 | super.setUp() 37 | self.step = MockStep() 38 | self.config = Configuration(excluded: [], extensions: [], verbose: false, path: "") 39 | self.context = PipelineState(config: config) 40 | } 41 | 42 | func testRunIsCalledOnStep() { 43 | var pipeline = Pipeline(steps: [step], context: context) 44 | do { 45 | try pipeline.run() 46 | XCTAssertEqual(step.numRuns, 1) 47 | } catch { 48 | XCTFail("Should not throw.") 49 | } 50 | } 51 | 52 | func testRunIsCalledMultipleTimes() { 53 | var pipeline = Pipeline(steps: [step, step, step], context: context) 54 | do { 55 | try pipeline.run() 56 | XCTAssertEqual(step.numRuns, 3) 57 | } catch { 58 | XCTFail("Should not throw.") 59 | } 60 | } 61 | 62 | func testFailureIsReturnedOnAnException() { 63 | let step = MockThrowingStep() 64 | var pipeline = Pipeline(steps: [step], context: context) 65 | do { 66 | try pipeline.run() 67 | XCTFail("Should not succeed.") 68 | } catch { 69 | XCTAssert(true) 70 | } 71 | } 72 | 73 | func testCaseSensitivityDifferenceBetweenXcodeAndFileSystem() { 74 | let fileFolder = #file.split(separator: "/").dropLast(3).joined(separator: "/") 75 | let path = "/" + fileFolder.appending("/Fixtures/CaseSensitivity/") 76 | let steps: [Step] = [FindFiles(), 77 | FindXcodeProjects(), 78 | FindXcodeFiles(), 79 | FindXcodeReferences(), 80 | FindUnreferencedFiles()] 81 | let config = Configuration(excluded: [], 82 | extensions: [".swift"], 83 | verbose: true, 84 | path: path) 85 | let context = PipelineState(config: config) 86 | var pipeline = Pipeline(steps: steps, context: context) 87 | do { 88 | try pipeline.run() 89 | XCTAssert(context.unreferencedFiles.all.isEmpty) 90 | } catch { 91 | XCTFail("Should not throw.") 92 | } 93 | } 94 | 95 | func testPerformanceOfCaseSensitivityFixture() { 96 | let fileFolder = #file.split(separator: "/").dropLast(3).joined(separator: "/") 97 | let path = "/" + fileFolder.appending("/Fixtures/CaseSensitivity/") 98 | let steps: [Step] = [FindFiles(), 99 | FindXcodeProjects(), 100 | FindXcodeFiles(), 101 | FindXcodeReferences(), 102 | FindUnreferencedFiles()] 103 | let config = Configuration(excluded: [], 104 | extensions: [".swift"], 105 | verbose: false, 106 | path: path) 107 | let context = PipelineState(config: config) 108 | var pipeline = Pipeline(steps: steps, context: context) 109 | self.measure { 110 | do { 111 | try pipeline.run() 112 | XCTAssert(context.unreferencedFiles.all.isEmpty) 113 | } catch { 114 | XCTFail("Should not throw.") 115 | } 116 | } 117 | } 118 | 119 | func testPerformanceOfSimpleFixture() { 120 | let fileFolder = #file.split(separator: "/").dropLast(3).joined(separator: "/") 121 | let path = "/" + fileFolder.appending("/Fixtures/SimpleProject/") 122 | let steps: [Step] = [FindFiles(), 123 | FindXcodeProjects(), 124 | FindXcodeFiles(), 125 | FindXcodeReferences(), 126 | FindUnreferencedFiles()] 127 | let config = Configuration(excluded: [], 128 | extensions: [".swift"], 129 | verbose: false, 130 | path: path) 131 | let context = PipelineState(config: config) 132 | var pipeline = Pipeline(steps: steps, context: context) 133 | self.measure { 134 | do { 135 | try pipeline.run() 136 | XCTAssert(context.unreferencedFiles.all.isEmpty) 137 | } catch { 138 | XCTFail("Should not throw.") 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/StepFactory/FindAllStepFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindAllStepFactoryTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable implicitly_unwrapped_optional 12 | class FindAllStepFactoryTests: XCTestCase { 13 | var testFactory: FindAllStepFactory! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | self.testFactory = FindAllStepFactory() 18 | } 19 | 20 | func testDeleteSteps() { 21 | let steps = testFactory.steps(delete: true) 22 | XCTAssertEqual(steps.count, 9) 23 | } 24 | 25 | func testNonDeleteSteps() { 26 | let steps = testFactory.steps(delete: false) 27 | XCTAssertEqual(steps.count, 7) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/StepFactory/FindUnbuiltStepFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnbuiltStepFactoryTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable implicitly_unwrapped_optional 12 | class FindUnbuiltStepFactoryTests: XCTestCase { 13 | var testFactory: FindUnbuiltStepFactory! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | self.testFactory = FindUnbuiltStepFactory() 18 | } 19 | 20 | func testDeleteSteps() { 21 | let steps = testFactory.steps(delete: true) 22 | XCTAssertEqual(steps.count, 5) 23 | } 24 | 25 | func testNonDeleteSteps() { 26 | let steps = testFactory.steps(delete: false) 27 | XCTAssertEqual(steps.count, 4) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/StepFactory/FindUnreferencedStepFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnreferencedStepFactoryTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable implicitly_unwrapped_optional 12 | class FindUnreferencedStepFactoryTests: XCTestCase { 13 | var testFactory: FindUnreferencedStepFactory! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | self.testFactory = FindUnreferencedStepFactory() 18 | } 19 | 20 | func testDeleteSteps() { 21 | let steps = testFactory.steps(delete: true) 22 | XCTAssertEqual(steps.count, 6) 23 | } 24 | 25 | func testNonDeleteSteps() { 26 | let steps = testFactory.steps(delete: false) 27 | XCTAssertEqual(steps.count, 5) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/StepFactory/StepFactoryTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StepFactoryTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/17/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | class StepFactoryTests: XCTestCase { 12 | func testFindAllFactoryIsReturnedWhenExpected() { 13 | let factory = AbstractStepFactory.stepFactory(for: .findAll) 14 | XCTAssertNotNil(factory as? FindAllStepFactory) 15 | } 16 | 17 | func testFindUnbuiltFactoryIsReturnedWhenExpected() { 18 | let factory = AbstractStepFactory.stepFactory(for: .findUnbuilt) 19 | XCTAssertNotNil(factory as? FindUnbuiltStepFactory) 20 | } 21 | 22 | func testFindUnreferencedFactoryIsReturnedWhenExpected() { 23 | let factory = AbstractStepFactory.stepFactory(for: .findUnreferenced) 24 | XCTAssertNotNil(factory as? FindUnreferencedStepFactory) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/DeleteUnbuiltFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteUnbuiltFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/11/19. 6 | // 7 | 8 | import Foundation 9 | import PathKit 10 | import xcodeproj 11 | import XCTest 12 | @testable import XCUtilityFramework 13 | 14 | // swiftlint:disable implicitly_unwrapped_optional force_try 15 | class DeleteUnbuiltFilesTests: XCTestCase { 16 | let folderPath = NSTemporaryDirectory() + "DeleteUnbuiltFileTests" 17 | var testPath: String! 18 | var swiftFilePath: String! 19 | var objCHeaderPath: String! 20 | var objCImplPath: String! 21 | var otherFilePath: String! 22 | var xcodePathString: String! 23 | var xcodePath: Path! 24 | var testXcodeProject: XcodeProject! 25 | var testXcodeProjectPath: String! 26 | 27 | override func setUp() { 28 | super.setUp() 29 | testPath = folderPath + "/SimpleProject" 30 | testXcodeProjectPath = testPath + "/SimpleProject.xcodeproj" 31 | try? FileManager.default.removeItem(atPath: folderPath) 32 | try! FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true) 33 | try! FileManager.default.copyItem(atPath: Fixtures.fixtureSimpleProjectPath, toPath: testPath) 34 | testXcodeProject = XcodeProject(path: testXcodeProjectPath) 35 | } 36 | 37 | func testDeletingUnbuiltFiles() { 38 | let testStep = DeleteUnbuiltFiles() 39 | let config = Configuration(excluded: [], extensions: [], verbose: true, path: testPath) 40 | let context = PipelineState(config: config) 41 | context.xcodeProjects.append(testXcodeProject) 42 | testXcodeProject.loadFiles(context: context) 43 | XCTAssertEqual(testXcodeProject.files.count, 3) 44 | if let firstFile = testXcodeProject.files.first { 45 | XCTAssertNotNil(firstFile) 46 | testXcodeProject.unbuiltFiles.append(firstFile) 47 | } else { 48 | XCTFail("Files Expected.") 49 | } 50 | try! testStep.run(context: context) 51 | try! testXcodeProject.save() 52 | testXcodeProject = XcodeProject(path: testXcodeProjectPath) 53 | testXcodeProject.loadFiles(context: context) 54 | XCTAssertEqual(testXcodeProject.files.count, 2) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/DeleteUnreferencedFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteUnreferencedFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/13/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable implicitly_unwrapped_optional force_try 12 | class DeleteUnreferencedFilesTests: XCTestCase { 13 | let folderPath = NSTemporaryDirectory() + "DeleteFileTests" 14 | var swiftFilePath: String! 15 | var objCHeaderPath: String! 16 | var objCImplPath: String! 17 | var otherFilePath: String! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | let folderURL = URL(fileURLWithPath: folderPath) 22 | swiftFilePath = folderPath + "/File.swift" 23 | objCHeaderPath = folderPath + "/AnotherFile.h" 24 | objCImplPath = folderPath + "/AnotherFile.m" 25 | otherFilePath = folderPath + "/AnotherFile.txt" 26 | 27 | try? FileManager.default.removeItem(at: folderURL) 28 | try! FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) 29 | FileManager.default.createFile(atPath: swiftFilePath, contents: nil) 30 | FileManager.default.createFile(atPath: objCHeaderPath, contents: nil) 31 | FileManager.default.createFile(atPath: objCImplPath, contents: nil) 32 | FileManager.default.createFile(atPath: otherFilePath, contents: nil) 33 | } 34 | 35 | func testOnlyUnreferencedFilesAreDeleted() { 36 | let config = Configuration(excluded: [], 37 | extensions: [], 38 | verbose: false, 39 | path: folderPath) 40 | let context = PipelineState(config: config) 41 | context.unreferencedFiles.add(File(filename: swiftFilePath)) 42 | context.unreferencedFiles.add(File(filename: objCHeaderPath)) 43 | context.unreferencedFiles.add(File(filename: objCImplPath)) 44 | 45 | try! DeleteUnreferencedFiles().run(context: context) 46 | XCTAssertFalse(FileManager.default.fileExists(atPath: swiftFilePath)) 47 | XCTAssertFalse(FileManager.default.fileExists(atPath: objCHeaderPath)) 48 | XCTAssertFalse(FileManager.default.fileExists(atPath: objCImplPath)) 49 | XCTAssertTrue(FileManager.default.fileExists(atPath: otherFilePath)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/13/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable force_try implicitly_unwrapped_optional 12 | final class FindFilesTests: XCTestCase { 13 | let folderPath = NSTemporaryDirectory() + "FileGatherTests" 14 | var config: Configuration! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | let folderURL = URL(fileURLWithPath: folderPath) 19 | self.config = Configuration(excluded: [], 20 | extensions: [], 21 | verbose: true, 22 | path: folderPath) 23 | try? FileManager.default.removeItem(at: folderURL) 24 | try! FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true) 25 | FileManager.default.createFile(atPath: folderPath + "/File.swift", contents: nil) 26 | FileManager.default.createFile(atPath: folderPath + "/AnotherFile.h", contents: nil) 27 | FileManager.default.createFile(atPath: folderPath + "/AnotherFile.m", contents: nil) 28 | FileManager.default.createFile(atPath: folderPath + "/AnotherFile.txt", contents: nil) 29 | } 30 | 31 | func testExceptionIsThrownWithBadPath() { 32 | let config = Configuration(excluded: [], 33 | extensions: [], 34 | verbose: false, 35 | path: "khadsdhh") 36 | let context = PipelineState(config: config) 37 | do { 38 | try FindFiles().run(context: context) 39 | XCTFail("Exception should be thrown for a bad path.") 40 | } catch { 41 | XCTAssertNotNil(error) 42 | } 43 | } 44 | 45 | func testAllFilesAreGatheredWithNoExtensions() { 46 | let context = PipelineState(config: config) 47 | do { 48 | try FindFiles().run(context: context) 49 | XCTAssertEqual(context.files.all.count, 4) 50 | } catch { 51 | XCTFail("Should not throw.") 52 | } 53 | } 54 | 55 | func testAllFilesAreGatheredWhenVerbose() { 56 | let context = PipelineState(config: config) 57 | do { 58 | try FindFiles().run(context: context) 59 | XCTAssertEqual(context.files.all.count, 4) 60 | for file in context.files.all { 61 | XCTAssert(!file.hasProjectReferences) 62 | } 63 | } catch { 64 | XCTFail("Should not throw.") 65 | } 66 | } 67 | 68 | func testAllFilesAreGatheredWhenOneExtensionIsUsed() { 69 | self.config = Configuration(excluded: [], 70 | extensions: [".swift"], 71 | verbose: true, 72 | path: folderPath) 73 | let context = PipelineState(config: config) 74 | do { 75 | try FindFiles().run(context: context) 76 | XCTAssertEqual(context.files.all.count, 1) 77 | for file in context.files.all { 78 | XCTAssert(!file.hasProjectReferences) 79 | } 80 | } catch { 81 | XCTFail("Should not throw.") 82 | } 83 | } 84 | 85 | func testAllFilesAreGatheredWhenMultipleExtensionsAreUsed() { 86 | self.config = Configuration(excluded: [], 87 | extensions: [".swift", ".h", ".m"], 88 | verbose: true, 89 | path: folderPath) 90 | let context = PipelineState(config: config) 91 | do { 92 | try FindFiles().run(context: context) 93 | XCTAssertEqual(context.files.all.count, 3) 94 | for file in context.files.all { 95 | XCTAssert(!file.hasProjectReferences) 96 | } 97 | } catch { 98 | XCTFail("Should not throw.") 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindUnbuiltFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnbuiltFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import PathKit 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | class FindUnbuiltFilesTests: XCTestCase { 13 | func testUnusedFilesAreFound() { 14 | let testStep = FindUnbuiltFiles() 15 | let config = Configuration(excluded: [], 16 | extensions: [], 17 | verbose: false, 18 | path: Fixtures.fixtureSimpleProject) 19 | let state = PipelineState(config: config) 20 | let builtFileString = "builtFile" 21 | let builtFile = XcodeFile(fileReference: nil, path: Path(builtFileString)) 22 | let unbuiltFileString = "unbuiltFile" 23 | let unbuiltFile = XcodeFile(fileReference: nil, path: Path(unbuiltFileString)) 24 | state.xcodeProjects.append(XcodeProject(path: Fixtures.fixtureSimpleProject)!) 25 | XCTAssertEqual(state.xcodeProjects.count, 1) 26 | state.xcodeProjects.first?.sourceBuildFiles.append(unbuiltFileString) 27 | state.xcodeProjects.first?.files.append(builtFile) 28 | state.xcodeProjects.first?.files.append(unbuiltFile) 29 | do { 30 | try testStep.run(context: state) 31 | XCTAssertEqual(state.xcodeProjects.first?.unbuiltFiles.count, 1 ) 32 | } catch { 33 | XCTFail("Exception not expected. \(error.localizedDescription)") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindUnreferencedFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindUnreferencedFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/13/19. 6 | // 7 | 8 | import PathKit 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | class FindUnreferencedFilesTests: XCTestCase { 13 | func testUnreferencedFilesAreFound() { 14 | let config = Configuration(excluded: [], extensions: [], verbose: false, path: "") 15 | let context = PipelineState(config: config) 16 | context.files.add(File(filename: "used", referenceCount: 1)) 17 | context.files.add(File(filename: "unused")) 18 | let testStep = FindUnreferencedFiles() 19 | testStep.run(context: context) 20 | 21 | XCTAssertEqual(context.unreferencedFiles.all.count, 1) 22 | XCTAssertEqual(context.unreferencedFiles.all[0].path.string, "unused") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindXcodeFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 2/13/19. 6 | // 7 | 8 | import PathKit 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | // swiftlint:disable implicitly_unwrapped_optional 13 | class FindXcodeFilesTests: XCTestCase { 14 | var config: Configuration! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | config = Configuration(excluded: [], 19 | extensions: [], 20 | verbose: false, 21 | path: Fixtures.fixtureSimpleProjectPath) 22 | } 23 | 24 | func testAllReferencesAreGatheredFromSimpleProject() { 25 | let context = PipelineState(config: config) 26 | if let xcodeProj = XcodeProject(path: Fixtures.fixtureSimpleProject) { 27 | context.xcodeProjects.append(xcodeProj) 28 | } 29 | XCTAssertEqual(context.xcodeProjects.count, 1) 30 | let xcodeRefStep = FindXcodeFiles() 31 | do { 32 | try xcodeRefStep.run(context: context) 33 | XCTAssertEqual(context.xcodeProjects.count, 1) 34 | XCTAssertEqual(context.xcodeProjects.first?.files.count, 3) 35 | } catch { 36 | XCTFail("No Exception Expected.") 37 | } 38 | } 39 | 40 | func testOnlyReferencesWithExtensionsAreGatheredFromSimpleProject() { 41 | config = Configuration(excluded: [], 42 | extensions: [".swift"], 43 | verbose: false, 44 | path: Fixtures.fixtureSimpleProjectPath) 45 | let context = PipelineState(config: config) 46 | if let xcodeProj = XcodeProject(path: Fixtures.fixtureSimpleProject) { 47 | context.xcodeProjects.append(xcodeProj) 48 | } 49 | XCTAssertEqual(context.xcodeProjects.count, 1) 50 | let xcodeRefStep = FindXcodeFiles() 51 | 52 | do { 53 | try xcodeRefStep.run(context: context) 54 | XCTAssertEqual(context.xcodeProjects.count, 1) 55 | XCTAssertEqual(context.xcodeProjects.first?.files.count, 2) 56 | for xcodeFile in context.xcodeProjects.first?.files ?? [] { 57 | XCTAssertTrue(xcodeFile.path.string.hasSuffix(".swift")) 58 | } 59 | } catch { 60 | XCTFail("No Exception Expected.") 61 | } 62 | } 63 | 64 | func testOnlyReferencesWithMultipleExtensionsAreGatheredFromSimpleProject() { 65 | config = Configuration(excluded: [], 66 | extensions: [".swift", ".plist"], 67 | verbose: false, 68 | path: Fixtures.fixtureSimpleProjectPath) 69 | let context = PipelineState(config: config) 70 | if let xcodeProj = XcodeProject(path: Fixtures.fixtureSimpleProject) { 71 | context.xcodeProjects.append(xcodeProj) 72 | } 73 | XCTAssertEqual(context.xcodeProjects.count, 1) 74 | let xcodeRefStep = FindXcodeFiles() 75 | 76 | do { 77 | try xcodeRefStep.run(context: context) 78 | XCTAssertEqual(context.xcodeProjects.count, 1) 79 | XCTAssertEqual(context.xcodeProjects.first?.files.count, 3) 80 | for xcodeFile in context.xcodeProjects.first?.files ?? [] { 81 | XCTAssertTrue(xcodeFile.path.string.hasSuffix(".swift") || xcodeFile.path.string.hasSuffix(".plist")) 82 | } 83 | } catch { 84 | XCTFail("No Exception Expected.") 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindXcodeProjectsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeProjectsTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | // swiftlint:disable force_try 12 | class FindXcodeProjectsTests: XCTestCase { 13 | func testExceptionIsThrownWhenGivenABadPath() { 14 | let testStep = FindXcodeProjects() 15 | let config = Configuration(excluded: [], 16 | extensions: [], 17 | verbose: false, 18 | path: "ksjdhf") 19 | let context = PipelineState(config: config) 20 | do { 21 | try testStep.run(context: context) 22 | XCTFail("Exception Expected.") 23 | } catch { 24 | XCTAssert(true) 25 | } 26 | } 27 | 28 | func testSimpleProjectIsFoundWhenSearchingDirectory() { 29 | let testStep = FindXcodeProjects() 30 | let config = Configuration(excluded: [], 31 | extensions: [], 32 | verbose: false, 33 | path: Fixtures.fixtureSimpleProjectPath) 34 | let context = PipelineState(config: config) 35 | try! testStep.run(context: context) 36 | XCTAssertEqual(context.xcodeProjects.count, 1) 37 | } 38 | 39 | func testFixtureProjectsAreFound() { 40 | let testStep = FindXcodeProjects() 41 | let config = Configuration(excluded: [], 42 | extensions: [], 43 | verbose: false, 44 | path: Fixtures.fixturesFolder) 45 | let context = PipelineState(config: config) 46 | try! testStep.run(context: context) 47 | XCTAssertEqual(context.xcodeProjects.count, 2) 48 | } 49 | 50 | func testXcodeProjectCanBeExcluded() { 51 | let testStep = FindXcodeProjects() 52 | let config = Configuration(excluded: ["SimpleProject"], 53 | extensions: [], 54 | verbose: false, 55 | path: Fixtures.fixturesFolder) 56 | let context = PipelineState(config: config) 57 | try! testStep.run(context: context) 58 | XCTAssertEqual(context.xcodeProjects.count, 1) 59 | } 60 | 61 | func testMultipleXcodeProjectsCanBeExcluded() { 62 | let testStep = FindXcodeProjects() 63 | let config = Configuration(excluded: [".xcodeproj"], 64 | extensions: [], 65 | verbose: true, 66 | path: Fixtures.fixturesFolder) 67 | let context = PipelineState(config: config) 68 | try! testStep.run(context: context) 69 | XCTAssertEqual(context.xcodeProjects.count, 0) 70 | } 71 | 72 | func testInvalidXcodeProjectsFailsSilently() { 73 | let testStep = FindXcodeProjects() 74 | let config = Configuration(excluded: [], 75 | extensions: [], 76 | verbose: true, 77 | path: Fixtures.fixtureInvalidProjectPath) 78 | let context = PipelineState(config: config) 79 | try! testStep.run(context: context) 80 | XCTAssertEqual(context.xcodeProjects.count, 0) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindXcodeReferencesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeReferencesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/2/19. 6 | // 7 | 8 | import PathKit 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | class FindXcodeReferencesTests: XCTestCase { 13 | func testThatThing() { 14 | let testStep = FindXcodeReferences() 15 | do { 16 | let config = Configuration(excluded: [], 17 | extensions: [], 18 | verbose: true, 19 | path: Fixtures.fixtureSimpleProject) 20 | let state = PipelineState(config: config) 21 | state.xcodeProjects.append(XcodeProject(path: Fixtures.fixtureSimpleProject)!) 22 | let usedFilePathString = "used" 23 | let usedFilePath = Path(usedFilePathString) 24 | let usedXcodeFile = XcodeFile(fileReference: nil, path: Path(usedFilePathString)) 25 | let usedFile = File(filename: usedFilePathString) 26 | 27 | let unusedFilePathString = "unused" 28 | let unusedFilePath = Path(unusedFilePathString) 29 | let unusedFile = File(filename: unusedFilePathString) 30 | 31 | state.files.add(unusedFile) 32 | state.files.add(usedFile) 33 | state.xcodeProjects.first?.files.append(usedXcodeFile) 34 | try testStep.run(context: state) 35 | XCTAssertEqual(state.xcodeProjects.first?.files.count, 1) 36 | XCTAssertEqual(state.files.all.count, 2) 37 | let usedFileResult = state.files.file(for: usedFilePath)! 38 | XCTAssertTrue(usedFileResult.hasProjectReferences) 39 | let unusedFileResult = state.files.file(for: unusedFilePath)! 40 | XCTAssertFalse(unusedFileResult.hasProjectReferences) 41 | } catch { 42 | XCTFail("Exception not expected. \(error.localizedDescription)") 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Pipeline/Steps/FindXcodeSourceBuildFilesTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindXcodeSourceBuildFilesTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/4/19. 6 | // 7 | 8 | import XCTest 9 | @testable import XCUtilityFramework 10 | 11 | class FindXcodeSourceBuildFilesTests: XCTestCase { 12 | func testSourceBuildFilesAreGatheredInSimpleProject() { 13 | let testStep = FindXcodeSourceBuildFiles() 14 | let config = Configuration(excluded: [], 15 | extensions: [], 16 | verbose: false, 17 | path: Fixtures.fixtureSimpleProject) 18 | let state = PipelineState(config: config) 19 | do { 20 | guard let xcodeProject = XcodeProject(path: state.config.path) else { 21 | XCTFail("XcodeProject should be returned.") 22 | return 23 | } 24 | 25 | state.xcodeProjects.append(xcodeProject) 26 | try testStep.run(context: state) 27 | XCTAssertEqual(state.xcodeProjects.first?.sourceBuildFiles.count, 2) 28 | } catch { 29 | XCTFail("Exception not expected. \(error.localizedDescription)") 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/XCUtilityFrameworkTests/Yaml/YamlParserTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YamlParserTests.swift 3 | // XCUtilityFrameworkTests 4 | // 5 | // Created by Jeff Lett on 3/11/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | @testable import XCUtilityFramework 11 | 12 | class YamlParserTests: XCTestCase { 13 | func testParserThrowsWhenGivenABadPath() { 14 | let validYaml = Fixtures.fixtureConfigPath + "not-there.yml" 15 | do { 16 | _ = try YamlParser.parse(validYaml) 17 | XCTFail("Excepted Expected for bad yaml path.") 18 | } catch YamlParser.YamlParserError.fileNotFound(let message) { 19 | XCTAssertNotNil(message) 20 | } catch { 21 | XCTFail("File not found exception expected.") 22 | } 23 | } 24 | 25 | func testParserThrowsWhenGivenFileWithInvalidFormat() { 26 | let invalidYaml = Fixtures.fixtureConfigPath + "invalid.yml" 27 | do { 28 | _ = try YamlParser.parse(invalidYaml) 29 | XCTFail("Excepted Expected for bad yaml format.") 30 | } catch YamlParser.YamlParserError.yamlParsing(let message) { 31 | XCTAssertNotNil(message) 32 | } catch { 33 | XCTFail("Parsing exception expected.") 34 | } 35 | } 36 | 37 | func testParserReturnsValidYaml() { 38 | let validYaml = Fixtures.fixtureConfigPath + "simple.yml" 39 | do { 40 | let output = try YamlParser.parse(validYaml) 41 | XCTAssertNotNil(output["excluded"]) 42 | } catch { 43 | XCTFail("Parsing exception expected.") 44 | } 45 | } 46 | 47 | func testParserReturnsWhenGivenEmptyYaml() { 48 | let emptyYaml = Fixtures.fixtureConfigPath + "empty.yml" 49 | do { 50 | let output = try YamlParser.parse(emptyYaml) 51 | XCTAssertNotNil(output) 52 | } catch { 53 | XCTFail("Parsing exception expected.") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /xcutility.xcodeproj/xcshareddata/xcschemes/XCUtilityFramework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | --------------------------------------------------------------------------------