diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c583a534..6e56e1eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Select Xcode Version run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Build Package Integration - run: xcrun swift build --package-path Examples/ExamplePackageIntegration + run: xcrun swift build --package-path "Examples/Example Package Integration" spm-prebuilt-package-integration: name: Build Prebuilt Package Integration on Xcode 16 diff --git a/Documentation/Manual.md b/Documentation/Manual.md index 05f8a667..0284cec1 100644 --- a/Documentation/Manual.md +++ b/Documentation/Manual.md @@ -511,7 +511,7 @@ UIKit applications’ natural root is the `UIApplicationDelegate`-conforming app ## Example applications -We’ve tied everything together with an example multi-user notes application backed by SwiftUI. You can compile and run this code in [an example single-module Xcode project](../Examples/ExampleProjectIntegration). This same multi-user notes app also exists in [an example multi-module Xcode project](../Examples/ExampleMultiProjectIntegration), and also in [an example Xcode project using CocoaPods](../Examples/ExampleCocoaPodsIntegration). We have also created [an example multi-module `Package.swift` that integrates with SafeDI](../Examples/ExamplePackageIntegration). +We’ve tied everything together with an example multi-user notes application backed by SwiftUI. You can compile and run this code in [an example single-module Xcode project](../Examples/ExampleProjectIntegration). This same multi-user notes app also exists in [an example multi-module Xcode project](../Examples/ExampleMultiProjectIntegration), and also in [an example Xcode project using CocoaPods](../Examples/ExampleCocoaPodsIntegration). We have also created [an example multi-module `Package.swift` that integrates with SafeDI](../Examples/Example Package Integration). ## Under the hood diff --git a/Examples/ExamplePackageIntegration/Package.resolved b/Examples/Example Package Integration/Package.resolved similarity index 100% rename from Examples/ExamplePackageIntegration/Package.resolved rename to Examples/Example Package Integration/Package.resolved diff --git a/Examples/ExamplePackageIntegration/Package.swift b/Examples/Example Package Integration/Package.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Package.swift rename to Examples/Example Package Integration/Package.swift diff --git a/Examples/ExamplePackageIntegration/Sources/ChildAModule/ChildA.swift b/Examples/Example Package Integration/Sources/ChildAModule/ChildA.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/ChildAModule/ChildA.swift rename to Examples/Example Package Integration/Sources/ChildAModule/ChildA.swift diff --git a/Examples/ExamplePackageIntegration/Sources/ChildBModule/ChildB.swift b/Examples/Example Package Integration/Sources/ChildBModule/ChildB.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/ChildBModule/ChildB.swift rename to Examples/Example Package Integration/Sources/ChildBModule/ChildB.swift diff --git a/Examples/ExamplePackageIntegration/Sources/ChildCModule/ChildC.swift b/Examples/Example Package Integration/Sources/ChildCModule/ChildC.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/ChildCModule/ChildC.swift rename to Examples/Example Package Integration/Sources/ChildCModule/ChildC.swift diff --git a/Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/Grandchild C/GrandchildC.swift b/Examples/Example Package Integration/Sources/GrandchildrenModule/Grandchild C/GrandchildC.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/Grandchild C/GrandchildC.swift rename to Examples/Example Package Integration/Sources/GrandchildrenModule/Grandchild C/GrandchildC.swift diff --git a/Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/GrandchildA.swift b/Examples/Example Package Integration/Sources/GrandchildrenModule/GrandchildA.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/GrandchildA.swift rename to Examples/Example Package Integration/Sources/GrandchildrenModule/GrandchildA.swift diff --git a/Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/GrandchildB.swift b/Examples/Example Package Integration/Sources/GrandchildrenModule/GrandchildB.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/GrandchildrenModule/GrandchildB.swift rename to Examples/Example Package Integration/Sources/GrandchildrenModule/GrandchildB.swift diff --git a/Examples/ExamplePackageIntegration/Sources/RootModule/Root.swift b/Examples/Example Package Integration/Sources/RootModule/Root.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/RootModule/Root.swift rename to Examples/Example Package Integration/Sources/RootModule/Root.swift diff --git a/Examples/ExamplePackageIntegration/Sources/SharedModule/SharedThing.swift b/Examples/Example Package Integration/Sources/SharedModule/SharedThing.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/SharedModule/SharedThing.swift rename to Examples/Example Package Integration/Sources/SharedModule/SharedThing.swift diff --git a/Examples/ExamplePackageIntegration/Sources/SharedModule/UserDefaults.swift b/Examples/Example Package Integration/Sources/SharedModule/UserDefaults.swift similarity index 100% rename from Examples/ExamplePackageIntegration/Sources/SharedModule/UserDefaults.swift rename to Examples/Example Package Integration/Sources/SharedModule/UserDefaults.swift diff --git a/Plugins/InstallSafeDITool/InstallCLIPluginCommand.swift b/Plugins/InstallSafeDITool/InstallCLIPluginCommand.swift index 140f6222..af4ff67c 100644 --- a/Plugins/InstallSafeDITool/InstallCLIPluginCommand.swift +++ b/Plugins/InstallSafeDITool/InstallCLIPluginCommand.swift @@ -67,19 +67,19 @@ struct InstallSafeDITool: CommandPlugin { let (downloadedURL, _) = try await URLSession.shared.download( for: URLRequest(url: githubDownloadURL) ) - let downloadedFileAttributes = try FileManager.default.attributesOfItem(atPath: downloadedURL.path()) + let downloadedFileAttributes = try FileManager.default.attributesOfItem(atPath: downloadedURL.path(percentEncoded: false)) guard let currentPermissions = downloadedFileAttributes[.posixPermissions] as? NSNumber, // Add executable attributes to the downloaded file. - chmod(downloadedURL.path(), mode_t(currentPermissions.uint32Value) | S_IXUSR | S_IXGRP | S_IXOTH) == 0 + chmod(downloadedURL.path(percentEncoded: false), mode_t(currentPermissions.uint32Value) | S_IXUSR | S_IXGRP | S_IXOTH) == 0 else { - Diagnostics.error("Failed to make downloaded file \(downloadedURL.path()) executable") + Diagnostics.error("Failed to make downloaded file \(downloadedURL.path(percentEncoded: false)) executable") exit(1) } try FileManager.default.createDirectory( at: expectedToolFolder, withIntermediateDirectories: true ) - if FileManager.default.fileExists(atPath: expectedToolLocation.path()) { + if FileManager.default.fileExists(atPath: expectedToolLocation.path(percentEncoded: false)) { try FileManager.default.removeItem(at: expectedToolLocation) } try FileManager.default.moveItem( @@ -87,7 +87,7 @@ struct InstallSafeDITool: CommandPlugin { to: expectedToolLocation ) let gitIgnoreLocation = context.safediFolder.appending(component: ".gitignore") - if !FileManager.default.fileExists(atPath: gitIgnoreLocation.path()) { + if !FileManager.default.fileExists(atPath: gitIgnoreLocation.path(percentEncoded: false)) { try """ */\(expectedToolLocation.lastPathComponent) """.write( @@ -143,19 +143,19 @@ struct InstallSafeDITool: CommandPlugin { let (downloadedURL, _) = try await URLSession.shared.download( for: URLRequest(url: githubDownloadURL) ) - let downloadedFileAttributes = try FileManager.default.attributesOfItem(atPath: downloadedURL.path()) + let downloadedFileAttributes = try FileManager.default.attributesOfItem(atPath: downloadedURL.path(percentEncoded: false)) guard let currentPermissions = downloadedFileAttributes[.posixPermissions] as? NSNumber, // Add executable attributes to the downloaded file. - chmod(downloadedURL.path(), mode_t(currentPermissions.uint32Value) | S_IXUSR | S_IXGRP | S_IXOTH) == 0 + chmod(downloadedURL.path(percentEncoded: false), mode_t(currentPermissions.uint32Value) | S_IXUSR | S_IXGRP | S_IXOTH) == 0 else { - Diagnostics.error("Failed to make downloaded file \(downloadedURL.path()) executable") + Diagnostics.error("Failed to make downloaded file \(downloadedURL.path(percentEncoded: false)) executable") exit(1) } try FileManager.default.createDirectory( at: expectedToolFolder, withIntermediateDirectories: true ) - if FileManager.default.fileExists(atPath: expectedToolLocation.path()) { + if FileManager.default.fileExists(atPath: expectedToolLocation.path(percentEncoded: false)) { try FileManager.default.removeItem(at: expectedToolLocation) } try FileManager.default.moveItem( @@ -163,7 +163,7 @@ struct InstallSafeDITool: CommandPlugin { to: expectedToolLocation ) let gitIgnoreLocation = safediFolder.appending(component: ".gitignore") - if !FileManager.default.fileExists(atPath: gitIgnoreLocation.path()) { + if !FileManager.default.fileExists(atPath: gitIgnoreLocation.path(percentEncoded: false)) { try """ */\(expectedToolLocation.lastPathComponent) """.write( diff --git a/Plugins/SafeDIGenerator/SafeDIGenerateDependencyTree.swift b/Plugins/SafeDIGenerator/SafeDIGenerateDependencyTree.swift index 2f60abc6..d8d17030 100644 --- a/Plugins/SafeDIGenerator/SafeDIGenerateDependencyTree.swift +++ b/Plugins/SafeDIGenerator/SafeDIGenerateDependencyTree.swift @@ -56,28 +56,28 @@ struct SafeDIGenerateDependencyTree: BuildToolPlugin { ) let includeCSV = context.safediFolder.appending(components: "configuration", "include.csv") - let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path()) { + let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path(percentEncoded: false)) { [ "--include-file-path", - includeCSV.path(), + includeCSV.path(percentEncoded: false), ] } else { [] } let additionalImportedModulesCSV = context.safediFolder.appending(components: "configuration", "additionalImportedModules.csv") - let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path()) { + let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path(percentEncoded: false)) { [ "--additional-imported-modules-file-path", - additionalImportedModulesCSV.path(), + additionalImportedModulesCSV.path(percentEncoded: false), ] } else { [] } let arguments = [ - inputSourcesFile.path(), + inputSourcesFile.path(percentEncoded: false), "--dependency-tree-output", - outputSwiftFile.path(), + outputSwiftFile.path(percentEncoded: false), ] + includeArguments + additionalImportedModulesArguments let downloadedToolLocation = context.downloadedToolLocation @@ -87,7 +87,7 @@ struct SafeDIGenerateDependencyTree: BuildToolPlugin { Using a debug SafeDITool binary, which is 15x slower than the release version. To install the release SafeDITool binary for version \(safeDIVersion), run: - \tswift package --package-path \(context.package.directoryURL.path()) --allow-network-connections all --allow-writing-to-package-directory safedi-release-install + \tswift package --package-path "\(context.package.directoryURL.path(percentEncoded: false))" --allow-network-connections all --allow-writing-to-package-directory safedi-release-install """) } @@ -173,28 +173,28 @@ extension Target { ) let includeCSV = context.safediFolder.appending(components: "configuration", "include.csv") - let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path()) { + let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path(percentEncoded: false)) { [ "--include-file-path", - includeCSV.path(), + includeCSV.path(percentEncoded: false), ] } else { [] } let additionalImportedModulesCSV = context.safediFolder.appending(components: "configuration", "additionalImportedModules.csv") - let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path()) { + let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path(percentEncoded: false)) { [ "--additional-imported-modules-file-path", - additionalImportedModulesCSV.path(), + additionalImportedModulesCSV.path(percentEncoded: false), ] } else { [] } let arguments = [ - inputSourcesFile.path(), + inputSourcesFile.path(percentEncoded: false), "--dependency-tree-output", - outputSwiftFile.path(), + outputSwiftFile.path(percentEncoded: false), ] + includeArguments + additionalImportedModulesArguments let downloadedToolLocation = context.downloadedToolLocation diff --git a/Plugins/SafeDIPrebuiltGenerator/SafeDIGenerateDependencyTree.swift b/Plugins/SafeDIPrebuiltGenerator/SafeDIGenerateDependencyTree.swift index d2bfb66a..ee80833a 100644 --- a/Plugins/SafeDIPrebuiltGenerator/SafeDIGenerateDependencyTree.swift +++ b/Plugins/SafeDIPrebuiltGenerator/SafeDIGenerateDependencyTree.swift @@ -56,28 +56,28 @@ struct SafeDIGenerateDependencyTree: BuildToolPlugin { ) let includeCSV = context.safediFolder.appending(components: "configuration", "include.csv") - let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path()) { + let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path(percentEncoded: false)) { [ "--include-file-path", - includeCSV.path(), + includeCSV.path(percentEncoded: false), ] } else { [] } let additionalImportedModulesCSV = context.safediFolder.appending(components: "configuration", "additionalImportedModules.csv") - let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path()) { + let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path(percentEncoded: false)) { [ "--additional-imported-modules-file-path", - additionalImportedModulesCSV.path(), + additionalImportedModulesCSV.path(percentEncoded: false), ] } else { [] } let arguments = [ - inputSourcesFile.path(), + inputSourcesFile.path(percentEncoded: false), "--dependency-tree-output", - outputSwiftFile.path(), + outputSwiftFile.path(percentEncoded: false), ] + includeArguments + additionalImportedModulesArguments let downloadedToolLocation = context.downloadedToolLocation @@ -93,11 +93,11 @@ struct SafeDIGenerateDependencyTree: BuildToolPlugin { var description: String { """ Install the release SafeDITool binary for version \(safeDIVersion): - \tswift package --package-path \(packagePath) --allow-network-connections all --allow-writing-to-package-directory safedi-release-install + \tswift package --package-path "\(packagePath)" --allow-network-connections all --allow-writing-to-package-directory safedi-release-install """ } } - throw NoReleaseBinaryFoundError(safeDIVersion: safeDIVersion, packagePath: context.package.directoryURL.path()) + throw NoReleaseBinaryFoundError(safeDIVersion: safeDIVersion, packagePath: context.package.directoryURL.path(percentEncoded: false)) } else { struct NoReleaseBinaryAvailableError: Error, CustomStringConvertible { var description: String { @@ -183,28 +183,28 @@ extension Target { ) let includeCSV = context.safediFolder.appending(components: "configuration", "include.csv") - let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path()) { + let includeArguments: [String] = if FileManager.default.fileExists(atPath: includeCSV.path(percentEncoded: false)) { [ "--include-file-path", - includeCSV.path(), + includeCSV.path(percentEncoded: false), ] } else { [] } let additionalImportedModulesCSV = context.safediFolder.appending(components: "configuration", "additionalImportedModules.csv") - let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path()) { + let additionalImportedModulesArguments: [String] = if FileManager.default.fileExists(atPath: additionalImportedModulesCSV.path(percentEncoded: false)) { [ "--additional-imported-modules-file-path", - additionalImportedModulesCSV.path(), + additionalImportedModulesCSV.path(percentEncoded: false), ] } else { [] } let arguments = [ - inputSourcesFile.path(), + inputSourcesFile.path(percentEncoded: false), "--dependency-tree-output", - outputSwiftFile.path(), + outputSwiftFile.path(percentEncoded: false), ] + includeArguments + additionalImportedModulesArguments let downloadedToolLocation = context.downloadedToolLocation diff --git a/Plugins/Shared.swift b/Plugins/Shared.swift index 7be35897..a224dac9 100644 --- a/Plugins/Shared.swift +++ b/Plugins/Shared.swift @@ -29,7 +29,7 @@ import PackagePlugin // As of Xcode 15.0, Xcode command plugins have no way to read the package manifest, therefore we must hardcode the version number. // It is okay for this number to be behind the most current release if the inputs and outputs to SafeDITool have not changed. // Unlike SPM plugins, Xcode plugins can not determine the current version number, so we must hardcode it. - "1.5.0" + "1.5.1" } var safeDIOrigin: URL { @@ -59,7 +59,7 @@ import PackagePlugin } var downloadedToolLocation: URL? { - guard FileManager.default.fileExists(atPath: expectedToolLocation.path()) else { return nil } + guard FileManager.default.fileExists(atPath: expectedToolLocation.path(percentEncoded: false)) else { return nil } return expectedToolLocation } } @@ -111,7 +111,7 @@ extension PackagePlugin.PluginContext { var downloadedToolLocation: URL? { guard let expectedToolLocation, - FileManager.default.fileExists(atPath: expectedToolLocation.path()) + FileManager.default.fileExists(atPath: expectedToolLocation.path(percentEncoded: false)) else { return nil } return expectedToolLocation } diff --git a/README.md b/README.md index 20a60716..66607138 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ If your first-party code is entirely contained in a Swift Package with one or mo ] ``` -You can see this integration in practice in the [ExamplePackageIntegration](Examples/ExamplePackageIntegration) package. +You can see this integration in practice in the [Example Package Integration](Examples/Example Package Integration) package. Unlike the `SafeDIGenerator` Xcode project plugin, the `SafeDIGenerator` Swift package plugin finds source files in dependent modules without additional configuration steps. If you find that SafeDI’s generated dependency tree is missing required imports, you may create a `.safedi/configuration/additionalImportedModules.csv` with a comma-separated list of module names to import. The `.safedi/` folder must be placed in the same folder as your `Package.swift` file. diff --git a/SafeDI.podspec b/SafeDI.podspec index aa39bbc6..16e59b56 100644 --- a/SafeDI.podspec +++ b/SafeDI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'SafeDI' - s.version = '1.5.0' + s.version = '1.5.1' s.summary = 'Compile-time-safe dependency injection' s.homepage = 'https://github.com/dfed/SafeDI' s.license = 'MIT' diff --git a/Sources/SafeDITool/SafeDITool.swift b/Sources/SafeDITool/SafeDITool.swift index 06dc5430..2be304aa 100644 --- a/Sources/SafeDITool/SafeDITool.swift +++ b/Sources/SafeDITool/SafeDITool.swift @@ -50,7 +50,7 @@ struct SafeDITool: AsyncParsableCommand, Sendable { // MARK: Internal static var currentVersion: String { - "1.5.0" + "1.5.1" } func run() async throws {