Skip to content
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

Add async overloads of withTemporaryFile and withTemporaryDirectory #396

Merged
merged 1 commit into from
Feb 13, 2023
Merged
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
137 changes: 137 additions & 0 deletions Sources/TSCBasic/TemporaryFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,39 @@ public func withTemporaryFile<Result>(
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted when
/// the cleanup block is called.
///
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
///
/// - Parameters:
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
/// set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - suffix: The suffix to the temporary file name.
/// - body: A closure to execute that receives the TemporaryFile as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryFile` function.
/// The cleanup block should be called when the temporary file is no longer needed.
///
/// - Throws: TempFileError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryFile<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", _ body: (TemporaryFile, @escaping (TemporaryFile) async -> Void) async throws -> Result
) async throws -> Result {
return try await body(TemporaryFile(dir: dir, prefix: prefix, suffix: suffix)) { tempFile in
#if os(Windows)
_ = tempFile.path.pathString.withCString(encodedAs: UTF16.self) {
_wunlink($0)
}
#else
unlink(tempFile.path.pathString)
#endif
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
///
Expand All @@ -167,6 +200,40 @@ public func withTemporaryFile<Result>(
}
}

/// Creates a temporary file and evaluates a closure with the temporary file as an argument.
/// The temporary file will live on disk while the closure is evaluated and will be deleted afterwards.
///
/// This function is basically a wrapper over posix's mkstemps() function to create disposable files.
///
/// - Parameters:
/// - dir: If specified the temporary file will be created in this directory otherwise environment variables
/// TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env variables are
/// set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - suffix: The suffix to the temporary file name.
/// - deleteOnClose: Whether the file should get deleted after the call of `body`
/// - body: A closure to execute that receives the TemporaryFile as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryFile` function.
///
/// - Throws: TempFileError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryFile<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryFile", suffix: String = "", deleteOnClose: Bool = true, _ body: (TemporaryFile) async throws -> Result
) async throws -> Result {
try await withTemporaryFile(dir: dir, prefix: prefix, suffix: suffix) { tempFile, cleanup in
let result: Result
do {
result = try await body(tempFile)
if (deleteOnClose) { await cleanup(tempFile) }
} catch {
if (deleteOnClose) { await cleanup(tempFile) }
throw error
}
return result
}
}

// FIXME: This isn't right place to declare this, probably POSIX or merge with FileSystemError?
//
/// Contains the error which can be thrown while creating a directory using POSIX's mkdir.
Expand Down Expand Up @@ -252,6 +319,44 @@ public func withTemporaryDirectory<Result>(
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted when
/// the cleanup closure is called. This allows the temporary directory to have an arbitrary lifetime.
///
/// This function is basically a wrapper over posix's mkdtemp() function.
///
/// - Parameters:
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
/// variables are set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryDirectory` function.
/// The cleanup block should be called when the temporary directory is no longer needed.
///
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryDirectory<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory" , _ body: (AbsolutePath, @escaping (AbsolutePath) async -> Void) async throws -> Result
) async throws -> Result {
// Construct path to the temporary directory.
let templatePath = try AbsolutePath(validating: prefix + ".XXXXXX", relativeTo: determineTempDirectory(dir))

// Convert templatePath to a C style string terminating with null char to be an valid input
// to mkdtemp method. The XXXXXX in this string will be replaced by a random string
// which will be the actual path to the temporary directory.
var template = [UInt8](templatePath.pathString.utf8).map({ Int8($0) }) + [Int8(0)]

if TSCLibc.mkdtemp(&template) == nil {
throw MakeDirectoryError(errno: errno)
}

return try await body(AbsolutePath(validating: String(cString: template))) { path in
_ = try? FileManager.default.removeItem(atPath: path.pathString)
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
///
Expand All @@ -277,3 +382,35 @@ public func withTemporaryDirectory<Result>(
}
}

/// Creates a temporary directory and evaluates a closure with the directory path as an argument.
/// The temporary directory will live on disk while the closure is evaluated and will be deleted afterwards.
///
/// This function is basically a wrapper over posix's mkdtemp() function.
///
/// - Parameters:
/// - dir: If specified the temporary directory will be created in this directory otherwise environment
/// variables TMPDIR, TEMP and TMP will be checked for a value (in that order). If none of the env
/// variables are set, dir will be set to `/tmp/`.
/// - prefix: The prefix to the temporary file name.
/// - removeTreeOnDeinit: If enabled try to delete the whole directory tree otherwise remove only if its empty.
/// - body: A closure to execute that receives the absolute path of the directory as an argument.
/// If `body` has a return value, that value is also used as the
/// return value for the `withTemporaryDirectory` function.
///
/// - Throws: MakeDirectoryError and rethrows all errors from `body`.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public func withTemporaryDirectory<Result>(
dir: AbsolutePath? = nil, prefix: String = "TemporaryDirectory", removeTreeOnDeinit: Bool = false , _ body: (AbsolutePath) async throws -> Result
) async throws -> Result {
try await withTemporaryDirectory(dir: dir, prefix: prefix) { path, cleanup in
let result: Result
do {
result = try await body(path)
if removeTreeOnDeinit { await cleanup(path) }
} catch {
if removeTreeOnDeinit { await cleanup(path) }
throw error
}
return result
}
}