[Review] SE-0534: Opt-in exact matching for version identifiers with build metadata

Hello, Swift community!

The review of SE-0534: Opt-in exact matching for version identifiers with build metadata begins now and runs through July 6, 2026.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager by email or DM. When contacting the review manager directly, please put "SE-0534" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at:

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you,

Franz

6 Likes

Yes please, this has been sorely missed! Is there a future direction to standardize the metadata evaluation ordering? I would be nice to support a few standard strategies.

1 Like

+1

If I understand the behavior currently it will just pick one of 1.0.0+debug and `1.0.0+release` and you have no way to control which it picks. I know of friends that this has bit so this seems like a worthwhile change to SwiftPM.

1 Like

For the scenarios this proposal is trying to address, my assumption is that users would use an exact literal requirement when they need a specific metadata variant. In that model, SE-0534 does not need to define "metadata evaluation ordering": .exactLiteral(...) is for cases where the user already knows the exact published version identifier they want.

Metadata-based ordering or selection strategies may well have valid use cases, but I think that would be separate future work rather than part of this proposal. SE-0534 keeps SemVer precedence unchanged and only adds an opt-in literal match that includes build metadata.

Sounds like this would be a welcome addition. I'm not sure exactLiteral describes exactly what it's doing that differentiates it from exact. Since this build metadata is used to describe a specific build, would .build() make more sense?

1 Like

What about exact("", buildMetadata:)? Indicating both it is an exact version and adds the additional buildMetadata requirement.

2 Likes

The difference between .exact(...) and .exactLiteral(...) is that .exact(...) preserves the current behavior and ignores build metadata for matching, while .exactLiteral(...) matches exactly what the user wrote, including build metadata. That is why I chose the literal suffix for the new API.

1 Like

exact(..., buildMetadata:) does make the additional build metadata constraint explicit, but I worry that it requires users to split a single published version identifier into separate pieces. In source-control tags and registry versions, the version is normally written and displayed as one identifier, such as 1.2.3+debug. .exactLiteral("1.2.3+debug") keeps that identifier intact and makes it clear that SwiftPM should match the literal version string the user provided.

Another concern with exact(..., buildMetadata:) is that it creates two places where build metadata might appear. For example, users may reasonably write .exact("1.2.3+debug", buildMetadata: "debug"), and SwiftPM would then need extra rules for whether that is accepted, rejected, or normalized. .exactLiteral("1.2.3+debug") avoids that ambiguity by keeping the published version identifier as a single value.

Might it be helpful to use a new word other than exact because it exists already with the legacy meaning? We could say something like “specific” maybe…?

The semantic is definitely welcome, many people expect swiftpm to be working this way. Although they also think cargo locks versions like that (it doesn’t unless a special syntax is used) so there’s confusion around this in many ecosystems tbh :sweat_smile:

1 Like

Fair point. Naming is hard, and as a non-native English speaker I find near-synonyms especially difficult to judge.

That said, I do not think introducing a completely different term for every slightly different concept is always the right answer. One example that came to mind is SE-0453: it started with the name Vector, which triggered a long naming discussion, especially around how that term is understood by people coming from C++ and Rust. The accepted name eventually became InlineArray.

The main reason I prefer .exactLiteral(...) over a new term like .specific(...) is that the semantic model is still an exact requirement. The difference is only the matching rule: .exact(...) uses SwiftPM’s existing SemVer-based exact matching, while .exactLiteral(...) matches the published version identifier literally, including build metadata.

So I chose a compositional name, exact + literal, rather than introducing a new standalone term. It is a little longer, but it keeps the API close to the existing concept and avoids making users learn a separate requirement category. Most use cases will continue to be covered by .exact(...); .exactLiteral(...) is only for the narrower case where the literal published version identifier matters.

1 Like

I'm not real familiar with the Package syntax, but could this be an overload of exact with another parameter? .exact("1.2.3+debug", matchingMetadata: true) would trigger the behavior proposed.

3 Likes

Would the following be an option at all?

  1. Have the new API use exact in its name
  2. Deprecate the existing exact
  3. Add a replacement to the existing exact using a name that better describes what it does.

Fair. exact is actually a terrible name as well since given SemVer, it's not really that exact. But literal already has meaning in the Swift language and refers to constant values, e.g. String literals ExpressibleByStringLiteral.

Naming is definitely hard. That's why I'd rather move away from the exact term for this since my worry is that it will cause a lot of confusion. That said, I don't think many teams will use this since it means updating your manifest every time there's a new build you want to refer to.

2 Likes

Thanks, these are good points.

For this feature, I think a good API name should:

  1. distinguish itself clearly from the existing .exact(...);
  2. imply that build metadata participates in matching;
  3. avoid changing the conceptual model of dependency requirements;
  4. avoid source or behavior changes to existing manifests;
  5. be understandable at the call site without requiring users to remember the details of SemVer precedence.

