1111//===----------------------------------------------------------------------===//
1212
1313import TSCBasic
14+ @_implementationOnly import Yams
1415
1516/// Resolver for a job's argument template.
16- public struct ArgsResolver {
17+ public final class ArgsResolver {
1718 /// The map of virtual path to the actual path.
18- public var pathMapping : [ VirtualPath : AbsolutePath ]
19+ public var pathMapping : [ VirtualPath : String ]
1920
2021 /// The file system used by the resolver.
2122 private let fileSystem : FileSystem
@@ -24,13 +25,16 @@ public struct ArgsResolver {
2425 // FIXME: We probably need a dedicated type for this...
2526 private let temporaryDirectory : VirtualPath
2627
28+ private let lock = Lock ( )
29+
2730 public init ( fileSystem: FileSystem , temporaryDirectory: VirtualPath ? = nil ) throws {
2831 self . pathMapping = [ : ]
2932 self . fileSystem = fileSystem
3033
3134 if let temporaryDirectory = temporaryDirectory {
3235 self . temporaryDirectory = temporaryDirectory
3336 } else {
37+ // FIXME: withTemporaryDirectory uses FileManager.default, need to create a FileSystem.temporaryDirectory api.
3438 let tmpDir : AbsolutePath = try withTemporaryDirectory ( removeTreeOnDeinit: false ) { path in
3539 // FIXME: TSC removes empty directories even when removeTreeOnDeinit is false. This seems like a bug.
3640 try fileSystem. writeFileContents ( path. appending ( component: " .keep-directory " ) ) { $0 <<< " " }
@@ -60,26 +64,80 @@ public struct ArgsResolver {
6064 return flag
6165
6266 case . path( let path) :
63- // Return the path from the temporary directory if this is a temporary file.
64- if path. isTemporary {
65- let actualPath = temporaryDirectory. appending ( component: path. name)
66- return actualPath. name
67- }
68-
69- // If there was a path mapping, use it.
70- if let actualPath = pathMapping [ path] {
71- return actualPath. pathString
67+ return try lock. withLock {
68+ return try unsafeResolve ( path: path)
7269 }
73-
74- // Otherwise, return the path.
75- return path. name
7670 case . responseFilePath( let path) :
7771 return " @ \( try resolve ( . path( path) ) ) "
7872 case let . joinedOptionAndPath( option, path) :
7973 return option + ( try resolve ( . path( path) ) )
8074 }
8175 }
8276
77+ /// Needs to be done inside of `lock`. Marked unsafe to make that more obvious.
78+ private func unsafeResolve( path: VirtualPath ) throws -> String {
79+ // If there was a path mapping, use it.
80+ if let actualPath = pathMapping [ path] {
81+ return actualPath
82+ }
83+
84+ // Return the path from the temporary directory if this is a temporary file.
85+ if path. isTemporary {
86+ let actualPath = temporaryDirectory. appending ( component: path. name)
87+ if case let . fileList( _, fileList) = path {
88+ switch fileList {
89+ case let . list( items) :
90+ try createFileList ( path: actualPath, contents: items)
91+ case let . outputFileMap( map) :
92+ try createFileList ( path: actualPath, outputFileMap: map)
93+ }
94+ }
95+ let result = actualPath. name
96+ pathMapping [ path] = result
97+ return result
98+ }
99+
100+ // Otherwise, return the path.
101+ let result = path. name
102+ pathMapping [ path] = result
103+ return result
104+ }
105+
106+ private func createFileList( path: VirtualPath , contents: [ VirtualPath ] ) throws {
107+ // FIXME: Need a way to support this for distributed build systems...
108+ if let absPath = path. absolutePath {
109+ try fileSystem. writeFileContents ( absPath) { out in
110+ for path in contents {
111+ try ! out <<< unsafeResolve ( path: path) <<< " \n "
112+ }
113+ }
114+ }
115+ }
116+
117+ private func createFileList( path: VirtualPath , outputFileMap: OutputFileMap ) throws {
118+ // FIXME: Need a way to support this for distributed build systems...
119+ if let absPath = path. absolutePath {
120+ // This uses Yams to escape and quote strings, but not to output the whole yaml file because
121+ // it sometimes outputs mappings in explicit block format (https://yaml.org/spec/1.2/spec.html#id2798057)
122+ // and the frontend (llvm) only seems to support implicit block format.
123+ try fileSystem. writeFileContents ( absPath) { out in
124+ for (input, map) in outputFileMap. entries {
125+ out <<< quoteAndEscape ( path: input) <<< " : \n "
126+ for (type, output) in map {
127+ out <<< " " <<< type. name <<< " : " <<< quoteAndEscape ( path: output) <<< " \n "
128+ }
129+ }
130+ }
131+ }
132+ }
133+
134+ private func quoteAndEscape( path: VirtualPath ) -> String {
135+ let inputNode = Node . scalar ( Node . Scalar ( try ! unsafeResolve ( path: path) , Tag ( . str) , . doubleQuoted) )
136+ let string = try ! Yams . serialize ( node: inputNode)
137+ // Remove the newline from the end
138+ return string. trimmingCharacters ( in: . whitespacesAndNewlines)
139+ }
140+
83141 private func createResponseFileIfNeeded( for job: Job , resolvedArguments: inout [ String ] , forceResponseFiles: Bool ) throws -> Bool {
84142 if forceResponseFiles ||
85143 ( job. supportsResponseFiles && !commandLineFitsWithinSystemLimits( path: resolvedArguments [ 0 ] , args: resolvedArguments) ) {
0 commit comments