Skip to content

Commit

Permalink
Improving SwiftPackage analyzing (#15)
Browse files Browse the repository at this point in the history
* Improving SwiftPackage analyzing

* Fixing some leftovers

* Removing commented out code

* Surfacing swift package warnings

* Renaming and better warning formatting

* Fixing tests

* Update README.md (#18)

* Adding a comment for clarification

---------

Co-authored-by: Alex Guretzki <[email protected]>
  • Loading branch information
goergisn and Alex Guretzki authored Sep 19, 2024
1 parent c7af217 commit b4a4010
Show file tree
Hide file tree
Showing 15 changed files with 975 additions and 345 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Alternatively you could use `xcrun swift-api-digester -diagnose-sdk` and pass th

## How it works

![image](https://github.com/user-attachments/assets/6f8b8927-d08b-487d-9d80-e5ee1b8d8302)
![image](https://github.com/user-attachments/assets/cc04d21a-06f6-42bc-8e73-4aef7af21d7a)


### Project Builder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private extension SDKDump.Element {
if isObjcAccessible { components += ["@objc"] }
if isInlinable { components += ["@inlinable"] }
if isOverride { components += ["override"] }
if declKind != .import {
if declKind != .import && declKind != .case {
if isOpen {
components += ["open"]
} else if isInternal {
Expand Down
191 changes: 178 additions & 13 deletions Sources/Helpers/Models/SwiftPackageDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,150 @@

import Foundation

struct SwiftPackageDescription: Codable {
// See: https://docs.swift.org/package-manager/PackageDescription/index.html
// See: https://github.com/swiftlang/swift-package-manager/blob/main/Sources/PackageDescription/PackageDescriptionSerialization.swift

struct SwiftPackageDescription: Codable, Equatable {

let name: String
let platforms: [Platform]
let defaultLocalization: String?
let products: [Product]

let targets: [Target]
let products: [Product]
let dependencies: [Dependency]

let toolsVersion: String

var warnings = [String]()

init(
defaultLocalization: String?,
name: String,
platforms: [Platform] = [],
products: [Product] = [],
targets: [Target] = [],
dependencies: [Dependency] = [],
toolsVersion: String,
warnings: [String] = []
) {
self.defaultLocalization = defaultLocalization
self.name = name
self.platforms = platforms
self.products = products
self.targets = targets
self.dependencies = dependencies
self.toolsVersion = toolsVersion
self.warnings = warnings
}

enum CodingKeys: String, CodingKey {
case defaultLocalization = "default_localization"
case name
case platforms
case products
case targets
case dependencies
case toolsVersion = "tools_version"
}
}

extension SwiftPackageDescription {

struct Product: Codable {
struct Platform: Codable, Equatable, Hashable {

let name: String
let version: String
}
}

extension SwiftPackageDescription.Platform: CustomStringConvertible {
var description: String {
"\(name)(\(version))"
}
}

extension SwiftPackageDescription {

struct Product: Codable, Equatable, Hashable {

// TODO: Add `rule` property

let name: String
let targets: [String]
}
}

extension SwiftPackageDescription.Product: CustomStringConvertible {
var description: String {
let targetsDescription = targets.map { "\"\($0)\"" }.joined(separator: ", ")
return ".library(name: \"\(name)\", targets: [\(targetsDescription)])"
}
}

extension SwiftPackageDescription {

struct Dependency: Codable, Equatable {

let identity: String
let requirement: Requirement
let type: String
let url: String?
}
}

extension SwiftPackageDescription.Dependency: CustomStringConvertible {

var description: String {
var description = ".package("

var fields = [String]()

if let url {
fields += ["url: \"\(url)\""]
}

fields += [requirement.description]

description += fields.joined(separator: ", ")

description += ")"
return description
}
}

extension SwiftPackageDescription.Dependency {

struct Requirement: Codable, Equatable {

// TODO: Which other requirements exist?

let exact: [String]?
}
}

extension SwiftPackageDescription.Dependency.Requirement: CustomStringConvertible {

var description: String {
if let exactVersion = exact?.first {
return "exact: \"\(exactVersion)\""
}

return "UNKNOWN_REQUIREMENT"
}
}

extension SwiftPackageDescription {

struct Target: Codable {
struct Target: Codable, Equatable {

enum ModuleType: String, Codable {
enum ModuleType: String, Codable, Equatable {
case swiftTarget = "SwiftTarget"
case binaryTarget = "BinaryTarget"
case clangTarget = "ClangTarget"
}

enum TargetType: String, Codable {
enum TargetType: String, Codable, Equatable {
case library = "library"
case binary = "binary"
case test = "test"
Expand All @@ -53,30 +160,88 @@ extension SwiftPackageDescription {
let path: String
let moduleType: ModuleType

/// `.product(name: ...)` dependency
let productDependencies: [String]?
let productMemberships: [String]?
let sources: [String]
/// `.target(name: ...) dependency
let targetDependencies: [String]?

let resources: [Resource]?
// Ignoring following properties for now as they are not handled in the `PackageAnalyzer`
// and thus would produce changes that are not visible
//
// let productMemberships: [String]?
// let sources: [String]
// let resources: [Resource]?

init(
name: String,
type: TargetType,
path: String,
moduleType: ModuleType,
productDependencies: [String]? = nil,
targetDependencies: [String]? = nil
) {
self.name = name
self.type = type
self.path = path
self.moduleType = moduleType
self.productDependencies = productDependencies
self.targetDependencies = targetDependencies
}

enum CodingKeys: String, CodingKey {
case moduleType = "module_type"
case name
case productDependencies = "product_dependencies"
case productMemberships = "product_memberships"
case sources
case targetDependencies = "target_dependencies"
case type
case path
case resources
}
}
}

extension SwiftPackageDescription.Target.TargetType: CustomStringConvertible {
var description: String {
switch self {
case .binary: "binaryTarget"
case .library: "target"
case .test: "testTarget"
}
}
}

extension SwiftPackageDescription.Target: CustomStringConvertible {

var description: String {
var description = ".\(type.description)(name: \"\(name)\""

var dependencyDescriptions = [String]()

if let targetDependenciesDescriptions = targetDependencies?.map({ ".target(name: \"\($0)\")" }) {
dependencyDescriptions += targetDependenciesDescriptions
}

if let productDependenciesDescriptions = productDependencies?.map({ ".product(name: \"\($0)\", ...)" }) {
dependencyDescriptions += productDependenciesDescriptions
}

if !dependencyDescriptions.isEmpty {
// `, dependencies: [.target(name: ...), .target(name: ...), .product(name: ...), ...]`
description += ", dependencies: [\(dependencyDescriptions.joined(separator: ", "))]"
}

description += ", path: \"\(path)\""

description += ")"

return description
}
}

extension SwiftPackageDescription.Target {

struct Resource: Codable {
struct Resource: Codable, Equatable {

// TODO: Add `rule` property

let path: String
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import Foundation

enum PackageFileHelperError: LocalizedError {
enum SwiftPackageFileHelperError: LocalizedError {
case packageDescriptionError(_ description: String)
case couldNotGeneratePackageDescription
case couldNotConsolidateTargetsInPackageFile
Expand All @@ -23,7 +23,7 @@ enum PackageFileHelperError: LocalizedError {
}
}

struct PackageFileHelper {
struct SwiftPackageFileHelper {

private let fileHandler: FileHandling
private let xcodeTools: XcodeTools
Expand Down Expand Up @@ -89,7 +89,7 @@ struct PackageFileHelper {

// MARK: Generate Package Description

private extension PackageFileHelper {
private extension SwiftPackageFileHelper {

func generatePackageDescription(at projectDirectoryPath: String) throws -> SwiftPackageDescription {

Expand All @@ -109,7 +109,7 @@ private extension PackageFileHelper {
// That we have to get rid of first to generate the description object

if firstLine.starts(with: errorTag) {
throw PackageFileHelperError.packageDescriptionError(result)
throw SwiftPackageFileHelperError.packageDescriptionError(result)
}

if firstLine.starts(with: warningTag) {
Expand All @@ -120,23 +120,29 @@ private extension PackageFileHelper {
warnings += [warning]
}

if firstLine.starts(with: "{"),
let packageDescriptionData = packageDescriptionLines.joined(separator: newLine).data(using: .utf8) {
var packageDescription = try JSONDecoder().decode(SwiftPackageDescription.self, from: packageDescriptionData)
packageDescription.warnings = warnings
return packageDescription
if
firstLine.starts(with: "{"),
let packageDescriptionData = packageDescriptionLines.joined(separator: newLine).data(using: .utf8)
{
return try decodePackageDescription(from: packageDescriptionData, warnings: warnings)
}

packageDescriptionLines.removeFirst()
}

throw PackageFileHelperError.couldNotGeneratePackageDescription
throw SwiftPackageFileHelperError.couldNotGeneratePackageDescription
}

func decodePackageDescription(from packageDescriptionData: Data, warnings: [String]) throws -> SwiftPackageDescription {
var packageDescription = try JSONDecoder().decode(SwiftPackageDescription.self, from: packageDescriptionData)
packageDescription.warnings = warnings
return packageDescription
}
}

// MARK: Update Package Content

private extension PackageFileHelper {
private extension SwiftPackageFileHelper {

/// Generates a library entry from the name and available target names to be inserted into the `Package.swift` file
func consolidatedLibraryEntry(
Expand All @@ -162,7 +168,7 @@ private extension PackageFileHelper {
if let productsRange = packageContent.range(of: "products: [", options: .caseInsensitive) {
updatedContent.insert(contentsOf: consolidatedEntry, at: productsRange.upperBound)
} else {
throw PackageFileHelperError.couldNotConsolidateTargetsInPackageFile
throw SwiftPackageFileHelperError.couldNotConsolidateTargetsInPackageFile
}
return updatedContent
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Pipeline/Modules/ABIGenerator/ABIGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct ABIGenerator: ABIGenerating {

private let shell: ShellHandling
private let xcodeTools: XcodeTools
private let packageFileHelper: PackageFileHelper
private let packageFileHelper: SwiftPackageFileHelper
private let fileHandler: FileHandling
private let logger: Logging?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct PackageABIGenerator: ABIGenerating {

let fileHandler: FileHandling
let xcodeTools: XcodeTools
let packageFileHelper: PackageFileHelper
let packageFileHelper: SwiftPackageFileHelper
let logger: Logging?

func generate(
Expand Down
4 changes: 2 additions & 2 deletions Sources/Pipeline/Modules/ProjectBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ private extension ProjectBuilder {
// If a project scheme was provided we just try to build it
schemeToBuild = scheme
} else {
// Creating an `.library(name: "_allTargets", targets: [ALL_TARGETS])`
// Creating an `.library(name: "_AllTargets", targets: [ALL_TARGETS])`
// so we only have to build once and then can generate ABI files for every module from a single build
schemeToBuild = "_AllTargets"
let packageFileHelper = PackageFileHelper(fileHandler: fileHandler, xcodeTools: xcodeTools)
let packageFileHelper = SwiftPackageFileHelper(fileHandler: fileHandler, xcodeTools: xcodeTools)
try packageFileHelper.preparePackageWithConsolidatedLibrary(named: schemeToBuild, at: projectDirectoryPath)
}

Expand Down
Loading

0 comments on commit b4a4010

Please sign in to comment.