I think .exact("1.2.3+debug", matchingMetadata: true) has the nice property that it keeps the published version identifier as a single value. My concern is that the behavioral difference is then carried by a boolean argument that is easy to miss at the call site:

.exact("1.2.3+debug")
.exact("1.2.3+debug", matchingMetadata: true)

These look very similar, but they have importantly different matching behavior.

Deprecating the existing .exact(...) and replacing it with clearer terminology would be a much larger source-compatibility and migration discussion. I think that is outside the scope of SE-0534, which is intended to preserve existing behavior and add only a narrow opt-in API.

On literal, I agree the word already has meaning in Swift. I was using it in the general sense of matching the value as written by the user, rather than applying SwiftPM’s existing SemVer-based exact matching rule. But I understand the concern that both exact and literal carry existing associations.

I also agree that the use case is expected to be narrow. Most packages should continue to use .exact(...) or ranges. The new API is intended for cases where a root package really does want to pin a specific published version identifier, including build metadata.

That is why I have preferred exactLiteral: it keeps this as an exact dependency requirement while making the literal identifier matching rule explicit. I am not opposed to other names if they satisfy the same constraints and the community can converge on one.

1 Like

Today, .exact("1.0.0+debug") does not guarantee selection of that exact identifier because SwiftPM ignores build metadata when matching exact version requirements

Firstly: I 100% agree with what your pitch is asking for. This is how it should work.

However after doing some research, I conclude this is actually just a longstanding bug with SPM. The SemVer 2.0.0. rules clearly state (italics mine):

  • Build metadata MUST be ignored when determining version precedence.
  • Precedence refers to how versions are compared to each other when ordered

However, the whole point of an "exact" version is specifically to avoid precedence and ordering of versions. We should ONLY be ordering versions when the user has used the from: Version API.

I believe this was simply an oversight/misunderstanding at the original creation of SPM. Because I checked SPM's actual code. Guess what? Nowhere does it actually test whether exact actually works as it should with build metadata specifically. So I added some new cases in VersionSetSpecifierTests, and they all failed:

// Fails ❌
    func testThatExactRespectsBuildMetadataVsNone() {
        XCTAssertNotEqual(VersionSetSpecifier.exact("1.0.0+debug").difference(.exact("1.0.0")), .empty)
    }
// Fails ❌
    func testThatExactRespectsBuildMetadataReleaseVsDebug() {
        XCTAssertNotEqual(VersionSetSpecifier.exact("1.0.0+release").difference(.exact("1.0.0+debug")), .empty)
    }
// Fails ❌
    func testThatExactRespectsBuildMetadataDebugVsRelease() {
        XCTAssertNotEqual(VersionSetSpecifier.exact("1.0.0+debug").difference(.exact("1.0.0+release")), .empty)
    }
// Fails ❌
    func testThatExactRespectsBuildMetadataVsDifferentNumbering() {
        XCTAssertNotEqual(VersionSetSpecifier.exact("1.0.0+debug.1").difference(.exact("1.0.0+debug.2")), .empty)
    }

This would have been caught a long time ago if the tests properly covered these cases.

Also, there is no need to add new syntax. Existing SPM syntax already allows for:

    dependencies: [
        .package(
            url: "https://github.com/gistya/spm-test",
            exact: Version(0, 5, 2, prereleaseIdentifiers: ["alpha", "4"], buildMetadataIdentifiers: ["debug"])
        )
    ],

... which it treats identically to simply writing exact: "0.5.2-alpha.4+debug", due to the fact SPM has a string-parsing algorithm that looks for the -, +, and . separators allowed by the Backus Naur Form Grammar.

This parser is well-covered in the SPM tests, e.g.:

XCTAssertEqual(Version(1, 2, 3, prereleaseIdentifiers: ["beta1"], buildMetadataIdentifiers: ["alpha1"]).description, "1.2.3-beta1+alpha1" as String)

Note: I don't know how likely it is that anyone in the SPM community has packages that depend on the existing, buggy behavior, but it might be good to bump the SPM version to 7.0 to communicate it has potentially breaking changes. That way anyone whose package depends on the bug can simply update their use of exact to instead use from.

TLDR:

We just need to fix the bug in SPM where it does not respect build metadata specifiers when evaluating if a given version exactly matches the exact version you specified. I recommend that you update your pitch to fix the existing behavior, instead of leaving it broken while adding a redundant new API.

After re-reading the SemVer 2.0.0 specification and looking at semver/semver#1134 and semver/semver#1148, I do not think SwiftPM’s current .exact(...) behavior is necessarily wrong.

