Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ let package = Package(
name: "InstallSafeDITool",
targets: ["InstallSafeDITool"]
),
.plugin(
name: "SafeDIConfigurationInit",
targets: ["SafeDIConfigurationInit"]
),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
Expand Down Expand Up @@ -101,6 +105,20 @@ let package = Package(
dependencies: []
),

.plugin(
name: "SafeDIConfigurationInit",
capability: .command(
intent: .custom(
verb: "safedi-init",
description: "Creates a SafeDIConfiguration.swift file in the specified target."
),
permissions: [
.writeToPackageDirectory(reason: "Creates a SafeDIConfiguration.swift file in the target's source directory."),
]
),
dependencies: []
),

.plugin(
name: "SafeDIGenerator",
capability: .buildTool(),
Expand Down
132 changes: 132 additions & 0 deletions Plugins/SafeDIConfigurationInit/SafeDIConfigurationInit.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation
import PackagePlugin

@main
struct SafeDIConfigurationInit: CommandPlugin {
func performCommand(
context: PackagePlugin.PluginContext,
arguments: [String]
) throws {
var argumentExtractor = ArgumentExtractor(arguments)
let targetArguments = argumentExtractor.extractOption(named: "target")
let target: Target
if let targetName = targetArguments.first {
guard let matchingTarget = try context.package.targets(named: [targetName]).first else {
Diagnostics.error("No target named '\(targetName)' found in package")
return
}
target = matchingTarget
} else {
guard let firstTarget = context.package.targets.first(where: { $0 is SourceModuleTarget }) else {
Diagnostics.error("No source module target found in package")
return
}
target = firstTarget
}

let outputURL = context.package.directoryURL.appending(components: "Sources", target.name, "SafeDIConfiguration.swift")
try writeConfigurationFile(to: outputURL)
Diagnostics.remark("Created SafeDIConfiguration.swift in \(target.name)")
}
}

#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin

extension SafeDIConfigurationInit: XcodeCommandPlugin {
func performCommand(
context: XcodeProjectPlugin.XcodePluginContext,
arguments: [String]
) throws {
var argumentExtractor = ArgumentExtractor(arguments)
let targetArguments = argumentExtractor.extractOption(named: "target")
let target: XcodeTarget
if let targetName = targetArguments.first {
guard let matchingTarget = context.xcodeProject.targets.first(where: { $0.displayName == targetName }) else {
Diagnostics.error("No target named '\(targetName)' found in project")
return
}
target = matchingTarget
} else {
guard let firstTarget = context.xcodeProject.targets.first else {
Diagnostics.error("No target found in project")
return
}
target = firstTarget
}

let outputURL = context.xcodeProject.directoryURL.appending(components: target.displayName, "SafeDIConfiguration.swift")
try writeConfigurationFile(to: outputURL)
Diagnostics.remark("Created SafeDIConfiguration.swift in \(target.displayName)")
}
}
#endif

private func writeConfigurationFile(to outputURL: URL) throws {
guard !FileManager.default.fileExists(atPath: outputURL.path(percentEncoded: false)) else {
Diagnostics.error("SafeDIConfiguration.swift already exists at \(outputURL.path(percentEncoded: false)). To reconfigure SafeDI, edit the existing file.")
return
}

try configurationFileContent.write(
to: outputURL,
atomically: true,
encoding: .utf8
)
}

private let configurationFileContent = """
// Distributed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import SafeDI

@SafeDIConfiguration
enum SafeDIConfiguration {
\t/// The names of modules to import in the generated dependency tree.
\t/// This list is in addition to the import statements found in files that declare @Instantiable types.
\tstatic let additionalImportedModules: [StaticString] = []

\t/// Directories containing Swift files to include, relative to the executing directory.
\t/// This property only applies to SafeDI repos that utilize the SPM plugin via an Xcode project.
\tstatic let additionalDirectoriesToInclude: [StaticString] = []
}

"""
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ enum MySafeDIConfiguration {

The `additionalDirectoriesToInclude` property specifies folders outside of your root module that SafeDI will scan for Swift source files. Paths must be relative to the project directory. You can see [an example of this configuration](Examples/ExampleMultiProjectIntegration/ExampleMultiProjectIntegration/SafeDIConfiguration.swift) in the [ExampleMultiProjectIntegration](Examples/ExampleMultiProjectIntegration) project.

##### Scaffolding a configuration file

To scaffold a `SafeDIConfiguration.swift` file with defaults, run:

```
swift package --allow-writing-to-package-directory safedi-init --target YourRootTarget
```

##### Swift package

If your first-party code is entirely contained in a Swift Package with one or more modules, you can add the following lines to your root target’s definition:
Expand Down
Loading