-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
[PackageLoading] Improve flexibility in formatting Package manifests and correctness in parsing Swift tools version specifications #2937
Conversation
FIXME: Line feed I tried replacing the // This doesn't fix test failures:
static let regex = try! NSRegularExpression(
pattern: "^//[\\h--\\v--\\n--\\r]*?swift-tools-version:(.*?)(?:;.*|$)",
options: [.caseInsensitive])
// This doesn't fix test failures either:
static let regex = try! NSRegularExpression(
pattern: "^//\(allowedHorizontalWhitespaceCharactersGroup)*?swift-tools-version:(.*?)(?:;.*|$)",
options: [.caseInsensitive])
private static let allowedHorizontalWhitespaceCharactersGroup = "[\\u0009\\u0020\\u00A0\\u1680\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000]" I suspect the test failures are related to the implementation of UPDATE: Carriage return does not cause test failures, only line feed does. |
Thanks for the PR, @WowbaggersLiquidLunch. I will take a look and see if I can find out the issue with the regex. |
hi @WowbaggersLiquidLunch thanks for the contribution. Could you share some background as to the motivation for this change? Is it to match the implementation to the documentation or has the single space restriction caused real issues for you? |
It started as a personal preference where I always follow slashes with a tab character, so that comments in both A few days ago, I stumbled across the documentation of The only issue I have with the single space restriction is just a minor annoyance, that all comments and documentation in my source files line up, except for the |
Thanks for providing some background; it's certainly reasonable to want some flexibility here, all the more so since the comments already state that that was the intent. Looking at the regular expression again: I'm not actually familiar with I think this is a good change and my only concerns would be:
I think that the first case is probably a small risk only, and libSwiftPM should provide a function that could look at the start of a file to see if that file looks like a package manifest. I think that any code that looks at this line would be updated fairly quickly. To deal with the second concern, there should probably be an error if the tools version is set to 5.3 or earlier and the space after the comment is not exactly one whitespace character (ASCII 32). The error should explain that if supporting tools version 5.3 or earlier, a single space needs to be used so that they can properly parse the tools version. |
I used
I just tried replacing assertFailure("//\nswift-tools-version:5.3\n", "//\nswift-tools-version:5.3")
assertFailure("// \nswift-tools-version:5.3\n", "// \nswift-tools-version:5.3")
assertFailure("//\n swift-tools-version:5.3\n", "//\n swift-tools-version:5.3")
assertFailure("//\rswift-tools-version:5.3\n", "//\rswift-tools-version:5.3")
assertFailure("// \rswift-tools-version:5.3\n", "// \rswift-tools-version:5.3")
assertFailure("//\r swift-tools-version:5.3\n", "//\r swift-tools-version:5.3")
assertFailure("//\r\nswift-tools-version:5.3\n", "//\r\nswift-tools-version:5.3")
assertFailure("//\n\rswift-tools-version:5.3\n", "//\n\rswift-tools-version:5.3")
assertFailure("//\u{B}swift-tools-version:5.3\n", "//\u{B}swift-tools-version:5.3")
assertFailure("//\u{2028}swift-tools-version:5.3\n", "//\u{2028}swift-tools-version:5.3")
assertFailure("//\u{2029}swift-tools-version:5.3\n", "//\u{2029}swift-tools-version:5.3") to let validVersions = [
/* other valid versions omitted here */
"//\nswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"// \nswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\n swift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\rswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"// \rswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\r swift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\r\nswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\n\rswift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\u{B}swift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\u{2028}swift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
"//\u{2029}swift-tools-version:5.3\n": (5, 3, 0, "5.3.0"),
]
for (version, result) in validVersions {
try load(ByteString(encodingAsUTF8: version)) { toolsVersion in
XCTAssertEqual(toolsVersion.major, result.0)
XCTAssertEqual(toolsVersion.minor, result.1)
XCTAssertEqual(toolsVersion.patch, result.2)
XCTAssertEqual(toolsVersion.description, result.3)
}
}
They are very similar to the errors from when the regex uses In both cases, it seems like I'm not familiar with how SPM finds the "swift-tools-version" line, so I'm probably wrong: If vertical whitespace characters are allowed, would there be a risk of misrecognising the actual "swift-tools-version" line? For example, would SPM mistakenly use this for the swift version: let package = Package(
name: """
//
swift-tools-version:5.3
""",
/* other parameters omitted here */
) |
This is a very good point. I'll add a comparison between the user-specified version and 5.3, and present an error if the user-specified one is lower or equal. The tests will need to be changed too. What method should I use for asserting that SPM presents an error? I'm not sure if |
I think I figured out why This discovery raises another question from me: How does SPM deal with other systems that use characters other than
|
Thanks for all the additional information, and sorry for the delay in replying. I didn't realize that the existing code looked specifically for
Rather poorly, I suspect. It doesn't currently run on any platforms in which |
@swift-ci please smoke test |
I just pushed a new commit that makes SPM to throw an error if anything but a single U+0020 is used as the spacing after While reading through
|
Although SPM currently doesn't run on any platform in which
where between "5.3" and "42" is a single carriage return, and "42" is left there as a mistake/typo. SPM still will interpret this correctly as 5.3, instead of 5.342, but the splitting-manifest-by-newline part will just return a longer "first line". This probably doesn't matter much, but maybe can be improved by a future pull request? |
One more question (very likely not my last): Should I add an entry to |
This comment has been minimized.
This comment has been minimized.
@swift-ci please smoke test |
Thanks again for your work on this, and sorry again for the delay in replying. I completely see your point about how editing might be done on different platforms than those on which SwiftPM runs. My comment about MacOS Classic not being interesting anymore was more of a pragmatic observation about that particular platform, but I do agree in general, and the parsing logic should really consider anything up to the first member of I think that it makes sense to look for alternate spellings, but as a diagnostic rather than something that gets accepted as a given value. I don't think there's a lot of value in allowing a great deal of flexibility, especially since older SwiftPM's couldn't parse them, but as you point out there is definitely value in diagnosing improper spellings rather than just silently falling back on 3.1. So diagnosing malformed expressions would be great, even if it's just a warning saying that it's falling back on 3.1 (but ideally also "did you mean Lastly, about Thanks! |
BTW, the two tests that failed seem to be in the compiler itself and seem unrelated to this change. |
Thanks for the answers. I want to work on making the parsing logic recognise all line terminating characters, and on improving the diagnostics for malformed "swift-tools-version". Should I include them in this PR, or should I open new PRs for them?
I'm not really convinced by this argument, because old versions of SwiftPM's couldn't parse horizontal whitespace characters other than a single space, either. Do you mean there are different levels to "couldn't parse", where some are more disruptive than others? |
If the code is separate, or if one change cleanly builds on another, then separate PRs would make sense just to make them easier to review and test. But if the code would have to be written one way for one PR and then immediately changed for another, the it makes more sense to put them in the same PR. I could see handling of arbitrary newlines being in this PR, and then maybe diagnostics for "slightly malformed" tools version declarations be a separate PR. You make a good point about the single space — I guess either way, that means that older versions of SwiftPM will not recognize the tools version declaration at all, meaning that (I think) they'll fall back to SwiftPM 3.1 since that was when this comment format was introduce (meaning: if a recognized comment isn't found, the manifest is assumed to be so old that it predates the introduction of the swift tools version comment). What I was trying to express was that allowing for more variations seems to just invite more ways to express this string that old versions can't read. |
The diagnostics improvement I'm aiming for is the ideal version of this:
I think the change involves additional capturing groups in the regex pattern, which overlaps with the regex change already in this PR. It will change test cases already changed by this PR as well. So maybe the diagnostics improvement need to be put in this PR, along with handling of arbitrary newlines? For emitting warnings, should I use |
I think I should give my motivation for wanting to allow spacing between
Mostly it's because in Swift source code, colons are often followed by a space, e.g. let foo: Int
let foobar = [foo1: bar1, foo2: bar2] So a user might use (and/or prefer) a space following Additionally, I think since the PR is already changing the spacing in one place, spacing change in a second place maybe could just tag along.
If SPM (with the diagnostics improvement) is able to catch the backwards-incompatibility while understanding the version the user intends to specify, and suggest the user a correction, would it still be a problem? |
I intend to introduce a possibly source-breaking change, and I need some guidance for it. The motivation come in 2 parts. The first part is a problem with the current It uses // swift-tools-version:5.3 (with a trailing // swift-tools-version:5.3 (with a leading The second part is that in order to recognise all Unicode line terminators, Now that I'm working with let splitted = bytes.contents.split(separator: UInt8(ascii: "\n"), maxSplits: 1, omittingEmptySubsequences: false) with something like this /// The position of the first Unicode line terminator in (or, in lieu thereof, the end position of) the manifest.
let indexOfFirstNewline = manifestContents.firstIndex(where: \.isNewline) ?? manifestContents.endIndex
/// The first line of the manifest, excluding the line terminator, if any.
let firstLineOfManifest = manifestContents.prefix(upTo: indexOfFirstNewline)
/// The manifest excluding the first line.
let manifestContentsSansFirstLine = manifestContents.suffix(from: indexOfFirstNewline) One side effect of this change is that all package manifests that start with a single line terminator will result in an error, regardless of the second line's validity as the tools version specification. There are 3 possible solutions that I can think of:
For both solutions 2 and 3, SPM can check if the first leading line terminator is I'm leaning towards solution 3, with the additional EDIT: This is partially wrong. Using |
I think the removal of the silent fallback to Swift 3.1 is possibly source breaking. I'm not very confident in my assessment, though. As you pointed in an earlier review, SwiftPM already rejects Swift < 4:
The only thing I'm not sure about is whether this "Swift ≥ 4" restriction is for the root manifest only, or for all levels of manifests. If it's for root manifest only, then the removal of the fallback is still source-breaking. However, in either case, the silent fallback seems harmful to me, so maybe this souce-break is justified, if it is a break? |
Just in case they're lost in this long thread, there are 3 unresolved reviews from much earlier: #2937 (comment) The code they refer to are now outdated, but I think they're worth a second look and some discussion. |
Right, since SwiftPM already rejects older packages, this isn't source-breaking (it just shows a different error message). So I don't see an issue here.
No, this seems to be for all packages, so I have no concerns about that. |
Thank you for summarizing the remaining issues — they have indeed become lost in all the discussion. Your thoroughness is really appreciated, and I'm keen to see this merged sooner rather than later, so we should try to see what can be left to follow-up PRs.
Thanks, I have commented on all three. I think that the commit I referenced earlier extended the check for < 4 to all packages, so I don't think there is a source-breaking change here. It seems to me that this PR is in great shape, and I would like to see how we can get this merged. While there can always be improvements, those could be done as follow-on PRs if you feel that the major outstanding issues have been resolved. With regard to the versioning, I think that it is unlikely that this will go into a 5.3.x toolchain. I think we've been using 999.0 as the generic "future toolchain" (usually through its symbol, Thanks again for all your work on this! I'd suggest wrapping this up so it can be merged and then doing anything further in follow-on PRs. As far as I know, the only thing remaining in this PR would be to change to |
@swift-ci please smoke test |
Some places, such as test cases and comments, must have hard-coded version numbers. For them, some of the version numbers are changed from assuming 5.3.1 to assuming 5.4 as the Swift version where pull request swiftlang#2937 lands. Many version numbers in the tests are prepended with "999" to make them work. 2 FIXME comments are added to the beginning of `ToolsVersionLoader.swift` and `ToolsVersionLoaderTests.swift` explaining what to look out for, and what has to be done, when replacing Swift Next with a concurrent version.
I replaced most references of Swift 5.3 and 5.3.1 with some form of Swift Next in 70bc2e2. The rest are the ones that have to be hard-coded, such those as tests and comments; they are changed as well but differently: First, all hard-coded references to 5.3 and 5.3.1 are replaced with references to 5.4. (assumed to be 5.4 because of swiftlang/swift-evolution#1199 and a short discussion it spawned on the Swift Forums that I can't find anymore; UPDATE: Swift Next confirmed to be 5.4). These include intentionally misspelt version numbers and those for testing/drawing attention to boundary conditions, in both comments and test cases. Then, all hard-coded version numbers for testing valid Swift tools version specifications under the new relaxed rule are prefixed with "999" to let the tests pass. Those for testing valid specifications under the old rule are not changed; all those for invalid specifications are not changed either. I added 2
Yep, I think this PR should be ready for merging now. There are still some Thank you for the continuous review! It really helped this PR grow from a regex correction to a more comprehensive and better thought-out improvement. |
Great, thank you for the work on this! I'll take one last look while testing, but then this sounds ready to be merged! |
@swift-ci please smoke test |
@swift-ci please smoke test |
The unit test failures look unrelated:
Not really sure what's going on there — it seems unexpected that there would be actual network access to GitHub during the unit tests, especially in a way that could fail if remote packages change. |
I have no idea of what went wrong in the tests. SwiftPM doesn't seem to depend on any of those packages fetched. For what it's worth, I locally rebased a copy of my branch onto the main branch, and all tests passed (on macOS 10.15.7, Swift 5.3.1). |
Could someone summon the CI bot again? I don't have commit access. |
@swift-ci please smoke test |
1 similar comment
@swift-ci please smoke test |
Looks like everything passed this time. @WowbaggersLiquidLunch, is this ready to merge as far as you are concerned? Thanks again for all your work on this! |
Yes, I think it's ready to merge. |
Great, thanks! |
Yay! |
…and correctness in parsing Swift tools version specifications (swiftlang#2937) * allow any number of any horizontal whitespace character between "//" and "swift-tools-version" - Replaced the regex pattern for matching 1 single space character (" ") with the pattern `\h*?` for matching 0 or more (as few as possible) horizontal whitespace character between "//" and "swift-tools-version". - Made the comments right above `regex` into `regex`'s documentation by changing "//"s into "///"s. - Specified that only _horizontal_ whitespace characters are allowed. - Expanded test cases to account for different numbers of different whitespace characters. - FIXME: Newline and line feed cause tests failures. * [NFC] improve `regex`'s documentation style * [NFC][Gardening] add missing period puctuation in `regex`'s documentation * throw an error for Swift tools version ≤ 5.3 when anything but a single U+0020 is used as the spacing between "//" and "swift-tools-version" Up to and through Swift 5.3, the format of the swift tools version specification must be prefixed exactly with "// swift-tools-version:", where there is 1 and only 1 space character (U+0020) between "//" and "swift-tools-version". Even though future versions (> 5.3) will support any combination of horizontal whitespace characters for the spacing, if the specified Swift tools version ≤ 5.3, then the specification must use "// swift-tools-version:" to be backward compatible with lower Swift versions. A new capture group is added to the regex pattern, for the continuous sequence of whitespace characters between "//" and "swift-tools-version". The capture group for the version specifier remains unchanged, but becomes the 2nd capture group and the 3rd matching range. If both the whitespace sequence and the version specifier are present, and if the version specifier is valid, then compare the version with 5.3. If Swift tools version ≤ 5.3, then throw an error if the whitespace sequence contains anything but a single U+0020. The error informs the user the whitespace characters used, and informs the user that only a single U+0020 is valid. The test function `testBasics()` is renamed to `testValidVersions` to better reflect what it tests. All valid versions that use anything but a single U+0020 as spacing have their version specifier raised to above 5.3. A new test function `testBackwardCompatibilityError()` is added to verify that if Swift tools version ≤ 5.3, anything but U+0020 is rejected as the spacing between "//" and "swift-tools-version". Version specifications are moved from `testNonMatching()` to a new test function `testDefault()`, because Swift tools version defaults to 3.1 if the specification reaches a newline character before any pre-defined misspelling. * replace all occurrences of "`Package.swift`" with "the manifest" in the previous commit (981d23e) The manifest is not necessarily always `Package.swift`. There could be version-specific manifests. * [NFC][Gardening] correct comments on valid versions "Swift ≥ 5.3" is corrected to "Swift > 5.3". "for Swift > 5.3" is added to those missing this qualifier. At the moment of this commit, build fails because of the following errors in `SwiftDriver`: - Cannot find 'SwiftDriverExecutor' in scope - No type named 'ExternalDependencyArtifactMap' in module 'SwiftDriver' * [NFC] add an entry describing changes introduced by swiftlang#2937 * use the existing constant of known version 5.3 * use raw string for regex pattern Using raw string avoids the confusion around consecutive backslashes in regex pattern. * [Gardening][NFC] fix comment typos * [NFC][Gardening] fix typo * [NFC][Gardening] improve comments for valid versions and backward-compatibility test cases 1. Replaced parenthesised examples for whitespace characters with their code points. The examples had been silently converted to spaces (U+0020) when it was edited in Xcode last time. 2. Replaced "space character" with "space", "tab character" with "character tabulation", and "no space" with "no spacing". 3. Removed redundant slashes. * [NFC][Gardening] improve the description of additional valid spacing between "//" and "swift-tools-version" in the tools version specification - Removed "at the top of", because the Swift tools version specification can start from the second line, with only 1 U+000A in front of it. - Emphasized "horizontal". - Replaced the middle one of the 3 consecutive U+0020 in the second example with the a U+0009. This was the intention when the entry was first added, but the editor silently changed the character tabulation to a space. * allow any combination of line terminators at the start of a manifest file ## Principal Changes This commit introduces 2 backward-incompatible improvements to the parsing of the Swift tools version specification in a manifest file: 1. Instead of just line feed (`U+000A`), now all [Unicode line terminators](https://www.unicode.org/reports/tr14/) are recognised. This improvement is also source breaking. 2. Instead of either 0 or 1 line feed, now a manifest accepts unlimited number of Unicode line terminators at the start of file. This improvement partially is introduced to help with the 1st's backward-compatibility. ### Recognition of all Unicode line terminators when searching for the tools version specification in a manifest file Up through Swift 5.3.0, SPM looked for the tools version specification line by splitting the raw bytes (`[UInt8]`) of a manifest at the first `0x0A` it finds. This is done in [`ToolsVersionLoader.split(_ bytes: ByteString) -> (versionSpecifier: String?, rest: [UInt8])`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L160), and once again in [``ToolsVersionLoader.load(file: AbsolutePath, fileSystem: FileSystem) throws -> ToolsVersion`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L129)'s [first `guard` statement's `else` clause](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L137) if the the specification is invalid. Because some Unicode line terminators such as `U+2028` are more than 1-byte long regardless of the encoding, a manifest file needs to have its contents represented in `String` instead of `[UInt8]`, in order for all line terminators to be recognised easily and correctly. A new function is introduced for this task: `ToolsVersionLoader.func split(_ manifestContents: String) -> ManifestComponents`.Through the new type `ManifestComponents`, the tools version specification line is returned directly. This eliminates the need for a 2nd splicing if the specification is invalid. This new function also resolves an old [`FIXME`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L173), by returning `Substring`s in the new type `ManifestComponents`. #### Source-breakages 1. Because the new function works with `String`, a manifest must be UTF-8-encoded, or SPM throws an error informing the user that the file is not correctly encoded. This behaviour is source breaking for all existing manifests that contain invalid [invalid byte sequences](https://en.wikipedia.org/wiki/UTF-8#Invalid_sequences_and_error_handling) such as `0x7F8F`. 2. Until this commit, SPM throws a `ToolsVersionLoader.Error.malformedToolsVersion` when it sees a tools version specification (represented in `String`) such as `"// swift-\rtool-version:5.3\n"`. This is because although the specification is invalid, it contains one of the 2 pre-defined misspellings: "swift-tool" and "tool-version". With this commit, `"\r"` (`U+000D`) becomes a recognised line terminator, so the tools version specification becomes `"// swift-"` for the same manifest file. Because `"// swift-"` does not contain either misspellings, SPM silently falls back to using Swift 3.1 as the lowest supported version. ### Unlimited leading line terminators in a manifest file The old [`ToolsVersionLoader.split(_:)`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L160) uses `Collection.split(separator:maxSplits:omittingEmptySubsequences:)` to split a manifest at the first `U+000A`. This approach has an unaddressed edge case, in which if a manifest starts with a `U+000A`, the second line is mistakenly treated as the tools version specification line. This becomes a problem when the new `ToolsVersionLoader.split(_:)`, introduced in the 1st improvement, takes advantage of `String`'s subrange-accessing subscripts that always correctly slices out the actual first line from a manifest as its tools version specification. To maintain source stability, the new function skips (but records) all leading line terminators, and treat only the first line that starts with a non-line-terminating character as the tools version specification line. Additionally, this new behaviour eliminates this common source of typo bugs where a shebang-like directive is not at the start of file. #### Backward-incompatibility For Swift ≤ 5.3, SPM allows at most 1 `U+000A` and nothing more before the tools version specification. The new function records the entire sequence of leading line terminators in a manifest, then its calling function checks if the sequence is either empty or 1 `U+000A`. If not, SPM throws an error informing the user that the line terminators used are not backward-compatible, and suggests a correction. ### Deprecations 1. Although the old [`ToolsVersionLoader.split(_:)`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L160) is replaced by the new one, it's not removed. Because it's declared `public`, removing it is source-breaking. It is instead restored to how it is in the main branch (with only a minor modification to the regex matching part to preserve its main-branch behaviour), then marked as deprecated, with a message that directs the user to the new function. All calls to the old function in the project are replaced by calls to the new one, with the exception of [the one in `ToolsVersionWriter.swift`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/Workspace/ToolsVersionWriter.swift#L37). The one in `ToolsVersionWriter.swift` needs the old function to pass all tests. 2. [`ToolsVersionLoader.Error.malformedToolsVersion(_:_:)`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L104) is replaced by `ToolsVersionLoader.Error.malformedToolsVersionSpecification(_:)`. The new case distinguishes different kinds of malformation in a tools version specification, and inform the user accordingly. `malformedToolsVersion(_:_:)` is not removed because [`ToolsVersionLoader.Error`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Sources/PackageLoading/ToolsVersionLoader.swift#L90) is declared `public`. It's marked as deprecated, with a message that directs the user to the new case. All calls to the old case in this project are replaced by calls to the new one. ### Tests - 8 failing test cases with line terminators other than `U+000A` are kept and/or introduced, in order to draw attention to the source breakage. - New test cases are added to ensure that all line terminators are handled correctly. - Some test failure messages are improved, to clarify for future maintainers of what went wrong. - [`ToolsVersionLoader.assertFailure(_:_:file:line:)`](https://github.com/WowbaggersLiquidLunch/swift-package-manager/blob/49cfc46bc5defd3ce8e0c0261e3e2cb475bcdb91/Tests/PackageLoadingTests/ToolsVersionLoaderTests.swift#L184) is expanded to handle different kinds of malformations in a tools version specification, shadowing the introduction of `ToolsVersionLoader.Error.malformedToolsVersionSpecification(_:)`. ### Alternative approach A similar result could be achieved by changing the regex pattern to `^($)*//(\h*?)swift-tools-version:(.*?)(?:;.*|$)`, and try to match the entire manifest to it. However, it comes with 3 drawbacks: 1. Because the entire manifest is matched, the entire manifest needs to be decoded using UTF-8. This is source-breaking, as explained above. 2. Matching an entire manifest is inefficient. 3. Regular expression is usually less understandable than `String`'s index-find and substring-getting methods. It also becomes much harder to maintain when the pattern gets more complicated. * [NFC][Gardening] use more approchable language in the changelog "ye olde days" is quirky, but not very approachable to readers whose first language isn't English. Also, not everyone is familiar with the shell notation. Co-Authored-By: Anders Bertelrud <[email protected]> * [NFC][Gardening] refer to Swift Package Manager as "the package manager" in the changelog This follows the precedence set by the Swift 3.0 entry in the changelog. * [NFC][Gardening] remove the invalid byte sequence example in the changelog "`0x7F8F`" is removed, so nobody gets the idea that there's something special about that code. "UTF-8" clarifies what encoding the byte sequence is invalid in. Co-Authored-By: Anders Bertelrud <[email protected]> * remove `ToolsVersionLoader.Error.malformedToolsVersion(_:_:)` It can be removed instead of just deprecated, because `libSwiftPM` isn't API-stable yet. * [NFC][Gardening] remove deprecation list Moved notice of `ToolsVersionLoader.Error.malformedToolsVersion(specifier: String, currentToolsVersion: ToolsVersion)` from deprecation to removal in the changelog following 2e753b8. Removed notice of deprecation of `ToolsVersionLoader.split(_ bytes: ByteString) -> (versionSpecifier: String?, rest: [UInt8])`. * [NFC][Gardening] use 4 spaces instead of a tab for indentation * [Gardening] use shortcut reference links in the change log, like how apple/swift does it * improve diagnostics of manifest files Previous silent fallback to Swift 3.1 is replaced with errors visible to the user. Manifest and Swift tools version specification malformation errors are now more fine-grained. Each error points to exactly where the misspelling and malformation is and suggests a correction. Regular expressions are replaced by working with substrings directly, wherever possible. `regex` is returned to its main-branch form, because it's not used anywhere but in the original (now deprecated) `ToolsVersionLoader.split(_:)`. `ToolsVersionLoaderTests.assertFailute(_:_:_:file:line:)` is removed because new test cases introduced in this commit already test at a higher resolution than `assertFailure` can provide. * use strings instead of substrings in associated values for `ToolsVersionLoader.Error` cases `String` is a currency type in Swift. `Substring` is not. * restructure `ToolsVersionLoader.Error.ToolsVersionSpecificationMalformationLocation` to extract (or distribute?) common errors from different locations * recognise any number of newline characters (U+000A) to be backward compatible with Swift ≤ 5.3 * [NFC] correct documentation for the deprecated `split(_:)` and explanation for not using `Collection.split(separator:maxSplits:omittingEmptySubsequences:)` following commit d8887d0 * use strings instead of substrings in associated values for `ToolsVersionLoader.Error.BackwardIncompatibilityPre5_3_1` cases `String` is a currency type in Swift; `Substring` is not. * allow leading whitespace (instead of just line terminators) and spacing after the label A lot of documentation is improved as well. * [NFC] update FIXME for `ToolsVersionLoader.split(_: ByteString)` * [NFC] remove FIXME on error messages * fix mostly typo errors in `ToolsVersionLoaderTests` * [NFC] fix a typo: "whatespace" → "whitespace" * [NFC] improve documentation on the order of diagnosis of a manifest's correctness - Replaced some references to the Swift tools version specification with reference to the manifest, to make the documentation more correct. For example, leading whitespace is not part of the specification, but part of the manifest. - Expanded the order with sub-orders, because understanding the order of diagnosis and errors thrown requires documentation on the most detailed ordering. - Added explanations/rationales for why the ordering is designed this way. - Remove the FIXME callout. The rationale makes it clear the current ordering is likely the optimal one. * [NFC] fix typo: "Swift version specification" → "Swift tools version specification" * [NFC] remove FIXME label for the UTF-8-related source breakage The breakage is the intention, not an error. * [NFC] replace the FIXME callout to label's case-insensitivity with an explanation * improve handling of unforeseen consequences - `fatalError`s are replaced by `ToolsVersionLoader.Error` cases, so SwiftPM will emit diagnosis instead of simply crashing when it encounters an unaccounted-for error in the manifest. - The error messages are reworded to be more detailed and more user-friendly, now with potential suggestions for recovery and bug-reporting included. * reword some error messages: "lowest supported version by" → "lowest Swift version supported by" * [NFC] draw attention to the mismatch between `regex`'s behaviour and its documentation by adding a `Bug` callout that describes the mismatch. * fix typo: "ToolsVersion.currentVersion" → "ToolsVersion.currentToolsVersion" (cherry picked from commit 98dcd5f) * add missing parameter in `Error.backwardIncompatiblePre5_3_1(.unidentified, specifiedVersion: version)` and change the error message to suggest the specified version instead of the current version (cherry picked from commit 336cad2) * [NFC] fix typo: "valid" → "invalid" (cherry picked from commit d739dd2) * adapt `writeToolsVersion(at:version:fs:)` to using the new `ToolsVersionLoader.split(_:)` This allows the old `ToolsVersionLoader.split(_:)` and `regex` to be fully removed instead of just deprecated. In implementing this change, the logic of the new `ToolsVersionLoader.split(_:)` is updated for the following 2 purposes: 1. Primarily to partially match the old `split(_:)`'s behaviour in deciding if the label of the Swift tools version specification is malformed. 2. Secondarily to provide a special path of diagnosis for when the label is prefixed with "swift-tools-version:" (case-insensitive). The comments left in the source provides much better details. (cherry picked from commit 857655a) * [NFC] explain why a "missing version specifier" error might be a misspelt label or version specifier in truth. * replace "newline characters" with "line feeds" to disambiguate * [NFC] minor re-wording of a documentation comment line * replace reference to Swift 5.3 and 5.3.1 with Swift Next Some places, such as test cases and comments, must have hard-coded version numbers. For them, some of the version numbers are changed from assuming 5.3.1 to assuming 5.4 as the Swift version where pull request swiftlang#2937 lands. Many version numbers in the tests are prepended with "999" to make them work. 2 FIXME comments are added to the beginning of `ToolsVersionLoader.swift` and `ToolsVersionLoaderTests.swift` explaining what to look out for, and what has to be done, when replacing Swift Next with a concurrent version. * [NFC] use space for indentation Co-authored-by: Anders Bertelrud <[email protected]>
ORIGINAL DESCRIPTION
SR-13566
This pull request changes the regex pattern on line 181 of swift-package-manager/Sources/PackageLoading/ToolsVersionLoader.swift to allow any combination of horizontal whitespace characters between "//" and "swift-tools-version" in the swift tools version specification in
Package.swift
The old regex pattern is
^// swift-tools-version:(.*?)(?:;.*|$)
The new regex pattern is
^//\h*?swift-tools-version:(.*?)(?:;.*|$)
NEW DESCRIPTION (2020-11-04)
This PR proposes the following changes to the parsing of the
Package
manifest and its Swift tools version specification, starting from Swift Next (assumed to be Swift 5.4):Allow leading (any combination of all) whitespace characters in the
Package
manifest, instead of just line feed characters (U+000A
).Rationale: This allows SwiftPM to skip all initial whitespace-only lines (if any) when looking for the Swift tools version specification, eliminating a common source of bug for Swift packages. Additionally, this allows SwiftPM to recognise all Unicode line terminators, because all of them are whitespace characters.
Allow any combination of all horizontal whitespace characters¹ immediately after the comment marker (
//
) in the Swift tools version specification, instead of just a single space (U+0020
).Rationale: Sometimes a user may prefer to follow the comment marker with a character tabulation (
U+0009
) or 2 space characters, or some other horizontal whitespace characters, in order to line up developer (//
) and documentation (///
) comments throughout the source files. For example:Allow any combination of all horizontal whitespace characters immediately before the version specifier in the Swift tools version specification.
Rationale: This mirrors the most common style of type declaration in Swift sources. For example,
Recognise all Unicode line terminators instead of just line feed (
U+000A
) in thePackage
manifest up to the line terminator that immediately follows the Swift tools version specification line².Rationale: With Swift becoming supported on more and more platforms (many of them Unix-unlike or POSIX-incompliant), SwiftPM will see more and more
Package
manifests that use line terminators other than line feed. Further, even though many systems are not yet supported, Swift source files can still be edited on them, and many IDEs/editors silently changes the line terminators to the platforms' native ones when saving the edited files. SwiftPM needs to recognise all possible line terminators³ before it becomes a problemEnd the silent fallback to using Swift 3.1 as the lowest supported version. This used to happen when the Swift tools version specification is malformed AND SwiftPM couldn't find either
swift-tool
ortool-version
in the specification.Rationale: It is actively harmful to hide this behaviour from the user. It is also actively harmful to decide on a Swift version without user consent. With source breaks from past Swift releases accumulated, this could result in unexpected behaviour in Swift packages. This behaviour was originally intended to peacefully accept Swift packages created before the Swift tools version specification was required. However, this purpose has become outdated.
Additional user-facing changes as the result of implementing the changes above:
If the manifest has a formatting error, SwiftPM now identifies more accurately where the error is, and provides to the user a more detailed description/explanation and a suggestion tailored for each error.
SwiftPM now throws an error if the manifest is not properly encoded in UTF-8.
Additional SwiftPM client-facing changes as the result of implementing the changes above:
ToolsVersionLoader.Error.malformedToolsVersion(specifier: String, currentToolsVersion: ToolsVersion)
is replaced by more granular error cases.ToolsVersionLoader.split(_ bytes: ByteString) -> (versionSpecifier: String?, rest: [UInt8])
andToolsVersionLoader.regex
are replaced by theSubstring
-orientedToolsVersionLoader.split(_ manifest: String) -> ManifestComponents
.¹ Whitespace characters consists of horizontal and vertical whitespace characters. No whitespace character can be both horizontal and vertical. All vertical whitespace characters are line terminators, and vice versa. In the PR's implementation, horizontal whitespace characters are not identified directly, but through the process of elimination: All line terminators are "stripped" from the manifest first, then the horizontal whitespace characters are identified by being whitespace characters.
² Everything after the Swift tools version specification, such as the initilisation of the
Package
instance, is handled by the Swift compiler's lexer. It's beyond the power ofToolsVersionLoader
and SwiftPM's manifest-loading in general.³ Only Unicode line terminators are recognised, so line terminators in some other encodings such as EBCDIC are not recognised. This is fine because if a
Package
manifest isn't encoded in UTF-8, then it's already entirely unreadable by SwiftPM.