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

feat: add support for Swift package registries #1318

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

luispadron
Copy link
Collaborator

@luispadron luispadron commented Oct 30, 2024

Fixes #1016

This adds initial support for consuming packages using a Swift Package Registry.

The majority of the changes are in the new registry_swift_package rule which creates a repository to build the targets from a package downloaded via a registry.

Notably, because the identity of the package is used to derive the repository name, packages used via a registry have the following label: @swiftpkg_[org].[name], so for example @swiftpkg_swift_collections becomes @swiftpkg_apple.swift_collections if using the swift-collections package from the apple organization (as registries support multiple orgs).

The registry_swift_package rule is mostly just an updated version of #1043 (thanks @watt)

Open questions

  • Should the label replace . with _ in identity? E.g. @swiftpkg_apple.swift_collections vs. @swiftpkg_apple_swift_collections

Follow-up

  • rule_swift_package_manager already has a bootstrapping issue, if there is no Package.resolved defined already then the Bazel repositories fail to generate. The addition of registry support adds another bootstrapping issue with the registries.json file (users will need to run swift package-registry commands to create it currently). We should address both of these by making bootstrapping a new project easier.

Caveats


Example

The following shows an example of a swift_binary target depending on an SPM dependency downloaded from a registry defined in registries.json.

Package.swift

let package = Package(
    name: "swift_package_registry_example",
    dependencies: [
        .package(id: "apple.swift-collections", exact: "1.1.3"),
    ]
)

Package.resolved

{
  "pins" : [
    {
      "identity" : "apple.swift-collections",
      "kind" : "registry",
      "location" : "",
      "state" : {
        "version" : "1.1.3"
      }
    }
  ],
  "version" : 2
}

registries.json

{
  "authentication" : {

  },
  "registries" : {
    "[default]" : {
      "supportsAvailability" : false,
      "url" : "https://fake-spm-registry.deno.dev/"
    }
  },
  "version" : 1
}

MODULE.bazel

swift_deps = use_extension(
    "@rules_swift_package_manager//:extensions.bzl",
    "swift_deps",
)
swift_deps.from_package(
    registries = "//:registries.json",
    resolved = "//:Package.resolved",
    swift = "//:Package.swift",
)
swift_deps.configure_swift_package(
    replace_scm_with_registry = True,
)
use_repo(
    swift_deps,
    "swift_package",
    "swiftpkg_apple.swift_collections",
)

BUILD.bazel

swift_binary(
    name = "swift_package_registry_example",
    srcs = ["main.swift"],
    module_name = "swift_package_registry_example",
    visibility = ["//visibility:public"],
    deps = ["@swiftpkg_apple.swift_collections//:Collections"],
)

@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch 2 times, most recently from a5adfda to 6739de1 Compare October 30, 2024 17:08
@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch 4 times, most recently from 452f05d to ad96158 Compare November 5, 2024 07:15
@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch 2 times, most recently from 671c25d to f921aba Compare November 12, 2024 02:41
@luispadron luispadron marked this pull request as ready for review November 12, 2024 02:49
@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch 3 times, most recently from a541b18 to 9838fcd Compare November 12, 2024 04:50
@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch 4 times, most recently from 3d7452d to 01abd62 Compare November 12, 2024 16:45
@luispadron
Copy link
Collaborator Author

luispadron commented Nov 12, 2024

Im testing in a real registry implementation now and realizing some issues, going to make some updates but the majority of the design is ready for review

@brentleyjones
Copy link
Collaborator

We should address both of these by making bootstrapping a new project easier.

💯 (as a follow-up).

@brentleyjones
Copy link
Collaborator

I can maintain the fake, read-only registry until we have an alternative or it somehow starts costing me too much money

Can we spin up a local webserver as part of CI instead?

@cgrindel
Copy link
Owner

Should the label replace . with _ in identity? E.g. @swiftpkg_apple.swift_collections vs. @swiftpkg_apple_swift_collections

