SE-0386: `package` access modifier

There is a language out there which actually has external, but the semantics are different as in Solidity as far as I undestand it, it's forbidden to call that code internally.

External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f()does not work, but this.f() works).

Not sure if Swift does ever need something like this. :man_shrugging:


Well the ceiling for external is public. We don't know if the future evolution of Swift and SPM will introduce something that is again above a package and below what public offers, but right now it is and that's where external could exist.

  • What is your evaluation of the proposal?

-1.

This is a limited, inflexible solution to a niche need that doesn't scale with the language. If this proposal covers a real need, there seems to be better, more flexible, and more scalable solutions than simply adding a new visibility modifier.

The proposal's casual dismissal of the existing @_spi features discards a legitimate and powerful direction to solve the asserted problem. Not only could the SPI feature be expanded to solve the issues identified in the proposal, but such an expansion would create a feature far more flexible and powerful than any expansion of access levels. Not only could it solve the package issue, but it could solve things like test-only API, or custom internal visibility for particular subsets of packages or targets.

  • Is the problem being addressed significant enough to warrant a change to Swift?

Perhaps, though the motivation is rather theoretical right now. If Apple were to replace Xcode projects with package definitions, and not improve other aspects of packages at the same time, then there may be a need for something to customize visibility of certain symbols. Given the limited, inflexible, and unscalable nature of this proposal, it doesn't seem like a good idea to create a solution for a problem that doesn't really exist yet.

  • Does this proposal fit well with the feel and direction of Swift?

Given the dismissal of pretty much all other access control pitches, most of which would have more immediate impact to the language and its users, I don't find this proposal matches with prior indicated direction.

  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

No other language I've used had such visibility. Are there any that have an equivalent?

  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Participated in this review thread, read the proposal. Didn't participate in the pitch thread (IIRC) as I thought, like all visibility proposals, this wouldn't go anywhere.

13 Likes

I'm +0 on the feature but I prefer packagewide, packageinternal or packageprivate to package.

1 Like

I'm a bit late, but I wanted to add one comment: I like the general idea, I think it will be a useful addition, but I'm -1 on the proposal as presented currently.

The problem I have with it is the choice to restrict subclassing within the package and outside of the module. This makes the new access level less useful, because it cannot be used for a subset of use cases (those where a parent class is defined somewhere within a package and then subclassed in other modules). If you want to build this kind of class hierarchy, your choices are either to keep all classes within one module, or rearchitect it differently, or make the parent class open, which of course makes it open to external users, which we wanted to avoid.

Note that we do not have the same problem here as in open vs. public: the modules within a package will be written & maintained by the same person/team. By allowing subclassing within the package, we don't create an API expectation that will have to be taken into account and maintained forever, as with marking a class open, because no one outside of the package will be using this API anyway. And the people who are working on the package can be expected to use it as designed, and can change the call site if needed. If a class needs to be exposed across a package that should not be subclassed outside of the module, it can be just marked as such in the documentation and caught in pull requests, or fixed later if necessary.

In other words: making package allow subclassing within the whole package creates a new feature in Swift that you can simply choose to not use if that's your chosen approach. Disallowing subclassing outside of the origin module would instead be adding a limitation to the new feature which cannot be disabled if someone chooses so.

So my preference would be to make package work as "Available in Package / Subclassable in Package".

Let me take my review-manager hat off for a moment and talk this through as a language designer.

Swift's central goal for access control is to encourage what we see as good program design, where you can break your code down into well-thought-out layers of libraries that can independently evolve and grow as you continue to work on them. This goal can sometimes come into conflict with the goal of feeling "lightweight", which in many ways comes down to discouraging programmers from brooding endlessly over details that don't really matter. That could certainly happen with access control, if it were too fine-grained.

The balance that Swift strikes is centered around the idea of co-development. Different groups of people work on different parts of a program. Swift's access control encourages you to define boundaries between parts that can be thought of as little library units: types, files, modules. That's how we promote that first goal of layering and code re-use. But as long as you have the same people working on the same code, these boundaries within the code aren't that important. People working on the same code need to be talking to each other anyway, and they can agree on standards, and they can repair any little breaches just as easily as they broke them in the first place. It's when the boundaries in code reflect real differences between groups of programmers and how they contribute their software to the whole that we first run into hard barriers to evolving the code; those are the boundaries that are truly important to enforce in access control. Without that in play, there's no reason we shouldn't keep things lightweight.

