-
Notifications
You must be signed in to change notification settings - Fork 226
/
Module.swift
134 lines (124 loc) · 6.25 KB
/
Module.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//
// Module.swift
// SourceKitten
//
// Created by JP Simard on 2015-01-07.
// Copyright (c) 2015 SourceKitten. All rights reserved.
//
import Foundation
import Yams
/// Represents source module to be documented.
public struct Module {
/// Module Name.
public let name: String
/// Compiler arguments required by SourceKit to process the source files in this Module.
public let compilerArguments: [String]
/// Source files to be documented in this Module.
public let sourceFiles: [String]
/// Documentation for this Module. Typically expensive computed property.
public var docs: [SwiftDocs] {
var fileIndex = 1
let sourceFilesCount = sourceFiles.count
return sourceFiles.sorted().compactMap {
let filename = $0.bridge().lastPathComponent
if let file = File(path: $0) {
fputs("Parsing \(filename) (\(fileIndex)/\(sourceFilesCount))\n", stderr)
fileIndex += 1
return SwiftDocs(file: file, arguments: compilerArguments)
}
fputs("Could not parse `\(filename)`. Please open an issue at https://github.com/jpsim/SourceKitten/issues with the file contents.\n", stderr)
return nil
}
}
public init?(spmName: String) {
let yamlPath = ".build/debug.yaml"
guard let yaml = try? Yams.compose(yaml: String(contentsOfFile: yamlPath, encoding: .utf8)),
let commands = yaml?["commands"]?.mapping?.values else {
fatalError("SPM build manifest does not exist at `\(yamlPath)` or does not match expected format.")
}
guard let moduleCommand = commands.first(where: { $0["module-name"]?.string == spmName }) else {
fputs("Could not find SPM module '\(spmName)'. Here are the modules available:\n", stderr)
let availableModules = commands.compactMap({ $0["module-name"]?.string })
fputs("\(availableModules.map({ " - " + $0 }).joined(separator: "\n"))\n", stderr)
return nil
}
guard let imports = moduleCommand["import-paths"]?.array(of: String.self),
let otherArguments = moduleCommand["other-args"]?.array(of: String.self),
let sources = moduleCommand["sources"]?.array(of: String.self) else {
fatalError("SPM build manifest does not match expected format.")
}
name = spmName
compilerArguments = {
var arguments = sources
arguments.append(contentsOf: ["-module-name", spmName])
arguments.append(contentsOf: otherArguments)
arguments.append(contentsOf: ["-I"])
arguments.append(contentsOf: imports)
return arguments
}()
sourceFiles = sources
}
/**
Failable initializer to create a Module by the arguments necessary pass in to `xcodebuild` to build it.
Optionally pass in a `moduleName` and `path`.
- parameter xcodeBuildArguments: The arguments necessary pass in to `xcodebuild` to build this Module.
- parameter name: Module name. Will be parsed from `xcodebuild` output if nil.
- parameter path: Path to run `xcodebuild` from. Uses current path by default.
*/
public init?(xcodeBuildArguments: [String], name: String? = nil, inPath path: String = FileManager.default.currentDirectoryPath) {
let name = name ?? moduleName(fromArguments: xcodeBuildArguments)
// Executing normal build
fputs("Running xcodebuild\n", stderr)
if let output = XcodeBuild.run(arguments: xcodeBuildArguments, inPath: path),
let arguments = parseCompilerArguments(xcodebuildOutput: output, language: .swift, moduleName: name),
let moduleName = moduleName(fromArguments: arguments) {
self.init(name: moduleName, compilerArguments: arguments)
return
}
// Check New Build System is used
fputs("Checking xcodebuild -showBuildSettings\n", stderr)
if let output = XcodeBuild.run(arguments: xcodeBuildArguments + ["-showBuildSettings"], inPath: path),
let projectTempRoot = parseProjectTempRoot(xcodebuildOutput: output),
let arguments = checkNewBuildSystem(in: projectTempRoot, moduleName: name),
let moduleName = moduleName(fromArguments: arguments) {
self.init(name: moduleName, compilerArguments: arguments)
return
}
// Executing `clean build` is a fallback.
let xcodeBuildOutput = XcodeBuild.cleanBuild(arguments: xcodeBuildArguments, inPath: path) ?? ""
guard let arguments = parseCompilerArguments(xcodebuildOutput: xcodeBuildOutput, language: .swift, moduleName: name) else {
fputs("Could not parse compiler arguments from `xcodebuild` output.\n", stderr)
fputs("Please confirm that `xcodebuild` is building a Swift module.\n", stderr)
let file = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("xcodebuild-\(NSUUID().uuidString).log")
try! xcodeBuildOutput.data(using: .utf8)?.write(to: file)
fputs("Saved `xcodebuild` log file: \(file.path)\n", stderr)
return nil
}
guard let moduleName = moduleName(fromArguments: arguments) else {
fputs("Could not parse module name from compiler arguments.\n", stderr)
return nil
}
self.init(name: moduleName, compilerArguments: arguments)
}
/**
Initializer to create a Module by name and compiler arguments.
- parameter name: Module name.
- parameter compilerArguments: Compiler arguments required by SourceKit to process the source files in this Module.
*/
public init(name: String, compilerArguments: [String]) {
self.name = name
self.compilerArguments = compilerArguments
sourceFiles = compilerArguments.filter({
$0.bridge().isSwiftFile() && $0.isFile
}).map {
return URL(fileURLWithPath: $0).resolvingSymlinksInPath().path
}
}
}
// MARK: CustomStringConvertible
extension Module: CustomStringConvertible {
/// A textual representation of `Module`.
public var description: String {
return "Module(name: \(name), compilerArguments: \(compilerArguments), sourceFiles: \(sourceFiles))"
}
}