Skip to content

Commit f1162dd

Browse files
authored
Merge pull request #19 from apple/update
Sync with SwiftPM trunk
2 parents edc19d3 + 8782285 commit f1162dd

30 files changed

+1274
-31
lines changed

Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ let package = Package(
2323
.library(
2424
name: "SwiftToolsSupport-auto",
2525
targets: ["TSCBasic", "TSCUtility"]),
26+
27+
.library(
28+
name: "TSCTestSupport",
29+
targets: ["TSCTestSupport"]),
30+
.executable(
31+
name: "TSCTestSupportExecutable",
32+
targets: ["TSCTestSupportExecutable"]),
2633
],
2734
dependencies: [
2835

Sources/TSCBasic/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,10 @@ install(TARGETS TSCBasic
6262
LIBRARY DESTINATION lib
6363
RUNTIME DESTINATION bin)
6464
endif()
65+
66+
# Don't use GNU strerror_r on Android.
67+
if(CMAKE_SYSTEM_NAME STREQUAL Android)
68+
target_compile_options(TSCBasic PUBLIC "SHELL:-Xcc -U_GNU_SOURCE")
69+
endif()
70+
71+
set_property(GLOBAL APPEND PROPERTY TSC_EXPORTS TSCBasic)

Sources/TSCBasic/FileSystem.swift

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ public enum FileSystemError: Swift.Error {
5858

5959
/// An unspecific operating system error.
6060
case unknownOSError
61+
62+
/// File or folder already exists at destination.
63+
///
64+
/// This is thrown when copying or moving a file or directory but the destination
65+
/// path already contains a file or folder.
66+
case alreadyExistsAtDestination
6167
}
6268

6369
extension FileSystemError {
@@ -179,11 +185,16 @@ public protocol FileSystem: class {
179185
/// Change file mode.
180186
func chmod(_ mode: FileMode, path: AbsolutePath, options: Set<FileMode.Option>) throws
181187

182-
183188
/// Returns the file info of the given path.
184189
///
185190
/// The method throws if the underlying stat call fails.
186191
func getFileInfo(_ path: AbsolutePath) throws -> FileInfo
192+
193+
/// Copy a file or directory.
194+
func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws
195+
196+
/// Move a file or directory.
197+
func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws
187198
}
188199

189200
/// Convenience implementations (default arguments aren't permitted in protocol
@@ -408,6 +419,18 @@ private class LocalFileSystem: FileSystem {
408419
try setMode(path: (path as! URL).path)
409420
}
410421
}
422+
423+
func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
424+
guard exists(sourcePath) else { throw FileSystemError.noEntry }
425+
guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination }
426+
try FileManager.default.copyItem(at: sourcePath.asURL, to: destinationPath.asURL)
427+
}
428+
429+
func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
430+
guard exists(sourcePath) else { throw FileSystemError.noEntry }
431+
guard !exists(destinationPath) else { throw FileSystemError.alreadyExistsAtDestination }
432+
try FileManager.default.moveItem(at: sourcePath.asURL, to: destinationPath.asURL)
433+
}
411434
}
412435

413436
// FIXME: This class does not yet support concurrent mutation safely.
@@ -675,6 +698,45 @@ public class InMemoryFileSystem: FileSystem {
675698
public func chmod(_ mode: FileMode, path: AbsolutePath, options: Set<FileMode.Option>) throws {
676699
// FIXME: We don't have these semantics in InMemoryFileSystem.
677700
}
701+
702+
public func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
703+
// Get the source node.
704+
guard let source = try getNode(sourcePath) else {
705+
throw FileSystemError.noEntry
706+
}
707+
708+
// Create directory to destination parent.
709+
guard let destinationParent = try getNode(destinationPath.parentDirectory) else {
710+
throw FileSystemError.noEntry
711+
}
712+
713+
// Check that the parent is a directory.
714+
guard case .directory(let contents) = destinationParent.contents else {
715+
throw FileSystemError.notDirectory
716+
}
717+
718+
guard contents.entries[destinationPath.basename] == nil else {
719+
throw FileSystemError.alreadyExistsAtDestination
720+
}
721+
722+
contents.entries[destinationPath.basename] = source
723+
}
724+
725+
public func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
726+
// Get the source parent node.
727+
guard let sourceParent = try getNode(sourcePath.parentDirectory) else {
728+
throw FileSystemError.noEntry
729+
}
730+
731+
// Check that the parent is a directory.
732+
guard case .directory(let contents) = sourceParent.contents else {
733+
throw FileSystemError.notDirectory
734+
}
735+
736+
try copy(from: sourcePath, to: destinationPath)
737+
738+
contents.entries[sourcePath.basename] = nil
739+
}
678740
}
679741

