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

Honza Dvorsky jan.dvorsky at me.com
Wed Jul 13 03:56:08 CDT 2016

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.)

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.

Something like:
*$ swift package show-target-visibility*
Found 3 modules



- Honza

On Tue, Jul 12, 2016 at 8:16 PM Ankit Agarwal via swift-build-dev <
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
> 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:
>    1.
>    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.
>    2.
>    Specify external target dependencies of a target.
> swift-evolution thread
> <https://lists.swift.org/pipermail/swift-build-dev/Week-of-Mon-20160704/000531.html>
> Motivation1. 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 Solution1. 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.
>    1. Package has only one target.
>    2. 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 Design1. 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
> 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/11441dd8/attachment.html>

More information about the swift-build-dev mailing list