Skip to content

Sync latest TSC changes from main #2965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions swift-tools-support-core/Sources/TSCBasic/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public struct AbsolutePath: Hashable {
/// True if the path is the root directory.
public var isRoot: Bool {
#if os(Windows)
return _impl.string.withCString(encodedAs: UTF16.self, PathIsRootW)
return _impl.string.withCString(encodedAs: UTF16.self, PathCchIsRoot)
#else
return _impl == PathImpl.root
#endif
Expand Down Expand Up @@ -243,14 +243,6 @@ public struct RelativePath: Hashable {
return _impl.basename
}

/// Returns the basename without the extension.
public var basenameWithoutExt: String {
if let ext = self.extension {
return String(basename.dropLast(ext.count + 1))
}
return basename
}

/// Suffix (including leading `.` character) if any. Note that a basename
/// that starts with a `.` character is not considered a suffix, nor is a
/// trailing `.` character.
Expand Down
86 changes: 65 additions & 21 deletions swift-tools-support-core/Sources/TSCBasic/Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,11 @@ public final class Process: ObjectIdentifierProtocol {
}
}

/// Launch the subprocess.
public func launch() throws {
/// Launch the subprocess. Returns a WritableByteStream object that can be used to communicate to the process's
/// stdin. If needed, the stream can be closed using the close() API. Otherwise, the stream will be closed
/// automatically.
@discardableResult
public func launch() throws -> WritableByteStream {
precondition(arguments.count > 0 && !arguments[0].isEmpty, "Need at least one argument to launch the process.")
precondition(!launched, "It is not allowed to launch the same process object again.")

Expand All @@ -351,12 +354,15 @@ public final class Process: ObjectIdentifierProtocol {
throw Process.Error.missingExecutableProgram(program: executable)
}

#if os(Windows)
#if os(Windows)
_process = Foundation.Process()
_process?.arguments = Array(arguments.dropFirst()) // Avoid including the executable URL twice.
_process?.executableURL = executablePath.asURL
_process?.environment = environment

let stdinPipe = Pipe()
_process?.standardInput = stdinPipe

if outputRedirection.redirectsOutput {
let stdoutPipe = Pipe()
let stderrPipe = Pipe()
Expand All @@ -379,6 +385,8 @@ public final class Process: ObjectIdentifierProtocol {
}

try _process?.run()

return stdinPipe.fileHandleForWriting
#else
// Initialize the spawn attributes.
#if canImport(Darwin) || os(Android)
Expand Down Expand Up @@ -453,14 +461,17 @@ public final class Process: ObjectIdentifierProtocol {
#endif
}

// Workaround for https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=89e435f3559c53084498e9baad22172b64429362
// Change allowing for newer version of glibc
guard let devNull = strdup("/dev/null") else {
throw SystemError.posix_spawn(0, arguments)
}
defer { free(devNull) }
// Open /dev/null as stdin.
posix_spawn_file_actions_addopen(&fileActions, 0, devNull, O_RDONLY, 0)
var stdinPipe: [Int32] = [-1, -1]
try open(pipe: &stdinPipe)

let stdinStream = try LocalFileOutputByteStream(filePointer: fdopen(stdinPipe[1], "wb"), closeOnDeinit: true)

// Dupe the read portion of the remote to 0.
posix_spawn_file_actions_adddup2(&fileActions, stdinPipe[0], 0)

// Close the other side's pipe since it was dupped to 0.
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[0])
posix_spawn_file_actions_addclose(&fileActions, stdinPipe[1])

var outputPipe: [Int32] = [-1, -1]
var stderrPipe: [Int32] = [-1, -1]
Expand All @@ -471,7 +482,7 @@ public final class Process: ObjectIdentifierProtocol {
// Open the write end of the pipe.
posix_spawn_file_actions_adddup2(&fileActions, outputPipe[1], 1)

// Close the other ends of the pipe.
// Close the other ends of the pipe since they were dupped to 1.
posix_spawn_file_actions_addclose(&fileActions, outputPipe[0])
posix_spawn_file_actions_addclose(&fileActions, outputPipe[1])

Expand All @@ -483,7 +494,7 @@ public final class Process: ObjectIdentifierProtocol {
try open(pipe: &stderrPipe)
posix_spawn_file_actions_adddup2(&fileActions, stderrPipe[1], 2)

// Close the other ends of the pipe.
// Close the other ends of the pipe since they were dupped to 2.
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[0])
posix_spawn_file_actions_addclose(&fileActions, stderrPipe[1])
}
Expand All @@ -500,11 +511,14 @@ public final class Process: ObjectIdentifierProtocol {
throw SystemError.posix_spawn(rv, arguments)
}

// Close the local read end of the input pipe.
try close(fd: stdinPipe[0])

if outputRedirection.redirectsOutput {
let outputClosures = outputRedirection.outputClosures

// Close the write end of the output pipe.
try close(fd: &outputPipe[1])
// Close the local write end of the output pipe.
try close(fd: outputPipe[1])

// Create a thread and start reading the output on it.
var thread = Thread { [weak self] in
Expand All @@ -517,8 +531,8 @@ public final class Process: ObjectIdentifierProtocol {

// Only schedule a thread for stderr if no redirect was requested.
if !outputRedirection.redirectStderr {
// Close the write end of the stderr pipe.
try close(fd: &stderrPipe[1])
// Close the local write end of the stderr pipe.
try close(fd: stderrPipe[1])

// Create a thread and start reading the stderr output on it.
thread = Thread { [weak self] in
Expand All @@ -530,6 +544,8 @@ public final class Process: ObjectIdentifierProtocol {
self.stderr.thread = thread
}
}

return stdinStream
#endif // POSIX implementation
}

Expand Down Expand Up @@ -731,11 +747,15 @@ private func open(pipe: inout [Int32]) throws {
}

/// Close the given fd.
private func close(fd: inout Int32) throws {
let rv = TSCLibc.close(fd)
guard rv == 0 else {
throw SystemError.close(rv)
private func close(fd: Int32) throws {
func innerClose(_ fd: inout Int32) throws {
let rv = TSCLibc.close(fd)
guard rv == 0 else {
throw SystemError.close(rv)
}
}
var innerFd = fd
try innerClose(&innerFd)
}

extension Process.Error: CustomStringConvertible {
Expand Down Expand Up @@ -788,3 +808,27 @@ extension ProcessResult.Error: CustomStringConvertible {
}
}
}

#if os(Windows)
extension FileHandle: WritableByteStream {
public var position: Int {
return Int(offsetInFile)
}

public func write(_ byte: UInt8) {
write(Data([byte]))
}

public func write<C: Collection>(_ bytes: C) where C.Element == UInt8 {
write(Data(bytes))
}

public func flush() {
synchronizeFile()
}

public func close() throws {
closeFile()
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public final class TerminalController {
/// Computes the terminal type of the stream.
public static func terminalType(_ stream: LocalFileOutputByteStream) -> TerminalType {
#if os(Windows)
return _isatty(_fileno(stream.filePointer)) == 0 ? .file : .tty
return _isatty(_fileno(stream.filePointer)) == 0 ? .file : .dumb
#else
if ProcessEnv.vars["TERM"] == "dumb" {
return .dumb
Expand Down
5 changes: 5 additions & 0 deletions swift-tools-support-core/Sources/TSCUtility/Platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public enum Platform: Equatable {
case android
case darwin
case linux(LinuxFlavor)
case windows

/// Recognized flavors of linux.
public enum LinuxFlavor: Equatable {
Expand All @@ -27,6 +28,9 @@ public enum Platform: Equatable {
public static var currentPlatform = Platform._findCurrentPlatform(localFileSystem)
/// Attempt to match `uname` with recognized platforms.
public static func _findCurrentPlatform(_ fs: FileSystem) -> Platform? {
#if os(Windows)
return .windows
#else
guard let uname = try? Process.checkNonZeroExit(args: "uname").spm_chomp().lowercased() else { return nil }
switch uname {
case "darwin":
Expand All @@ -36,6 +40,7 @@ public enum Platform: Equatable {
default:
return nil
}
#endif
}

public static func _findCurrentPlatformLinux(_ fs: FileSystem) -> Platform? {
Expand Down
26 changes: 3 additions & 23 deletions swift-tools-support-core/Tests/TSCBasicTests/PathTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ class PathTests: XCTestCase {
XCTAssertEqual(RelativePath(".").dirname, ".")
}

func testBasenameExtraction() {
func testBaseNameExtraction() {
XCTAssertEqual(AbsolutePath("/").basename, "/")
XCTAssertEqual(AbsolutePath("/a").basename, "a")
XCTAssertEqual(AbsolutePath("/./a").basename, "a")
Expand All @@ -149,28 +149,6 @@ class PathTests: XCTestCase {
XCTAssertEqual(RelativePath(".").basename, ".")
}

func testBasenameWithoutExtExtraction() {
XCTAssertEqual(AbsolutePath("/").basenameWithoutExt, "/")
XCTAssertEqual(AbsolutePath("/a").basenameWithoutExt, "a")
XCTAssertEqual(AbsolutePath("/./a").basenameWithoutExt, "a")
XCTAssertEqual(AbsolutePath("/../..").basenameWithoutExt, "/")
XCTAssertEqual(AbsolutePath("/a.txt").basenameWithoutExt, "a")
XCTAssertEqual(AbsolutePath("/b/c/../a.txt").basenameWithoutExt, "a")
XCTAssertEqual(AbsolutePath("/b/c/../a.bcd.txt").basenameWithoutExt, "a.bcd")
XCTAssertEqual(RelativePath("../..").basenameWithoutExt, "..")
XCTAssertEqual(RelativePath("../a").basenameWithoutExt, "a")
XCTAssertEqual(RelativePath("../a/..").basenameWithoutExt, "..")
XCTAssertEqual(RelativePath("a/..").basenameWithoutExt, ".")
XCTAssertEqual(RelativePath("./..").basenameWithoutExt, "..")
XCTAssertEqual(RelativePath("a/../////../////./////").basenameWithoutExt, "..")
XCTAssertEqual(RelativePath("abc").basenameWithoutExt, "abc")
XCTAssertEqual(RelativePath("").basenameWithoutExt, ".")
XCTAssertEqual(RelativePath(".").basenameWithoutExt, ".")
XCTAssertEqual(RelativePath("a.txt").basenameWithoutExt, "a")
XCTAssertEqual(RelativePath("b/c/../a.txt").basenameWithoutExt, "a")
XCTAssertEqual(RelativePath("b/c/../a.bcd.txt").basenameWithoutExt, "a.bcd")
}

func testSuffixExtraction() {
XCTAssertEqual(RelativePath("a").suffix, nil)
XCTAssertEqual(RelativePath("a").extension, nil)
Expand Down Expand Up @@ -365,5 +343,7 @@ class PathTests: XCTestCase {

// FIXME: We also need tests for join() operations.

// FIXME: We also need tests for dirname, basename, suffix, etc.

// FIXME: We also need test for stat() operations.
}
17 changes: 17 additions & 0 deletions swift-tools-support-core/Tests/TSCBasicTests/ProcessTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ class ProcessTests: XCTestCase {
XCTAssertEqual(result2, "hello\n")
}

func testStdin() throws {
var stdout = [UInt8]()
let process = Process(args: script("in-to-out"), outputRedirection: .stream(stdout: { stdoutBytes in
stdout += stdoutBytes
}, stderr: { _ in }))
let stdinStream = try process.launch()

stdinStream.write("hello\n")
stdinStream.flush()

try stdinStream.close()

try process.waitUntilExit()

XCTAssertEqual(String(decoding: stdout, as: UTF8.self), "hello\n")
}

func testStdoutStdErr() throws {
// A simple script to check that stdout and stderr are captured separatly.
do {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env python

import sys

sys.stdout.write(sys.stdin.readline())