The problem we're looking at here is that the language doesn't let a group of programmers simultaneously enforce those boundaries with other groups — the only boundaries that are truly important to enforce — while also splitting their own code into multiple modules, unless the modules don't need to share any private interfaces with each other. That's a significant problem that I think is worth addressing.

The design of the @_spi attribute really is focused on needing to poke a very specific hole in access control across one of these boundaries between groups. That's why it requires a group name on both declaration and import, and it's why individual files need to import the SPI separately: those are precautions meant to strongly discourage other uses of the SPI. That might not be how some other people use the term "SPI" (although I wasn't aware this was a common term outside of Apple? The other uses I can find on the internet are unrelated), but it is explicitly how that attribute is designed. The effects of that are not what we want for boundaries that are purely internal to a group, because that gets us back to programmers brooding on unimportant things; even just in this thread, it sounds like the people who want to use the attribute this way are talking about drawing all sorts of fine-grained distinctions.

I am very open to the idea that package is not the right term for this concept of a bunch of code written by the same programming group. (Put aside other questions about the keyword for a second, like whether it should have a -private suffix.) The very nice thing about package as a term is that it directly invokes a very important use case for this: related library modules in a single open-source package. But that comes with two disadvantages that I can see:

  • Other kinds of programming groups can have the same problem without necessarily thinking of themselves as writing a "package". The team writing the data layer for a medium-sized app might well have the exact same problem of wanting to split their code into multiple modules without giving access to their internal APIs to the UI layer, but they'd probably find it strange to see package throughout their codebase referring to their team's code.

  • It's not necessarily true that everything in a SwiftPM package ought to have access to package-private APIs. I'm not particularly motivated by the idea of a package that contains code from teams that don't closely collaborate — that seems like a perfectly reasonable thing for SwiftPM to be opinionated about, and if that discourages using package in such packages, so be it. But @taylorswift's example of a sample project that should only be using public API seems much more compelling. I believe SwiftPM is looking at features for making this kind of supplemental code explicit in a package, so that might give an avenue for excluding them from the scope of package, but you can already see the difficulty here: it's hard to talk about this difference, and it might be a little surprising that anything in the package wouldn't have access to package-privatte APIs.

I wonder if it might not be better to use a keyword that recognizes the organizational differences being assumed here, something like teamprivate. Using a (slightly) longer compound keyword also has the benefit of calling out the unusually-expanded scope, the same way that fileprivate calls special attention to the fact that a declaration needs to be used from other scopes within the same file.

17 Likes

A lot of people like to talk about SOLID principles, and the "S" there stands for the Single Responsibility Principle. Sometimes you see that expressed as "A class should have only one reason to change", but I've noticed that formulation can be ambiguous. From the person who came up with the term:

this gets to the crux of the Single Responsibility Principle. This principle is about people.

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.

So it comes from the same sort of direction - a people-centric approach to encapsulation.

Right, and I think the reason people are attracted to SPI as a superior alternative is that it scales not just to multiple modules, but also to multiple packages. That's the thing I'm not really convinced about - is the package the maximum unit of programmer coordination?

I don't think it is. Sometimes I expose interfaces for benchmarking, or fuzz-testing, or to write private utilities, and I don't want any of those targets to be forced to live in the same package. They are not deliverables; they are team-internal tools which generally use the public API but, by their nature, should also be able to depend on implementation details.

Similarly, if I was organising something on the scale of Apple's SDK, I wouldn't want to make the entire SDK a single mammoth package including every single library and private utility - even though they are different modules which may need to share private implementation details.

I'd be okay with something based on the idea of team-oriented access, but it all depends on where those boundaries actually are. If I think about scaling this up to coordination across packages, I can't shake the idea that the solution will look almost exactly like @_spi. Maybe they'll be called "team names" or something, and maybe you won't need to declare them on every import.

5 Likes

I’m not sure what you’re arguing here. Apple would never put the entire OS in the same “team” because that does not reflect the reality of how the OS is developed. We use @_spi for SPI at Apple — which is of course why the feature exists (unofficially) in Swift and has the design it does — precisely because changes to framework SPI need to be carefully coordinated and staged between different development teams.

I’ve never said that @_spi is a useless feature, just that its design is not what I think we want for intra-team interfaces. If you change the design by dropping two of the three distinctive aspects, then okay, but now we’re talking about a totally different design, basically teamprivate where a module can be part of multiple teams, and the obvious question is whether that flexibility really bears its weight.

If all you care about is letting different packages be part of the same “team”, I think the right way to do that would be to let that be configured in SwiftPM, not to muddy the concept of a team. I’ll leave it to other people to opine on whether that’s a good idea.

1 Like

Let me start by saying that I completely agree with the gist of your post, as I understood it: optimizing for the "physical" boundaries of a codebase, including the boundaries between teams working on the same product, is more important than obsessing over specific visibility rules of specific members of specific types.

But the available accessibility identifiers will inevitably drive the structure of a codebase when it comes to code ownership. For example, because I can define something as fileprivate, I can make sure that the owner of that file, whether an individual or a team, will be informed and asked for permission if something changes there, and the owner can decide if some components declared in that file should be visible outside the file or not.

We can also take advantage of the concept of a "module" to enforce similar rules on a larger scale, so when a team owns a series of modules, it can use public to decide what should be seen outside the modules, by other teams, for example.

The current situation has limitations, though:

  • there's no way to enforce visibility across the modules owned by a team, but not outside;
  • once in a module, there's no way to further split it in smaller domains, for simple code organization even if the owner is still a single team, or for clarifying the boundaries between subteams

The package access modifier could help in this situation, but I think it will drive a specific way of using packages to distribute ownership among teams.

In our case, for example, there's a single app that's worked on by several teams. The app is modular, it contains some packages related to layers of the architecture, within which there are modules assigned to different teams, and we use folders to organize the modules per team.

What we're missing right now is a way to further split large modules into smaller components, without them exposing stuff to the rest of the app, but at the same time being able to share code across them.

This could be done in several ways. The feature introduced by this proposal would compel us to turn what are currently modules into packages, so we could have smaller modules inside. But the package, as a tool, seems to be not just for code organization within an app, considering all the attributes of the manifest and the package resolution step in Xcode, so the proliferation of packages in the app might not really match well with our needs.

A different idea could be that of a "submodule", that is, essentially, a logical boundary within a the module itself, that, in following the "physical boundaries matter" approach, could be realized with something like folderprivate, which I personally think would be a great idea.

So, I'm not opposed to the package modifier at all: I'm just saying that for large projects that are worked on by multiple teams, it might not be the right tool for the job, and could produce an unnecessary package proliferation just for team ownership needs.

1 Like

Right - that's exactly what I'm saying. There is generally not just one perspective of which "teams" exist.

To use a fictional, Apple-related example - let's imagine WebKit needs special APIs from audio or graphics subsystems. I'm supposing they are generally considered separate teams from the perspective of those within the organisation, yet because they are part of the same organisation ("Tim team Apple" if you like), they can collaborate and share private implementation details to a greater extent than they can with those outside of the organisation. They are part of multiple teams.

I think the concept of a team is muddy to begin with.

It seems to me that you are suggesting that team == package, and I do not agree with that. Again, consider whether WebKit and CoreAudio would need to be part of the same package in order to share a private interface were they both written in Swift. I think that shouldn't be required, and that collaboration can exist across packages to a similar extent as it can within a single package.

Note that the feature as proposed doesn’t actually require “packages” for visibility purposes to correspond to SPM packages, or any other specific organization; the “package” name is specified by a command-line argument to the compiler, and in a non-SPM build system you should be able to use whatever unit of organization is appropriate for your use case as the package-name.

That said, I think that using the name “package” is inappropriate and misleading for exactly this reason.

5 Likes

Thank you @John_McCall for taking the conversation in this direction - I was hoping someone with the knowledge and insight to do so would address the topic more holistically.

I think I like the idea of teamprivate, but my understanding of exactly how that would work is a bit fuzzy. I look forward to reading more as the idea evolves.

I think you're questioning whether the package boundary would ever be a useful boundary to enforce, but I'm not totally sure, so I want to ask:

Do you think it is possible that we come to the conclusion that teamprivate, packageprivate and folderprivate all be added to the language because they all represent different boundaries that are worth enforcing in different situations? (The heart of the question being, do you see them as orthogonal features and do you see them each as potentially valuable?)

I know that there's a lot of dislike for fileprivate, which I don't currently know the foundation of, and I've said that I think it's a well-named and useful concept, and that I use it all the time. Without intending to spark any kind of detailed debate here, I just want to say that given my positive perspective toward fileprivate, I find the concept of packageprivate entirely unobjectionable and the concept of folderprivate quite exciting. The file, the folder and the package are all indisputable boundaries (and seem to me to be sibling concepts), one of which we already provide a modifier for that I make great use of, so to me filling in those gaps would be a big +1 (until/unless I'm convinced otherwise).

Ok, maybe "package" is disputed...

I don't think this would apply to our case: in developing an iOS app, we're still pretty much in the SPM world, we have packages, targets et cetera and we would likely use this access modifier to mean exactly "within the current Swift package".

Same here. I get the impression that the criticism comes form a place where member access is pretty much tied to a object-oriented, class hierarchy-based world.

For example, to me it's completely natural, fine, maintainable et cetera to add an extension in a file – to a type not defined in the file – with fileprivate access, I do it all the time, I've been doing it for years, and I find that it conforms perfectly to the usual principles (SOLID, cohesion-over-coupling, you name it): one might even realize that without it you would need to follow some "design patterns" to compensate for the lack of it. This is also why I agree with John's point that we spend to much time focusing on things that don't matter that much.

In listing the undeniable physical boundaries I would say that

  • package (current proposal),
  • module (internal),
  • folder (currently missing),
  • file (fileprivate, or private at top level),

are the key ones, I would very much support a plan that fills the gaps.

4 Likes

i'm having a really hard time understanding how this is different from an SPI name. isn't this just assigning group membership to modules, but in a less granular way?

1 Like

I’m unclear on what the functional difference is, except that with @_spi you need to annotate imports and with package you don’t.

You currently need to annotate imports. Given the effort put into this proposal instead focused on the SPI feature (or whatever we want to call it), we can make it look like whatever we want. Heck, it doesn't even need to be called SPI at the end of the day. It's the capabilities we want, and it provides a convenient focus point when discussing alternatives to this proposal.

2 Likes

Well, let's talk through this. Suppose we clone the @_spi attribute — I don't want to argue about names, so let's call it @frog — and remove the need to annotate imports. So you can write @frog("Private") on a declaration, and clients can use that declaration without needing to do anything. If that's all we do, the attribute is non-functional as access control; the declaration might as well just be public. So we need some way to say that some clients can't access the declaration, or better, for clients to opt in to being able to access it. The most logical place for that in source code is an annotation on the import, but here we specifically want to remove that, so the only reasonable alternative I can see is to make it a compiler flag, something like -enable-frog Private.

Now, that specific spelling is a bit problematic, because Private is apparently global. For one, that means the compiler doesn't know which modules are supposed to be providing MyName frogs, which I guess means it just has to expose any frogs it sees with that name for any module being imported. If you specifically needed the MyName frog from a particular module (which seems likely), and your build environment is misconfigured so that that's not available, your code will not build correctly, and there won't be any immediate clue as to what went wrong. Even more importantly, though, it means there's a real risk of collision, so it's actually important that we not call frogs something like Private, or else clients will be pulling in the Private frogs from every single module they import.

To solve the problem with collisions, we need frog names to be namespaced. To solve the build-system problem, we need frog namespaces to be tied in some straightforward way to modules so that we can know for a particular namespace which modules to expect that frog for. The logical conclusion is that frogs should be namespaced either by module or by some sort of meta-module. I think any sensible language design that drops import annotations is going to have to follow this line of reasoning, at least to this point.

Now, having come that far, this proposal is suggesting a very simple system. Once you've identified a grouping of modules to serve as a meta-module, the proposal says, you don't really need separate frogs within that grouping, and you don't really want any of the frogs in that grouping to be usable outside of that grouping. That simplifies the language design quite a bit, because you don't need a frog name anymore, so you can just write @frog; and @frog is effectively now just an access modifier that's somewhere between internal and public.

The argument for why those restrictions make sense is basically what I said above: once you've removed the requirement of import annotations, you have a kind of access control that's very lightweight and permissive about use sites within the scope of who can access it. Given that you do want to restrict uses — since you didn't just make the declaration public — you must be relying to some degree on programmer discipline to ensure that the declaration is used appropriately. That's a perfectly reasonable choice, as long as the scope of who can access it is stays within a group of programmers with relatively good internal communication, code review practices, low barriers to fixing inappropriate uses, etc. As soon as that stops being true, using this kind of permissive access control stops making much sense, and you really should be giving it the full @_spi treatment (assuming you aren't willing/ready to commit to a public API).

Most of the broad counter-arguments I'm seeing here fall into one of three camps:

  1. People who really do want a feature with the exact design of @_spi. This is a fine thing to want, but I don't think it's relevant as an objection to this proposal, for the reasons I've already stated: @_spi addresses a related but crucially different use case that requires a different design approach.

  2. People who want to have multiple frogs within a frog namespace or different but overlapping frog namespaces. Right now, this seems very unmotivated to me. In my experience, even in very complex development situations, there's usually a pretty clear line that you can draw between inside and outside of your development group. If you're talking about internal APIs within your development group, it's best to stick with loose, non-specific access control that you maintain with discipline, communication, and code review. If you're talking about poking a targeted hole for the use of a client that's outside your development group, you really don't want that, and you need something like @_spi. To take this objection more seriously, I need to understand what concrete problems people think that would solve with this.

  3. People who are fine with most of the basic design of this proposal but don't like that the access modifier is tied directly to SwiftPM packages. This is something I'm very interested in getting more discussion.

I don't mean that to be an exhaustive list of the feedback in this thread; there's a lot of good, specific feedback like Jakub's point about subclassing above. But these seem to be the gists of the broad disagreements with the design that I see.

12 Likes

I really like this framing because it grounds the discussion in how humans get work done together. For me, though, a framing that emphasizes the team as a unit of access control misses a key aspect of how humans do software development. We need to care about the practical constraints of software development practices, particularly how libraries are built and distributed.

Imagine that you belong to a team that is responsible for several packages, some of which are interrelated. It would be fairly typical for those separate packages to be located in distinct git repositories, have independent continuous integration systems, and in some cases produce binary artifacts that are separately distributed. Some of these packages might have dependencies on one another, including dependencies on the internal implementation details of modules in the other packages. In this scenario, the impact of changing an implementation detail of a module is very different if the dependencies on that detail come from outside of or inside of the same package. If the dependents are inside the package, then the developer making the change can confidently address the effects of that change atomically in a single pull request. CI will build and test all the affected modules together and if there are binary artifacts then a new, self consistent bundle of those artifacts can be published. If, on the other hand, the dependents belong to another package then the developer must take that into account even if that other package is owned by the same team. The developer needs to identify the affected packages, decide how exactly to handle coordination of the breaking change, and then execute a much different workflow. Maybe they choose to open simultaneous pull requests in the repositories for each package, counting on the fact that their team's CI and distribution pipelines are set up to handle tied changes like this. Or maybe they'll think better of it and decide to stage the change because a breaking change between packages could result in temporary ABI incompatibilities for teammates who haven't rebuilt upstream packages and won't download new binary artifacts for a while.

The point is that if you assume a package is something that generally evolves independently of other packages (regardless of ownership), then the language ought to help you reason about which changes might impact independent evolution. The proposal takes an opinionated stance that a package is the basic unit that encapsulates modules that evolve together closely. I understand that isn't the case for every team and every development style, but I think it is a reasonable assumption and helps us keep the design focused on solving a particular problem well.

There are some objections you might have to my arguments above if you make different assumptions about how software is built and distributed. If your team's packages tend to always be built together from source, using SPM to model dependencies and coordinate changes by pinning versions, then sure, making simultaneous pull requests to separate repositories might be fine for you. Similarly, if you work on a constellation of packages that are compiled separately but all reside in a single repository, then yes, coordinating these inter-package changes is also less of a concern for you. However, Swift's design is heavily influenced by the requirements of developing software runtimes where the components are both compiled and distributed separately. Library evolution has an enormous impact on the design of the language. This proposal is unapologetically designed to be especially well suited to the development workflows where library evolution is a chief concern. I think we maybe haven't spelled that out as explicitly as we ought to in the proposal.

Finally, you might still be objecting to the proposal because it doesn't attempt to solve the problem of privileged inter-package coordination (@_spi) and therefore the story feels too incomplete. I agree that a feature like @_spi is very important and something we should be working towards also making a formal part of the language - this proposal is not meant to compete with that. But as John detailed, we developed the existing unofficial @_spi feature with different design constraints and I think those differing characteristics are fundamental enough to justify separate features rather than one super flexible but complicated feature. The team that put this proposal together has extensive experience with @_spi because we built it and we work closely with the large number of teams who use it at Apple to manage complexity in the SDK. Today, teams at Apple are using @_spi for both inter-package and intra-package coordination and we know from experience that the feature is clunky for the latter purpose. There is a lot of interest from users in a lower friction, cleaner mechanism for keeping package-private interfaces truly private.

I do think there are some aspects of the proposed package design that are unsatisfying. Within Apple, this access level would be clearer if it were named project because that's what a collection of modules that build together are more commonly called. But I think SPM packages are a fundamental part of the Swift ecosystem that we should bias towards using the package terminology. And @taylorswift's point about documentation and example targets in a package not fitting well into this story is a good one, but I don't think it's a fatal flaw. Having a mechanism to exclude targets from package access seems like a good solution.

Overall, my feeling even after taking all of the arguments that have been made into account is that we have actually struck the right balance in choosing packages as the unit for this access control level. It is opinionated and by itself it doesn't solve all the problems that individual teams have in managing complexity, but it is a coherent addition to the language that solves a problem that isn't addressed at all today.

5 Likes

This is essentially where I stand, and raised a similar concern about @package(...) imports for SwiftPM scripting: Pre-Pitch: `@package` argument syntax. It feels like a layering violation to use "package" in this sense in the frontend.

Now, we could hand-wave it/rationalize it by saying that we're defining a new concept in the frontend for a group of related modules and we're calling it a "package", and SwiftPM will treat all the targets in the same SPM package as being in the same "package access scope" and other build systems can define a package however they want. That feels close to sufficient.

But, then we have to weigh @taylorswift's concern that you emphasized about whether targets like examples should be in the same "package access scope". I think this is a valid concern and more likely than it seems. Take swift-format for example; we might benefit from a "package access scope" among our internal modules, but we also have an explicitly defined public API surface. The swift-format executable target should only be able to use the public APIs from the SwiftFormat and SwiftFormatConfiguration modules; it should not have access to any non-public APIs and I wouldn't want to accidentally use one by having it subsumed into the same access scope.

So, SPM would need a way to semantically delineate certain kinds of targets. Could we say that executable targets don't belong in the same package access scope? I'm not sure; answering that "no" satisfies the swift-format case, but there may be other use cases where a tool would want to use APIs from the same package but not make them public. The same goes for testing support targets; maybe you want something that can be imported by all your test targets in the same package but isn't public to external clients of your package or to other non-test targets in the same package.

Then, if we instead need a way to let certain targets opt-out of this kind of access, the once simple analogy of "anything in the same SPM package can use package decls" falls apart and it becomes a lot harder to explain to users and to reason about.


I think the reason folks are reaching for @_spi as a comparison is because it feels like what this proposal does is create an implicit, automatic, unutterable @_spi among a group of modules, but we're treating it as a separate concept (and I think the implementation uses a different concept instead of building on SPI). Does it make sense to align these in some fashion? Let's ignore the name "package" for the time being and steal John's "frog" instead. If we said that -frog-name did two things:

  1. Treat decls like frog class as if they were declared @_spi(«generated frog SPI name») public class
  2. Treat import X as @_spi(«generated frog SPI name») import X if X was compiled with the same -frog-name as the importing module

Does that reveal any possible improvements/directions?

If we avoid the package name, maybe we could have SPM treat all targets in the same package as having the same -frog-name by default, but also provide a way for targets in the package manifest to provide a custom frogName: instead? And if any target in the manifest provides a custom frogName: then the default behavior goes away and you have to be explicit throughout. So swift-format could look something like this:

let package = Package(
  .executableTarget("swift-format"),
  .target("SwiftFormat", frogName: "SwiftFormat"),
  .target("SwiftFormatConfiguration", frogName: "SwiftFormat"),
  .target("SwiftFormatRules", frogName: "SwiftFormat"),
  .target("SwiftFormatPrettyPrint", frogName: "SwiftFormat")
)

That might strike a balance where we make the easy thing easy and obvious while still making the more interesting cases possible. And folks would still have explicit @_spi for those more fine-grained cases where the access boundaries aren't clean partitions around groups of modules.

But, we would still need to figure out what to call this frog; under a design like the above, I don't think package is suitable.

4 Likes

It sounds like a solution to this is to allow a SwiftPM configuration to define "packages", rather than introduce an actual package access control keyword (which has many downsides noted in this thread).

People who want to have multiple frogs within a frog namespace or different but overlapping frog namespaces. Right now, this seems very unmotivated to me.

I'll note that this is the same crux of saying modules in packages need a special keyword rather than just having access to internal. I find this special layer of granularity within a package also quite unmotivating.