Skip to content

[5.5] Use stat Instead of lstat #699

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
Jun 4, 2021
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
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions Sources/SwiftDriver/Utilities/VirtualPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -715,18 +715,33 @@ extension TSCBasic.FileSystem {
try resolvingVirtualPath(path, apply: exists)
}

/// Retrieves the last modification time of the file referenced at the given path.
///
/// If the given file path references a symbolic link, the modification time for the *linked file*
/// - not the symlink itself - is returned.
///
/// - Parameter file: The path to a file.
/// - Throws: `SystemError` if the underlying `stat` operation fails.
/// - Returns: A `Date` value containing the last modification time.
public func lastModificationTime(for file: VirtualPath) throws -> Date {
try resolvingVirtualPath(file) { path in
#if os(macOS)
var s = Darwin.stat()
let err = lstat(path.pathString, &s)
let err = stat(path.pathString, &s)
guard err == 0 else {
throw SystemError.stat(errno, path.pathString)
}
let ti = (TimeInterval(s.st_mtimespec.tv_sec) - kCFAbsoluteTimeIntervalSince1970) + (1.0e-9 * TimeInterval(s.st_mtimespec.tv_nsec))
return Date(timeIntervalSinceReferenceDate: ti)
#else
return try localFileSystem.getFileInfo(file).modTime
// `getFileInfo` is going to ask Foundation to stat this path, and
// Foundation is always going to use `lstat` to do so. This is going to
// do the wrong thing for symbolic links, for which we always want to
// retrieve the mod time of the underlying file. This makes build systems
// that regenerate lots of symlinks but do not otherwise alter the
// contents of files - like Bazel - quite happy.
let path = resolveSymlinks(path)
return try localFileSystem.getFileInfo(path).modTime
#endif
}
}
Expand Down
78 changes: 78 additions & 0 deletions Tests/SwiftDriverTests/IncrementalCompilationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,22 @@ extension IncrementalCompilationTests {
tryNoChange(checkDiagnostics: true)
}
}

func testSymlinkModification() throws {
// Remap
// main.swift -> links/main.swift
// other.swift -> links/other.swift
for (file, _) in self.inputPathsAndContents {
try localFileSystem.createDirectory(tempDir.appending(component: "links"))
let linkTarget = tempDir.appending(component: "links").appending(component: file.basename)
try localFileSystem.move(from: file, to: linkTarget)
try localFileSystem.removeFileTree(file)
try localFileSystem.createSymbolicLink(file, pointingAt: linkTarget, relative: false)
}
try tryInitial()
try checkReactionToTouchingSymlinks(checkDiagnostics: true)
try checkReactionToTouchingSymlinkTargets(checkDiagnostics: true)
}
}

// MARK: - Incremental test stages
Expand Down Expand Up @@ -535,6 +551,68 @@ extension IncrementalCompilationTests {
],
whenAutolinking: autolinkLifecycleExpectations)
}

private func checkReactionToTouchingSymlinks(
checkDiagnostics: Bool = false,
extraArguments: [String] = []
) throws {
for (file, _) in self.inputPathsAndContents {
try localFileSystem.removeFileTree(file)
let linkTarget = tempDir.appending(component: "links").appending(component: file.basename)
try localFileSystem.createSymbolicLink(file, pointingAt: linkTarget, relative: false)
}
try doABuild(
"touch both symlinks; non-propagating",
checkDiagnostics: checkDiagnostics,
extraArguments: extraArguments,
expectingRemarks: [
"Enabling incremental cross-module building",
"Incremental compilation: Read dependency graph",
"Incremental compilation: May skip current input: {compile: main.o <= main.swift}",
"Incremental compilation: May skip current input: {compile: other.o <= other.swift}",
"Incremental compilation: Skipping input: {compile: main.o <= main.swift}",
"Incremental compilation: Skipping input: {compile: other.o <= other.swift}",
"Incremental compilation: Skipping job: Linking theModule",
"Skipped Compiling main.swift",
"Skipped Compiling other.swift",
],
whenAutolinking: autolinkLifecycleExpectations)
}

private func checkReactionToTouchingSymlinkTargets(
checkDiagnostics: Bool = false,
extraArguments: [String] = []
) throws {
for (file, contents) in self.inputPathsAndContents {
let linkTarget = tempDir.appending(component: "links").appending(component: file.basename)
try! localFileSystem.writeFileContents(linkTarget) { $0 <<< contents }
}
try doABuild(
"touch both symlink targets; non-propagating",
checkDiagnostics: checkDiagnostics,
extraArguments: extraArguments,
expectingRemarks: [
"Enabling incremental cross-module building",
"Incremental compilation: Read dependency graph",
"Incremental compilation: Scheduing changed input {compile: main.o <= main.swift}",
"Incremental compilation: Scheduing changed input {compile: other.o <= other.swift}",
"Incremental compilation: Queuing (initial): {compile: main.o <= main.swift}",
"Incremental compilation: Queuing (initial): {compile: other.o <= other.swift}",
"Incremental compilation: not scheduling dependents of main.swift; unknown changes",
"Incremental compilation: not scheduling dependents of other.swift; unknown changes",
"Found 2 batchable jobs",
"Forming into 1 batch",
"Adding {compile: main.swift} to batch 0",
"Adding {compile: other.swift} to batch 0",
"Forming batch job from 2 constituents: main.swift, other.swift",
"Starting Compiling main.swift, other.swift",
"Finished Compiling main.swift, other.swift",
"Incremental compilation: Scheduling all post-compile jobs because something was compiled",
"Starting Linking theModule",
"Finished Linking theModule",
],
whenAutolinking: autolinkLifecycleExpectations)
}
}

// MARK: - Incremental test perturbation helpers
Expand Down