[swift-build-dev] Proposal: SwiftPM Target Access Control

Daniel Dunbar daniel_dunbar at apple.com
Wed Jul 13 10:30:06 CDT 2016


> On Jul 13, 2016, at 1:56 AM, Honza Dvorsky <jan.dvorsky at me.com> wrote:
> 
> Very happy to see this proposal, thanks Ankit for pushing it forward! I can't wait to be able to hide many example executables in my packages, as I've been getting a steady stream of complaints from people being annoyed about the polluted compilation log and slower compilation times.
> 
> However I'm leaning towards keeping the default module visibility to public, not private. I appreciate all the arguments that compare targets with code in Swift, where the default is internal - and it makes sense. But the more pragmatic side of me feels that it might become a piece of boilerplate we'll have to write in our manifests for a long time, without much benefit (but with regret). And adding magic to sometimes export by default (single module packages or modules matching the packages name) IMO just complicates the conceptual model of how SwiftPM treats package manifests. I think we should be very careful with adding such nonlinear behaviors (where e.g. adding another module suddenly breaks the package's visible targets), and in this case I don't believe it's justified. (That's while completely ignoring the fact that such a change would break 100% of packages out there, which could be a toll on the good will the project seems to have right now. Not that we should never make breaking changes, I just feel we should give a good reason we're making them, potentially to allow a new feature. Which is not the case here.) 

I agree, this is a big concern of mine as well.

Ankit and I discussed this at length last night and he has updated the proposal here:
  https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md <https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md>

> That's my two cents. :)
> 
> Overall I'm enthusiastic about this proposal, it will solve a few real issues I'm having with my projects! 
> 
> While I don't think we should add this to the proposal - it might be very useful to add a mode to SwiftPM that dumps the module visibility information. I think that should be added regardless of what the default visibility ends up being.

I agree. I would like a `swift package describe` which shows how the convention system and manifest are causing the package to be interpreted. It would show things like the targets, dependencies, etc.

 - Daniel

> Something like:
> $ swift package show-target-visibility
> Found 3 modules
> 
> Public:
> Foo
> 
> Private:
> PrivateBar
> ExampleFoo
> 
> - Honza
> 
> On Tue, Jul 12, 2016 at 8:16 PM Ankit Agarwal via swift-build-dev <swift-build-dev at swift.org <mailto:swift-build-dev at swift.org>> wrote:
> I have updated the proposal accommodating recent discussion
> 
> Link: https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md <https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md>
> 
> SwiftPM Target Access Control
> Proposal: SE-XXXX <https://github.com/apple/swift-evolution/blob/master/proposals/xxxx-swiftpm-target-access-control.md>
> Author: Ankit Aggarwal <https://github.com/aciidb0mb3r>
> Status: In Discussion
> Review manager: TBD
> Introduction
> This proposal aims to address two issues:
> 
> Control over the targets exposed (and built) when a SwiftPM package is used as a dependency i.e. the targets which are exported and can be used in other packages.
> 
> Specify external target dependencies of a target.
> 
> swift-evolution thread <https://lists.swift.org/pipermail/swift-build-dev/Week-of-Mon-20160704/000531.html>
> Motivation
> 1. Control over exposed targets:
> 
> SwiftPM allows multiple targets (or modules) inside a package. Most of the time package author will want to provide one (or more) stable public/exported target which should be utilised by other packages. We should actively discourage use of targets which are not meant to be imported by other packages.
> 
> Additionally packages usually contain sample usage or example targets which are useful during development or testing of the package but are redundant when the package is used as a dependency. This increases compilation time for the user of the package which can be avoided.
> 
> As a concrete example: Vapor has a target called Development <https://github.com/qutheory/vapor/tree/master/Sources/Development>.
> 
> 2. Specify external target dependencies of a target:
> 
> Currently all the targets of an external dependency are implicitly built and exposed to the user package. This works well for one target package but becomes unclear which targets are using which target of an external dependency.
> 
> Moreover user of a package may only be interested in few targets of a dependency instead of all the exposed targets. Currently there is no way to state this in Package.swift.
> 
> For e.g.: One would like to use the targets libc, POSIX, Basic of SwiftPM but don't want other targets to be built or exposed in their package.
> 
> Proposed Solution
> 1. Control over exposed targets:
> 
> I propose that all targets should by default be private/unexported. Authors should explicitly mark the targets they want to expose as exported/public.
> 
> To mark a target as exported/public I propose PackageDescription's Target gains a flags property which would be a Set of the following Flag enum declared inside Target class:
> 
> public enum Flag {
>     /// Makes the target public or "exported" for other packages to use.
>     case public
> }
> The Flag enum will be flexible in case we need to add more attributes in future as opposed to a boolean property to mark the public nature of the target.
> 
> exported is also a choice instead of public which matches the semantics here. However public is equally clear in current context.
> 
> We can keep some obvious defaults for targets which can be implicitly public for e.g. 
> 
> Package has only one target.
> Target with same name as package.
> Or have all targets be public (the current behaviour) until some target uses the public flag assuming full control over all the exported target. This has an advantage that only larger projects which cares about this need to maintain it.
> 
> However I believe private by default and explicit public declaration is the right way to go here to avoid the misuse of packages/targets which are not intended to act as a dependency and the public targets will become obvious (and documented) in the manifest file.
> 
> It should be noted that this behaviour cannot be enforced by the compiler right now and there is no way to stop symbols from other modules from leaking out. For e.g. there could be a type used in the public interface which belongs to a private target.
> 
> Dependencies of the public targets will also leak and can be imported since they'll become transitive dependency of some target.
> 
> Hopefully we can enforce this using compiler feature in future.
> 
> Swift compiler might gain support for package-level namespaces and access control in future to solve problems like module name collision i.e. two packages have modules with same name. At that point we will probably need to rethink the manifest file.
> 
> 2. Specify external target dependencies of a target:
> 
> I propose that enum Target.Dependency gains a new case External(package: String, target: String) to declare dependency on an external package's target. The enum would look like this after modification:
> 
> /// The description for an individual target or package dependency.
> public enum Dependency {
>     /// A dependency on a target in the same project.
>     case Target(name: String)
>     /// A dependency on a target in a external package.
>     case External(package: String, target: String)
> }
> Note that the package name is not really needed (at least currently) because the target names has to be unique across the dependency graph but it keeps the manifest file cleaner i.e. which external package this external target belongs to.
> 
> An external package dependency declaration implicitly becomes dependency of each target in the package. I propose this behaviour should be retained but if a target dependency contains an External declaration then all other targets which wants to use that external dependency should explicitly state their dependency on that external package using External.
> 
> Detailed Design
> 1. Control over exposed targets:
> 
> Consider a package with following structure: 
> 
> ├── Package.swift
> └── Sources
>     ├── FooLibrary
>     │   └── Foo.swift
>     └── SampleCLI
>         └── main.swift
> The manifest with a public target could look like:
> 
> import PackageDescription
> 
> let package = Package(
>    name: "FooLibrary",
>    targets: [
> 
>        Target(name: "FooLibrary", flags: [.public]),
>        Target(name: "SampleCLI", dependencies: ["FooLibrary"]),
>    ])
> When this package is used as a dependency only FooLibrary is built and is importable.
> 
> 2. Specify external target dependencies of a target:
> 
> Consider a dependency with following manifest file:
> 
> import PackageDescription
> 
> let package = Package(
>    name: "FooLibrary",
>    targets: [
>        Target(name: "Foo"),
> 
>        Target(name: "Bar", dependencies: ["Foo"], flags: [.public]),
>        Target(name: "Baz", flags: [.public]),
>    ])
> To get only the Bar target from the above package, following manifest could be written:
> 
> import PackageDescription
> 
> let package = Package(
> 
>    name: "BarUser",
>    targets: [
>         Target(name: "BarUser", 
>                dependencies: [.External(package: "FooLibrary", target: "Bar")
>                ])
>    ],
> 
>    dependencies: [
>        .Package(
>            url: "../FooLibrary",
>  
>            majorVersion: 1)
>    ])
> Note: In this case since Bar depends on Foo, Foo will be also be implicitly built but Baz need not be compiled at all.
> 
> Also Note: If the external dependency is not declared then both Bar and Baz will be available to BarUser.
> 
> Impact on Existing Code
> 1. Control over exposed targets:
> 
> All targets will become private by default so package authors will need to mark the targets they want to expose as public.
> 
> 2. Specify external target dependencies of a target:
> 
> None as all the public targets will still be dependencies to the overall package when External is not used.
> 
> Alternatives Considered
> None at this time.
> 
> _______________________________________________
> swift-build-dev mailing list
> swift-build-dev at swift.org <mailto:swift-build-dev at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-build-dev <https://lists.swift.org/mailman/listinfo/swift-build-dev>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-build-dev/attachments/20160713/540ec782/attachment.html>


More information about the swift-build-dev mailing list