SemVer clearly says build metadata is ignored when determining precedence, but it does not fully define equality or dependency-matching semantics. In particular, it does not specify how a package manager should model exact requirements, resolver sets, lockfiles, or source-control tag selection. Those details are left to package managers.

That distinction matters: “ignored for precedence” does not necessarily mean “ignored for every form of identity.” It is reasonable to treat 1.0.0 and 1.0.0+debug as having the same ordering position while still treating them as distinct published identifiers.

SwiftPM currently implements .exact(...) using Version ==. Version == is defined as neither < nor >, and < follows SemVer precedence rules. As a result, versions that differ only in build metadata compare equal in SwiftPM today. In other words, SwiftPM treats .exact("1.0.0+debug") as precedence-based exact, not literally exact.

I agree this can be surprising, and the SemVer ambiguity discussions show that literal equality is also a reasonable interpretation. But changing .exact(...) would still be a behavior change to an established SwiftPM interpretation, not simply a correction of a clear SemVer violation. That is why I think preserving .exact(...) and adding an explicit opt-in literal-matching API is the safer design.

It's 100% wrong. Exact should always mean "exact". This is not ambiguous.

That's because equality is outside SemVer's scope.

Nobody could imagine someone would get this part wrong:

  • X == Y between two strings
  • "1.0+debug" != "1.0"

It's not an "interpretation"—it's a bug—and changing the behavior is the point of a bug fix.

I feel that we're at risk of collectively gaslighting ourselves with euphemisms if we convince ourselves that there should be room for interpretation of the word "exact" or that there is logical or valid rationale for ever having an exact version not actually refer to an exact version. We should be able to collectively agree to maintain standards of logical behavior and clear meaning.

No, it's incredibly unsafe to leave a bug like this in production, especially once it's known about.

This bug can allow an attacker to effectively inject a new version of a dependency that you did not explicitly specify by adding a higher precedence version and relying on you trusting SPM to mean "exact" when it says "exact" to keep your dependency versions pinned through updates.

I realize that may sound like hyperbole, but it really isn't. Read about the XZ dependency that a github user "Jian Tan" nearly succeeded in compromising linux with. Or just this May, the big Github breach. Or the Dependency Confusion hack that hit Apple amongst others a few years back.

Besides hacks, this bug can also result in the wrong commit of your own code being released to the public, which is never safe or good.

Anyone who needs to rely on this unsafe existing buggy version of Version.exact(..) can simply not update their swift-tools-version once the fix comes out.

2 Likes

Please do not characterize my explanation as “gaslighting.” I am making a good-faith technical distinction between what SemVer explicitly requires and established SwiftPM behavior that SemVer does not explicitly define. You may disagree with that distinction, but attributing manipulative intent is neither accurate nor constructive.

I am happy to continue discussing the technical and security implications of either design.

On compatibility, this is not simply a matter of independently bumping SwiftPM to 7.0. SwiftPM is distributed as part of Swift toolchain releases, and preserving the old behavior for existing manifests would require an explicit compatibility mechanism, potentially based on swift-tools-version. That design and implementation would itself need to be considered.

I agree that selecting a different build-metadata variant during a fresh resolution is undesirable; that is the problem SE-0534 addresses.

The cited security examples do not establish that publishing a new metadata variant can replace a dependency already pinned in Package.resolved. Build metadata does not create a higher-precedence version under SemVer, so the scenario described is selection among precedence-equivalent identifiers, not injection of a higher-precedence version. SwiftPM also records the resolved source-control revision in the lockfile.

Demonstrating that an existing pin can be silently replaced during ordinary resolution would therefore require a concrete SwiftPM reproduction that distinguishes ordinary resolution from an explicit dependency update, removal of the lockfile, or mutation of an existing tag.

If such a reproduction exists, it should be investigated as a separate security issue. Without one, I do not think the broader security analogy resolves the compatibility question before this proposal: whether to change the established behavior of .exact(...) or add an opt-in API for literal identifier matching.

I tend to agree that a new word would be better.

It would be nice if the values had semantically meaningful names, but we're already in a bit of a mess.

Semantically "exact" ought to mean exact, but it turns out not to in this context. It means "nearly exact if you ignore the metadata". Or, in other words, "not exact".

"Exact literal" ought to be a tautology, and having two values with the word "exact" in them, one of which is more exact than the other, is... non-optimal.

Also, "literal" has a computer science language meaning, and a natural language meaning, and it's not totally clear which one we're using here. At least, not to the casual observer who just wants to be able to guess the correct value to use.

How about .pinned()?

1 Like

How about also adding a clearer synonym for the current .exact mode (.version perhaps?), and then deprecating the use of .exact moving forwards?

Then we end up with two more meaningful values that are easier to tell apart.