@@ -257,6 +257,82 @@ extension TibsBuilder {
257
257
return result
258
258
}
259
259
260
+ #if os(Windows)
261
+ private func quoteWindowsCommandLine( _ commandLine: [ String ] ) -> String {
262
+ func quoteWindowsCommandArg( arg: String ) -> String {
263
+ // Windows escaping, adapted from Daniel Colascione's "Everyone quotes
264
+ // command line arguments the wrong way" - Microsoft Developer Blog
265
+ if !arg. contains ( where: { " \t \n \" " . contains ( $0) } ) {
266
+ return arg
267
+ }
268
+
269
+ // To escape the command line, we surround the argument with quotes. However
270
+ // the complication comes due to how the Windows command line parser treats
271
+ // backslashes (\) and quotes (")
272
+ //
273
+ // - \ is normally treated as a literal backslash
274
+ // - e.g. foo\bar\baz => foo\bar\baz
275
+ // - However, the sequence \" is treated as a literal "
276
+ // - e.g. foo\"bar => foo"bar
277
+ //
278
+ // But then what if we are given a path that ends with a \? Surrounding
279
+ // foo\bar\ with " would be "foo\bar\" which would be an unterminated string
280
+
281
+ // since it ends on a literal quote. To allow this case the parser treats:
282
+ //
283
+ // - \\" as \ followed by the " metachar
284
+ // - \\\" as \ followed by a literal "
285
+ // - In general:
286
+ // - 2n \ followed by " => n \ followed by the " metachar
287
+ // - 2n+1 \ followed by " => n \ followed by a literal "
288
+ var quoted = " \" "
289
+ var unquoted = arg. unicodeScalars
290
+
291
+ while !unquoted. isEmpty {
292
+ guard let firstNonBackslash = unquoted. firstIndex ( where: { $0 != " \\ " } ) else {
293
+ // String ends with a backslash e.g. foo\bar\, escape all the backslashes
294
+ // then add the metachar " below
295
+ let backslashCount = unquoted. count
296
+ quoted. append ( String ( repeating: " \\ " , count: backslashCount * 2 ) )
297
+ break
298
+ }
299
+ let backslashCount = unquoted. distance ( from: unquoted. startIndex, to: firstNonBackslash)
300
+ if ( unquoted [ firstNonBackslash] == " \" " ) {
301
+ // This is a string of \ followed by a " e.g. foo\"bar. Escape the
302
+ // backslashes and the quote
303
+ quoted. append ( String ( repeating: " \\ " , count: backslashCount * 2 + 1 ) )
304
+ quoted. append ( String ( unquoted [ firstNonBackslash] ) )
305
+ } else {
306
+ // These are just literal backslashes
307
+ quoted. append ( String ( repeating: " \\ " , count: backslashCount) )
308
+ quoted. append ( String ( unquoted [ firstNonBackslash] ) )
309
+ }
310
+ // Drop the backslashes and the following character
311
+ unquoted. removeFirst ( backslashCount + 1 )
312
+ }
313
+ quoted. append ( " \" " )
314
+ return quoted
315
+ }
316
+ return commandLine. map ( quoteWindowsCommandArg) . joined ( separator: " " )
317
+ }
318
+ #endif
319
+
320
+ private func escapeCommand( _ args: [ String ] ) -> String {
321
+ let escaped : String
322
+ #if os(Windows)
323
+ escaped = quoteWindowsCommandLine ( args)
324
+ #else
325
+ escaped = args. joined ( separator: " " )
326
+ #endif
327
+ return escapePath ( path: escaped)
328
+ }
329
+
330
+ private func escapePath( path: String ) -> String {
331
+ // Ninja escapes using $, this only matters during build lines
332
+ // since those are terminated by a :
333
+ return path. replacingOccurrences ( of: " : " , with: " $: " )
334
+ }
335
+
260
336
public func writeNinja< Output: TextOutputStream > ( to stream: inout Output ) {
261
337
writeNinjaHeader ( to: & stream)
262
338
stream. write ( " \n \n " )
@@ -276,23 +352,36 @@ extension TibsBuilder {
276
352
}
277
353
278
354
public func writeNinjaRules< Output: TextOutputStream > ( to stream: inout Output ) {
355
+ #if os(Windows)
356
+ let callCmd = " cmd.exe /C "
357
+ #else
358
+ let callCmd = " "
359
+ #endif
360
+ let swiftIndexCommand = callCmd + """
361
+ \( escapeCommand ( [ toolchain. swiftc. path] ) ) $in $IMPORT_PATHS -module-name $MODULE_NAME \
362
+ -index-store-path index -index-ignore-system-modules \
363
+ -output-file-map $OUTPUT_FILE_MAP \
364
+ -emit-module -emit-module-path $MODULE_PATH -emit-dependencies \
365
+ -pch-output-dir pch -module-cache-path ModuleCache \
366
+ $EMIT_HEADER $BRIDGING_HEADER $EXTRA_ARGS \
367
+ && \( toolchain. tibs. path) swift-deps-merge $out $DEP_FILES > $out.d
368
+ """
369
+ let ccIndexCommand = callCmd + """
370
+ \( escapeCommand ( [ toolchain. clang. path] ) ) -fsyntax-only $in $IMPORT_PATHS -index-store-path index \
371
+ -index-ignore-system-symbols -fmodules -fmodules-cache-path=ModuleCache \
372
+ -MMD -MF $OUTPUT_NAME.d -o $out $EXTRA_ARGS && touch $out
373
+ """
279
374
stream. write ( """
280
375
rule swiftc_index
281
376
description = Indexing Swift Module $MODULE_NAME
282
- command = \( toolchain. swiftc. path) $in $IMPORT_PATHS -module-name $MODULE_NAME \
283
- -index-store-path index -index-ignore-system-modules \
284
- -output-file-map $OUTPUT_FILE_MAP \
285
- -emit-module -emit-module-path $MODULE_PATH -emit-dependencies \
286
- -pch-output-dir pch -module-cache-path ModuleCache \
287
- $EMIT_HEADER $BRIDGING_HEADER $EXTRA_ARGS \
288
- && \( toolchain. tibs. path) swift-deps-merge $out $DEP_FILES > $out.d
377
+ command = \( swiftIndexCommand)
289
378
depfile = $out.d
290
379
deps = gcc
291
380
restat = 1 # Swift doesn't rewrite modules that haven't changed
292
381
293
382
rule cc_index
294
383
description = Indexing $in
295
- command = \( toolchain . clang . path ) -fsyntax-only $in $IMPORT_PATHS -index-store-path index -index-ignore-system-symbols -fmodules -fmodules-cache-path=ModuleCache -MMD -MF $OUTPUT_NAME.d -o $out $EXTRA_ARGS && touch $out
384
+ command = \( ccIndexCommand )
296
385
depfile = $out.d
297
386
deps = gcc
298
387
""" )
@@ -324,9 +413,9 @@ extension TibsBuilder {
324
413
}
325
414
326
415
stream. write ( """
327
- build \( outputs. joined ( separator: " " ) ) : \
328
- swiftc_index \( module. sources. map { $0. path } . joined ( separator: " " ) ) \
329
- | \( deps. joined ( separator: " " ) )
416
+ build \( escapePath ( path : outputs. joined ( separator: " " ) ) ) : \
417
+ swiftc_index \( module. sources. map { escapePath ( path : $0. path) } . joined ( separator: " " ) ) \
418
+ | \( escapePath ( path : deps. joined ( separator: " " ) ) )
330
419
MODULE_NAME = \( module. name)
331
420
MODULE_PATH = \( module. emitModulePath)
332
421
IMPORT_PATHS = \( module. importPaths. map { " -I \( $0) " } . joined ( separator: " " ) )
@@ -341,8 +430,8 @@ extension TibsBuilder {
341
430
public func writeNinjaSnippet< Output: TextOutputStream > ( for tu: TibsResolvedTarget . ClangTU , to stream: inout Output ) {
342
431
343
432
stream. write ( """
344
- build \( tu. outputPath) : \
345
- cc_index \( tu. source. path) | \( toolchain. clang. path) \( tu. generatedHeaderDep ?? " " )
433
+ build \( escapePath ( path : tu. outputPath) ) : \
434
+ cc_index \( escapePath ( path : tu. source. path) ) | \( escapePath ( path : toolchain. clang. path) ) \( tu. generatedHeaderDep ?? " " )
346
435
IMPORT_PATHS = \( tu. importPaths. map { " -I \( $0) " } . joined ( separator: " " ) )
347
436
OUTPUT_NAME = \( tu. outputPath)
348
437
EXTRA_ARGS = \( tu. extraArgs. joined ( separator: " " ) )
0 commit comments