-
Notifications
You must be signed in to change notification settings - Fork 80
/
Copy pathFilePathResolver.swift
145 lines (118 loc) · 7.41 KB
/
FilePathResolver.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
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 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
//
//===----------------------------------------------------------------------===//
public import SWBUtil
public import SWBMacro
import SWBProtocol
/// A FilePathResolver is used to resolve the absolute paths for Reference objects.
public final class FilePathResolver: Sendable
{
/// The MacroEvaluationScope used to expand source trees.
private let scope: MacroEvaluationScope
/// The evaluated value $(PROJECT_DIR) which is used as the default backstop for source trees which evaluate to relative paths, or to nothing at all.
private let projectDir: Path
/// Private table used to cache paths already evaluated for FileGroups.
private let fileGroupCache = Cache<FileGroup, Path>()
/// Private table use to cache paths already evaluated for build settings in the MacroEvaluationScope.
private let buildSettingCache = Cache<MacroDeclaration, Path>()
public init(scope: MacroEvaluationScope, projectDir: Path? = nil)
{
self.scope = scope
self.projectDir = projectDir ?? scope.evaluate(BuiltinMacros.PROJECT_DIR)
if let projectDir {
precondition(projectDir.isAbsolute, "projectDir must be an absolute path but it is \(projectDir)")
} else {
precondition(self.projectDir.isAbsolute, "$(PROJECT_DIR) must be an absolute path but it is \(self.projectDir)")
}
}
/// Resolve and return the absolute path for a Reference.
public func resolveAbsolutePath(_ reference: Reference) -> Path
{
// If this is a FileGroup, look it up in the cache.
if let fileGroup = reference as? FileGroup
{
return fileGroupCache.getOrInsert(fileGroup) {
return computeAbsolutePath(fileGroup)
}
}
return computeAbsolutePath(reference)
}
/// Computes the absolute path for a Reference and returns it. This method does no memoizing of the result, so resolveAbsolutePath() is the preferred client method.
private func computeAbsolutePath(_ reference: Reference) -> Path
{
// Evaluate the path for the reference.
switch reference
{
case let groupTreeReference as GroupTreeReference:
// A GroupTreeReference has a parent, source tree, and path. Use those to resolve it.
// First evaluate the reference's path. If it is absolute, then we can return it immediately.
let evaluatedRefPath = self.resolvePath(groupTreeReference.path, forReference: groupTreeReference)
if evaluatedRefPath.isAbsolute { return evaluatedRefPath.normalize() }
// Now get absolute path to use as the source tree.
let sourceTreePath = self.resolveSourceTree(groupTreeReference.sourceTree, forReference: groupTreeReference)
// The reference's path is relative, to append it to the source tree.
return sourceTreePath.join(evaluatedRefPath, normalize: true)
case let productReference as ProductReference:
// In general a product reference's path is $(TARGET_BUILD_DIR) followed by its name and in install-style builds it is $(BUILT_PRODUCTS_DIR) followed by its name.
let sourceTreePath: Path
if scope.evaluate(BuiltinMacros.DEPLOYMENT_LOCATION) {
sourceTreePath = resolveSourceTree(.buildSetting("BUILT_PRODUCTS_DIR"), forReference: reference)
} else {
sourceTreePath = resolveSourceTree(.buildSetting("TARGET_BUILD_DIR"), forReference: reference)
}
// FIXME: We are relying on the product reference name being constant here. This is currently true, given how our path resolver works, but it is possible to construct an Xcode project for which this doesn't work (Xcode doesn't, however, handle that situation very well). We should resolve this: <rdar://problem/29410050> Swift Build doesn't support product references with non-constant basenames
return sourceTreePath.join(productReference.name)
default:
preconditionFailure("Cannot resolve the path for a \(type(of: reference))")
}
}
/// Resolve and return the absolute path for a Reference's source tree. This method will always return an absolute path even if sourceTree is .Absolute, so that it can be prepended to a path even if that path should be absolute but is not. So the caller should use the result appropriately based on what it will be prepended to.
func resolveSourceTree(_ sourceTree: SourceTree, forReference reference: Reference) -> Path
{
// Resolve the source tree.
// Note: If the Reference's path is relative, then we want to always append it to something, so we always compute a value for the source tree here, defaulting to $(PROJECT_DIR) if no other value can be determined
switch sourceTree
{
case .absolute:
// Return the value of $(PROJECT_DIR) as the source root in case the Reference's path is not actually absolute.
return projectDir
case .groupRelative:
// The reference is group-relative, so the source tree is the path of the Reference's parent.
let parentPath = (reference as? GroupTreeReference)?.parent.map(resolveAbsolutePath)
// If we couldn't get a path for the parent (e.g., because there isn't a parent), then default to the value of $(PROJECT_DIR).
return parentPath ?? projectDir
// Replace SOURCE_ROOT with PROJECT_DIR during reference resolution. (When is SOURCE_ROOT not equal to PROJECT_DIR? -> When SRCROOT is overridden on the command line.) This substitution does not apply to build settings expansion - it only applies to the reference SourceTree.
case .buildSetting(BuiltinMacros.SOURCE_ROOT.name):
return projectDir
case .buildSetting(let buildSetting):
guard !buildSetting.isEmpty else { return projectDir }
// Look up the build setting declaration in the scope's namespace. If it is not defined, then we return the value of $(PROJECT_DIR).
guard let buildSettingDecl = scope.table.namespace.lookupMacroDeclaration(buildSetting) else { return projectDir }
// Get or compute the value for the setting.
return buildSettingCache.getOrInsert(buildSettingDecl) {
let pathString: String = scope.evaluateAsString(buildSettingDecl)
var pathForSetting = Path(pathString)
// If the path for the setting is not absolute, then we append it to the value of $(PROJECT_DIR).
if !pathForSetting.isAbsolute
{
pathForSetting = projectDir.join(pathForSetting)
}
// Use the value as the result.
return pathForSetting.normalize()
}
}
}
/// Resolve and return the path for a Reference's path property, evaluating any build settings in it if necessary.
func resolvePath(_ refPath: MacroStringExpression, forReference reference: Reference) -> Path
{
return Path(scope.evaluate(refPath))
}
}