Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scan Subcommand #50

Merged
merged 6 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
[![Xcode Build](https://github.com/FelixHerrmann/swift-package-list/actions/workflows/xcodebuild.yml/badge.svg)](https://github.com/FelixHerrmann/swift-package-list/actions/workflows/xcodebuild.yml)
[![SwiftLint](https://github.com/FelixHerrmann/swift-package-list/actions/workflows/swiftlint.yml/badge.svg)](https://github.com/FelixHerrmann/swift-package-list/actions/workflows/swiftlint.yml)

A command-line tool to generate a JSON, PLIST, Settings.bundle or PDF file with all used SPM-dependencies of an Xcode project or workspace.
A command-line tool to get all used SPM-dependencies of an Xcode project or workspace.

The output includes all the `Package.resolved` informations and the license from the checkouts.
You can also generate a JSON, PLIST, Settings.bundle or PDF file.

This includes all the `Package.resolved` informations and the license from the checkouts.
Additionally there is a Swift Package to read the generated package-list file from the application's bundle with a top-level function or pre-build UI.


Expand All @@ -32,20 +34,36 @@ Clone or download this repository and run `make install`, `make update` or `make

### Usage

Open the terminal and run `swift-package-list <project-path>` with the path to the `.xcodeproj` or `.xcworkspace` file you want to generate the list from.
#### Scan Command

Open the terminal and run `swift-package-list scan <project-path>` with the path to the `.xcodeproj` or `.xcworkspace` file you want to get the JSON output from.

In addition to that you can specify the following options:

| Option | Description |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| -d, --derived-data-path \<derived-data-path\> | The path to your DerivedData-folder. (default: ~/Library/Developer/Xcode/DerivedData) |
| -s, --source-packages-path \<source-packages-path\> | The path to a custom SourcePackages-folder. |
| --requires-license | Will skip the packages without a license-file. |
| --version | Show the version. |
| -h, --help | Show help information. |

#### Generate Command

Open the terminal and run `swift-package-list generate <project-path>` with the path to the `.xcodeproj` or `.xcworkspace` file you want to generate the list from.

In addition to that you can specify the following options:

| Option | Description |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| -d, --derived-data-path \<derived-data-path\> | The path to your DerivedData-folder. (default: ~/Library/Developer/Xcode/DerivedData) |
| -s, --source-packages-path <source-packages-path> | The path to a custom SourcePackages-folder. |
| -o, --output-path \<output-path\> | The path where the package-list file will be stored. (default: ~/Desktop) |
| -f, --file-type \<file-type\> | The file type of the generated package-list file. Available options are json, plist, settings-bundle and pdf. (default: json) |
| -c, --custom-file-name <custom-file-name> | A custom filename to be used instead of the default ones. |
| --requires-license | Will skip the packages without a license-file. |
| --version | Show the version. |
| -h, --help | Show help information. |
| Option | Description |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| -d, --derived-data-path \<derived-data-path\> | The path to your DerivedData-folder. (default: ~/Library/Developer/Xcode/DerivedData) |
| -s, --source-packages-path \<source-packages-path\> | The path to a custom SourcePackages-folder. |
| -o, --output-path \<output-path\> | The path where the package-list file will be stored. (default: ~/Desktop) |
| -f, --file-type \<file-type\> | The file type of the generated package-list file. Available options are json, plist, settings-bundle and pdf. (default: json) |
| -c, --custom-file-name \<custom-file-name\> | A custom filename to be used instead of the default ones. |
| --requires-license | Will skip the packages without a license-file. |
| --version | Show the version. |
| -h, --help | Show help information. |

### Run Script Phase

Expand All @@ -56,7 +74,7 @@ You can easily set up a Run Script Phase in your target of your Xcode project to
```shell
if command -v swift-package-list &> /dev/null; then
OUTPUT_PATH=$SOURCE_ROOT/$TARGETNAME
swift-package-list "$PROJECT_FILE_PATH" --output-path "$OUTPUT_PATH" --requires-license
swift-package-list generate "$PROJECT_FILE_PATH" --output-path "$OUTPUT_PATH" --requires-license
else
echo "warning: swift-package-list not installed"
fi
Expand All @@ -78,7 +96,7 @@ you just need a slightly modified script for the Run Script Phase:
if command -v swift-package-list &> /dev/null; then
OUTPUT_PATH=$SOURCE_ROOT/$TARGETNAME
WORKSPACE_FILE_PATH=${PROJECT_FILE_PATH%.xcodeproj}.xcworkspace
swift-package-list "$WORKSPACE_FILE_PATH" --output-path "$OUTPUT_PATH" --requires-license
swift-package-list generate "$WORKSPACE_FILE_PATH" --output-path "$OUTPUT_PATH" --requires-license
else
echo "warning: swift-package-list not installed"
fi
Expand Down
61 changes: 61 additions & 0 deletions Sources/SwiftPackageListCommand/SwiftPackageList+Generate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// SwiftPackageList+Generate.swift
// SwiftPackageListCommand
//
// Created by Felix Herrmann on 21.05.23.
//

import Foundation
import ArgumentParser
import SwiftPackageListCore

extension SwiftPackageList {
struct Generate: ParsableCommand {
static var configuration: CommandConfiguration {
return CommandConfiguration(abstract: "Generate the specified output for all packages.")
}

@OptionGroup var options: Options

@Option(name: .shortAndLong, help: "The path where the package-list file will be stored.")
var outputPath = "\(NSHomeDirectory())/Desktop"

// swiftlint:disable:next line_length
@Option(name: .shortAndLong, help: "The file type of the generated package-list file. Available options are json, plist, settings-bundle and pdf.")
var fileType: FileType = .json

@Option(name: .shortAndLong, help: "A custom filename to be used instead of the default ones.")
var customFileName: String?

mutating func run() throws {
guard let project = Project(path: options.projectPath) else {
throw ValidationError("The project file is not an Xcode Project or Workspace")
}

guard FileManager.default.fileExists(atPath: project.packageResolvedFileURL.path) else {
throw CleanExit.message("This project has no Swift-Package dependencies")
}

let checkoutsDirectory: URL
if let sourcePackagesPath = options.sourcePackagesPath {
checkoutsDirectory = URL(fileURLWithPath: sourcePackagesPath).appendingPathComponent("checkouts")
} else {
guard let buildDirectory = try project.buildDirectory(in: options.derivedDataPath) else {
throw RuntimeError("No build-directory found in your DerivedData-folder")
}
checkoutsDirectory = buildDirectory.appendingPathComponent("/SourcePackages/checkouts")
}
guard FileManager.default.fileExists(atPath: checkoutsDirectory.path) else {
throw RuntimeError("No checkouts-directory found in your SourcePackages-folder")
}

let packageResolved = try PackageResolved(at: project.packageResolvedFileURL)
let packages = try packageResolved.packages(in: checkoutsDirectory, requiresLicense: options.requiresLicense)

let outputURL = fileType.outputURL(at: outputPath, customFileName: customFileName)
let outputGenerator = fileType.outputGenerator(outputURL: outputURL, packages: packages, project: project)
try outputGenerator.generateOutput()
throw CleanExit.message("Generated \(outputURL.path)")
}
}
}
25 changes: 25 additions & 0 deletions Sources/SwiftPackageListCommand/SwiftPackageList+Options.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// SwiftPackageList+Options.swift
// SwiftPackageListCommand
//
// Created by Felix Herrmann on 21.05.23.
//

import Foundation
import ArgumentParser

extension SwiftPackageList {
struct Options: ParsableArguments {
@Argument(help: "The path to your .xcodeproj or .xcworkspace file.")
var projectPath: String

@Option(name: .shortAndLong, help: "The path to your DerivedData-folder.")
var derivedDataPath = "\(NSHomeDirectory())/Library/Developer/Xcode/DerivedData"

@Option(name: .shortAndLong, help: "The path to a custom SourcePackages-folder.")
var sourcePackagesPath: String?

@Flag(help: "Will skip the packages without a license-file.")
var requiresLicense = false
}
}
52 changes: 52 additions & 0 deletions Sources/SwiftPackageListCommand/SwiftPackageList+Scan.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// SwiftPackageList+Scan.swift
// SwiftPackageListCommand
//
// Created by Felix Herrmann on 21.05.23.
//

import Foundation
import ArgumentParser
import SwiftPackageListCore

extension SwiftPackageList {
struct Scan: ParsableCommand {
static var configuration: CommandConfiguration {
return CommandConfiguration(abstract: "Print all packages as JSON to the console.")
}

@OptionGroup var options: Options

mutating func run() throws {
guard let project = Project(path: options.projectPath) else {
throw ValidationError("The project file is not an Xcode Project or Workspace")
}

guard FileManager.default.fileExists(atPath: project.packageResolvedFileURL.path) else {
throw CleanExit.message("This project has no Swift-Package dependencies")
}

let checkoutsDirectory: URL
if let sourcePackagesPath = options.sourcePackagesPath {
checkoutsDirectory = URL(fileURLWithPath: sourcePackagesPath).appendingPathComponent("checkouts")
} else {
guard let buildDirectory = try project.buildDirectory(in: options.derivedDataPath) else {
throw RuntimeError("No build-directory found in your DerivedData-folder")
}
checkoutsDirectory = buildDirectory.appendingPathComponent("/SourcePackages/checkouts")
}
guard FileManager.default.fileExists(atPath: checkoutsDirectory.path) else {
throw RuntimeError("No checkouts-directory found in your SourcePackages-folder")
}

let packageResolved = try PackageResolved(at: project.packageResolvedFileURL)
let packages = try packageResolved.packages(in: checkoutsDirectory, requiresLicense: options.requiresLicense)

let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(packages)
let jsonString = String(decoding: jsonData, as: UTF8.self)
print(jsonString)
}
}
}
20 changes: 20 additions & 0 deletions Sources/SwiftPackageListCommand/SwiftPackageList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// SwiftPackageList.swift
// SwiftPackageListCommand
//
// Created by Felix Herrmann on 01.11.21.
//

import Foundation
import ArgumentParser

@main
struct SwiftPackageList: ParsableCommand {
static var configuration: CommandConfiguration {
return CommandConfiguration(
discussion: "A command-line tool to get all used SPM-dependencies of an Xcode project or workspace.",
version: "3.0.0",
subcommands: [Scan.self, Generate.self]
)
}
}
71 changes: 0 additions & 71 deletions Sources/SwiftPackageListCommand/SwiftPackageListCommand.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,42 @@ final class SwiftPackageListCommandTests: XCTestCase {
XCTAssertNotNil(Int(minorVersionNumber))
XCTAssertNotNil(Int(patchVersionNumber))
}

func testScanSubcommandExecution() throws {
let swiftPackageListBinary = productsDirectory.appendingPathComponent("swift-package-list")

let process = Process()
process.executableURL = swiftPackageListBinary
process.arguments = ["scan", "--help"]

let pipe = Pipe()
process.standardOutput = pipe

try process.run()
process.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)

XCTAssertTrue(output.contains("USAGE: swift-package-list scan"))
}

func testGenerateSubcommandExecution() throws {
let swiftPackageListBinary = productsDirectory.appendingPathComponent("swift-package-list")

let process = Process()
process.executableURL = swiftPackageListBinary
process.arguments = ["generate", "--help"]

let pipe = Pipe()
process.standardOutput = pipe

try process.run()
process.waitUntilExit()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)

XCTAssertTrue(output.contains("USAGE: swift-package-list generate"))
}
}