I like the underscores version. However, it is not a strong preference.

@cgrindel
Copy link
Owner

I can maintain the fake, read-only registry until we have an alternative or it somehow starts costing me too much money
Can we spin up a local webserver as part of CI instead?

+1 to @brentleyjones suggestion. Again, I am not too familiar with Swift registry hosting. If it is static, can we use GitHub to host it? I presume the functionality just needs a URL.

@luispadron luispadron force-pushed the luis/add-swift-package-registry-support branch from 01abd62 to 2a46f9e Compare November 12, 2024 18:17
@luispadron
Copy link
Collaborator Author

I can maintain the fake, read-only registry until we have an alternative or it somehow starts costing me too much money

Can we spin up a local webserver as part of CI instead?

I tried this originally but local host doesn't support https and so the bazel download stuff fails because of insecure connection.

@luispadron
Copy link
Collaborator Author

I can maintain the fake, read-only registry until we have an alternative or it somehow starts costing me too much money

Can we spin up a local webserver as part of CI instead?

+1 to @brentleyjones suggestion. Again, I am not too familiar with Swift registry hosting. If it is static, can we use GitHub to host it? I presume the functionality just needs a URL.

It is not static, it needs to be a web server which follows the registry spec.

@luispadron
Copy link
Collaborator Author

Im testing in a real registry implementation now and realizing some issues, going to make some updates but the majority of the design is ready for review

Done! I fixed my fake registry implementation, the .zip needed to contain a single directory named scope.name where i had name-version

@cgrindel
Copy link
Owner

@luispadron When you are ready to merge, if you don't want to babysit the merge, you can specify @mergifyio queue in a comment. This will add it to the merge queue that we are using.

@luispadron
Copy link
Collaborator Author

Should the label replace . with _ in identity? E.g. @swiftpkg_apple.swift_collections vs. @swiftpkg_apple_swift_collections

I think the answer is here is: no? Thoughts? Doing the replacement would require manipulating the identity in a fair amount of the rules and im not sure it gives us much (plus the label is still different than non registry so we don't gain anything from doing the replacement)

Copy link
Contributor

@watt watt left a comment

Choose a reason for hiding this comment

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

🎉

url = archive_url,
output = output,
sha256 = checksum,
stripPrefix = "{scope}.{name}".format(scope = id.scope, name = id.name),
Copy link
Contributor

Choose a reason for hiding this comment

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

This prefix isn't guaranteed unfortunately; it's going to be whatever the name of the parent folder happened to be when someone ran the publish command. I worked around this by downloading to a temp directory first and then using shell commands to move the contents back to the repo root.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh rough yeah, I had been using <name>-<version> for the prefix in my fake registry but then our Artifactory implementation was using scope.name.

I'll update this to work like you described since it looks like we cant use strip_prefix here

"{version}.zip".format(version = version),
)
repository_ctx.download_and_extract(
headers = _REGISTRY_ZIP_ACCEPT_HEADER,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is the bit that requires Bazel 7.1, which was a concern previously. Not sure if that's still a concern, but may be worth documenting as a requirement for using a registry.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can add a comment

@cgrindel whats the minimum supported Bazel version for these rules? We need to set the headers here which is only possible in Bazel 7.1

Copy link
Collaborator

@brentleyjones brentleyjones Nov 13, 2024

Choose a reason for hiding this comment

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

Personally I think having this specific feature labelled as requiring Bazel 7.1 is fine. If people don't use this feature, it won't break people using less than 7.1, right? If so, it's not breaking anyone and people just need to upgrade if they want to use the new feature.

Copy link
Owner

Choose a reason for hiding this comment

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

I think @brentleyjones is right. Let's document that it is only available in Bazel 7.1 or later. Go for it.

)

