├── .github └── CODEOWNERS ├── .gitignore ├── .infrastructure └── azp │ └── security-pipeline.yml ├── LICENSE ├── README.md ├── spm-licenses.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── spm-licenses.xcscheme └── spm-licenses ├── Extensions ├── CollectionExtensions.swift └── StringExtensions.swift ├── Tools └── Xcode.swift └── main.swift /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Security checks owner is Security team 2 | .infrastructure/azp/security-pipeline.yml @MacPaw/security -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | .DS_Store 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | 53 | # Carthage 54 | # 55 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 56 | # Carthage/Checkouts 57 | 58 | Carthage/Build 59 | 60 | # fastlane 61 | # 62 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 63 | # screenshots whenever they are needed. 64 | # For more information about the recommended setup visit: 65 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 66 | 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots/**/*.png 70 | fastlane/test_output 71 | -------------------------------------------------------------------------------- /.infrastructure/azp/security-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: security-scan 2 | trigger: none 3 | pr: 4 | - master 5 | 6 | variables: 7 | - group: vault 8 | - group: github 9 | 10 | resources: 11 | repositories: 12 | - repository: sec-azp-pipelines 13 | type: github 14 | name: MacPaw/sec-azp-pipelines 15 | ref: refs/heads/main 16 | endpoint: MacPaw 17 | 18 | stages: 19 | - stage: main 20 | pool: 21 | name: default-sre 22 | displayName: scan 23 | jobs: 24 | - template: azp/templates/base-security-checks.yaml@sec-azp-pipelines 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 MacPaw 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spm-licenses 2 | Command line tool to collect LICENSE files from SPM dependencies 3 | 4 | ## Usage 5 | 6 | ```bash 7 | ./spm-licenses -w /path/to/workspace -o /output/file 8 | ``` 9 | The output is a plist file ready to be included into settings bundle as "Acknowledgements" section. 10 | -------------------------------------------------------------------------------- /spm-licenses.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A79A41E52379C22300EAC9B1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79A41E42379C22300EAC9B1 /* main.swift */; }; 11 | A79A427E2379C54700EAC9B1 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79A427D2379C54700EAC9B1 /* StringExtensions.swift */; }; 12 | A79A42812379C56600EAC9B1 /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79A42802379C56600EAC9B1 /* CollectionExtensions.swift */; }; 13 | A79A42842379C84900EAC9B1 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A79A42832379C84900EAC9B1 /* Xcode.swift */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | A79A41DF2379C22300EAC9B1 /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | ); 24 | runOnlyForDeploymentPostprocessing = 1; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | A712DB5B2379D18B00999416 /* licenses.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = licenses.plist; path = ../../../Desktop/licenses.plist; sourceTree = ""; }; 30 | A79A41E12379C22300EAC9B1 /* spm-licenses */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "spm-licenses"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | A79A41E42379C22300EAC9B1 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 32 | A79A427D2379C54700EAC9B1 /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 33 | A79A42802379C56600EAC9B1 /* CollectionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtensions.swift; sourceTree = ""; }; 34 | A79A42832379C84900EAC9B1 /* Xcode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Xcode.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | A79A41DE2379C22300EAC9B1 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | A79A41D82379C22300EAC9B1 = { 49 | isa = PBXGroup; 50 | children = ( 51 | A79A41E32379C22300EAC9B1 /* spm-licenses */, 52 | A79A41E22379C22300EAC9B1 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | A79A41E22379C22300EAC9B1 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | A79A41E12379C22300EAC9B1 /* spm-licenses */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | A79A41E32379C22300EAC9B1 /* spm-licenses */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | A79A42822379C84200EAC9B1 /* Tools */, 68 | A79A427C2379C53C00EAC9B1 /* Extensions */, 69 | A79A41E42379C22300EAC9B1 /* main.swift */, 70 | A712DB5B2379D18B00999416 /* licenses.plist */, 71 | ); 72 | path = "spm-licenses"; 73 | sourceTree = ""; 74 | }; 75 | A79A427C2379C53C00EAC9B1 /* Extensions */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | A79A427D2379C54700EAC9B1 /* StringExtensions.swift */, 79 | A79A42802379C56600EAC9B1 /* CollectionExtensions.swift */, 80 | ); 81 | path = Extensions; 82 | sourceTree = ""; 83 | }; 84 | A79A42822379C84200EAC9B1 /* Tools */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | A79A42832379C84900EAC9B1 /* Xcode.swift */, 88 | ); 89 | path = Tools; 90 | sourceTree = ""; 91 | }; 92 | /* End PBXGroup section */ 93 | 94 | /* Begin PBXNativeTarget section */ 95 | A79A41E02379C22300EAC9B1 /* spm-licenses */ = { 96 | isa = PBXNativeTarget; 97 | buildConfigurationList = A79A41E82379C22300EAC9B1 /* Build configuration list for PBXNativeTarget "spm-licenses" */; 98 | buildPhases = ( 99 | A79A41DD2379C22300EAC9B1 /* Sources */, 100 | A79A41DE2379C22300EAC9B1 /* Frameworks */, 101 | A79A41DF2379C22300EAC9B1 /* CopyFiles */, 102 | ); 103 | buildRules = ( 104 | ); 105 | dependencies = ( 106 | ); 107 | name = "spm-licenses"; 108 | productName = "spm-licenses"; 109 | productReference = A79A41E12379C22300EAC9B1 /* spm-licenses */; 110 | productType = "com.apple.product-type.tool"; 111 | }; 112 | /* End PBXNativeTarget section */ 113 | 114 | /* Begin PBXProject section */ 115 | A79A41D92379C22300EAC9B1 /* Project object */ = { 116 | isa = PBXProject; 117 | attributes = { 118 | LastSwiftUpdateCheck = 1110; 119 | LastUpgradeCheck = 1110; 120 | ORGANIZATIONNAME = MacPaw; 121 | TargetAttributes = { 122 | A79A41E02379C22300EAC9B1 = { 123 | CreatedOnToolsVersion = 11.1; 124 | }; 125 | }; 126 | }; 127 | buildConfigurationList = A79A41DC2379C22300EAC9B1 /* Build configuration list for PBXProject "spm-licenses" */; 128 | compatibilityVersion = "Xcode 9.3"; 129 | developmentRegion = en; 130 | hasScannedForEncodings = 0; 131 | knownRegions = ( 132 | en, 133 | Base, 134 | ); 135 | mainGroup = A79A41D82379C22300EAC9B1; 136 | productRefGroup = A79A41E22379C22300EAC9B1 /* Products */; 137 | projectDirPath = ""; 138 | projectRoot = ""; 139 | targets = ( 140 | A79A41E02379C22300EAC9B1 /* spm-licenses */, 141 | ); 142 | }; 143 | /* End PBXProject section */ 144 | 145 | /* Begin PBXSourcesBuildPhase section */ 146 | A79A41DD2379C22300EAC9B1 /* Sources */ = { 147 | isa = PBXSourcesBuildPhase; 148 | buildActionMask = 2147483647; 149 | files = ( 150 | A79A427E2379C54700EAC9B1 /* StringExtensions.swift in Sources */, 151 | A79A42842379C84900EAC9B1 /* Xcode.swift in Sources */, 152 | A79A42812379C56600EAC9B1 /* CollectionExtensions.swift in Sources */, 153 | A79A41E52379C22300EAC9B1 /* main.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | A79A41E62379C22300EAC9B1 /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | CLANG_ANALYZER_NONNULL = YES; 165 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 167 | CLANG_CXX_LIBRARY = "libc++"; 168 | CLANG_ENABLE_MODULES = YES; 169 | CLANG_ENABLE_OBJC_ARC = YES; 170 | CLANG_ENABLE_OBJC_WEAK = YES; 171 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 172 | CLANG_WARN_BOOL_CONVERSION = YES; 173 | CLANG_WARN_COMMA = YES; 174 | CLANG_WARN_CONSTANT_CONVERSION = YES; 175 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 177 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 178 | CLANG_WARN_EMPTY_BODY = YES; 179 | CLANG_WARN_ENUM_CONVERSION = YES; 180 | CLANG_WARN_INFINITE_RECURSION = YES; 181 | CLANG_WARN_INT_CONVERSION = YES; 182 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 183 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 184 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 186 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 187 | CLANG_WARN_STRICT_PROTOTYPES = YES; 188 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 189 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | COPY_PHASE_STRIP = NO; 193 | DEBUG_INFORMATION_FORMAT = dwarf; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu11; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 205 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 206 | GCC_WARN_UNDECLARED_SELECTOR = YES; 207 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 208 | GCC_WARN_UNUSED_FUNCTION = YES; 209 | GCC_WARN_UNUSED_VARIABLE = YES; 210 | MACOSX_DEPLOYMENT_TARGET = 10.15; 211 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 212 | MTL_FAST_MATH = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = macosx; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | A79A41E72379C22300EAC9B1 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_ENABLE_OBJC_WEAK = YES; 231 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 232 | CLANG_WARN_BOOL_CONVERSION = YES; 233 | CLANG_WARN_COMMA = YES; 234 | CLANG_WARN_CONSTANT_CONVERSION = YES; 235 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 236 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 237 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 238 | CLANG_WARN_EMPTY_BODY = YES; 239 | CLANG_WARN_ENUM_CONVERSION = YES; 240 | CLANG_WARN_INFINITE_RECURSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 244 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 245 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | MACOSX_DEPLOYMENT_TARGET = 10.15; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = macosx; 268 | SWIFT_COMPILATION_MODE = wholemodule; 269 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 270 | }; 271 | name = Release; 272 | }; 273 | A79A41E92379C22300EAC9B1 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | CODE_SIGN_STYLE = Automatic; 277 | DEVELOPMENT_TEAM = S8EX82NJP6; 278 | ENABLE_HARDENED_RUNTIME = YES; 279 | PRODUCT_NAME = "$(TARGET_NAME)"; 280 | SWIFT_VERSION = 5.0; 281 | }; 282 | name = Debug; 283 | }; 284 | A79A41EA2379C22300EAC9B1 /* Release */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | CODE_SIGN_STYLE = Automatic; 288 | DEVELOPMENT_TEAM = S8EX82NJP6; 289 | ENABLE_HARDENED_RUNTIME = YES; 290 | PRODUCT_NAME = "$(TARGET_NAME)"; 291 | SWIFT_VERSION = 5.0; 292 | }; 293 | name = Release; 294 | }; 295 | /* End XCBuildConfiguration section */ 296 | 297 | /* Begin XCConfigurationList section */ 298 | A79A41DC2379C22300EAC9B1 /* Build configuration list for PBXProject "spm-licenses" */ = { 299 | isa = XCConfigurationList; 300 | buildConfigurations = ( 301 | A79A41E62379C22300EAC9B1 /* Debug */, 302 | A79A41E72379C22300EAC9B1 /* Release */, 303 | ); 304 | defaultConfigurationIsVisible = 0; 305 | defaultConfigurationName = Release; 306 | }; 307 | A79A41E82379C22300EAC9B1 /* Build configuration list for PBXNativeTarget "spm-licenses" */ = { 308 | isa = XCConfigurationList; 309 | buildConfigurations = ( 310 | A79A41E92379C22300EAC9B1 /* Debug */, 311 | A79A41EA2379C22300EAC9B1 /* Release */, 312 | ); 313 | defaultConfigurationIsVisible = 0; 314 | defaultConfigurationName = Release; 315 | }; 316 | /* End XCConfigurationList section */ 317 | }; 318 | rootObject = A79A41D92379C22300EAC9B1 /* Project object */; 319 | } 320 | -------------------------------------------------------------------------------- /spm-licenses.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /spm-licenses.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /spm-licenses.xcodeproj/xcshareddata/xcschemes/spm-licenses.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /spm-licenses/Extensions/CollectionExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionExtensions.swift 3 | // spm-licenses 4 | // 5 | // Created by Sergii Kryvoblotskyi on 11/11/19. 6 | // Copyright © 2019 MacPaw. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Collection { 12 | 13 | /// Returns the element at the specified index iff it is within bounds, otherwise nil. 14 | subscript (safe index: Index) -> Element? { 15 | return indices.contains(index) ? self[index] : nil 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spm-licenses/Extensions/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StringExtensions.swift 3 | // spm-licenses 4 | // 5 | // Created by Sergii Kryvoblotskyi on 11/11/19. 6 | // Copyright © 2019 MacPaw. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension String { 12 | 13 | var nsString: NSString { 14 | (self as NSString) 15 | } 16 | 17 | var pathExtension: String { 18 | return nsString.pathExtension 19 | } 20 | 21 | var lastPathComponent: String { 22 | return nsString.lastPathComponent 23 | } 24 | 25 | var deletingLastPathComponent: String { 26 | return nsString.deletingLastPathComponent 27 | } 28 | 29 | var stringByDeletingPathExtension: String { 30 | return nsString.deletingPathExtension 31 | } 32 | 33 | var expandingTildeInPath: String { 34 | return nsString.expandingTildeInPath 35 | } 36 | 37 | func appendingPathComponent(_ component: String) -> String { 38 | return nsString.appendingPathComponent(component) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spm-licenses/Tools/Xcode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Xcode.swift 3 | // spm-licenses 4 | // 5 | // Created by Sergii Kryvoblotskyi on 11/11/19. 6 | // Copyright © 2019 MacPaw. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Xcode { 12 | 13 | static var derivedDataURL: URL { 14 | if let overridenPath = readOverridenDerivedDataPath() { 15 | return URL(fileURLWithPath: overridenPath.expandingTildeInPath) 16 | } 17 | let defaultPath = "~/Library/Developer/Xcode/DerivedData/".expandingTildeInPath 18 | return URL(fileURLWithPath: defaultPath) 19 | } 20 | } 21 | 22 | //defaults read com.apple.dt.Xcode.plist IDECustomDerivedDataLocation 23 | //If the line returns 24 | // 25 | //The domain/default pair of (com.apple.dt.Xcode.plist, IDECustomDerivedDataLocation) does not exist 26 | //it's the default path ~/Library/Developer/Xcode/DerivedData/ otherwise the custom path. 27 | private extension Xcode { 28 | 29 | static func readOverridenDerivedDataPath() -> String? { 30 | let task = Process() 31 | let pipe = Pipe() 32 | task.executableURL = URL(fileURLWithPath: "/usr/bin/defaults") 33 | task.arguments = ["read","com.apple.dt.Xcode.plist", "IDECustomDerivedDataLocation"] 34 | task.standardOutput = pipe 35 | try? task.run() 36 | let handle = pipe.fileHandleForReading 37 | let data = handle.readDataToEndOfFile() 38 | let path = String(data: data, encoding: String.Encoding.utf8) 39 | return (path?.isEmpty ?? true) ? nil : path 40 | } 41 | } 42 | 43 | extension Xcode { 44 | 45 | struct Project { 46 | let url: URL 47 | let info: [String: Any] 48 | var workspacePath: String? { 49 | return info["WorkspacePath"] as? String 50 | } 51 | } 52 | } 53 | 54 | extension Xcode.Project { 55 | 56 | struct License { 57 | 58 | let url: URL 59 | let name: String 60 | } 61 | } 62 | 63 | extension Xcode.Project.License { 64 | 65 | func makeRepresentation() throws -> [String: String] { 66 | let data = try Data(contentsOf: url) 67 | let text = String(data: data, encoding: .utf8) ?? "" 68 | return [ 69 | "Title": name, 70 | "Type": "PSGroupSpecifier", 71 | "FooterText": text 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /spm-licenses/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // spm-licenses 4 | // 5 | // Created by Sergii Kryvoblotskyi on 11/11/19. 6 | // Copyright © 2019 MacPaw. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let arguments = CommandLine.arguments 12 | guard let workspaceIndex = arguments.firstIndex(of: "-w"), let workspacePath = arguments[safe: workspaceIndex + 1] else { 13 | print("Workspace path is missing. Specify -w.") 14 | exit(0) 15 | } 16 | 17 | guard let outputIndex = arguments.firstIndex(of: "-o"), let outputPath = arguments[safe: outputIndex + 1] else { 18 | print("Output path is missing. Specify -o.") 19 | exit(0) 20 | } 21 | 22 | let fileManager = FileManager.default 23 | 24 | let workspaceURL = URL(fileURLWithPath: workspacePath.expandingTildeInPath) 25 | if !fileManager.fileExists(atPath: workspaceURL.path) { 26 | print("xcworkspace not found at \(workspaceURL)") 27 | exit(0) 28 | } 29 | 30 | let packageURL = workspaceURL.appendingPathComponent("xcshareddata/swiftpm/Package.resolved") 31 | if !fileManager.fileExists(atPath: packageURL.path) { 32 | print("Package.resolved not found at \(packageURL)") 33 | exit(0) 34 | } 35 | 36 | let packageData = try Data(contentsOf: packageURL) 37 | let packageInfo = try JSONSerialization.jsonObject(with: packageData, options: .allowFragments) 38 | 39 | guard let package = packageInfo as? [String: Any] else { 40 | print("Invalid package format") 41 | exit(0) 42 | } 43 | 44 | guard let object = package["object"] as? [String: Any] else { 45 | print("Invalid obejct format") 46 | exit(0) 47 | } 48 | 49 | guard let pins = object["pins"] as? [[String: Any]] else { 50 | print("Invalid pins format") 51 | exit(0) 52 | } 53 | 54 | let projectsRL = Xcode.derivedDataURL 55 | func projectsInfo(at url: URL) throws -> [Xcode.Project] { 56 | try fileManager 57 | .contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) 58 | .map { $0.appendingPathComponent("info.plist") } 59 | .compactMap { 60 | guard let info = NSDictionary(contentsOf: $0) as? [String: Any] else { return nil } 61 | return Xcode.Project(url: $0, info: info) 62 | } 63 | } 64 | let projects = try projectsInfo(at: projectsRL) 65 | 66 | guard let currentProject = projects.first(where: ({ $0.workspacePath == workspacePath.expandingTildeInPath })) else { 67 | print("Derived data missing for workspace") 68 | exit(0) 69 | } 70 | 71 | let checkouts = currentProject.url.deletingLastPathComponent().appendingPathComponent("SourcePackages/checkouts") 72 | let checkedDependencies = try fileManager.contentsOfDirectory(at: checkouts, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) 73 | 74 | let licences: [Xcode.Project.License] = checkedDependencies.compactMap { 75 | let licenseURL = $0.appendingPathComponent("LICENSE") 76 | if fileManager.fileExists(atPath: licenseURL.path) { 77 | return Xcode.Project.License(url: licenseURL, name: $0.lastPathComponent) 78 | } 79 | let licenseTXTURL = $0.appendingPathComponent("LICENSE.txt") 80 | if fileManager.fileExists(atPath: licenseTXTURL.path) { 81 | return Xcode.Project.License(url: licenseTXTURL, name: $0.lastPathComponent) 82 | } 83 | return nil 84 | } 85 | 86 | let plistEntries = try licences.map { try $0.makeRepresentation() } 87 | 88 | 89 | let data = try PropertyListSerialization.data(fromPropertyList: plistEntries, format: .xml, options: .zero) 90 | try data.write(to: URL(fileURLWithPath: outputPath.expandingTildeInPath)) 91 | 92 | print("Licenses have been saved to \(outputPath)") 93 | --------------------------------------------------------------------------------