forked from swiftlang/swift-package-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathClangModuleBuildDescription.swift
552 lines (457 loc) · 21.3 KB
/
ClangModuleBuildDescription.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2015-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Basics
import PackageGraph
import PackageLoading
import PackageModel
import struct PackageGraph.ModulesGraph
import struct PackageGraph.ResolvedModule
import struct SPMBuildCore.BuildParameters
import struct SPMBuildCore.BuildToolPluginInvocationResult
import struct SPMBuildCore.CommandPluginResult
@available(*, deprecated, renamed: "ClangModuleBuildDescription")
public typealias ClangTargetBuildDescription = ClangModuleBuildDescription
/// Build description for a Clang target i.e. C language family module.
public final class ClangModuleBuildDescription {
/// The package this target belongs to.
public let package: ResolvedPackage
/// The target described by this target.
public let target: ResolvedModule
/// The underlying clang target.
public let clangTarget: ClangModule
/// The tools version of the package that declared the target. This can
/// can be used to conditionalize semantically significant changes in how
/// a target is built.
public let toolsVersion: ToolsVersion
/// The build parameters.
let buildParameters: BuildParameters
/// The destination for while this module is built.
public var destination: BuildParameters.Destination {
self.buildParameters.destination
}
/// The build environment.
var buildEnvironment: BuildEnvironment {
buildParameters.buildEnvironment
}
/// The list of all resource files in the target, including the derived ones.
public var resources: [Resource] {
self.target.underlying.resources + self.pluginDerivedResources
}
/// The list of files in the target that were marked as ignored.
public var ignored: [AbsolutePath] {
self.target.underlying.ignored
}
/// The list of other kinds of files in the target.
public var others: [AbsolutePath] {
self.target.underlying.others
}
/// Path to the bundle generated for this module (if any).
var bundlePath: AbsolutePath? {
guard !self.resources.isEmpty else {
return .none
}
if let bundleName = target.underlying.potentialBundleName {
return self.buildParameters.bundlePath(named: bundleName)
} else {
return .none
}
}
/// The modulemap file for this target, if any.
public private(set) var moduleMap: AbsolutePath?
/// Path to the temporary directory for this target.
var tempsPath: AbsolutePath
/// The directory containing derived sources of this target.
///
/// These are the source files generated during the build.
private var derivedSources: Sources
/// These are the source files derived from plugins.
private var pluginDerivedSources: Sources
/// These are the resource files derived from plugins.
private var pluginDerivedResources: [Resource]
/// Path to the resource accessor header file, if generated.
public private(set) var resourceAccessorHeaderFile: AbsolutePath?
/// Path to the resource Info.plist file, if generated.
public private(set) var resourceBundleInfoPlistPath: AbsolutePath?
/// The objects in this target.
public var objects: [AbsolutePath] {
get throws {
try compilePaths().map(\.object)
}
}
/// Paths to the binary libraries the target depends on.
var libraryBinaryPaths: Set<AbsolutePath> = []
/// Any addition flags to be added. These flags are expected to be computed during build planning.
var additionalFlags: [String] = []
/// The filesystem to operate on.
private let fileSystem: FileSystem
/// If this target is a test target.
public var isTestTarget: Bool {
target.type == .test
}
/// The results of applying any build tool plugins to this target.
public let buildToolPluginInvocationResults: [BuildToolPluginInvocationResult]
/// Create a new target description with target and build parameters.
init(
package: ResolvedPackage,
target: ResolvedModule,
toolsVersion: ToolsVersion,
additionalFileRules: [FileRuleDescription] = [],
buildParameters: BuildParameters,
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [],
prebuildCommandResults: [CommandPluginResult] = [],
fileSystem: FileSystem,
observabilityScope: ObservabilityScope
) throws {
guard let clangTarget = target.underlying as? ClangModule else {
throw InternalError("underlying target type mismatch \(target)")
}
self.package = package
self.clangTarget = clangTarget
self.fileSystem = fileSystem
self.target = target
self.toolsVersion = toolsVersion
self.buildParameters = buildParameters
self.tempsPath = target.tempsPath(buildParameters)
self.derivedSources = Sources(paths: [], root: tempsPath.appending("DerivedSources"))
// We did not use to apply package plugins to C-family targets in prior tools-versions, this preserves the behavior.
if toolsVersion >= .v5_9 {
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
(self.pluginDerivedSources, self.pluginDerivedResources) = ModulesGraph.computePluginGeneratedFiles(
target: target,
toolsVersion: toolsVersion,
additionalFileRules: additionalFileRules,
buildParameters: buildParameters,
buildToolPluginInvocationResults: buildToolPluginInvocationResults,
prebuildCommandResults: prebuildCommandResults,
observabilityScope: observabilityScope
)
} else {
self.buildToolPluginInvocationResults = []
self.pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
self.pluginDerivedResources = []
}
// Try computing modulemap path for a C library. This also creates the file in the file system, if needed.
if target.type == .library {
// If there's a custom module map, use it as given.
if case .custom(let path) = clangTarget.moduleMapType {
self.moduleMap = path
}
// If a generated module map is needed, generate one now in our temporary directory.
else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType {
let path = tempsPath.appending(component: moduleMapFilename)
let moduleMapGenerator = ModuleMapGenerator(
targetName: clangTarget.name,
moduleName: clangTarget.c99name,
publicHeadersDir: clangTarget.includeDir,
fileSystem: fileSystem
)
try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: path)
self.moduleMap = path
}
// Otherwise there is no module map, and we leave `moduleMap` unset.
}
// Do nothing if we're not generating a bundle.
if bundlePath != nil {
try self.generateResourceAccessor()
let infoPlistPath = tempsPath.appending("Info.plist")
if try generateResourceInfoPlist(fileSystem: fileSystem, target: target, path: infoPlistPath) {
resourceBundleInfoPlistPath = infoPlistPath
}
}
}
/// An array of tuples containing filename, source, object and dependency path for each of the source in this target.
public func compilePaths()
throws -> [(filename: RelativePath, source: AbsolutePath, object: AbsolutePath, deps: AbsolutePath)]
{
let sources = [
target.sources.root: target.sources.relativePaths,
derivedSources.root: derivedSources.relativePaths,
pluginDerivedSources.root: pluginDerivedSources.relativePaths
]
return try sources.flatMap { root, relativePaths in
try relativePaths.map { source in
let path = root.appending(source)
let object = try AbsolutePath(validating: "\(source.pathString).o", relativeTo: tempsPath)
let deps = try AbsolutePath(validating: "\(source.pathString).d", relativeTo: tempsPath)
return (source, path, object, deps)
}
}
}
/// Determines the arguments needed to run `swift-symbolgraph-extract` for
/// this module.
package func symbolGraphExtractArguments() throws -> [String] {
var args = [String]()
args += ["-module-name", self.target.c99name]
args += try self.buildParameters.tripleArgs(for: self.target)
args += ["-module-cache-path", try self.buildParameters.moduleCache.pathString]
if self.clangTarget.isCXX {
args += ["-cxx-interoperability-mode=default"]
}
if let cxxLanguageStandard = self.clangTarget.cxxLanguageStandard {
args += ["-Xcc", "-std=\(cxxLanguageStandard)"]
}
args += ["-I", self.clangTarget.includeDir.pathString]
args += self.additionalFlags.asSwiftcCCompilerFlags()
// Unconditionally use clang modules with swift tools.
args += try self.clangModuleArguments().asSwiftcCCompilerFlags()
args += try self.currentModuleMapFileArguments().asSwiftcCCompilerFlags()
return args
}
/// Builds up basic compilation arguments for a source file in this target; these arguments may be different for C++
/// vs non-C++.
/// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock
/// avoidance with clients. Callers should always specify what they want based either the user's indication or on a
/// default value (possibly based on the filename suffix).
public func basicArguments(
isCXX isCXXOverride: Bool? = .none,
isC: Bool = false
) throws -> [String] {
// For now fall back on the hold semantics if the C++ nature isn't specified. This is temporary until clients
// have been updated.
let isCXX = isCXXOverride ?? clangTarget.isCXX
var args = [String]()
// Only enable ARC on macOS.
if self.buildParameters.triple.isDarwin() {
args += ["-fobjc-arc"]
}
args += try self.buildParameters.tripleArgs(for: target)
args += optimizationArguments
args += activeCompilationConditions
args += ["-fblocks"]
// Enable index store, if appropriate.
if let supported = try? ClangSupport.supportsFeature(
name: "index-unit-output-path",
toolchain: self.buildParameters.toolchain
), supported {
args += self.buildParameters.indexStoreArguments(for: target)
}
// Enable Clang module flags, if appropriate.
let triple = self.buildParameters.triple
// Swift is able to use modules on non-Darwin platforms because it injects its own module maps
// via vfs. However, nothing does that for C based compilation, and so non-Darwin platforms can't
// support clang modules.
// Note that if modules get enabled for other platforms later, they can't be used with C++ until
// https://github.com/llvm/llvm-project/issues/55980 (crash on C++17 and later) is fixed.
// clang modules aren't fully supported in C++ mode in the current Darwin SDKs.
let enableModules = triple.isDarwin() && !isCXX
if enableModules {
args += try self.clangModuleArguments()
}
// Only add the build path to the framework search path if there are binary frameworks to link against.
if !libraryBinaryPaths.isEmpty {
args += ["-F", buildParameters.buildPath.pathString]
}
args += ["-I", clangTarget.includeDir.pathString]
args += additionalFlags
args += buildParameters.sanitizers.compileCFlags()
// Add arguments from declared build settings.
args += try self.buildSettingsFlags()
// Include the path to the resource header unless the arguments are
// being evaluated for a C file. A C file cannot depend on the resource
// accessor header due to it exporting a Foundation type (`NSBundle`).
if let resourceAccessorHeaderFile, !isC {
args += ["-include", resourceAccessorHeaderFile.pathString]
}
args += self.buildParameters.toolchain.extraFlags.cCompilerFlags
// User arguments (from -Xcc) should follow generated arguments to allow user overrides
args += self.buildParameters.flags.cCompilerFlags
// Add extra C++ flags if this target contains C++ files.
if isCXX {
args += self.buildParameters.toolchain.extraFlags.cxxCompilerFlags
// User arguments (from -Xcxx) should follow generated arguments to allow user overrides
args += self.buildParameters.flags.cxxCompilerFlags
}
// Enable the correct lto mode if requested.
switch self.buildParameters.linkingParameters.linkTimeOptimizationMode {
case nil:
break
case .full:
args += ["-flto=full"]
case .thin:
args += ["-flto=thin"]
}
// rdar://117578677
// Pass -fno-omit-frame-pointer to support backtraces
// this can be removed once the backtracer uses DWARF instead of frame pointers
if let omitFramePointers = self.buildParameters.debuggingParameters.omitFramePointers {
if omitFramePointers {
args += ["-fomit-frame-pointer"]
} else {
args += ["-fno-omit-frame-pointer"]
}
}
// Pass default include paths from the toolchain.
for includeSearchPath in self.buildParameters.toolchain.includeSearchPaths {
args += ["-I", includeSearchPath.pathString]
}
// FIXME: Remove this once it becomes possible to express this dependency in a package manifest.
//
// On Linux/Android swift-corelibs-foundation depends on dispatch library which is
// currently shipped with the Swift toolchain.
if (triple.isLinux() || triple.isAndroid()) && self.package.id == .plain("swift-corelibs-foundation") {
let swiftCompilerPath = self.buildParameters.toolchain.swiftCompilerPath
let toolchainResourcesPath = swiftCompilerPath.parentDirectory
.parentDirectory
.appending(components: ["lib", "swift"])
args += ["-I", toolchainResourcesPath.pathString]
}
// suppress warnings if the package is remote
if self.package.isRemote {
args += ["-w"]
// `-w` (suppress warnings) and `-Werror` (warnings as errors) flags are mutually exclusive
if let index = args.firstIndex(of: "-Werror") {
args.remove(at: index)
}
}
return args
}
public func emitCommandLine(for filePath: AbsolutePath) throws -> [String] {
let standards = [
(clangTarget.cxxLanguageStandard, SupportedLanguageExtension.cppExtensions),
(clangTarget.cLanguageStandard, SupportedLanguageExtension.cExtensions),
]
guard let path = try self.compilePaths().first(where: { $0.source == filePath }) else {
throw BuildDescriptionError.requestedFileNotPartOfTarget(
targetName: self.target.name,
requestedFilePath: filePath
)
}
let isCXX = path.source.extension.map { SupportedLanguageExtension.cppExtensions.contains($0) } ?? false
let isC = path.source.extension.map { $0 == SupportedLanguageExtension.c.rawValue } ?? false
var args = try basicArguments(isCXX: isCXX, isC: isC)
args += ["-MD", "-MT", "dependencies", "-MF", path.deps.pathString]
// Add language standard flag if needed.
if let ext = path.source.extension {
for (standard, validExtensions) in standards {
if let standard, validExtensions.contains(ext) {
args += ["-std=\(standard)"]
}
}
}
args += ["-c", path.source.pathString, "-o", path.object.pathString]
let clangCompiler = try buildParameters.toolchain.getClangCompiler().pathString
args.insert(clangCompiler, at: 0)
return args
}
/// Returns the build flags from the declared build settings.
private func buildSettingsFlags() throws -> [String] {
let scope = buildParameters.createScope(for: target)
var flags: [String] = []
// C defines.
let cDefines = scope.evaluate(.GCC_PREPROCESSOR_DEFINITIONS)
flags += cDefines.map { "-D" + $0 }
// Header search paths.
let headerSearchPaths = scope.evaluate(.HEADER_SEARCH_PATHS)
flags += try headerSearchPaths.map {
"-I\(try AbsolutePath(validating: $0, relativeTo: target.sources.root).pathString)"
}
// Other C flags.
flags += scope.evaluate(.OTHER_CFLAGS)
// Other CXX flags.
flags += scope.evaluate(.OTHER_CPLUSPLUSFLAGS)
return flags
}
/// Optimization arguments according to the build configuration.
private var optimizationArguments: [String] {
switch buildParameters.configuration {
case .debug:
return ["-O0"]
case .release:
return ["-O2"]
}
}
/// A list of compilation conditions to enable for conditional compilation expressions.
private var activeCompilationConditions: [String] {
var compilationConditions = ["-DSWIFT_PACKAGE=1"]
switch buildParameters.configuration {
case .debug:
compilationConditions += ["-DDEBUG=1"]
case .release:
break
}
return compilationConditions
}
/// Enable Clang module flags.
private func clangModuleArguments() throws -> [String] {
let cachePath = try self.buildParameters.moduleCache.pathString
return [
"-fmodules",
"-fmodule-name=\(self.target.c99name)",
"-fmodules-cache-path=\(cachePath)",
]
}
private func currentModuleMapFileArguments() throws -> [String] {
// Pass the path to the current module's module map if present.
if let moduleMap = self.moduleMap {
return ["-fmodule-map-file=\(moduleMap.pathString)"]
}
return []
}
/// Generate the resource bundle accessor, if appropriate.
private func generateResourceAccessor() throws {
// Only generate access when we have a bundle and ObjC files.
guard let bundlePath, clangTarget.sources.containsObjcFiles else { return }
// Compute the basename of the bundle.
let bundleBasename = bundlePath.basename
let implContent =
"""
#import <Foundation/Foundation.h>
NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE() {
NSURL *bundleURL = [[[NSBundle mainBundle] bundleURL] URLByAppendingPathComponent:@"\(bundleBasename)"];
NSBundle *preferredBundle = [NSBundle bundleWithURL:bundleURL];
if (preferredBundle == nil) {
return [NSBundle bundleWithPath:@"\(bundlePath.pathString)"];
}
return preferredBundle;
}
"""
let implFileSubpath = try RelativePath(validating: "resource_bundle_accessor.m")
// Add the file to the derived sources.
derivedSources.relativePaths.append(implFileSubpath)
// Write this file out.
// FIXME: We should generate this file during the actual build.
try fileSystem.writeIfChanged(
path: derivedSources.root.appending(implFileSubpath),
string: implContent
)
let headerContent =
"""
#import <Foundation/Foundation.h>
#if __cplusplus
extern "C" {
#endif
NSBundle* \(target.c99name)_SWIFTPM_MODULE_BUNDLE(void);
#define SWIFTPM_MODULE_BUNDLE \(target.c99name)_SWIFTPM_MODULE_BUNDLE()
#if __cplusplus
}
#endif
"""
let headerFile = derivedSources.root.appending("resource_bundle_accessor.h")
self.resourceAccessorHeaderFile = headerFile
try fileSystem.writeIfChanged(
path: headerFile,
string: headerContent
)
}
}
extension ClangModuleBuildDescription {
package func dependencies(
using plan: BuildPlan
) -> [ModuleBuildDescription.Dependency] {
ModuleBuildDescription.clang(self).dependencies(using: plan)
}
package func recursiveDependencies(
using plan: BuildPlan
) -> [ModuleBuildDescription.Dependency] {
ModuleBuildDescription.clang(self).recursiveDependencies(using: plan)
}
}