680742
/// A rerooted view on an existing FileSystem.
@@ -767,6 +829,14 @@ public class RerootedFileSystemView: FileSystem {
767829
public func chmod(_ mode: FileMode, path: AbsolutePath, options: Set<FileMode.Option>) throws {
768830
try underlyingFileSystem.chmod(mode, path: path, options: options)
769831
}
832+
833+
public func copy(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
834+
try underlyingFileSystem.copy(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
835+
}
836+
837+
public func move(from sourcePath: AbsolutePath, to destinationPath: AbsolutePath) throws {
838+
try underlyingFileSystem.move(from: formUnderlyingPath(sourcePath), to: formUnderlyingPath(sourcePath))
839+
}
770840
}
771841

772842
/// Public access to the local FS proxy.

Sources/TSCBasic/OutputByteStream.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ public final class LocalFileOutputByteStream: FileOutputByteStream {
651651
private var closeOnDeinit: Bool
652652

653653
/// Instantiate using the file pointer.
654-
init(filePointer: UnsafeMutablePointer<FILE>, closeOnDeinit: Bool = true, buffered: Bool = true) throws {
654+
public init(filePointer: UnsafeMutablePointer<FILE>, closeOnDeinit: Bool = true, buffered: Bool = true) throws {
655655
self.filePointer = filePointer
656656
self.closeOnDeinit = closeOnDeinit
657657
super.init(buffered: buffered)

Sources/TSCLibc/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ install(TARGETS TSCLibc
2626
LIBRARY DESTINATION lib
2727
RUNTIME DESTINATION bin)
2828
endif()
29+
30+
set_property(GLOBAL APPEND PROPERTY TSC_EXPORTS TSCLibc)

Sources/TSCTestSupport/PseudoTerminal.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
@testable import TSCBasic
11+
import TSCBasic
1212
import TSCLibc
1313

1414
public final class PseudoTerminal {

Sources/TSCTestSupport/XCTAssertHelpers.swift

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public func XCTAssertThrows<T: Swift.Error>(
4747
} catch let error as T {
4848
XCTAssertEqual(error, expectedError, file: file, line: line)
4949
} catch {
50-
XCTFail("unexpected error thrown", file: file, line: line)
50+
XCTFail("unexpected error thrown: \(error)", file: file, line: line)
5151
}
5252
}
5353

@@ -61,9 +61,9 @@ public func XCTAssertThrows<T: Swift.Error, Ignore>(
6161
let result = try expression()
6262
XCTFail("body completed successfully: \(result)", file: file, line: line)
6363
} catch let error as T {
64-
XCTAssertTrue(errorHandler(error), "Error handler returned false")
64+
XCTAssertTrue(errorHandler(error), "Error handler returned false", file: file, line: line)
6565
} catch {
66-
XCTFail("unexpected error thrown", file: file, line: line)
66+
XCTFail("unexpected error thrown: \(error)", file: file, line: line)
6767
}
6868
}
6969

@@ -83,6 +83,76 @@ public func XCTNonNil<T>(
8383
}
8484
}
8585

86+
public func XCTAssertResultSuccess<Success, Failure: Error>(
87+
_ result: Result<Success, Failure>,
88+
file: StaticString = #file,
89+
line: UInt = #line
90+
) {
91+
switch result {
92+
case .success:
93+
return
94+
case .failure(let error):
95+
XCTFail("unexpected error: \(error)", file: file, line: line)
96+
}
97+
}
98+
99+
public func XCTAssertResultSuccess<Success, Failure: Error>(
100+
_ result: Result<Success, Failure>,
101+
file: StaticString = #file,
102+
line: UInt = #line,
103+
_ body: (Success) throws -> Void
104+
) rethrows {
105+
switch result {
106+
case .success(let value):
107+
try body(value)
108+
case .failure(let error):
109+
XCTFail("unexpected error: \(error)", file: file, line: line)
110+
}
111+
}
112+
113+
public func XCTAssertResultFailure<Success, Failure: Error>(
114+
_ result: Result<Success, Failure>,
115+
file: StaticString = #file,
116+
line: UInt = #line
117+
) {
118+
switch result {
119+
case .success(let value):
120+
XCTFail("unexpected success: \(value)", file: file, line: line)
121+
case .failure:
122+
return
123+
}
124+
}
125+
126+
public func XCTAssertResultFailure<Success, Failure: Error, ExpectedFailure: Error>(
127+
_ result: Result<Success, Failure>,
128+
equals expectedError: ExpectedFailure,
129+
file: StaticString = #file,
130+
line: UInt = #line
131+
) where ExpectedFailure: Equatable {
132+
switch result {
133+
case .success(let value):
134+
XCTFail("unexpected success: \(value)", file: file, line: line)
135+
case .failure(let error as ExpectedFailure):
136+
XCTAssertEqual(error, expectedError, file: file, line: line)
137+
case .failure(let error):
138+
XCTFail("unexpected error: \(error)", file: file, line: line)
139+
}
140+
}
141+
142+
public func XCTAssertResultFailure<Success, Failure: Error>(
143+
_ result: Result<Success, Failure>,
144+
file: StaticString = #file,
145+
line: UInt = #line,
146+
_ body: (Failure) throws -> Void
147+
) rethrows {
148+
switch result {
149+
case .success(let value):
150+
XCTFail("unexpected success: \(value)", file: file, line: line)
151+
case .failure(let error):
152+
try body(error)
153+
}
154+
}
155+
86156
public func XCTAssertNoDiagnostics(_ engine: DiagnosticsEngine, file: StaticString = #file, line: UInt = #line) {
87157
let diagnostics = engine.diagnostics
88158
if diagnostics.isEmpty { return }

Sources/TSCTestSupport/XCTestCasePerf.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,20 @@
99
*/
1010

1111
import XCTest
12+
import TSCBasic
1213

1314
/// A helper class to write performance tests for SwiftPM.
1415
///
1516
/// Subclasses of this will always build the tests but only run
16-
/// run the tests when ENABLE_PERF_TESTS is defined. This is very
17-
/// useful because we always want to be able to compile the perf tests
17+
/// run the tests when `TSC_ENABLE_PERF_TESTS` is present in the environment.
18+
/// This is useful because we always want to be able to compile the perf tests
1819
/// even if they are not run locally.
1920
open class XCTestCasePerf: XCTestCase {
20-
21-
#if os(macOS) && !ENABLE_PERF_TESTS
21+
#if os(macOS)
2222
override open class var defaultTestSuite: XCTestSuite {
23+
if ProcessEnv.vars.keys.contains("TSC_ENABLE_PERF_TESTS") {
24+
return super.defaultTestSuite
25+
}
2326
return XCTestSuite(name: String(describing: type(of: self)))
2427
}
2528
#endif

Sources/TSCUtility/Archiver.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import TSCBasic
12+
import Dispatch
13+
14+
/// The `Archiver` protocol abstracts away the different operations surrounding archives.
15+
public protocol Archiver {
16+
17+
/// A set of extensions the current archiver supports.
18+
var supportedExtensions: Set<String> { get }
19+
20+
/// Asynchronously extracts the contents of an archive to a destination folder.
21+
///
22+
/// - Parameters:
23+
/// - archivePath: The `AbsolutePath` to the archive to extract.
24+
/// - destinationPath: The `AbsolutePath` to the directory to extract to.
25+
/// - completion: The completion handler that will be called when the operation finishes to notify of its success.
26+
func extract(
27+
from archivePath: AbsolutePath,
28+
to destinationPath: AbsolutePath,
29+
completion: @escaping (Result<Void, Error>) -> Void
30+
)
31+
}
32+
33+
/// An `Archiver` that handles ZIP archives using the command-line `zip` and `unzip` tools.
34+
public struct ZipArchiver: Archiver {
35+
public var supportedExtensions: Set<String> { ["zip"] }
36+
37+
/// The file-system implementation used for various file-system operations and checks.
38+
private let fileSystem: FileSystem
39+
40+
/// Creates a `ZipArchiver`.
41+
///
42+
/// - Parameters:
43+
/// - fileSystem: The file-system to used by the `ZipArchiver`.
44+
public init(fileSystem: FileSystem = localFileSystem) {
45+
self.fileSystem = fileSystem
46+
}
47+
48+
public func extract(
49+
from archivePath: AbsolutePath,
50+
to destinationPath: AbsolutePath,
51+
completion: @escaping (Result<Void, Error>) -> Void
52+
) {
53+
guard fileSystem.exists(archivePath) else {
54+
completion(.failure(FileSystemError.noEntry))
55+
return
56+
}
57+
58+
guard fileSystem.isDirectory(destinationPath) else {
59+
completion(.failure(FileSystemError.notDirectory))
60+
return
61+
}
62+
63+
DispatchQueue.global(qos: .userInitiated).async {
64+
do {
65+
let result = try Process.popen(args: "unzip", archivePath.pathString, "-d", destinationPath.pathString)
66+
guard result.exitStatus == .terminated(code: 0) else {
67+
throw try StringError(result.utf8stderrOutput())
68+
}
69+
70+
completion(.success(()))
71+
} catch {
72+
completion(.failure(error))
73+
}
74+
}
75+
}
76+
}

Sources/TSCUtility/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ target_link_libraries(TSCUtility PUBLIC
3434
# NOTE(compnerd) workaround for CMake not setting up include flags yet
3535
set_target_properties(TSCUtility PROPERTIES
3636
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
37+
38+
set_property(GLOBAL APPEND PROPERTY TSC_EXPORTS TSCUtility)

Sources/TSCUtility/CollectionExtensions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
extension Collection where Iterator.Element : Equatable {
1212
/// Split around a delimiting subsequence with maximum number of splits == 2
13-
func spm_split(around delimiter: [Iterator.Element]) -> ([Iterator.Element], [Iterator.Element]?) {
13+
public func spm_split(around delimiter: [Iterator.Element]) -> ([Iterator.Element], [Iterator.Element]?) {
1414

1515
let orig = Array(self)
1616
let end = orig.endIndex

0 commit comments

Comments
 (0)