# Download the `Package.swift` file to the root of the source archive output.
_download_package_swift(
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary? I think it's already inside the archive isn't it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah true.. I figured this was the point of this endpoint but we should probably just use the archive content and save ourselves a download. I'll update.

Comment on lines +60 to +67
"use_registry_identity_for_scm": attr.bool(
doc = """Look up source control dependencies in the registry and use \
their registry identity when possible to help deduplicate across the two \
origins.

NOTE: `replace_scm_with_registry = True` implies \
`use_registry_identity_for_scm = True`.
""",
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't these exclusive? The docs say you can only choose one of the scm transformation flags.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah sorry the note isn't clear, it's an error to pass both flags but thats because --replace-scm-with-registry already implies --use-registry-identity-for-scm. I'll update the doc comment here to note that both cannot be set at once though

Comment on lines +32 to +35
"config_path": attr.string(
doc = "The relative path within the runfiles tree for the Swift Package Manager configuration directory.",
default = ".swiftpm",
),
Copy link
Contributor

Choose a reason for hiding this comment

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

Not quite. By default, swift looks at both a shared config in ~/.swiftpm and a local project config at .swiftpm. The --config-path argument specifies a different shared config path (there is no argument to change the project config path).

Setting this to .swiftpm (the project config path) effectively means swift will only look at .swiftpm and ignore the shared path.

Setting it to any other value means swift will look at both that value and .swiftpm.

For a hermetic build I think the first (default) behavior is maybe the only sane one, which is why I was hesitant to expose this as a configurable option previously.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not quite. By default, swift looks at both a shared config in ~/.swiftpm and a local project config at .swiftpm. The --config-path argument specifies a different shared config path (there is no argument to change the project config path).

Yes thats how I understand this to work as well, the .swiftpm in this rule isn't in the project tree though its part of the rules runfiles.

Setting this to .swiftpm (the project config path) effectively means swift will only look at .swiftpm and ignore the shared path.

Maybe the name i picked is confusing but this .swiftpm directory is going to live in the rules runfiles, it does not mean a .swiftpm directory in the repo.

The goal of the swift_package_tool is to attempt to control where SPM looks for caches, configs, etc. By setting --config-path here SPM will use a registries.json file that exists in the swift_package_tool rules runfiles tree.

Comment on lines +60 to +63
# If registries_json is set, symlink the `.json` file to the `config_path/configuration` directory.
if [ -n "$registries_json" ]; then
mkdir -p "$config_path" && ln -sf "$(readlink -f "$registries_json")" "$config_path/registries.json"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't this blow away a real registries.json file if one exists? I think you're also missing the configuration directory in here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Won't this blow away a real registries.json file if one exists?

No, this is symlinking the real file into the rules runfiles so that SPM uses that one from the rules --config-path

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think you're also missing the configuration directory in here.

Seems to work without one but i'll update to follow the normal .swiftpm layout

let package = Package(
name: "swift_package_registry_example",
dependencies: [
.package(id: "apple.swift-collections", exact: "1.1.3"),
Copy link
Contributor

Choose a reason for hiding this comment

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

I ran into problems at first with --replace-scm-with-registry, because Package.resolved will contain a scoped ID like apple.swift-collections but every Package.swift will still reference the unscoped swift-collections.

To support that, I made changes to swiftpkg/internal/deps_indexes.bzl to remove the scope in _new_product_index_key. Not sure that's the ideal fix, but it worked.

Anyway, that's definitely something I would suggest testing out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To support that, I made changes to swiftpkg/internal/deps_indexes.bzl to remove the scope in _new_product_index_key. Not sure that's the ideal fix, but it worked.

Ooh could you elaborate a bit here, is this an issue with swift package resolve, etc or with the way the rules generate something. I dont actually know whats required in a normal SPM project to go from SCM -> Registry, do folks need to delete the Package.resolved and re-resolve to replace the SCM identities?

@brentleyjones
Copy link
Collaborator

I think the answer is here is: no?

Yeah, let's not do the replacement.

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

Successfully merging this pull request may close these issues.

Swift registry support
4 participants