From b62117c9d0e6b21a467aefe41c8c705d0332fb76 Mon Sep 17 00:00:00 2001 From: leogdion Date: Thu, 2 Jan 2025 15:26:53 -0500 Subject: [PATCH] Adding Writing (#3) --- .swift-format | 2 +- .swiftlint.yml | 2 +- Package.resolved | 11 +- Package.swift | 17 +- Sources/PackageDSLKit/Component.swift | 8 +- .../PackageDSLKit/ComponentBuildable.swift | 29 +- Sources/PackageDSLKit/ComponentWriter.swift | 66 ++ .../Dependency+ComponentBuildable.swift | 80 ++ Sources/PackageDSLKit/Dependency.swift | 55 +- Sources/PackageDSLKit/ImportDeclSyntax.swift | 2 +- Sources/PackageDSLKit/Index.swift | 16 +- Sources/PackageDSLKit/MissingSource.swift | 34 + Sources/PackageDSLKit/Modifier.swift | 4 +- Sources/PackageDSLKit/ModifierType.swift | 33 + Sources/PackageDSLKit/PackageDSLError.swift | 1 - Sources/PackageDSLKit/PackageDSLKit.swift | 222 ----- .../PackageDirectoryConfiguration.swift | 150 ++-- .../PackageDSLKit/PackageIndexStrategy.swift | 67 +- .../PackageDSLKit/PackageIndexWriter.swift | 113 +++ Sources/PackageDSLKit/PackageParser.swift | 146 ---- .../PackageDSLKit/PackageSpecifications.swift | 69 ++ Sources/PackageDSLKit/PackageVisitor.swift | 39 +- Sources/PackageDSLKit/PackageWriter.swift | 95 +++ Sources/PackageDSLKit/ParsingResult.swift | 2 +- Sources/PackageDSLKit/ParsingStrategy.swift | 20 +- .../Product+ComponentBuildable.swift | 83 ++ Sources/PackageDSLKit/Product.swift | 14 +- Sources/PackageDSLKit/Property.swift | 34 +- Sources/PackageDSLKit/PropertyVisitor.swift | 20 +- Sources/PackageDSLKit/PropertyWriter.swift | 45 ++ .../Resources/PackageDSL.swift.txt | 760 ++++++++++++++++++ Sources/PackageDSLKit/Source.swift | 34 + Sources/PackageDSLKit/SourceType.swift | 50 ++ Sources/PackageDSLKit/StructureStrategy.swift | 38 +- Sources/PackageDSLKit/SupportCodeBlock.swift | 46 ++ Sources/PackageDSLKit/SupportedPlatform.swift | 35 +- .../PackageDSLKit/SupportedPlatformSet.swift | 81 ++ .../Target+ComponentBuildable.swift | 63 ++ .../TestTarget+ComponentBuildable.swift | 63 ++ Sources/PackageDSLKit/TypeReference.swift | 23 +- Sources/PackageDSLKit/TypeSource.swift | 32 + Sources/package/FileManager.swift | 60 ++ Sources/package/Package+Command.swift | 45 +- Sources/package/Settings.swift | 20 +- TypeReference.swift | 19 - 45 files changed, 2234 insertions(+), 614 deletions(-) create mode 100644 Sources/PackageDSLKit/ComponentWriter.swift create mode 100644 Sources/PackageDSLKit/Dependency+ComponentBuildable.swift create mode 100644 Sources/PackageDSLKit/MissingSource.swift create mode 100644 Sources/PackageDSLKit/ModifierType.swift delete mode 100644 Sources/PackageDSLKit/PackageDSLKit.swift create mode 100644 Sources/PackageDSLKit/PackageIndexWriter.swift create mode 100644 Sources/PackageDSLKit/PackageSpecifications.swift create mode 100644 Sources/PackageDSLKit/PackageWriter.swift create mode 100644 Sources/PackageDSLKit/Product+ComponentBuildable.swift create mode 100644 Sources/PackageDSLKit/PropertyWriter.swift create mode 100644 Sources/PackageDSLKit/Resources/PackageDSL.swift.txt create mode 100644 Sources/PackageDSLKit/Source.swift create mode 100644 Sources/PackageDSLKit/SourceType.swift create mode 100644 Sources/PackageDSLKit/SupportCodeBlock.swift create mode 100644 Sources/PackageDSLKit/SupportedPlatformSet.swift create mode 100644 Sources/PackageDSLKit/Target+ComponentBuildable.swift create mode 100644 Sources/PackageDSLKit/TestTarget+ComponentBuildable.swift create mode 100644 Sources/PackageDSLKit/TypeSource.swift create mode 100644 Sources/package/FileManager.swift delete mode 100644 TypeReference.swift diff --git a/.swift-format b/.swift-format index 5c31a3e..83797e9 100644 --- a/.swift-format +++ b/.swift-format @@ -22,7 +22,7 @@ "prioritizeKeepingFunctionOutputTogether" : false, "respectsExistingLineBreaks" : true, "rules" : { - "AllPublicDeclarationsHaveDocumentation" : true, + "AllPublicDeclarationsHaveDocumentation" : false, "AlwaysUseLiteralForEmptyCollectionInit" : false, "AlwaysUseLowerCamelCase" : true, "AmbiguousTrailingClosureOverload" : true, diff --git a/.swiftlint.yml b/.swiftlint.yml index 8af5c43..2c21de4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -57,7 +57,6 @@ opt_in_rules: - optional_enum_case_matching - overridden_super_call - override_in_extension - - pattern_matching_keywords - prefer_self_type_over_type_of_self - prefer_zero_over_explicit_init - private_action @@ -117,6 +116,7 @@ identifier_name: excluded: - DerivedData - .build + - Compression.playground indentation_width: indentation_width: 2 file_name: diff --git a/Package.resolved b/Package.resolved index aa7f3ad..0187873 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3decd9c5eb6868fc5ea969ecd2b6cffa4288c57141e5c04692f90d05d8c01390", + "originHash" : "5c7d79d1516b47d715f10d9564b397b010b1595ee060d86d88874116b92a77e0", "pins" : [ { "identity" : "swift-argument-parser", @@ -28,6 +28,15 @@ "version" : "1.0.0" } }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version" : "1.6.2" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index 805c764..ee96ab2 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.4.0"), .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "601.0.0-prerelease-2024-11-18"), - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.0") + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.4.0"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.6.0") ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -25,14 +26,24 @@ let package = Package( name: "PackageDSLKit", dependencies: [ .product(name: "SwiftSyntax", package: "swift-syntax"), - .product(name: "SwiftParser", package: "swift-syntax") + .product(name: "SwiftParser", package: "swift-syntax"), + .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), + .product( + name: "Logging", + package: "swift-log", + condition: .when(platforms: [.linux, .openbsd, .wasi, .android, .windows]) + ) + ], + + resources: [ + .copy("Resources/PackageDSL.swift.txt") ] ), .executableTarget( name: "package", dependencies: [ "PackageDSLKit", - .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "ArgumentParser", package: "swift-argument-parser") ] ), .testTarget( diff --git a/Sources/PackageDSLKit/Component.swift b/Sources/PackageDSLKit/Component.swift index 625bd6e..2c0529c 100644 --- a/Sources/PackageDSLKit/Component.swift +++ b/Sources/PackageDSLKit/Component.swift @@ -27,8 +27,8 @@ // OTHER DEALINGS IN THE SOFTWARE. // -struct Component { - let name: String - let inheritedTypes: [String] - let properties: [String: Property] +internal struct Component: Sendable { + internal let name: String + internal let inheritedTypes: [String] + internal let properties: [String: Property] } diff --git a/Sources/PackageDSLKit/ComponentBuildable.swift b/Sources/PackageDSLKit/ComponentBuildable.swift index dd763e6..3fa043f 100644 --- a/Sources/PackageDSLKit/ComponentBuildable.swift +++ b/Sources/PackageDSLKit/ComponentBuildable.swift @@ -27,6 +27,31 @@ // OTHER DEALINGS IN THE SOFTWARE. // -protocol ComponentBuildable { - init?(component: Component) +import Foundation + +internal protocol ComponentBuildable { + associatedtype Requirements = Void + static var directoryName: String { get } + init(component: Component, requirements: Requirements) + static func requirements(from component: Component) -> Requirements? + func createComponent() -> Component +} + +extension ComponentBuildable { + internal init?(component: Component) { + guard let requirements = Self.requirements(from: component) else { + return nil + } + self.init(component: component, requirements: requirements) + } + + internal static func directoryURL(relativeTo packageDSLURL: URL) -> URL { + packageDSLURL.appending(path: self.directoryName, directoryHint: .isDirectory) + } +} + +extension Component { + internal func isType(of type: T.Type) -> Bool { + type.requirements(from: self) != nil + } } diff --git a/Sources/PackageDSLKit/ComponentWriter.swift b/Sources/PackageDSLKit/ComponentWriter.swift new file mode 100644 index 0000000..53611a2 --- /dev/null +++ b/Sources/PackageDSLKit/ComponentWriter.swift @@ -0,0 +1,66 @@ +// +// ComponentWriter.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 SwiftSyntax + +internal struct ComponentWriter { + private let propertyWriter = PropertyWriter() + internal func node(from component: Component) -> StructDeclSyntax { + let memberBlockList = MemberBlockItemListSyntax( + component.properties.values.map(propertyWriter.node(from:)).map { + MemberBlockItemSyntax(decl: $0) + } + ) + let inheritedTypes = component.inheritedTypes + .map { TokenSyntax.identifier($0) } + .map { + IdentifierTypeSyntax(name: $0) + } + .map { + InheritedTypeSyntax(type: $0) + } + .reversed() + .enumerated() + .map { index, expression in + if index == 0 { + return expression + } + return expression.with(\.trailingComma, .commaToken()) + } + .reversed() + let inheritedTypeList = InheritedTypeListSyntax(inheritedTypes) + let clause = InheritanceClauseSyntax(inheritedTypes: inheritedTypeList) + let memberBlock = MemberBlockSyntax(members: memberBlockList) + return StructDeclSyntax( + name: .identifier(component.name, leadingTrivia: .space), + inheritanceClause: clause, + memberBlock: memberBlock + ) + } +} diff --git a/Sources/PackageDSLKit/Dependency+ComponentBuildable.swift b/Sources/PackageDSLKit/Dependency+ComponentBuildable.swift new file mode 100644 index 0000000..e0463d7 --- /dev/null +++ b/Sources/PackageDSLKit/Dependency+ComponentBuildable.swift @@ -0,0 +1,80 @@ +// +// Dependency+ComponentBuildable.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +extension Dependency: ComponentBuildable { + internal typealias Requirements = DependencyType + public static let directoryName: String = "Dependencies" + + internal init(component: Component, requirements: Requirements) { + let package = + (component.properties["dependencies"]?.code.first?.filter({ character in + character.isLetter || character.isNumber + })).map(DependencyRef.init) + + self.init( + typeName: component.name, + type: requirements, + dependency: component.properties["dependency"]?.code.first, + package: package + ) + } + + internal static func requirements(from component: Component) -> DependencyType? { + guard let dependencyType = DependencyType(strings: component.inheritedTypes) else { + return nil + } + guard dependencyType.rawValue > 0 else { + return nil + } + return dependencyType + } + + internal func createComponent() -> Component { + var properties = [String: Property]() + let inheritedTypes: [String] + let name: String + + name = typeName + inheritedTypes = self.type.asInheritedTypes() + + if let dependency { + properties["dependency"] = Property( + name: "dependency", type: "Package.Dependency", code: [dependency] + ) + } + + if let package { + properties["package"] = Property( + name: "package", type: "PackageDependency", code: [package.asFunctionCall()] + ) + } + + return .init(name: name, inheritedTypes: inheritedTypes, properties: properties) + } +} diff --git a/Sources/PackageDSLKit/Dependency.swift b/Sources/PackageDSLKit/Dependency.swift index fc9552a..8210e8f 100644 --- a/Sources/PackageDSLKit/Dependency.swift +++ b/Sources/PackageDSLKit/Dependency.swift @@ -28,21 +28,18 @@ // public struct Dependency: TypeSource { - public let typeName: String - public struct DependencyType: OptionSet, Sendable { - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public var rawValue: Int - public typealias RawValue = Int public static let package = DependencyType(rawValue: 1) public static let target = DependencyType(rawValue: 2) private static let strings: [String] = ["PackageDependency", "TargetDependency"] + + public var rawValue: Int + public init(rawValue: Int) { + self.rawValue = rawValue + } public init?(strings: [String]) { let indicies = strings.map { Self.strings.firstIndex(of: $0) @@ -55,9 +52,45 @@ public struct Dependency: TypeSource { let rawValue = rawValues.reduce(0) { $0 + $1 } self.init(rawValue: rawValue) } + + internal func asInheritedTypes() -> [String] { + rawValue.powerOfTwoExponents().map { Self.strings[$0] } + } } - let type: DependencyType - let dependency: String? - let package: DependencyRef? + public let typeName: String + + public let type: DependencyType + public let dependency: String? + public let package: DependencyRef? + + public init( + typeName: String, + type: Dependency.DependencyType, + dependency: String? = nil, + package: DependencyRef? = nil + ) { + self.typeName = typeName + self.type = type + self.dependency = dependency + self.package = package + } +} + +extension Int { + fileprivate func powerOfTwoExponents() -> [Int] { + var number = self + var exponents: [Int] = [] + var currentExponent = 0 + + while number > 0 { + if number & 1 == 1 { + exponents.append(currentExponent) + } + number >>= 1 + currentExponent += 1 + } + + return exponents + } } diff --git a/Sources/PackageDSLKit/ImportDeclSyntax.swift b/Sources/PackageDSLKit/ImportDeclSyntax.swift index e4cef5e..9bf9e82 100644 --- a/Sources/PackageDSLKit/ImportDeclSyntax.swift +++ b/Sources/PackageDSLKit/ImportDeclSyntax.swift @@ -30,7 +30,7 @@ import SwiftSyntax extension ImportDeclSyntax { - static func module(_ moduleName: String) -> ImportDeclSyntax { + internal static func module(_ moduleName: String) -> ImportDeclSyntax { ImportDeclSyntax( path: ImportPathComponentListSyntax([ ImportPathComponentSyntax( diff --git a/Sources/PackageDSLKit/Index.swift b/Sources/PackageDSLKit/Index.swift index c7e1bf0..cb163ed 100644 --- a/Sources/PackageDSLKit/Index.swift +++ b/Sources/PackageDSLKit/Index.swift @@ -27,7 +27,14 @@ // OTHER DEALINGS IN THE SOFTWARE. // +import SwiftSyntax + public struct Index { + public let entries: [EntryRef] + public let dependencies: [DependencyRef] + public let testTargets: [TestTargetRef] + public let swiftSettings: [SwiftSettingRef] + public let modifiers: [Modifier] public init( entries: [EntryRef], dependencies: [DependencyRef], @@ -41,17 +48,12 @@ public struct Index { self.swiftSettings = swiftSettings self.modifiers = modifiers } - - public let entries: [EntryRef] - public let dependencies: [DependencyRef] - public let testTargets: [TestTargetRef] - public let swiftSettings: [SwiftSettingRef] - public let modifiers: [Modifier] } extension Index { internal init( - items: [(PackageIndexStrategy.ExpressionKind, String)], modifiers: [ModifierType: [String]] + items: [(PackageIndexStrategy.ExpressionKind, String)], + modifiers: [ModifierType: [String]] ) { var entries: [EntryRef] = [] var dependencies: [DependencyRef] = [] diff --git a/Sources/PackageDSLKit/MissingSource.swift b/Sources/PackageDSLKit/MissingSource.swift new file mode 100644 index 0000000..59be620 --- /dev/null +++ b/Sources/PackageDSLKit/MissingSource.swift @@ -0,0 +1,34 @@ +// +// MissingSource.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public struct MissingSource: Sendable { + public let source: Source + public let sourceType: SourceType + public let name: String +} diff --git a/Sources/PackageDSLKit/Modifier.swift b/Sources/PackageDSLKit/Modifier.swift index b744297..d40e1bd 100644 --- a/Sources/PackageDSLKit/Modifier.swift +++ b/Sources/PackageDSLKit/Modifier.swift @@ -28,6 +28,6 @@ // public struct Modifier { - let type: ModifierType - let code: [String] + public let type: ModifierType + public let code: [String] } diff --git a/Sources/PackageDSLKit/ModifierType.swift b/Sources/PackageDSLKit/ModifierType.swift new file mode 100644 index 0000000..8e27ec1 --- /dev/null +++ b/Sources/PackageDSLKit/ModifierType.swift @@ -0,0 +1,33 @@ +// +// ModifierType.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public enum ModifierType: String { + case supportedPlatforms + case defaultLocalization +} diff --git a/Sources/PackageDSLKit/PackageDSLError.swift b/Sources/PackageDSLKit/PackageDSLError.swift index b85130d..2e0fdfd 100644 --- a/Sources/PackageDSLKit/PackageDSLError.swift +++ b/Sources/PackageDSLKit/PackageDSLError.swift @@ -30,6 +30,5 @@ public enum PackageDSLError: Error { case custom(String, (any Sendable)?) case other(any Error) - case validationFailure([MissingSource]) } diff --git a/Sources/PackageDSLKit/PackageDSLKit.swift b/Sources/PackageDSLKit/PackageDSLKit.swift deleted file mode 100644 index d5f51aa..0000000 --- a/Sources/PackageDSLKit/PackageDSLKit.swift +++ /dev/null @@ -1,222 +0,0 @@ -// -// PackageDSLKit.swift -// PackageDSLKit -// -// Created by Leo Dion. -// Copyright © 2025 BrightDigit. -// -// 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. -// - -//// -//// PackageDSLKit.swift -//// PackageDSLKit -//// -//// Created by Leo Dion. -//// Copyright © 2024 BrightDigit. -//// -//// 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 SwiftSyntax -// import SwiftParser -// import Foundation -// import os -// -// -// -// -// -// -// -// -//// class PackageCallRewriter : SyntaxRewriter { -////// override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax { -////// } -//// } -//// -//// class PackageFileRewriter : SyntaxRewriter { -//// internal init(packageCallRewriter: SyntaxRewriter = PackageCallRewriter()) { -//// self.packageCallRewriter = packageCallRewriter -//// } -//// -//// let packageCallRewriter: SyntaxRewriter -//// -////// override func visit(_ node: SourceFileSyntax) -> SourceFileSyntax { -////// -////// } -//// } -// -// @available(*, deprecated) -// public enum PackageDSLKit { -// -// @available(*, deprecated) -// public static func parse(_ directoryURL: URL, with fileManager: FileManager) throws { -// let indexFileURL = directoryURL.appendingPathComponent("Index.swift") -// guard fileManager.fileExists(atPath: indexFileURL.path) else { -// throw PackageDSLError.custom("Could not find Index.swift file at \(indexFileURL)", indexFileURL) -// } -// let sourceCode = try String(contentsOf: indexFileURL) -// -// let sourceSyntax = Parser.parse(source: sourceCode) -// //PackageRewriter(viewMode: .fixedUp).visit(sourceSyntax) -// -// let packageVisitor = PackageIndexVisitor(viewMode: .fixedUp) -// packageVisitor.walk(sourceSyntax) -// dump(packageVisitor.items) -// let packageStatement = sourceSyntax.statements.first { syntax in -// guard case let .decl( declaration) = syntax.item else { -// return false -// } -// -// guard let patternBinding = declaration.as(VariableDeclSyntax.self)?.bindings.first else { -// return false -// } -// guard patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == "package" else { -// return false -// } -// guard let packageInitValue = patternBinding.initializer?.value else { -// return false -// } -// //dump(packageInitValue) -// guard let calledExpression = packageInitValue -// .as(FunctionCallExprSyntax.self)? -// .calledExpression -// .as(MemberAccessExprSyntax.self)? -// .base? -// .as(FunctionCallExprSyntax.self)? -// .calledExpression -// else { -// return false -// } -// guard let functionCall = calledExpression -// .cast(MemberAccessExprSyntax.self) -// .base? -// .cast(FunctionCallExprSyntax.self) -// else { -// return false -// } -// guard let identifier = functionCall -// .calledExpression -// .cast(DeclReferenceExprSyntax.self) -// .baseName -// .identifier else { -// return false -// } -// guard identifier.name == "Package" else { -// return false -// } -// return true -// } -// guard let packageStatement else { -// return -// } -// } -// -// public static func labeledExpression(for name: String, items: [String]) -> LabeledExprSyntax { -// LabeledExprSyntax( -// leadingTrivia: .newline, -// label: .identifier(name), -// colon: .colonToken(trailingTrivia: .space), -// expression: ClosureExprSyntax( -// statements: CodeBlockItemListSyntax( -// items.map{ name in -// CodeBlockItemSyntax( -// item: .expr( -// ExprSyntax( -// FunctionCallExprSyntax( -// leadingTrivia: .newline, -// calledExpression: DeclReferenceExprSyntax(baseName: .identifier(name)), -// leftParen: .leftParenToken(), -// arguments: LabeledExprListSyntax([]), -// rightParen: .rightParenToken(), -// trailingTrivia: .init(.newline) -// ) -// ) -// ) -// ) -// } -// ) -// ), -// trailingTrivia: .newline -// ) -// } -// -// public static func run() throws -> String { -// -// let declSyntax : DeclSyntax = .init(ImportDeclSyntax.module("PackageDescription")) -// -// let packageDecl = VariableDeclSyntax( -// leadingTrivia: .newline, -// bindingSpecifier: .keyword(.let), -// bindings: PatternBindingListSyntax([ -// PatternBindingSyntax( -// pattern: IdentifierPatternSyntax( -// leadingTrivia: .space, -// identifier: .identifier("package"), -// trailingTrivia: .space -// ), -// initializer: InitializerClauseSyntax( -// value: -// FunctionCallExprSyntax( -// leadingTrivia: .space, -// calledExpression: DeclReferenceExprSyntax(baseName: .identifier("Package")), -// leftParen: .leftParenToken(), -// arguments: LabeledExprListSyntax([ -// self.labeledExpression(for: "entries", items: ["FODWorkout"]) -// ]), -// rightParen: .rightParenToken() -// -// ) -// -// ) -// ) -// ]) -// ) -// let syntax = CodeBlockItemListSyntax([ -// CodeBlockItemSyntax(item: .decl(declSyntax)), -// CodeBlockItemSyntax(item: .decl(DeclSyntax(packageDecl))) -// ]) -// return syntax.description -// -// } -// -// } diff --git a/Sources/PackageDSLKit/PackageDirectoryConfiguration.swift b/Sources/PackageDSLKit/PackageDirectoryConfiguration.swift index 58917ac..d690ffc 100644 --- a/Sources/PackageDSLKit/PackageDirectoryConfiguration.swift +++ b/Sources/PackageDSLKit/PackageDirectoryConfiguration.swift @@ -28,34 +28,37 @@ // public struct PackageDirectoryConfiguration { - internal init( - index: Index, products: [Product] = [], dependencies: [Dependency] = [], targets: [Target] = [], - testTargets: [TestTarget] = [], supportedPlatforms: Set = .init() + public let index: Index + public let products: [Product] + public let dependencies: [Dependency] + public let targets: [Target] + public let testTargets: [TestTarget] + public let supportedPlatformSets: [SupportedPlatformSet] + public init( + index: Index, + products: [Product] = [], + dependencies: [Dependency] = [], + targets: [Target] = [], + testTargets: [TestTarget] = [], + supportedPlatformSets: [SupportedPlatformSet] = [] ) { self.index = index self.products = products self.dependencies = dependencies self.targets = targets self.testTargets = testTargets - self.supportedPlatforms = supportedPlatforms + self.supportedPlatformSets = supportedPlatformSets } - - public let index: Index - public let products: [Product] - public let dependencies: [Dependency] - public let targets: [Target] - public let testTargets: [TestTarget] - public let supportedPlatforms: Set } extension PackageDirectoryConfiguration { - init(from results: [ParsingResult]) throws(PackageDSLError) { + internal init(from results: [ParsingResult]) throws(PackageDSLError) { var index: Index? var products: [Product] = [] var dependencies: [Dependency] = [] var targets: [Target] = [] var testTargets: [TestTarget] = [] - var supportedPlatforms: Set = .init() + var supportedPlatformSets: [SupportedPlatformSet] = [] for result in results { switch result { case .packageIndex(let newItems, let modifiers): @@ -73,8 +76,8 @@ extension PackageDirectoryConfiguration { targets.append(target) } else if let testTarget = TestTarget(component: component) { testTargets.append(testTarget) - } else if let supportedPlatform = Set(component: component) { - supportedPlatforms.formUnion(supportedPlatform) + } else if let supportedPlatforms = SupportedPlatformSet(component: component) { + supportedPlatformSets.append(supportedPlatforms) } else { assertionFailure() } @@ -84,54 +87,58 @@ extension PackageDirectoryConfiguration { throw .custom("Missing Index", nil) } self.init( - index: index, products: products, dependencies: dependencies, targets: targets, - testTargets: testTargets, supportedPlatforms: supportedPlatforms) + index: index, + products: products, + dependencies: dependencies, + targets: targets, + testTargets: testTargets, + supportedPlatformSets: supportedPlatformSets + ) } -} - -enum SourceType: CaseIterable { - case product - case dependency - case testTarget - - func sources(from configuration: PackageDirectoryConfiguration) -> [any TypeSource] { - switch self { - case .product: return configuration.products - case .dependency: return configuration.dependencies - case .testTarget: return configuration.testTargets - } - } - func indexReferences(from index: Index) -> [any TypeReference] { - switch self { - case .product: return index.entries - case .dependency: return index.dependencies - case .testTarget: return index.testTargets - } + internal func createComponents() -> [Component] { + var components: [Component] = [] + components.append(contentsOf: products.map { $0.createComponent() }) + components.append(contentsOf: dependencies.map { $0.createComponent() }) + components.append(contentsOf: targets.map { $0.createComponent() }) + components.append(contentsOf: testTargets.map { $0.createComponent() }) + components.append(contentsOf: supportedPlatformSets.map { $0.createComponent() }) + return components } } -enum Source { - case index - case product(String) - case target(String) -} - -public struct MissingSource: Sendable { - let source: Source - let sourceType: SourceType - let name: String -} - extension PackageDirectoryConfiguration { - func validateDependencies() -> [MissingSource] { + public init(specifications: PackageSpecifications) { + let entries = specifications.products.map(EntryRef.init) + let dependencies = specifications.dependencies.map(DependencyRef.init) + let testTargets = specifications.testTargets.map(TestTargetRef.init) + let swiftSettings = specifications.swiftSettings + let index = Index( + entries: entries, + dependencies: dependencies, + testTargets: testTargets, + swiftSettings: swiftSettings, + modifiers: specifications.modifiers + ) + + self.init( + index: index, + products: specifications.products, + dependencies: specifications.dependencies, + targets: specifications.targets, + testTargets: specifications.testTargets, + supportedPlatformSets: specifications.supportedPlatformSets + ) + } + private func validateDependencies() -> [MissingSource] { let dependencyNames = Set( self.dependencies.map { $0.typeName } + self.targets.map { $0.typeName - }) + } + ) var missingSources: [MissingSource] = [] @@ -139,7 +146,8 @@ extension PackageDirectoryConfiguration { let productNames = Set( product.dependencies.map { $0.name - }) + } + ) let missingDependencies = productNames.subtracting(dependencyNames) missingSources.append( contentsOf: missingDependencies.map({ dependencyName in @@ -148,7 +156,8 @@ extension PackageDirectoryConfiguration { sourceType: .dependency, name: dependencyName ) - })) + }) + ) } for target in self.targets { @@ -160,7 +169,8 @@ extension PackageDirectoryConfiguration { sourceType: .dependency, name: dependencyName ) - })) + }) + ) } return missingSources } @@ -174,7 +184,7 @@ extension PackageDirectoryConfiguration { } } - func validateSourceType(sourceType: SourceType) -> [MissingSource] { + private func validateSourceType(sourceType: SourceType) -> [MissingSource] { let references = sourceType.indexReferences(from: self.index).map(\.name) let sources = sourceType.sources(from: self).map(\.typeName) return Set(references).subtracting(sources).map { @@ -182,33 +192,3 @@ extension PackageDirectoryConfiguration { } } } - -public struct PackageSpecifications { - public init( - products: [Product] = [], dependencies: [Dependency] = [], targets: [Target] = [], - testTargets: [TestTarget] = [], supportedPlatforms: Set = .init() - ) { - self.products = products - self.dependencies = dependencies - self.targets = targets - self.testTargets = testTargets - self.supportedPlatforms = supportedPlatforms - } - - public let products: [Product] - public let dependencies: [Dependency] - public let targets: [Target] - public let testTargets: [TestTarget] - public let supportedPlatforms: Set -} - -extension PackageSpecifications { - public init(from directoryConfiguration: PackageDirectoryConfiguration) throws(PackageDSLError) { - try directoryConfiguration.validate() - self.products = directoryConfiguration.products - self.dependencies = directoryConfiguration.dependencies - self.targets = directoryConfiguration.targets - self.testTargets = directoryConfiguration.testTargets - self.supportedPlatforms = directoryConfiguration.supportedPlatforms - } -} diff --git a/Sources/PackageDSLKit/PackageIndexStrategy.swift b/Sources/PackageDSLKit/PackageIndexStrategy.swift index 831ea30..1ef0c67 100644 --- a/Sources/PackageDSLKit/PackageIndexStrategy.swift +++ b/Sources/PackageDSLKit/PackageIndexStrategy.swift @@ -28,35 +28,22 @@ // import SwiftSyntax -import os -enum ModifierType: String { - case supportedPlatforms - case defaultLocalization -} - -// Strategy for package index parsing -class PackageIndexStrategy: ParsingStrategy { - // Add finalize and reset methods - func finalize() -> ParsingResult? { - guard !items.isEmpty else { return nil } - let result = ParsingResult.packageIndex(items, modifiers) - return result - } +#if canImport(os) + import os +#elseif canImport(Logging) + import Logging +#endif - func reset() { - items.removeAll() - modifiers.removeAll() - currentState = .root - } - enum ExpressionKind: String { +internal class PackageIndexStrategy: ParsingStrategy { + internal enum ExpressionKind: String { case entries case dependencies case testTargets case swiftSettings } - enum VisitorState: Equatable { + private enum VisitorState: Equatable { case root case variable case functionCall @@ -68,9 +55,29 @@ class PackageIndexStrategy: ParsingStrategy { private var items = [(ExpressionKind, String)]() private var modifiers = [ModifierType: [String]]() private var currentState: VisitorState = .root - private let logger = Logger(subsystem: "packagedsl", category: "package-index") + #if canImport(os) + private let logger = Logger(subsystem: "packagedsl", category: "structure") + #elseif canImport(Logging) + private let logger = Logger(label: "structure") + #endif + + internal func finalize() -> ParsingResult? { + guard !items.isEmpty else { + return nil + } + let result = ParsingResult.packageIndex(items, modifiers) + return result + } + + internal func reset() { + items.removeAll() + modifiers.removeAll() + currentState = .root + } - func shouldActivate(_ node: some SyntaxProtocol, currentStrategy: ParsingStrategy?) -> Bool { + internal func shouldActivate(_ node: some SyntaxProtocol, currentStrategy: ParsingStrategy?) + -> Bool + { // Don't activate if there's already a PackageIndexStrategy if currentStrategy is PackageIndexStrategy { return false @@ -85,7 +92,7 @@ class PackageIndexStrategy: ParsingStrategy { return false } - func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { switch (currentState, node.item) { case (.modifier(let index), .expr): assert(self.modifiers[index] != nil) @@ -103,7 +110,7 @@ class PackageIndexStrategy: ParsingStrategy { return .skipChildren } - func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { guard let patternBinding = node.bindings.first, patternBinding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == "package" else { @@ -113,7 +120,7 @@ class PackageIndexStrategy: ParsingStrategy { return .visitChildren } - func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { if node.calledExpression.is(MemberAccessExprSyntax.self) || node.calledExpression.is(DeclReferenceExprSyntax.self) { @@ -122,7 +129,7 @@ class PackageIndexStrategy: ParsingStrategy { return .skipChildren } - func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { switch (currentState, node.baseName.identifier?.name) { case (.functionCall, "Package"): return .visitChildren @@ -143,7 +150,7 @@ class PackageIndexStrategy: ParsingStrategy { } // add visit ClosureExpression - func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { guard currentState == .variable, let name = node.label?.identifier?.name, let expressionType = ExpressionKind(rawValue: name) @@ -154,11 +161,11 @@ class PackageIndexStrategy: ParsingStrategy { return .visitChildren } - func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { .skipChildren } - func visitPost(_ node: LabeledExprSyntax) { + internal func visitPost(_ node: LabeledExprSyntax) { if case let .modifier(modifier) = currentState, !node.trimmedDescription.isEmpty { assert(self.modifiers[modifier] != nil) self.modifiers[modifier]?.append(node.trimmedDescription) diff --git a/Sources/PackageDSLKit/PackageIndexWriter.swift b/Sources/PackageDSLKit/PackageIndexWriter.swift new file mode 100644 index 0000000..b9510db --- /dev/null +++ b/Sources/PackageDSLKit/PackageIndexWriter.swift @@ -0,0 +1,113 @@ +// +// PackageIndexWriter.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 SwiftSyntax + +public struct PackageIndexWriter { + private func labeledExpression(for name: String, items: [String]) -> LabeledExprSyntax? { + if items.isEmpty { + return nil + } + return LabeledExprSyntax( + leadingTrivia: .newline, + label: .identifier(name), + colon: .colonToken(trailingTrivia: .space), + expression: ClosureExprSyntax( + statements: CodeBlockItemListSyntax( + items.map { name in + CodeBlockItemSyntax( + item: .expr( + ExprSyntax( + FunctionCallExprSyntax( + leadingTrivia: .newline, + calledExpression: DeclReferenceExprSyntax(baseName: .identifier(name)), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax([]), + rightParen: .rightParenToken(), + trailingTrivia: .init(.newline) + ) + ) + ) + ) + } + ) + ), + trailingTrivia: .newline + ) + } + + public func writeIndex(_ index: Index) throws -> String { + let declSyntax: DeclSyntax = .init(ImportDeclSyntax.module("PackageDescription")) + + let labeledExpressions = [ + self.labeledExpression(for: "entries", items: index.entries.map(\.name)), + self.labeledExpression(for: "dependencies", items: index.dependencies.map(\.name)), + self.labeledExpression(for: "testTargets", items: index.testTargets.map(\.name)), + self.labeledExpression(for: "swiftSettings", items: index.swiftSettings.map(\.name)), + ] + .compactMap { $0 } + .reversed() + .enumerated() + .map { index, expression in + if index == 0 { + return expression + } + return expression.with(\.trailingComma, .commaToken()) + } + .reversed() + let packageDecl = VariableDeclSyntax( + leadingTrivia: .newline, + bindingSpecifier: .keyword(.let), + bindings: PatternBindingListSyntax([ + PatternBindingSyntax( + pattern: IdentifierPatternSyntax( + leadingTrivia: .space, + identifier: .identifier("package"), + trailingTrivia: .space + ), + initializer: InitializerClauseSyntax( + value: + FunctionCallExprSyntax( + leadingTrivia: .space, + calledExpression: DeclReferenceExprSyntax(baseName: .identifier("Package")), + leftParen: .leftParenToken(), + arguments: LabeledExprListSyntax(labeledExpressions), + rightParen: .rightParenToken() + ) + ) + ) + ]) + ) + let syntax = CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: .decl(declSyntax)), + CodeBlockItemSyntax(item: .decl(DeclSyntax(packageDecl))), + ]) + return syntax.description + } +} diff --git a/Sources/PackageDSLKit/PackageParser.swift b/Sources/PackageDSLKit/PackageParser.swift index bc44286..7e1e4b1 100644 --- a/Sources/PackageDSLKit/PackageParser.swift +++ b/Sources/PackageDSLKit/PackageParser.swift @@ -30,152 +30,6 @@ import Foundation import SwiftParser -// -// public struct SwiftSetting : Hashable { -// let typeName : String -// } - -extension SupportedPlatform { - init?(string: String) { - // Remove any whitespace and optional "SupportedPlatform." prefix - let cleanString = string.trimmingCharacters(in: .whitespaces) - .replacingOccurrences(of: "SupportedPlatform.", with: "") - - // Split into platform and version parts - // Example: "macOS(.v14)" -> ["macOS", "v14"] - guard let platformRange = cleanString.range(of: "("), - let versionEndRange = cleanString.range(of: ")", options: .backwards) - else { - return nil - } - - let osName = String(cleanString[.. 0 else { - return nil - } - self.init( - typeName: component.name, type: dependencyType, - dependency: component.properties["dependency"]?.code.first, package: package) - } -} -// -extension Target: ComponentBuildable { - init?(component: Component) { - guard component.inheritedTypes.contains("Target") else { - return nil - } - let dependencies = - component.properties["dependencies"]?.code.map { line in - DependencyRef( - name: line.filter({ character in - character.isLetter || character.isNumber - })) - } ?? [] - self.init(typeName: component.name, dependencies: dependencies) - } -} -// -extension TestTarget: ComponentBuildable { - init?(component: Component) { - guard component.inheritedTypes.contains("TestTarget") else { - return nil - } - let dependencies = - component.properties["dependencies"]?.code.map { line in - DependencyRef( - name: line.filter({ character in - character.isLetter || character.isNumber - })) - } ?? [] - self.init(typeName: component.name, dependencies: dependencies) - } -} -// -extension Set: ComponentBuildable where Element == SupportedPlatform { - init?(component: Component) { - guard component.inheritedTypes.contains("PlatformSet") else { - return nil - } - guard let body = component.properties["body"] else { - return nil - } - guard - body.type.trimmingCharacters(in: .whitespacesAndNewlines.union(.punctuationCharacters)) - == "any SupportedPlatforms" - else { - return nil - } - guard !body.code.isEmpty else { - return nil - } - let platformValues = body.code.map(SupportedPlatform.init) - let platforms = platformValues.compactMap { $0 } - assert(platforms.count == platformValues.count) - self.init(platforms) - // ?.code, !body.isEmpty else { - // return nil - // } - } -} -// -// extension Set : ComponentBuildable where Element == SwiftSetting { -// -// } - public struct PackageParser { public init() { } diff --git a/Sources/PackageDSLKit/PackageSpecifications.swift b/Sources/PackageDSLKit/PackageSpecifications.swift new file mode 100644 index 0000000..0712fef --- /dev/null +++ b/Sources/PackageDSLKit/PackageSpecifications.swift @@ -0,0 +1,69 @@ +// +// PackageSpecifications.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public struct PackageSpecifications { + public let products: [Product] + public let dependencies: [Dependency] + public let targets: [Target] + public let testTargets: [TestTarget] + public let supportedPlatformSets: [SupportedPlatformSet] + public let swiftSettings: [SwiftSettingRef] + public let modifiers: [Modifier] + + public init( + products: [Product] = [], + dependencies: [Dependency] = [], + targets: [Target] = [], + testTargets: [TestTarget] = [], + supportedPlatformSets: [SupportedPlatformSet] = [], + swiftSettings: [SwiftSettingRef] = [], + modifiers: [Modifier] = [] + ) { + self.products = products + self.dependencies = dependencies + self.targets = targets + self.testTargets = testTargets + self.supportedPlatformSets = supportedPlatformSets + self.swiftSettings = swiftSettings + self.modifiers = modifiers + } +} + +extension PackageSpecifications { + public init(from directoryConfiguration: PackageDirectoryConfiguration) throws(PackageDSLError) { + try directoryConfiguration.validate() + self.products = directoryConfiguration.products + self.dependencies = directoryConfiguration.dependencies + self.targets = directoryConfiguration.targets + self.testTargets = directoryConfiguration.testTargets + self.swiftSettings = directoryConfiguration.index.swiftSettings + self.supportedPlatformSets = directoryConfiguration.supportedPlatformSets + self.modifiers = directoryConfiguration.index.modifiers + } +} diff --git a/Sources/PackageDSLKit/PackageVisitor.swift b/Sources/PackageDSLKit/PackageVisitor.swift index ebe73f6..7f4837d 100644 --- a/Sources/PackageDSLKit/PackageVisitor.swift +++ b/Sources/PackageDSLKit/PackageVisitor.swift @@ -28,14 +28,25 @@ // import SwiftSyntax -import os -class PackageVisitor: SyntaxVisitor { +#if canImport(os) + import os +#elseif canImport(Logging) + import Logging +#endif + +internal class PackageVisitor: SyntaxVisitor { private var currentStrategy: ParsingStrategy? private let availableStrategies: [ParsingStrategy] - private let logger = Logger(subsystem: "packagedsl", category: "unified-visitor") - init( + private var results: [ParsingResult] = [] + #if canImport(os) + private let logger = Logger(subsystem: "packagedsl", category: "structure") + #elseif canImport(Logging) + private let logger = Logger(label: "structure") + #endif + + internal init( viewMode: SyntaxTreeViewMode = .fixedUp, strategies: [ParsingStrategy] = [PackageIndexStrategy(), StructureStrategy()] ) { @@ -44,9 +55,7 @@ class PackageVisitor: SyntaxVisitor { super.init(viewMode: viewMode) } - private var results: [ParsingResult] = [] - - func parse(_ node: some SyntaxProtocol) -> [ParsingResult] { + internal func parse(_ node: some SyntaxProtocol) -> [ParsingResult] { super.walk(node) self.finishCurrentStrategy() return results @@ -71,38 +80,38 @@ class PackageVisitor: SyntaxVisitor { } } - override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { checkForStrategyActivation(node) return currentStrategy?.visit(node) ?? .visitChildren } - override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { checkForStrategyActivation(node) return currentStrategy?.visit(node) ?? .visitChildren } - override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { currentStrategy?.visit(node) ?? .visitChildren } - override func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { currentStrategy?.visit(node) ?? .visitChildren } - override func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { currentStrategy?.visit(node) ?? .visitChildren } - override func visitPost(_ node: LabeledExprSyntax) { + override internal func visitPost(_ node: LabeledExprSyntax) { currentStrategy?.visitPost(node) } - override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { checkForStrategyActivation(node) return currentStrategy?.visit(node) ?? .visitChildren } - override func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { currentStrategy?.visit(node) ?? .visitChildren } } diff --git a/Sources/PackageDSLKit/PackageWriter.swift b/Sources/PackageDSLKit/PackageWriter.swift new file mode 100644 index 0000000..7d32861 --- /dev/null +++ b/Sources/PackageDSLKit/PackageWriter.swift @@ -0,0 +1,95 @@ +// +// PackageWriter.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 SwiftSyntax +import SwiftSyntaxBuilder + +public struct PackageWriter { + private static let compoenentTypes: [any ComponentBuildable.Type] = [ + Product.self, + Dependency.self, + TestTarget.self, + SupportedPlatformSet.self, + ] + private let fileManager: FileManager = .default + private let indexWriter: PackageIndexWriter = .init() + private let componentWriter: ComponentWriter = .init() + + public init() { + } + public func write(_ specification: PackageSpecifications, to url: URL) throws(PackageDSLError) { + let configuration = PackageDirectoryConfiguration(specifications: specification) + + let indexFileURL = url.appending(component: "Index.swift") + do { + try indexWriter.writeIndex(configuration.index).write( + to: indexFileURL, + atomically: true, + encoding: .utf8 + ) + } catch { + throw .other(error) + } + let components = configuration.createComponents() + var directoryCreated = [URL: Void]() + + for component in components { + let directoryURL: URL + let componentType = Self.compoenentTypes.first(where: { component.isType(of: $0) }) + guard let componentType else { + throw .custom("Unsupported component", component) + } + + directoryURL = componentType.directoryURL(relativeTo: url) + + if directoryCreated[directoryURL] == nil { + do { + try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true) + } catch { + throw .other(error) + } + directoryCreated[directoryURL] = () + } + + let filePath = + directoryURL + .appending(path: component.name) + .appendingPathExtension("swift") + .standardizedFileURL + + let node = componentWriter.node(from: component) + do { + try node.description.write(to: filePath, atomically: true, encoding: .utf8) + } catch { + throw .other(error) + } + } + } +} diff --git a/Sources/PackageDSLKit/ParsingResult.swift b/Sources/PackageDSLKit/ParsingResult.swift index 24a9834..8d3b581 100644 --- a/Sources/PackageDSLKit/ParsingResult.swift +++ b/Sources/PackageDSLKit/ParsingResult.swift @@ -27,7 +27,7 @@ // OTHER DEALINGS IN THE SOFTWARE. // -enum ParsingResult { +internal enum ParsingResult { case packageIndex([(PackageIndexStrategy.ExpressionKind, String)], [ModifierType: [String]]) case structure(Component) // Add appropriate structure data } diff --git a/Sources/PackageDSLKit/ParsingStrategy.swift b/Sources/PackageDSLKit/ParsingStrategy.swift index f346c4c..d3032a0 100644 --- a/Sources/PackageDSLKit/ParsingStrategy.swift +++ b/Sources/PackageDSLKit/ParsingStrategy.swift @@ -30,7 +30,7 @@ import SwiftSyntax // Protocol for different parsing strategies -protocol ParsingStrategy { +internal protocol ParsingStrategy { // Return true if this strategy should handle the current context func shouldActivate(_ node: some SyntaxProtocol, currentStrategy: ParsingStrategy?) -> Bool @@ -52,35 +52,35 @@ protocol ParsingStrategy { // Default implementations extension ParsingStrategy { - func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: DeclReferenceExprSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: LabeledExprSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { .visitChildren } - func visitPost(_ node: LabeledExprSyntax) {} + internal func visitPost(_ node: LabeledExprSyntax) {} - func reset() {} + internal func reset() {} } diff --git a/Sources/PackageDSLKit/Product+ComponentBuildable.swift b/Sources/PackageDSLKit/Product+ComponentBuildable.swift new file mode 100644 index 0000000..c5789ee --- /dev/null +++ b/Sources/PackageDSLKit/Product+ComponentBuildable.swift @@ -0,0 +1,83 @@ +// +// Product+ComponentBuildable.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +extension Product: ComponentBuildable { + public static let directoryName: String = "Products" + internal init(component: Component, requirements: Void) { + let dependencies = + component.properties["dependencies"]?.code.map { line in + DependencyRef( + name: line.filter({ character in + character.isLetter || character.isNumber + }) + ) + } ?? [] + let name = component.properties["name"]?.code.first + let productType = component.properties["productType"]?.code.compactMap( + ProductType.init(rawValue:) + ).first + + self.init( + typeName: component.name, + name: name, + dependencies: dependencies, + productType: productType + ) + } + + internal static func requirements(from component: Component) -> ()? { + guard component.inheritedTypes.contains("Product") else { + return nil + } + return () + } + internal func createComponent() -> Component { + .init( + name: typeName, + inheritedTypes: ["Product", "Target"], + properties: [ + "name": .init(name: "name", type: "String", code: [name]), + "dependencies": .init( + name: "dependencies", + type: "any Dependencies", + code: dependencies.map { $0.asFunctionCall() } + ), + "productType": .init( + name: "productType", + type: "ProductType", + code: [ + productType.map { + ".\($0.rawValue)" + } + ] + ), + ].compactMapValues { $0 } + ) + } +} diff --git a/Sources/PackageDSLKit/Product.swift b/Sources/PackageDSLKit/Product.swift index fc9cb0e..b25b96d 100644 --- a/Sources/PackageDSLKit/Product.swift +++ b/Sources/PackageDSLKit/Product.swift @@ -27,12 +27,20 @@ // OTHER DEALINGS IN THE SOFTWARE. // -protocol TypeSource { - var typeName: String { get } -} public struct Product: TypeSource { public let typeName: String public let name: String? public let dependencies: [DependencyRef] public let productType: ProductType? + public init( + typeName: String, + name: String? = nil, + dependencies: [DependencyRef] = [], + productType: ProductType? = nil + ) { + self.typeName = typeName + self.name = name + self.dependencies = dependencies + self.productType = productType + } } diff --git a/Sources/PackageDSLKit/Property.swift b/Sources/PackageDSLKit/Property.swift index db47bd4..ffb6bc2 100644 --- a/Sources/PackageDSLKit/Property.swift +++ b/Sources/PackageDSLKit/Property.swift @@ -27,23 +27,43 @@ // OTHER DEALINGS IN THE SOFTWARE. // -public struct Property { +public struct Property: Sendable { public let name: String public let type: String public let code: [String] + public init( + name: String, + type: String, + code: [String] + ) { + self.name = name + self.type = type + self.code = code + } } extension Property { - struct MissingFieldsError: OptionSet, Error { - var rawValue: Int + internal init?(name: String, type: String, code: [String?]) { + let code = code.compactMap(\.self) + guard !code.isEmpty else { + return nil + } + self.init(name: name, type: type, code: code) + } +} - typealias RawValue = Int +extension Property { + internal struct MissingFieldsError: OptionSet, Error { + internal var rawValue: Int + + internal typealias RawValue = Int - static let name = MissingFieldsError(rawValue: 1) - static let type = MissingFieldsError(rawValue: 2) + internal static let name = MissingFieldsError(rawValue: 1) + internal static let type = MissingFieldsError(rawValue: 2) // static let code = MissingFieldsError(rawValue: 4) } - init(name: String?, type: String?, code: [String]) throws(MissingFieldsError) { + + internal init(name: String?, type: String?, code: [String]) throws(MissingFieldsError) { var error: MissingFieldsError = [] if name == nil { error.insert(.name) diff --git a/Sources/PackageDSLKit/PropertyVisitor.swift b/Sources/PackageDSLKit/PropertyVisitor.swift index ac613c9..9518b67 100644 --- a/Sources/PackageDSLKit/PropertyVisitor.swift +++ b/Sources/PackageDSLKit/PropertyVisitor.swift @@ -29,37 +29,37 @@ import SwiftSyntax -class PropertyVisitor: SyntaxVisitor { - var name: String? - var type: String? - var code: [String] = [] +internal class PropertyVisitor: SyntaxVisitor { + internal private(set) var name: String? + internal private(set) var type: String? + internal private(set) var code: [String] = [] - func parse(_ node: VariableDeclSyntax) throws -> Property { + internal func parse(_ node: VariableDeclSyntax) throws -> Property { self.walk(node) return try Property(name: name, type: type, code: code) } - override func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: CodeBlockItemSyntax) -> SyntaxVisitorContinueKind { self.code.append(node.trimmedDescription) return .skipChildren } - override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { self.name = node.identifier.text return .skipChildren } - override func visit(_ node: TypeAnnotationSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: TypeAnnotationSyntax) -> SyntaxVisitorContinueKind { self.type = node.trimmedDescription return .skipChildren } - override func visit(_ node: MemberTypeSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: MemberTypeSyntax) -> SyntaxVisitorContinueKind { self.type = node.trimmedDescription return .skipChildren } - override func visit(_ node: SomeOrAnyTypeSyntax) -> SyntaxVisitorContinueKind { + override internal func visit(_ node: SomeOrAnyTypeSyntax) -> SyntaxVisitorContinueKind { self.type = node.trimmedDescription return .skipChildren } diff --git a/Sources/PackageDSLKit/PropertyWriter.swift b/Sources/PackageDSLKit/PropertyWriter.swift new file mode 100644 index 0000000..a6f9e38 --- /dev/null +++ b/Sources/PackageDSLKit/PropertyWriter.swift @@ -0,0 +1,45 @@ +// +// PropertyWriter.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 SwiftSyntax + +internal struct PropertyWriter { + internal func node(from property: Property) -> VariableDeclSyntax { + let codeBlocks = property.code.map(CodeBlockItemSyntax.init) + let codeBlockList = CodeBlockItemListSyntax(codeBlocks) + // swiftlint:disable:next force_try + return try! VariableDeclSyntax( + """ + var \(raw: property.name): \(raw: property.type) { + \(codeBlockList) + } + """ + ) + } +} diff --git a/Sources/PackageDSLKit/Resources/PackageDSL.swift.txt b/Sources/PackageDSLKit/Resources/PackageDSL.swift.txt new file mode 100644 index 0000000..28c3855 --- /dev/null +++ b/Sources/PackageDSLKit/Resources/PackageDSL.swift.txt @@ -0,0 +1,760 @@ +extension _PackageDescription_Product { +static func entry(_ entry: any Product) -> _PackageDescription_Product { +let targets = entry.productTargets.map(\.name) +switch entry.productType { +case .executable: +return Self.executable(name: entry.name, targets: targets) +case .library: +return Self.library(name: entry.name, type: entry.libraryType, targets: targets) +} +} +} +@resultBuilder +public enum SwiftSettingsBuilder { +public static func buildPartialBlock(first: SwiftSetting) -> [SwiftSetting] { +[first] +} +public static func buildPartialBlock(accumulated: [SwiftSetting], next: SwiftSetting) +-> [SwiftSetting] +{ +accumulated + [next] +} +public static func buildPartialBlock(accumulated: [SwiftSetting], next: [SwiftSetting]) +-> [SwiftSetting] +{ +accumulated + next +} +public static func buildPartialBlock(first: [SwiftSetting]) -> [SwiftSetting] { +first +} +public static func buildPartialBlock(first: any SwiftSettingsConvertible) -> [SwiftSetting] { +first.swiftSettings() +} +public static func buildPartialBlock( +accumulated: [SwiftSetting], +next: any SwiftSettingsConvertible +) -> [SwiftSetting] { +accumulated + next.swiftSettings() +} +} +extension _PackageDescription_Target { +static func entry(_ entry: Target, swiftSettings: [SwiftSetting] = []) +-> _PackageDescription_Target +{ +let dependencies = entry.dependencies.map(\.targetDependency) +switch entry.targetType { +case .executable: +return .executableTarget( +name: entry.name, +dependencies: dependencies, +path: entry.path, +resources: entry.resources, +swiftSettings: swiftSettings + entry.swiftSettings +) +case .regular: +return .target( +name: entry.name, +dependencies: dependencies, +path: entry.path, +resources: entry.resources, +swiftSettings: swiftSettings + entry.swiftSettings +) +case .test: +return .testTarget( +name: entry.name, +dependencies: dependencies, +path: entry.path, +resources: entry.resources, +swiftSettings: swiftSettings + entry.swiftSettings +) +} +} +} +public enum ProductType { +case library +case executable +} +public protocol UnsafeFlag: SwiftSettingConvertible, _Named { +var unsafeFlagArguments: [String] { get } +} +extension UnsafeFlag { +public var unsafeFlagArguments: [String] { +[name.camelToSnakeCaseFlag()] +} +public var setting: SwiftSetting { +.unsafeFlags(unsafeFlagArguments) +} +} +extension Product where Self: Target { +var productTargets: [Target] { +[self] +} +var targetType: TargetType { +switch productType { +case .library: +.regular +case .executable: +.executable +} +} +} +extension Package { +convenience init( +name: String? = nil, +@ProductsBuilder entries: @escaping () -> [any Product], +@PackageDependencyBuilder dependencies packageDependencies: @escaping () -> [any PackageDependency] = { [any PackageDependency] () }, +@TestTargetBuilder testTargets: @escaping () -> any TestTargets = { [any TestTarget]() }, +@SwiftSettingsBuilder swiftSettings: @escaping () -> [SwiftSetting] = { [SwiftSetting]() } +) { +let packageName: String +if let name { +packageName = name +} else { +var pathComponents = #filePath.split(separator: "/") +pathComponents.removeLast() +packageName = String(pathComponents.last!) +} +let allTestTargets = testTargets() +let entries = entries() +let products = entries.map(_PackageDescription_Product.entry) +var targets = entries.flatMap(\.productTargets) +let allTargetsDependencies = targets.flatMap { $0.allDependencies() } +let allTestTargetsDependencies = allTestTargets.flatMap { $0.allDependencies() } +let dependencies = allTargetsDependencies + allTestTargetsDependencies +let targetDependencies = dependencies.compactMap { $0 as? Target } +let packageTargetDependencies = dependencies.compactMap { $0 as? TargetDependency } +let allPackageDependencies = packageTargetDependencies.map(\.package) + packageDependencies() +targets += targetDependencies +targets += allTestTargets.map { $0 as Target } +let packgeTargets = Dictionary( +grouping: targets, +by: { $0.name } +) +.values +.compactMap(\.first) +.map { _PackageDescription_Target.entry($0, swiftSettings: swiftSettings()) } +let packageDeps = Dictionary( +grouping: allPackageDependencies, +by: { $0.packageName } +) +.values.compactMap(\.first).map(\.dependency) +self.init( +name: packageName, +products: products, +dependencies: packageDeps, +targets: packgeTargets +) +} +} +extension Package { +public func supportedPlatforms( +@SupportedPlatformBuilder supportedPlatforms: @escaping () -> any SupportedPlatforms +) -> Package { +platforms = .init(supportedPlatforms()) +return self +} +public func defaultLocalization(_ defaultLocalization: LanguageTag) -> Package { +self.defaultLocalization = defaultLocalization +return self +} +} +@resultBuilder +enum PackageDependencyBuilder { +internal static func buildPartialBlock(first: PackageDependency) -> [any PackageDependency] { +[first] +} +internal static func buildPartialBlock(accumulated: [any PackageDependency], next: PackageDependency) -> [any PackageDependency]{ +accumulated + [next] +} +} +protocol PackageDependency: _Named { +var packageName: String { get } +var dependency: _PackageDescription_PackageDependency { get } +} +extension PackageDependency where Self: TargetDependency { +var package: any PackageDependency { +self +} +var targetDependency: _PackageDescription_TargetDependency { +switch dependency.kind { +case .sourceControl(let name, let location, requirement: _): +let packageName = name ?? location.packageName ?? self.packageName +return .product(name: productName, package: packageName, condition: self.condition) +case .fileSystem(let name, let path): +if let packageName = name ?? path.components(separatedBy: "/").last { +return .product(name: productName, package: packageName, condition: self.condition) +} else { +return .byName(name: productName) +} +case .registry: +return .byName(name: productName) +@unknown default: +return .byName(name: productName) +} +} +} +extension PackageDependency { +var packageName: String { +switch dependency.kind { +case .sourceControl(let name, let location, requirement: _): +return name ?? location.packageName ?? self.name +case .fileSystem(let name, let path): +return name ?? path.packageName ?? self.name +case .registry(let id, requirement: _): +return id +@unknown default: +return name +} +} +} +public enum TargetType { +case regular +case executable +case test +} +public protocol _Depending { +@DependencyBuilder +var dependencies: any Dependencies { get } +} +extension _Depending { +public var dependencies: any Dependencies { +[Dependency]() +} +} +extension _Depending { +public func allDependencies() -> [Dependency] { +dependencies.compactMap { +$0 as? _Depending +} +.flatMap { +$0.allDependencies() +} +.appending(dependencies) +} +} +public protocol TestTarget: Target, GroupBuildable {} +extension TestTarget { +public var targetType: TargetType { +.test +} +} +@resultBuilder +enum DependencyBuilder { +static func buildPartialBlock(first: Dependency) -> any Dependencies { +[first] +} +static func buildPartialBlock(accumulated: any Dependencies, next: Dependency) -> any Dependencies +{ +accumulated + [next] +} +} +extension LanguageTag { +nonisolated(unsafe) static let english: LanguageTag = "en" +} +public protocol Target: _Depending, Dependency, _Named { +var targetType: TargetType { get } +@SwiftSettingsBuilder +var swiftSettings: [SwiftSetting] { get } +@ResourcesBuilder +var resources: [Resource] { get } +var path: String? { get } +} +extension Target { +public var targetType: TargetType { +.regular +} +public var targetDependency: _PackageDescription_TargetDependency { +.target(name: name) +} +public var swiftSettings: [SwiftSetting] { +[] +} +public var resources: [Resource] { +[] +} +public var path: String? { +nil +} +} +public protocol Dependencies: Sequence where Element == Dependency { +init(_ s: S) where S.Element == Dependency, S: Sequence +func appending(_ dependencies: any Dependencies) -> Self +} +public protocol SwiftSettingConvertible: SwiftSettingsConvertible { +var setting: SwiftSetting { get } +} +extension SwiftSettingConvertible { +public func swiftSettings() -> [SwiftSetting] { +[setting] +} +} +@resultBuilder +public enum ProductsBuilder { +public static func buildPartialBlock(first: [any Product]) -> [any Product] { +first +} +public static func buildPartialBlock(first: any Product) -> [any Product] { +[first] +} +public static func buildPartialBlock(accumulated: [any Product], next: any Product) +-> [any Product] +{ +accumulated + [next] +} +public static func buildPartialBlock(accumulated: [any Product], next: [any Product]) +-> [any Product] +{ +accumulated + next +} +} +@resultBuilder +enum SupportedPlatformBuilder { +static func buildPartialBlock(first: SupportedPlatform) -> any SupportedPlatforms { +[first] +} +static func buildPartialBlock(first: PlatformSet) -> any SupportedPlatforms { +first.body +} +static func buildPartialBlock(first: any SupportedPlatforms) -> any SupportedPlatforms { +first +} +static func buildPartialBlock( +accumulated: any SupportedPlatforms, +next: any SupportedPlatforms +) -> any SupportedPlatforms { +accumulated.appending(next) +} +static func buildPartialBlock( +accumulated: any SupportedPlatforms, +next: SupportedPlatform +) -> any SupportedPlatforms { +accumulated.appending([next]) +} +} +extension Array: Dependencies where Element == Dependency { +public func appending(_ dependencies: any Dependencies) -> [Dependency] { +self + dependencies +} +} +@resultBuilder +enum ResourcesBuilder { +static func buildPartialBlock(first: Resource) -> [Resource] { +[first] +} +static func buildPartialBlock(accumulated: [Resource], next: Resource) -> [Resource] { +accumulated + [next] +} +} +public protocol GroupBuildable { +associatedtype Output = Self +static func output(from array: [Self]) -> [Self.Output] +} +extension GroupBuildable where Output == Self { +static func output(from array: [Self]) -> [Self.Output] { +array +} +} +public enum FeatureState { +case upcoming +case experimental +} +extension FeatureState { +public func swiftSetting(name: String) -> SwiftSetting { +switch self { +case .experimental: +.enableExperimentalFeature(name) +case .upcoming: +.enableUpcomingFeature(name) +} +} +} +protocol PlatformSet { +@SupportedPlatformBuilder +var body: any SupportedPlatforms { get } +} +public typealias _PackageDescription_Product = PackageDescription.Product +public typealias _PackageDescription_Target = PackageDescription.Target +public typealias _PackageDescription_TargetDependency = PackageDescription.Target.Dependency +public typealias _PackageDescription_PackageDependency = PackageDescription.Package.Dependency +public typealias LibraryType = PackageDescription.Product.Library.LibraryType// +public struct GlobalActorIsolatedTypesUsability: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct RegionBasedIsolation: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct TransferringArgsAndResults: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct AccessLevelOnImport: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct NestedProtocols: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct VariadicGenerics: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct BitwiseCopyable: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct NoncopyableGenerics: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct MoveOnlyPartialConsumption: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct IsolatedAny: SwiftSettingFeature { +public var featureState: FeatureState { +return .experimental +} +} +public struct StrictConcurrency: SwiftSettingFeature { +public let featureState: FeatureState +public init (featureState: FeatureState = .experimental) { +self.featureState = featureState +} +} +public struct GlobalConcurrency: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct InferSendableFromCaptures: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct DeprecateApplicationMain: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct FullTypedThrows: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct ImportObjcForwardDeclarations: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct DynamicActorIsolation: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct IsolatedDefaultValues: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct InternalImportsByDefault: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct DisableOutwardActorInference: SwiftSettingFeature { +public var featureState: FeatureState { +return .upcoming +} +} +public struct Ounchecked: UnsafeFlag {} +public struct ParseableOutput: UnsafeFlag {} +public struct EmitObjcHeader: UnsafeFlag {} +public struct EnableLibraryEvolution: UnsafeFlag {} +struct WarnLongExpressionTypeChecking : UnsafeFlag { +internal init(milliseconds: Int) { +self.milliseconds = milliseconds +} +let milliseconds : Int +var unsafeFlagArguments: [String] { +[ +"-Xfrontend", +"-warn-long-expression-type-checking=\(milliseconds)" +] +} +} +public struct DisableIncrementalImports: UnsafeFlag {} +public struct WarnImplicitOverrides: UnsafeFlag {} +public struct ParseSil: UnsafeFlag {} +public struct ValidateClangModulesOnce: UnsafeFlag {} +public struct EmitModuleSummary: UnsafeFlag {} +public struct PrintAstDecl: UnsafeFlag {} +public struct TrackSystemDependencies: UnsafeFlag {} +public struct DisableAutolinkingRuntimeCompatibility: UnsafeFlag {} +public struct DumpTypeRefinementContexts: UnsafeFlag {} +public struct GlineTablesOnly: UnsafeFlag {} +public struct EmitSil: UnsafeFlag {} +public struct IndexFile: UnsafeFlag {} +public struct EmitSilgen: UnsafeFlag {} +public struct EmitAssembly: UnsafeFlag {} +public struct DisableClangTarget: UnsafeFlag {} +public struct DisableSandbox: UnsafeFlag {} +public struct ImportUnderlyingModule: UnsafeFlag {} +public struct Static: UnsafeFlag {} +public struct EmitClangHeaderNonmodularIncludes: UnsafeFlag {} +public struct SaveOptimizationRecord: UnsafeFlag {} +public struct RcacheCompileJob: UnsafeFlag {} +struct WarnLongFunctionBodies : UnsafeFlag { +internal init(milliseconds: Int) { +self.milliseconds = milliseconds +} +let milliseconds : Int +var unsafeFlagArguments: [String] { +[ +"-Xfrontend", +"-warn-long-function-bodies=\(milliseconds)" +] +} +} +public struct DisableAutolinkingRuntimeCompatibilityConcurrency: UnsafeFlag {} +public struct RmoduleRecovery: UnsafeFlag {} +public struct EmitIr: UnsafeFlag {} +public struct V: UnsafeFlag {} +public struct WarnConcurrency: UnsafeFlag {} +public struct O: UnsafeFlag {} +public struct EmitBc: UnsafeFlag {} +public struct EmitDigesterBaseline: UnsafeFlag {} +public struct Nostdimport: UnsafeFlag {} +public struct RmacroLoading: UnsafeFlag {} +public struct IndexIgnoreClangModules: UnsafeFlag {} +public struct EnableAutolinkingRuntimeCompatibilityBytecodeLayouts: UnsafeFlag {} +public struct Typecheck: UnsafeFlag {} +public struct EmitPcm: UnsafeFlag {} +public struct ScanDependencies: UnsafeFlag {} +public struct DisableAutolinkingRuntimeCompatibilityDynamicReplacements: UnsafeFlag {} +public struct MigrateKeepObjcVisibility: UnsafeFlag {} +public struct ApplicationExtension: UnsafeFlag {} +public struct RindexingSystemModule: UnsafeFlag {} +public struct Help: UnsafeFlag {} +public struct Version: UnsafeFlag {} +public struct DumpPcm: UnsafeFlag {} +public struct ParseAsLibrary: UnsafeFlag {} +public struct RskipExplicitInterfaceBuild: UnsafeFlag {} +public struct NoWarningsAsErrors: UnsafeFlag {} +public struct EmitObject: UnsafeFlag {} +public struct FixitAll: UnsafeFlag {} +public struct CacheDisableReplay: UnsafeFlag {} +public struct WarningsAsErrors: UnsafeFlag {} +public struct EnableIncrementalImports: UnsafeFlag {} +public struct SaveTemps: UnsafeFlag {} +public struct EnableBuiltinModule: UnsafeFlag {} +public struct EmbedBitcode: UnsafeFlag {} +public struct EmitModule: UnsafeFlag {} +public struct GdwarfTypes: UnsafeFlag {} +public struct Gnone: UnsafeFlag {} +public struct DisableActorDataRaceChecks: UnsafeFlag {} +public struct PrettyPrint: UnsafeFlag {} +public struct EmitModuleInterface: UnsafeFlag {} +public struct EnableExperimentalAdditiveArithmeticDerivation: UnsafeFlag {} +public struct DumpParse: UnsafeFlag {} +public struct EmitTbd: UnsafeFlag {} +public struct ColorDiagnostics: UnsafeFlag {} +public struct Onone: UnsafeFlag {} +public struct EmitExecutable: UnsafeFlag {} +public struct ResolveImports: UnsafeFlag {} +public struct ContinueBuildingAfterErrors: UnsafeFlag {} +public struct StaticExecutable: UnsafeFlag {} +public struct IndexIncludeLocals: UnsafeFlag {} +public struct StaticStdlib: UnsafeFlag {} +public struct DisableOnlyOneDependencyFile: UnsafeFlag {} +public struct SuppressRemarks: UnsafeFlag {} +public struct NoVerifyEmittedModuleInterface: UnsafeFlag {} +public struct CacheCompileJob: UnsafeFlag {} +public struct ProfileGenerate: UnsafeFlag {} +public struct EnableOnlyOneDependencyFile: UnsafeFlag {} +public struct PrintEducationalNotes: UnsafeFlag {} +public struct RcrossImport: UnsafeFlag {} +public struct NoColorDiagnostics: UnsafeFlag {} +public struct Osize: UnsafeFlag {} +public struct VerifyDebugInfo: UnsafeFlag {} +public struct EnableActorDataRaceChecks: UnsafeFlag {} +public struct WarnSwift3ObjcInferenceComplete: UnsafeFlag {} +public struct EnableBareSlashRegex: UnsafeFlag {} +public struct VerifyEmittedModuleInterface: UnsafeFlag {} +public struct SerializeDiagnostics: UnsafeFlag {} +public struct ProfileCoverageMapping: UnsafeFlag {} +public struct EmitLoadedModuleTrace: UnsafeFlag {} +public struct PrefixSerializedDebuggingOptions: UnsafeFlag {} +public struct EmitSupportedFeatures: UnsafeFlag {} +public struct PrintTargetInfo: UnsafeFlag {} +public struct MigratorUpdateSwift: UnsafeFlag {} +public struct EmbedBitcodeMarker: UnsafeFlag {} +public struct EmitIrgen: UnsafeFlag {} +public struct AvoidEmitModuleSourceInfo: UnsafeFlag {} +public struct SuppressWarnings: UnsafeFlag {} +public struct WarnSwift3ObjcInferenceMinimal: UnsafeFlag {} +public struct LinkObjcRuntime: UnsafeFlag {} +public struct MigratorUpdateSdk: UnsafeFlag {} +public struct EmitImportedModules: UnsafeFlag {} +public struct DisableMigratorFixits: UnsafeFlag {} +public struct RmoduleLoading: UnsafeFlag {} +public struct DriverTimeCompilation: UnsafeFlag {} +public struct RequireExplicitSendable: UnsafeFlag {} +public struct EmitDependencies: UnsafeFlag {} +public struct WholeModuleOptimization: UnsafeFlag {} +public struct EmitSibgen: UnsafeFlag {} +public struct IndexIgnoreSystemModules: UnsafeFlag {} +public struct DebugInfoStoreInvocation: UnsafeFlag {} +public struct EnableExperimentalForwardModeDifferentiation: UnsafeFlag {} +public struct Parse: UnsafeFlag {} +public struct EmitLibrary: UnsafeFlag {} +public struct RequireExplicitAvailability: UnsafeFlag {} +public struct DumpAst: UnsafeFlag {} +public struct NoWholeModuleOptimization: UnsafeFlag {} +public struct PrintAst: UnsafeFlag {} +public struct G: UnsafeFlag {} +public struct EmitSib: UnsafeFlag {} +public struct DumpUsr: UnsafeFlag {} +public struct RemoveRuntimeAsserts: UnsafeFlag {} +public struct EnableExperimentalConcisePoundFile: UnsafeFlag {} +public struct DumpTypeInfo: UnsafeFlag {} +public struct DisallowUseNewDriver: UnsafeFlag {} +extension Array: TestTargets where Element == any TestTarget { +public func appending(_ testTargets: any TestTargets) -> [any TestTarget] { +self + testTargets +} +} +public protocol TestTargets: Sequence where Element == any TestTarget { +init(_ s: S) where S.Element == any TestTarget, S: Sequence +func appending(_ testTargets: any TestTargets) -> Self +} +@resultBuilder +public enum GroupBuilder { +public static func buildPartialBlock(accumulated: [U], next: T) -> [U] +where T.Output == U { +accumulated + T.output(from: [next]) +} +public static func buildPartialBlock(first: T) -> [U] where T.Output == U { +T.output(from: [first]) +} +} +public protocol Dependency { +var targetDependency: _PackageDescription_TargetDependency { get } +} +extension String { +var packageName: String? { +split(separator: "/").last?.split(separator: ".").first.map(String.init) +} +func camelToSnakeCaseFlag(withSeparator separator: String = "-") -> String { +separator ++ enumerated() +.reduce("") { +$0 + ($1.offset > 0 && $1.element.isUppercase ? separator : "") ++ String($1.element).lowercased() +} +} +} +public struct Group { +internal init(_ name: String? = nil) { +self.name = name +} +public let name: String? +public func callAsFunction(@GroupBuilder content: () -> [T]) -> [T] { +content() +} +} +public protocol Product: _Named, GroupBuildable { +var productTargets: [Target] { get } +var productType: ProductType { get } +var libraryType: LibraryType? { get } +} +extension Product { +public var productType: ProductType { +.library +} +public var libraryType: LibraryType? { +nil +} +} +public protocol SwiftSettingsConvertible: GroupBuildable where Output == SwiftSetting { +func swiftSettings() -> [SwiftSetting] +} +extension SwiftSettingsConvertible { +public static func output(from array: [Self]) -> [SwiftSetting] { +array.flatMap { +$0.swiftSettings() +} +} +} +public protocol _Named { +var name: String { get } +} +extension _Named { +public var name: String { +"\(Self.self)" +} +} +protocol TargetDependency: Dependency, _Named { +var productName: String { get } +var package: PackageDependency { get } +var condition: TargetDependencyCondition? { get } +} +extension TargetDependency { +var productName: String { +name +} +var targetDependency: _PackageDescription_TargetDependency { +.product(name: name, package: package.packageName, condition: condition) +} +var condition: TargetDependencyCondition? { return nil } +} +public protocol SupportedPlatforms: Sequence where Element == SupportedPlatform { +init(_ s: S) where S.Element == SupportedPlatform, S: Sequence +func appending(_ platforms: any SupportedPlatforms) -> Self +} +@resultBuilder +enum TestTargetBuilder { +static func buildPartialBlock(first: [any TestTarget]) -> any TestTargets { +first +} +static func buildPartialBlock(first: any TestTarget) -> any TestTargets { +[first] +} +static func buildPartialBlock(accumulated: any TestTargets, next: any TestTarget) +-> any TestTargets +{ +accumulated + [next] +} +static func buildPartialBlock(accumulated: any TestTargets, next: any TestTargets) +-> any TestTargets +{ +accumulated.appending(next) +} +} +public protocol SwiftSettingFeature: _Named, SwiftSettingConvertible { +var featureState: FeatureState { get } +} +extension SwiftSettingFeature { +public var featureState: FeatureState { +.upcoming +} +public var setting: SwiftSetting { +featureState.swiftSetting(name: name) +} +} +extension Array: SupportedPlatforms where Element == SupportedPlatform { +public func appending(_ platforms: any SupportedPlatforms) -> Self { +self + .init(platforms) +} +} diff --git a/Sources/PackageDSLKit/Source.swift b/Sources/PackageDSLKit/Source.swift new file mode 100644 index 0000000..3b1cb6b --- /dev/null +++ b/Sources/PackageDSLKit/Source.swift @@ -0,0 +1,34 @@ +// +// Source.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public enum Source { + case index + case product(String) + case target(String) +} diff --git a/Sources/PackageDSLKit/SourceType.swift b/Sources/PackageDSLKit/SourceType.swift new file mode 100644 index 0000000..365a009 --- /dev/null +++ b/Sources/PackageDSLKit/SourceType.swift @@ -0,0 +1,50 @@ +// +// SourceType.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public enum SourceType: CaseIterable { + case product + case dependency + case testTarget + + internal func sources(from configuration: PackageDirectoryConfiguration) -> [any TypeSource] { + switch self { + case .product: return configuration.products + case .dependency: return configuration.dependencies + case .testTarget: return configuration.testTargets + } + } + internal func indexReferences(from index: Index) -> [any TypeReference] { + switch self { + case .product: return index.entries + + case .dependency: return index.dependencies + case .testTarget: return index.testTargets + } + } +} diff --git a/Sources/PackageDSLKit/StructureStrategy.swift b/Sources/PackageDSLKit/StructureStrategy.swift index a128acf..412f9fc 100644 --- a/Sources/PackageDSLKit/StructureStrategy.swift +++ b/Sources/PackageDSLKit/StructureStrategy.swift @@ -28,21 +28,27 @@ // import SwiftSyntax -// -// StructureStrategy.swift -// PackageDSLKit -// -// Created by Leo Dion on 12/31/24. -// -import os + +#if canImport(os) + import os +#elseif canImport(Logging) + import Logging +#endif // Strategy for structure parsing -class StructureStrategy: ParsingStrategy { +internal class StructureStrategy: ParsingStrategy { private var name: String? private var inheritedTypes: [String] = [] private var properties = [Property]() - func finalize() -> ParsingResult? { + + #if canImport(os) + private let logger = Logger(subsystem: "packagedsl", category: "structure") + #elseif canImport(Logging) + private let logger = Logger(label: "structure") + #endif + + internal func finalize() -> ParsingResult? { let propertyDictionary = Dictionary(grouping: self.properties) { property in property.name }.compactMapValues { properties in @@ -58,15 +64,15 @@ class StructureStrategy: ParsingStrategy { ) } - func reset() { + internal func reset() { name = nil inheritedTypes = [] properties = [] // structureData.removeAll() } - private let logger = Logger(subsystem: "packagedsl", category: "structure") - - func shouldActivate(_ node: some SyntaxProtocol, currentStrategy: ParsingStrategy?) -> Bool { + internal func shouldActivate(_ node: some SyntaxProtocol, currentStrategy: ParsingStrategy?) + -> Bool + { // Don't activate if there's already a StructureStrategy if currentStrategy is StructureStrategy { return false @@ -75,7 +81,7 @@ class StructureStrategy: ParsingStrategy { return node.is(StructDeclSyntax.self) } - func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { let visitor = PropertyVisitor(viewMode: .fixedUp) let property: Property do { @@ -88,12 +94,12 @@ class StructureStrategy: ParsingStrategy { return .skipChildren } - func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: InheritedTypeSyntax) -> SyntaxVisitorContinueKind { self.inheritedTypes.append(node.type.trimmedDescription) return .skipChildren } - func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + internal func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { self.name = node.name.text return .visitChildren } diff --git a/Sources/PackageDSLKit/SupportCodeBlock.swift b/Sources/PackageDSLKit/SupportCodeBlock.swift new file mode 100644 index 0000000..1da7023 --- /dev/null +++ b/Sources/PackageDSLKit/SupportCodeBlock.swift @@ -0,0 +1,46 @@ +// +// SupportCodeBlock.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 SwiftSyntax + +public enum SupportCodeBlock { + nonisolated(unsafe) public static var syntaxNode: any SyntaxProtocol = { + readSyntaxNode() + }() + + // swift-format-ignore NeverForceUnwrap NeverUseForceTry + private static func readSyntaxNode() -> any SyntaxProtocol { + // swiftlint:disable force_try force_unwrapping + let url = Bundle.module.url(forResource: "PackageDSL", withExtension: "lz4")! + let text = try! String(contentsOf: url) + // swiftlint:enable force_try force_unwrapping + return SourceFileSyntax(stringLiteral: text) + } +} diff --git a/Sources/PackageDSLKit/SupportedPlatform.swift b/Sources/PackageDSLKit/SupportedPlatform.swift index 412f662..fee78dc 100644 --- a/Sources/PackageDSLKit/SupportedPlatform.swift +++ b/Sources/PackageDSLKit/SupportedPlatform.swift @@ -28,11 +28,42 @@ // public struct SupportedPlatform: Hashable { - let osName: String - let version: Int + public let osName: String + public let version: Int public func hash(into hasher: inout Hasher) { hasher.combine(osName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()) hasher.combine(version) } } +extension SupportedPlatform { + public var code: String { + "SupportedPlatform.\(osName)(.v\(version)" + } + public init?(string: String) { + // Remove any whitespace and optional "SupportedPlatform." prefix + let cleanString = string.trimmingCharacters(in: .whitespaces) + .replacingOccurrences(of: "SupportedPlatform.", with: "") + + // Split into platform and version parts + // Example: "macOS(.v14)" -> ["macOS", "v14"] + guard let platformRange = cleanString.range(of: "("), + let versionEndRange = cleanString.range(of: ")", options: .backwards) + else { + return nil + } + + let osName = String(cleanString[.. + + public init(typeName: String, platforms: Set) { + self.typeName = typeName + self.platforms = platforms + } + + internal init(component: Component, requirements: Requirements) { + let platformValues = requirements.code.map(SupportedPlatform.init) + let platforms = platformValues.compactMap { $0 } + assert(platforms.count == platformValues.count) + self.init( + typeName: component.name, + platforms: .init(platforms) + ) + } + + internal static func requirements(from component: Component) -> Property? { + guard component.inheritedTypes.contains("PlatformSet") else { + return nil + } + guard let body = component.properties["body"] else { + return nil + } + guard + body.type.trimmingCharacters(in: .whitespacesAndNewlines.union(.punctuationCharacters)) + == "any SupportedPlatforms" + else { + return nil + } + guard !body.code.isEmpty else { + return nil + } + return body + } + + internal func createComponent() -> Component { + .init( + name: self.typeName, + inheritedTypes: ["PlatformSet"], + properties: [ + "body": .init( + name: "body", type: "any SupportedPlatforms", code: self.platforms.map(\.code) + ) + ] + ) + } +} diff --git a/Sources/PackageDSLKit/Target+ComponentBuildable.swift b/Sources/PackageDSLKit/Target+ComponentBuildable.swift new file mode 100644 index 0000000..53d6c29 --- /dev/null +++ b/Sources/PackageDSLKit/Target+ComponentBuildable.swift @@ -0,0 +1,63 @@ +// +// Target+ComponentBuildable.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +extension Target: ComponentBuildable { + internal static let directoryName: String = "Targets" + internal init(component: Component, requirements: Void) { + let dependencies = + component.properties["dependencies"]?.code.map { line in + DependencyRef( + name: line.filter({ character in + character.isLetter || character.isNumber + }) + ) + } ?? [] + self.init(typeName: component.name, dependencies: dependencies) + } + internal static func requirements(from component: Component) -> ()? { + guard component.inheritedTypes.contains("Target") else { + return nil + } + return () + } + + internal func createComponent() -> Component { + .init( + name: self.typeName, + inheritedTypes: ["Target"], + properties: [ + "dependencies": .init( + name: "dependencies", + type: "any Dependencies", + code: dependencies.map { $0.asFunctionCall() } + ) + ] + ) + } +} diff --git a/Sources/PackageDSLKit/TestTarget+ComponentBuildable.swift b/Sources/PackageDSLKit/TestTarget+ComponentBuildable.swift new file mode 100644 index 0000000..d6802b4 --- /dev/null +++ b/Sources/PackageDSLKit/TestTarget+ComponentBuildable.swift @@ -0,0 +1,63 @@ +// +// TestTarget+ComponentBuildable.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +extension TestTarget: ComponentBuildable { + internal static let directoryName: String = "Tests" + internal init(component: Component, requirements: Void) { + let dependencies = + component.properties["dependencies"]?.code.map { line in + DependencyRef( + name: line.filter({ character in + character.isLetter || character.isNumber + }) + ) + } ?? [] + self.init(typeName: component.name, dependencies: dependencies) + } + internal static func requirements(from component: Component) -> ()? { + guard component.inheritedTypes.contains("TestTarget") else { + return nil + } + return () + } + + internal func createComponent() -> Component { + .init( + name: self.typeName, + inheritedTypes: ["TestTarget"], + properties: [ + "dependencies": .init( + name: "dependencies", + type: "any Dependencies", + code: dependencies.map { $0.asFunctionCall() } + ) + ] + ) + } +} diff --git a/Sources/PackageDSLKit/TypeReference.swift b/Sources/PackageDSLKit/TypeReference.swift index f024a3d..b97bdc9 100644 --- a/Sources/PackageDSLKit/TypeReference.swift +++ b/Sources/PackageDSLKit/TypeReference.swift @@ -27,15 +27,30 @@ // OTHER DEALINGS IN THE SOFTWARE. // -protocol TypeReference { - var name: String { get } -} - public struct BasicTypeReference: TypeReference { public let name: String + public init(name: String) { + self.name = name + } } public typealias EntryRef = BasicTypeReference public typealias DependencyRef = BasicTypeReference public typealias TestTargetRef = BasicTypeReference public typealias SwiftSettingRef = BasicTypeReference + +public protocol TypeReference { + var name: String { get } +} + +extension TypeReference { + public func asFunctionCall() -> String { + "\(name)()" + } +} + +extension BasicTypeReference { + public init(source: TypeSource) { + self.init(name: source.typeName) + } +} diff --git a/Sources/PackageDSLKit/TypeSource.swift b/Sources/PackageDSLKit/TypeSource.swift new file mode 100644 index 0000000..a9cbb36 --- /dev/null +++ b/Sources/PackageDSLKit/TypeSource.swift @@ -0,0 +1,32 @@ +// +// TypeSource.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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. +// + +public protocol TypeSource { + var typeName: String { get } +} diff --git a/Sources/package/FileManager.swift b/Sources/package/FileManager.swift new file mode 100644 index 0000000..8efb619 --- /dev/null +++ b/Sources/package/FileManager.swift @@ -0,0 +1,60 @@ +// +// FileManager.swift +// PackageDSLKit +// +// Created by Leo Dion. +// Copyright © 2025 BrightDigit. +// +// 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 + +extension FileManager { + internal func readDirectoryContents(at path: String, fileExtension: String = "swift") throws + -> [String] + { + var contents: [String] = [] + let items = try contentsOfDirectory(atPath: path) + + // Process subdirectories (post-order) + for item in items { + let itemPath = (path as NSString).appendingPathComponent(item) + var isDirectory: ObjCBool = false + fileExists(atPath: itemPath, isDirectory: &isDirectory) + + if isDirectory.boolValue { + contents += try readDirectoryContents(at: itemPath, fileExtension: fileExtension) + } + } + + // Process files + for item in items where item.hasSuffix(".\(fileExtension)") { + let itemPath = (path as NSString).appendingPathComponent(item) + + let fileContents = try String(contentsOfFile: itemPath, encoding: .utf8) + contents.append(fileContents) + } + + return contents + } +} diff --git a/Sources/package/Package+Command.swift b/Sources/package/Package+Command.swift index 913546d..3398188 100644 --- a/Sources/package/Package+Command.swift +++ b/Sources/package/Package+Command.swift @@ -31,6 +31,8 @@ import ArgumentParser import Foundation import PackageDSLKit +// Usage + // package init // package target add "name" --product "" // package product add "name" @@ -73,6 +75,9 @@ extension Package { @Option var name: String? + @Option + var swiftVersion: String? + var packageName: String { self.name ?? self.settings.pathURL.lastPathComponent } @@ -90,12 +95,42 @@ extension Package { try self.settings.fileManager.createDirectory( at: self.settings.dslSourcesURL, withIntermediateDirectories: true, attributes: nil) } - // let currentDirectoryURL = URL(fileURLWithPath: self.settings.fileManager.currentDirectoryPath) - // let packageName = currentDirectoryURL.lastPathComponent - let packageSwiftURL = self.settings.dslSourcesURL.appending(path: "Index.swift") - // try PackageDSLKit.run() - // .write(to: packageSwiftURL, atomically: true, encoding: .utf8) + + let spec = PackageSpecifications( + products: [ + .init(typeName: "ProductA", dependencies: [DependencyRef(name: "Vapor")]) + ], + dependencies: [ + PackageDSLKit.Dependency( + typeName: "Vapor", type: [.package, .target], + dependency: ".package(url: \"https://github.com/vapor/vapor.git\", from: \"4.50.0\")", + package: nil) + ]) + let writer = PackageWriter() + try writer.write(spec, to: self.settings.dslSourcesURL) print("Written to:", "\(self.settings.pathURL.standardizedFileURL.path())") + + // swiftlint:disable:next force_try + let contents = try! settings.fileManager.readDirectoryContents( + at: self.settings.pathURL.path(), + fileExtension: "swift" + ) + + // Bundle.module + guard let exportPathURL = settings.exportPathURL else { return } + try? settings.fileManager.createDirectory( + at: exportPathURL, withIntermediateDirectories: true, attributes: nil) + let packageFileURL = exportPathURL.appendingPathComponent("Package.swift") + let strings = + [ + "// swift-tools-version: 6.0", + + SupportCodeBlock.syntaxNode.trimmedDescription, + ] + contents + let data = strings.joined(separator: "\n").data(using: .utf8)! + settings.fileManager.createFile(atPath: packageFileURL.path(), contents: data) + print(exportPathURL) + // TODO: Added Other Nessecary Files (Sources, Tests, etc...) } } } diff --git a/Sources/package/Settings.swift b/Sources/package/Settings.swift index a9711a2..9032710 100644 --- a/Sources/package/Settings.swift +++ b/Sources/package/Settings.swift @@ -31,14 +31,17 @@ import ArgumentParser import Foundation import PackageDSLKit -struct Settings: ParsableArguments, FileManaging { +internal struct Settings: ParsableArguments, FileManaging { @Option(help: .hidden) - var fileManagerType: FileManagerType = .fileManager + internal var fileManagerType: FileManagerType = .fileManager @Option - var path: String? + internal var path: String? - var pathURL: URL { + @Option + internal var exportPath: String? + + internal var pathURL: URL { if let path = self.path { return URL(fileURLWithPath: path) } else { @@ -46,7 +49,14 @@ struct Settings: ParsableArguments, FileManaging { } } - var dslSourcesURL: URL { + internal var exportPathURL: URL? { + guard let exportPath = self.exportPath else { + return nil + } + return URL(fileURLWithPath: exportPath) + } + + internal var dslSourcesURL: URL { self.pathURL.appendingPathComponent("Package") } } diff --git a/TypeReference.swift b/TypeReference.swift deleted file mode 100644 index 3e3be0f..0000000 --- a/TypeReference.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// TypeReference.swift -// Lint -// -// Created by Leo Dion on 1/1/25. -// - -protocol TypeReference { - var name: String { get } -} - -public struct BasicTypeReference: TypeReference { - public let name: String -} - -public typealias EntryRef = BasicTypeReference -public typealias DependencyRef = BasicTypeReference -public typealias TestTargetRef = BasicTypeReference -public typealias SwiftSettingRef = BasicTypeReference