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

[DNM] Refactor Path APIs with SwiftSystem #219

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from

Conversation

stevapple
Copy link

@stevapple stevapple commented Jun 6, 2021

This patch contains API changes. Minor version bump required.

Refactor Path APIs with SwiftSystem, and meanwhile align some APIs’ behavior. This includes:

  • No longer allow blank component "". This was in the doc but not actually taken into practice.
  • No longer include root component in AbsolutePath.components.
  • Add public var root: String? { get } for all Path instances.
  • Creating RelativePath against another AbsolutePath may throw now, because AbsolutePaths could have different roots on Windows.
  • Deprecate AbsolutePath.root for better stability and clearer usage.

TBD:

  • Fix tests on Windows.

This PR may require parallel changes in all the dependent packages:

  • swift-llbuild
  • swift-package-manager
  • sourcekit-lsp
  • swift-driver
  • swift-llbuild
  • swiftpm-on-llbuild2
  • swift-stress-tester
  • swift-tools-support-async

@stevapple
Copy link
Author

Please point out if we can minimize the impact while keeping correct behavior.

Also CC @compnerd @milseman

Copy link
Member

@milseman milseman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM. I'm not a domain expert on SPM or this use case. @compnerd what do you think about this approach?

(Sorry for the delay. I had a 90% completed review but got distracted before I could finish, and forgot about it)

/// Public initializer from String.
init(_ string: String)

/// Convenience initializer that verifies that the path lexically.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it verify?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initializer will throw if the argument isn’t a valid relative/absolute path.

/// component: the `.` string.
///
/// NOTE: Path components no longer include the root. Use `root` instead.
var components: [String] { get }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the main reason for this protocol in SPM?

Also, should these methods be customization hooks or should they just be on the extension? How many of them should be dropped or superseded by FilePath?

Copy link
Author

@stevapple stevapple Jun 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the main reason for this protocol in SPM?

These are just common APIs of RelativePath and AbsolutePath.

How many of them should be dropped or superseded by FilePath?

I don’t think I’m capable enough of answering this question. If we’d like to drop any of them, maybe we’d get other TSC specialists in here.

return "."
}
return filepath.string
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to put this on FilePath and Component itself, actually. We went with "" as the canonical empty path over "." because that's what it is at the OS layer as well as Collection semantics, but a common convention is to produce ".".

We'd want to come up with a good name for it for both FilePath and Component. Something better than stringOrCurrentDirectory.

You could consider extending FilePath to add it, picking your preferred name. I don't know if that's an easier way to structure your code for the future. Let me ask some folk's opinion on extending types from dependencies: @lorentey @natecook1000

public var pathString: String { filepath.isEmpty ? "." : filepath.string }

return "."
}
return dirname
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public var dirname: String { filepath.removingLastComponent().pathString }

return AbsolutePath("/")
#else
if let drive = ProcessEnv.vars["SystemDrive"] ?? ProcessEnv.vars["HomeDrive"] {
return AbsolutePath(drive + "\\")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the trailing backslash?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

%SystemDrive% appears like C:, and C:\ is the file path of its root.

Package.swift Outdated
@@ -28,7 +28,9 @@ let package = Package(
name: "TSCTestSupport",
targets: ["TSCTestSupport"]),
],
dependencies: [],
dependencies: [
.package(url: "https://github.com/apple/swift-system.git", .upToNextMinor(from: "0.0.1"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be 0.0.1 or 0.0.2?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be 0.0.3 given that the first two releases actually not compiles (0.0.2 only compiles with release config).

upToNextMinor() indicates the latest 0.0.x, once 0.0.3 is released it should resolve.

}

public func appending(components names: [String]) -> Self {
let components = names.map(FilePath.Component.init)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also consider throwing in a .lazy.

/// Root directory (whose string representation is just a path separator).
public static let root = AbsolutePath(PathImpl.root)
/// Returns the lowest common ancestor path.
public func lowestCommonAncestor(with path: AbsolutePath) -> AbsolutePath? {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Common ancestor queries is also a good candidate for FilePath. We had operations like lexicallyRelative(to:) in earlier pitches.

// Special case, which is a plain path without `..` components. It
// might be an empty path (when self and the base are equal).
let relComps = pathComps.dropFirst(baseComps.count)
public func relative(to base: AbsolutePath) throws -> RelativePath {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another candidate for FilePath.

} else if self.pathString.hasPrefix(dir.pathString + "/") {
return "./" + self.relative(to: dir).pathString
if let first = rel.components.first,
first != ".." {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the situation you're checking here? ..s should be removed as part of lexical normalization except at the start of relative paths.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just copied the old behavior here. The old code shows that the returned string starts with either .. or ..

@tomerd
Copy link
Contributor

tomerd commented Jul 7, 2021

@stevapple what is the status of this work? is this something you want and have the time to bring over the finish line? we are very interested in this.

@stevapple
Copy link
Author

@tomerd I’m afraid not yet. Here’s a simple checklist:

  • We would like to keep using POSIX path style for in-memory file systems, and POSIX FilePath is not supported on Windows yet. We’d better get @milseman in to see if it’s possible to add support for that. This would help to remove the .posix() workaround, which largely reduces API breakage.
  • Because this is an API breaking change, we also need to update and dependent projects against it. Such work needs to be done after the API is fixed.

@milseman also suggests to move some implementation details to swift-system, but this could be addressed in another PR.

@tomerd
Copy link
Contributor

tomerd commented Feb 2, 2022

@stevapple @compnerd we now have swift-system integrated into the main branch of TSC and can start making use of it / reduce code to help support windows paths etc better

@stevapple
Copy link
Author

stevapple commented Feb 3, 2022

@stevapple @compnerd we now have swift-system integrated into the main branch of TSC and can start making use of it / reduce code to help support windows paths etc better

IMO FilePath is too platform-specific to implement the general interface of TSC. For example, InMemoryFileSystem can only represent a simple FS, which falls to a rather small part of the Windows path system. By using FilePath, we need to take extra care about these unsupported paths.

My suggested migration path is to directly rewriting TSC's clients with FilePath, until we can finally deprecate the whole path submodule in TSC.

I'm also working on a high-level and cross-platform path package (something like karwa/swift-url, but I think that help little to TSC.

@stevapple
Copy link
Author

stevapple commented Mar 1, 2022

@tomerd @compnerd @milseman

While it's still undecided whether we're able to split POSIXPath out of FilePath, it's now possible for us to start the migration.

I introduced a convention that will automatically rebase a /-based path string to the system root, which enables smoother transition for non-Windows file systems.

One big breaking change now is that AbsolutePath.relative(to:) throws, although it rarely throws in production IMO. The other is about splitting root off components, which is relatively easy to follow in unit tests.

@stevapple
Copy link
Author

stevapple commented Mar 1, 2022

Some tests are failing / skipped because of:

  • helper functions broken on Windows;
  • path separator being \; on Windows instead of /:;
  • command line tools missing or different on Windows;
  • difference in executable and process;
  • difference in file permission;
  • bugs of Foundation.

@tomerd tomerd added the wip Work in progress label Mar 2, 2022
@stevapple
Copy link
Author

For anyone who may be concerned, you can start to review the code now.

I would investigate the impact upon TSC clients and open corresponding PR to test it with CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wip Work in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants