Proposal: SwiftPM Target Access Control


(Ankit Agarwal) #1

Hi swift packagers,

I am proposing access control to package targets.

Link:
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>
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.
   2.

   Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>
Motivation
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1.
Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the
package.

As a concrete example: Vapor has a target called Development
<https://github.com/qutheory/vapor/tree/master/Sources/Development>.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2.
Import selected targets:

Sometimes user of a package is only interested in few targets of a
dependency instead of all the targets. Currently there is no way to state
this in Package.swift and all the targets are implicitly built and exposed
to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM but
don't want other targets to be built or exposed in my package.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed
Solution
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1.
Control over exposed targets:

I propose that package authors be able mark the targets they don't want to
be exposed as private i.e. the privatetargets will be built when that
package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a
isPrivate boolean property which defaults to false.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2.
Import selected targets:

I propose that package user be able to specify the targets they want to
import into their package.

To specify the targets to be import I propose to add an optional string
array property targets in PackageDescription's Package.Dependency which
defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed
Design
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1.
Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift

The manifest with private target could look like:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])

When this package is used as a dependency only FooLibrary is built and is
importable.

Targets can have other targets as dependency inside a package. A private target
should only be a dependency to other private targets. For e.g. A manifest
like this should result in a build failure.

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error
FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate:
true), // Not an error because SampleCLI is private.
   ])

Error: FooCore is a private target, it cannot be a dependency to the public
target FooLibrary.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2.
Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])

To get only the Bar target from the above package, following manifest could
be written:

import PackageDescription
let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])

Note: In this case since Bar depends on Foo, Foo will be also be implicitly
built and be available.

Any target mentioned in targets and not present in the package should
result in build failure.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact
on Existing Code

There will be no impact on existing code as these features are additive.
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives
Considered

None at this time.

···

--
Ankit


(Daniel Dunbar) #2

Thanks for writing this up, I definitely want us to figure out how to
handle this problem.

One thing I would like to see addressed alongside this is the ability to
specify the dependency on a SwiftPM target level, since that is usually
where the dependency lies (one target imports another) and not necessarily
at the package level. What do you think?

- Daniel

···

On Thursday, July 7, 2016, Ankit Agarwal via swift-build-dev < swift-build-dev@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link:
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>
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.
   2.

   Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>
Motivation
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1.
Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the
package.

As a concrete example: Vapor has a target called Development
<https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2.
Import selected targets:

Sometimes user of a package is only interested in few targets of a
dependency instead of all the targets. Currently there is no way to state
this in Package.swift and all the targets are implicitly built and
exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM
but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed
Solution
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1.
Control over exposed targets:

I propose that package authors be able mark the targets they don't want to
be exposed as private i.e. the privatetargets will be built when that
package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a
isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2.
Import selected targets:

I propose that package user be able to specify the targets they want to
import into their package.

To specify the targets to be import I propose to add an optional string
array property targets in PackageDescription's Package.Dependency which
defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed
Design
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1.
Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift

The manifest with private target could look like:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])

When this package is used as a dependency only FooLibrary is built and is
importable.

Targets can have other targets as dependency inside a package. A private target
should only be a dependency to other private targets. For e.g. A manifest
like this should result in a build failure.

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])

Error: FooCore is a private target, it cannot be a dependency to the
public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2.
Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])

To get only the Bar target from the above package, following manifest
could be written:

import PackageDescription
let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])

Note: In this case since Bar depends on Foo, Foo will be also be
implicitly built and be available.

Any target mentioned in targets and not present in the package should
result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact
on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives
Considered

None at this time.

--
Ankit


(James Richard) #3

Happy to see this in motion! I wrote up a jira a while back for this: https://bugs.swift.org/browse/SR-859

+1 for the solution. I prefer the target-based system over source files.

···

On Jul 7, 2016, at 5:26 AM, Ankit Agarwal via swift-evolution <swift-evolution@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link: https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>Introduction

This proposal aims to address two issues:

Control over the targets exposed (and built) when a SwiftPM package is used as a dependency.

Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>Motivation

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1. Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the package.

As a concrete example: Vapor has a target called Development <https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2. Import selected targets:

Sometimes user of a package is only interested in few targets of a dependency instead of all the targets. Currently there is no way to state this in Package.swift and all the targets are implicitly built and exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed Solution

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1. Control over exposed targets:

I propose that package authors be able mark the targets they don't want to be exposed as private i.e. the privatetargets will be built when that package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2. Import selected targets:

I propose that package user be able to specify the targets they want to import into their package.

To specify the targets to be import I propose to add an optional string array property targets in PackageDescription's Package.Dependency which defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed Design

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1. Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift
The manifest with private target could look like:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])
When this package is used as a dependency only FooLibrary is built and is importable.

Targets can have other targets as dependency inside a package. A private target should only be a dependency to other private targets. For e.g. A manifest like this should result in a build failure.

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])
Error: FooCore is a private target, it cannot be a dependency to the public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2. Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])
To get only the Bar target from the above package, following manifest could be written:

import PackageDescription

let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])
Note: In this case since Bar depends on Foo, Foo will be also be implicitly built and be available.

Any target mentioned in targets and not present in the package should result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives Considered

None at this time.

--
Ankit

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Anders Bertelrud) #4

Hello Ankit,

Thanks a lot for taking the initiative for this! This looks like a great start.

I think what we will eventually want to do is to let package authors express the "role" of each target, which would then allow expression of such things as build-time vs run-time dependencies. For example, in some cases there may be a dependency on a build tool (that generates code or formats resources in some way) and that affects such things as the platform and architectures for which the target should be built.

But that can all be added in the future. I like the direction represented by this proposal, and I think it represents a good improvement on its own. I do agree with other comments that it would be a great addition to let dependencies be specified at the target level, not just the package level. That would be in scope for inclusion in this proposal.

Thanks!

Anders

···

On 2016-07-07, at 05.26, Ankit Agarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link: https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>Introduction

This proposal aims to address two issues:

Control over the targets exposed (and built) when a SwiftPM package is used as a dependency.

Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>Motivation

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1. Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the package.

As a concrete example: Vapor has a target called Development <https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2. Import selected targets:

Sometimes user of a package is only interested in few targets of a dependency instead of all the targets. Currently there is no way to state this in Package.swift and all the targets are implicitly built and exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed Solution

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1. Control over exposed targets:

I propose that package authors be able mark the targets they don't want to be exposed as private i.e. the privatetargets will be built when that package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2. Import selected targets:

I propose that package user be able to specify the targets they want to import into their package.

To specify the targets to be import I propose to add an optional string array property targets in PackageDescription's Package.Dependency which defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed Design

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1. Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift
The manifest with private target could look like:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])
When this package is used as a dependency only FooLibrary is built and is importable.

Targets can have other targets as dependency inside a package. A private target should only be a dependency to other private targets. For e.g. A manifest like this should result in a build failure.

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])
Error: FooCore is a private target, it cannot be a dependency to the public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2. Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])
To get only the Bar target from the above package, following manifest could be written:

import PackageDescription

let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])
Note: In this case since Bar depends on Foo, Foo will be also be implicitly built and be available.

Any target mentioned in targets and not present in the package should result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives Considered

None at this time.

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Tanner) #5

Really happy to see this proposal. It will cut build times for packages that use my library significantly.

The only point I would bring up is about using a `Bool` for `isPrivate`. This obviously only gives us 2 levels of access control. Something like this would give more flexibility:

enum AccessControl {
    case .public
    case .private
}

Target(name: "SampleCLI", dependencies: ["FooCore"], access: .private)

Packages could possibly want more fine grained control over the access control. e.g, Package Foo wants only packages that directly depend on Foo to receive a module, but not packages that depend on a package Bar that depends on Foo. This would be much easier to add as an additional case to the `enum AccessControl` but impossible to add for `isPrivate: Bool`

Thanks Ankit for the great proposal!
Tanner


(Ankit Agarwal) #6

Hi,

Thanks for feedback. I agree that specifying external dependencies with
targets would be great.
I think the only thing issue is to figure out is how to specify them. Some
thoughts:

Note: Currently target name has to be unique across all the targets in the
package (including its dependencies).

1. Target(name: "FooLibrary", dependencies: ["FooCore", "ExternalTarget"])

pro: This is probably the easiest way to specify the external dependency
it. It fits perfectly into the current semantics and just needs to be
implemented for external deps.
con: No way to know which targets are external dependencies by just looking
at manifest file. No way to know from which package that dep is coming from.

2. Target(name: "FooLibrary", dependencies: ["FooCore"],
externalDependencies: ["ExternalTarget"])

pro: Explicitly mentions what all external deps the target relies on.
con: Doesn't mention which package contains that external dep.

3. Target(name: "FooLibrary", dependencies: ["FooCore",
"SomePackage.ExternalTarget"])

pro: Mentions which package + target the external dependency belongs to.
con: is probably too verbose and stringly typed.

4. Target(name: "FooLibrary", dependencies: ["FooCore"],
externalDependencies: [("SomePackage", "ExternalTarget")])

pro: Mentions which package + target the external dependency belongs to.
con: verbose and stringly typed.

Would love some feedback or another way to better express the external
deps, will update the proposal then.

Thanks!

···

On Thu, Jul 7, 2016 at 9:35 PM, Anders Bertelrud <anders@apple.com> wrote:

Hello Ankit,

Thanks a lot for taking the initiative for this! This looks like a great
start.

I think what we will eventually want to do is to let package authors
express the "role" of each target, which would then allow expression of
such things as build-time vs run-time dependencies. For example, in some
cases there may be a dependency on a build tool (that generates code or
formats resources in some way) and that affects such things as the platform
and architectures for which the target should be built.

But that can all be added in the future. I like the direction represented
by this proposal, and I think it represents a good improvement on its own.
I do agree with other comments that it would be a great addition to let
dependencies be specified at the target level, not just the package level.
That would be in scope for inclusion in this proposal.

Thanks!

Anders

On 2016-07-07, at 05.26, Ankit Agarwal via swift-build-dev < > swift-build-dev@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link:
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>
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.
   2.

   Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>
Motivation
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1.
Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the
package.

As a concrete example: Vapor has a target called Development
<https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2.
Import selected targets:

Sometimes user of a package is only interested in few targets of a
dependency instead of all the targets. Currently there is no way to state
this in Package.swift and all the targets are implicitly built and
exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM
but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed
Solution
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1.
Control over exposed targets:

I propose that package authors be able mark the targets they don't want to
be exposed as private i.e. the privatetargets will be built when that
package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a
isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2.
Import selected targets:

I propose that package user be able to specify the targets they want to
import into their package.

To specify the targets to be import I propose to add an optional string
array property targets in PackageDescription's Package.Dependency which
defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed
Design
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1.
Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift

The manifest with private target could look like:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])

When this package is used as a dependency only FooLibrary is built and is
importable.

Targets can have other targets as dependency inside a package. A private target
should only be a dependency to other private targets. For e.g. A manifest
like this should result in a build failure.

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])

Error: FooCore is a private target, it cannot be a dependency to the
public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2.
Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])

To get only the Bar target from the above package, following manifest
could be written:

import PackageDescription
let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])

Note: In this case since Bar depends on Foo, Foo will be also be
implicitly built and be available.

Any target mentioned in targets and not present in the package should
result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact
on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives
Considered
None at this time.

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev

--
Ankit


(Ankit Agarwal) #7

I agree with Tanner, an AccessControl enum clearly defines the access of a
target and provides flexibility for future.

···

On Fri, Jul 8, 2016 at 3:17 AM, Tanner Nelson <me@tanner.xyz> wrote:

Really happy to see this proposal. It will cut build times for packages
that use my library significantly.

The only point I would bring up is about using a `Bool` for `isPrivate`.
This obviously only gives us 2 levels of access control. Something like
this would give more flexibility:

enum AccessControl {
    case .public
    case .private
}

Target(name: "SampleCLI", dependencies: ["FooCore"], access: .private)

Packages could possibly want more fine grained control over the access
control. e.g, Package Foo wants only packages that *directly* depend on
Foo to receive a module, but not packages that depend on a package Bar that
depends on Foo. This would be much easier to add as an additional case to
the `enum AccessControl` but impossible to add for `isPrivate: Bool`

Thanks Ankit for the great proposal!
Tanner

--
Ankit


(Daniel Dunbar) #8

Some minor points:

1. One major reason for wanting control over which targets are exported are
so packages can control which parts of their API is supported for external
use. This is very important to large projects undergoing active development
while also trying to support a well-defined, semantically versioned API.

2. In keeping with #1, and the design of the Swift language, I think the
right default is for a module to be private/unexported. This is basically
for the same reasons as `internal` is the default access control modifier
in Swift. This also relates to the discussion below.

3. If we are going to add this via a new Target attribute, I suggest we do
so using a `flags` parameter and accompanying enumeration. This will be
equally readable, I think, but more extensible over time.

4. Technically, this proposal is defining two new few features, the
public/private exported/imported part and the ability for packages to
depend on specific targets. They are related problems, but the proposals
(and implementation) are otherwise somewhat orthogonal. I'm fine combining
them into one proposal, but if it appears that one part is going to require
much more design or discussion than the other (or that the combined
discussion is too large) we might consider breaking them up.

There is a larger thing we need to consider with regard to the
public/private part of the proposal, and that is its relation to
hypothetical future support for package-level namespaces, whereby I mean
Swift language support for a new level of namespace (and accompanying
access control support in some form) for an entire Package. There are
several reasons I think that we will ultimately need such a thing, the most
major of which is the module-name collision problem (two packages that want
to use a shared common name for a module) that cannot be resolved by
SwiftPM without language support. Since such a feature is purely
hypothetical at this point, I don't think we need to block forward progress
in SwiftPM on it, but I do think we should discuss the consequences.

Some examples of how these things might be related:

1. The choice of `public/private` is probably wrong when viewed as a
language feature, since they already having existing meanings and
`internal` would be a more accurate name within the context of an
individual Package. I think we should include some discussion of the best
names if we reuse modifier names, or if there is a good argument for
finding alternate names ("exported/unexported"?).

2. Suppose Swift added support for a `package` namespace and access control
modifier. This might make it somewhat awkward if we had tied expectations
of what targets in an external dependency were built to a *manifest*-level
construct.

3. We should call out that public/private in this sense ultimately won't
have any enforcement by the compiler. If a public target exposes a type
declared in a private module implicitly (say via a return value), there
won't be any error, even though it breaks the encapsulation this feature
would partially be intended to provide. Similarly, nothing will prevent a
target from importing a private module from an external dependency if was
already a transitive dependency of some other public target.

It would be good to try and work through some of these issues and lay out
pros & cons as well as other strategies we could use to mitigate them. In
the short term I think we will need to accept and implement some form of
this proposal, so this doesn't mean working out what a full solution would
look like, let's just make sure we aren't (unknowingly) painting ourselves
into a corner.

- Daniel

···

On Thu, Jul 7, 2016 at 6:26 AM, Ankit Agarwal via swift-build-dev < swift-build-dev@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link:
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>
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.
   2.

   Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>
Motivation
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1.
Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the
package.

As a concrete example: Vapor has a target called Development
<https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2.
Import selected targets:

Sometimes user of a package is only interested in few targets of a
dependency instead of all the targets. Currently there is no way to state
this in Package.swift and all the targets are implicitly built and
exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM
but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed
Solution
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1.
Control over exposed targets:

I propose that package authors be able mark the targets they don't want to
be exposed as private i.e. the privatetargets will be built when that
package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a
isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2.
Import selected targets:

I propose that package user be able to specify the targets they want to
import into their package.

To specify the targets to be import I propose to add an optional string
array property targets in PackageDescription's Package.Dependency which
defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed
Design
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1.
Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift

The manifest with private target could look like:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])

When this package is used as a dependency only FooLibrary is built and is
importable.

Targets can have other targets as dependency inside a package. A private target
should only be a dependency to other private targets. For e.g. A manifest
like this should result in a build failure.

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])

Error: FooCore is a private target, it cannot be a dependency to the
public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2.
Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])

To get only the Bar target from the above package, following manifest
could be written:

import PackageDescription
let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])

Note: In this case since Bar depends on Foo, Foo will be also be
implicitly built and be available.

Any target mentioned in targets and not present in the package should
result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact
on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives
Considered

None at this time.

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Kostiantyn Koval) #9

Hi
Thanks for the proposal, looks very nice.

The Package.swift manifest files is very string typed already, (package name, url, target names, etc) and and think that is ok in our case.

We can make a type for external dependency that would have 2 properties: package and target.
That could help expressing a External dependency.

3. Target(name: "FooLibrary", dependencies: ["FooCore", External(package: “SomePackage”, target: "ExternalTarget"])
or
2. Target(name: "FooLibrary", dependencies: ["FooCore"], externalDependencies: Dependency(package: “SomePackage”, target: "ExternalTarget”])

Idea:
- How about making 2 types of targets in the Package, public and private

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary”),
   privateTargets: [
  Target(name: "SampleCLI”)
   ])
By keeping private and public targets separate it would be easier for package author to organise them and don’t mix theirs dependencies, like in the example below
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.

Thanks
- Kostiantyn

···

On 07 Jul 2016, at 20:45, Ankit Agarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi,

Thanks for feedback. I agree that specifying external dependencies with targets would be great.
I think the only thing issue is to figure out is how to specify them. Some thoughts:

Note: Currently target name has to be unique across all the targets in the package (including its dependencies).

1. Target(name: "FooLibrary", dependencies: ["FooCore", "ExternalTarget"])

pro: This is probably the easiest way to specify the external dependency it. It fits perfectly into the current semantics and just needs to be implemented for external deps.
con: No way to know which targets are external dependencies by just looking at manifest file. No way to know from which package that dep is coming from.

2. Target(name: "FooLibrary", dependencies: ["FooCore"], externalDependencies: ["ExternalTarget"])

pro: Explicitly mentions what all external deps the target relies on.
con: Doesn't mention which package contains that external dep.

3. Target(name: "FooLibrary", dependencies: ["FooCore", "SomePackage.ExternalTarget"])

pro: Mentions which package + target the external dependency belongs to.
con: is probably too verbose and stringly typed.

4. Target(name: "FooLibrary", dependencies: ["FooCore"], externalDependencies: [("SomePackage", "ExternalTarget")])

pro: Mentions which package + target the external dependency belongs to.
con: verbose and stringly typed.

Would love some feedback or another way to better express the external deps, will update the proposal then.

Thanks!

On Thu, Jul 7, 2016 at 9:35 PM, Anders Bertelrud <anders@apple.com <mailto:anders@apple.com>> wrote:
Hello Ankit,

Thanks a lot for taking the initiative for this! This looks like a great start.

I think what we will eventually want to do is to let package authors express the "role" of each target, which would then allow expression of such things as build-time vs run-time dependencies. For example, in some cases there may be a dependency on a build tool (that generates code or formats resources in some way) and that affects such things as the platform and architectures for which the target should be built.

But that can all be added in the future. I like the direction represented by this proposal, and I think it represents a good improvement on its own. I do agree with other comments that it would be a great addition to let dependencies be specified at the target level, not just the package level. That would be in scope for inclusion in this proposal.

Thanks!

Anders

On 2016-07-07, at 05.26, Ankit Agarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link: https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>Introduction

This proposal aims to address two issues:

Control over the targets exposed (and built) when a SwiftPM package is used as a dependency.

Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>Motivation

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1. Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the package.

As a concrete example: Vapor has a target called Development <https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2. Import selected targets:

Sometimes user of a package is only interested in few targets of a dependency instead of all the targets. Currently there is no way to state this in Package.swift and all the targets are implicitly built and exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed Solution

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1. Control over exposed targets:

I propose that package authors be able mark the targets they don't want to be exposed as private i.e. the privatetargets will be built when that package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains a isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2. Import selected targets:

I propose that package user be able to specify the targets they want to import into their package.

To specify the targets to be import I propose to add an optional string array property targets in PackageDescription's Package.Dependency which defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed Design

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1. Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift
The manifest with private target could look like:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])
When this package is used as a dependency only FooLibrary is built and is importable.

Targets can have other targets as dependency inside a package. A private target should only be a dependency to other private targets. For e.g. A manifest like this should result in a build failure.

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])
Error: FooCore is a private target, it cannot be a dependency to the public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2. Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])
To get only the Bar target from the above package, following manifest could be written:

import PackageDescription

let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])
Note: In this case since Bar depends on Foo, Foo will be also be implicitly built and be available.

Any target mentioned in targets and not present in the package should result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives Considered

None at this time.

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-build-dev

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Daniel Dunbar) #10

Hi
Thanks for the proposal, looks very nice.

The *Package.swift* manifest files is very string typed already, (package
name, url, target names, etc) and and think that is ok in our case.

We can make a type for external dependency that would have 2
properties: package and target.
That could help expressing a External dependency.

3. Target(name: "FooLibrary", dependencies: ["FooCore", External(package:
“SomePackage”, target: "ExternalTarget"])

I like this general direction... we would need to outline the exact
semantics (presumably `package` here is required to refer to a package
named in the overall package dependencies).

or

2. Target(name: "FooLibrary", dependencies: ["FooCore"],
externalDependencies: Dependency(package: “SomePackage”, target:
"ExternalTarget”])

Idea:
- How about making 2 types of targets in the Package, public and private

I'm less in favor of this, I think an attribute of the target is more
appropriate than a separate group. An attribute is more true to the actual
model the objects are defining, and I think in practice this will be more
annoying to the organization of targets than it is helpful. For example,
for many packages the right answer is to have *one* public target which is
the clean stable API you are trying to vend, so it doesn't add much value
to have other categories. For another, for complex packages with many
internal modules, the public/private nature may be driven by the layering
discipline of the project and only tangentially fall into the
public/private categories based on what the developers care to expose. If
you look at SwiftPM's manifest, for example, having two arrays would be
more annoying than helpful if we wanted to expose, say, libc, Basic and
Commands but not any of the others.

- Daniel

···

On Thu, Jul 7, 2016 at 2:02 PM, Kostiantyn Koval via swift-build-dev < swift-build-dev@swift.org> wrote:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary”),
   privateTargets: [ Target(name: "SampleCLI”)
   ])

By keeping private and public targets separate it would be easier for
package author to organise them and don’t mix theirs dependencies, like in
the example below

       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.

Thanks
- Kostiantyn

On 07 Jul 2016, at 20:45, Ankit Agarwal via swift-build-dev < > swift-build-dev@swift.org> wrote:

Hi,

Thanks for feedback. I agree that specifying external dependencies with
targets would be great.
I think the only thing issue is to figure out is how to specify them. Some
thoughts:

Note: Currently target name has to be unique across all the targets in the
package (including its dependencies).

1. Target(name: "FooLibrary", dependencies: ["FooCore", "ExternalTarget"])

pro: This is probably the easiest way to specify the external dependency
it. It fits perfectly into the current semantics and just needs to be
implemented for external deps.
con: No way to know which targets are external dependencies by just
looking at manifest file. No way to know from which package that dep is
coming from.

2. Target(name: "FooLibrary", dependencies: ["FooCore"],
externalDependencies: ["ExternalTarget"])

pro: Explicitly mentions what all external deps the target relies on.
con: Doesn't mention which package contains that external dep.

3. Target(name: "FooLibrary", dependencies: ["FooCore",
"SomePackage.ExternalTarget"])

pro: Mentions which package + target the external dependency belongs to.
con: is probably too verbose and stringly typed.

4. Target(name: "FooLibrary", dependencies: ["FooCore"],
externalDependencies: [("SomePackage", "ExternalTarget")])

pro: Mentions which package + target the external dependency belongs to.
con: verbose and stringly typed.

Would love some feedback or another way to better express the external
deps, will update the proposal then.

Thanks!

On Thu, Jul 7, 2016 at 9:35 PM, Anders Bertelrud <anders@apple.com> wrote:

Hello Ankit,

Thanks a lot for taking the initiative for this! This looks like a great
start.

I think what we will eventually want to do is to let package authors
express the "role" of each target, which would then allow expression of
such things as build-time vs run-time dependencies. For example, in some
cases there may be a dependency on a build tool (that generates code or
formats resources in some way) and that affects such things as the platform
and architectures for which the target should be built.

But that can all be added in the future. I like the direction
represented by this proposal, and I think it represents a good improvement
on its own. I do agree with other comments that it would be a great
addition to let dependencies be specified at the target level, not just the
package level. That would be in scope for inclusion in this proposal.

Thanks!

Anders

On 2016-07-07, at 05.26, Ankit Agarwal via swift-build-dev < >> swift-build-dev@swift.org> wrote:

Hi swift packagers,

I am proposing access control to package targets.

Link:
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md

Feedback appreciated!

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

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#introduction>
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.
   2.

   Import (and build) selected targets of a dependency.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#motivation>
Motivation
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets>1.
Control over exposed targets:

SwiftPM allows multiple targets (or modules) inside a package. 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 compile time for the user of the
package.

As a concrete example: Vapor has a target called Development
<https://github.com/qutheory/vapor/tree/master/Sources/Development>.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets>2.
Import selected targets:

Sometimes user of a package is only interested in few targets of a
dependency instead of all the targets. Currently there is no way to state
this in Package.swift and all the targets are implicitly built and
exposed to the user package.

For e.g.: I would like to use the targets libc, POSIX, Basic of SwiftPM
but don't want other targets to be built or exposed in my package.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#proposed-solution>Proposed
Solution
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-1>1.
Control over exposed targets:

I propose that package authors be able mark the targets they don't want
to be exposed as private i.e. the privatetargets will be built when that
package is root package but not when the package is used as a dependency.

To mark a target as private I propose PackageDescription's Target gains
a isPrivate boolean property which defaults to false.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-1>2.
Import selected targets:

I propose that package user be able to specify the targets they want to
import into their package.

To specify the targets to be import I propose to add an optional string
array property targets in PackageDescription's Package.Dependency which
defaults to nil i.e. all targets.

Instead of an optional string array property an enum can also be used:

enum ImportedTargets {
    case allTargets // Import all the targets, default value.
    case targets([String]) // Import only these targets.
}

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#detailed-design>Detailed
Design
<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#1-control-over-exposed-targets-2>1.
Control over exposed targets:

Consider a package with following structure:

├── Package.swift
└── Sources
    ├── FooLibrary
    │ └── Foo.swift
    └── SampleCLI
        └── main.swift

The manifest with private target could look like:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary"),
       Target(name: "SampleCLI", isPrivate: true),
   ])

When this package is used as a dependency only FooLibrary is built and
is importable.

Targets can have other targets as dependency inside a package. A private target
should only be a dependency to other private targets. For e.g. A manifest
like this should result in a build failure.

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooCore", isPrivate: true),
       Target(name: "FooLibrary", dependencies: ["FooCore"]), // Error FooCore is private.
       Target(name: "SampleCLI", dependencies: ["FooCore"], isPrivate: true), // Not an error because SampleCLI is private.
   ])

Error: FooCore is a private target, it cannot be a dependency to the
public target FooLibrary.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#2-import-selected-targets-2>2.
Import selected targets:

Consider a dependency with following manifest file:

import PackageDescription
let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "Foo"),
       Target(name: "Bar", dependencies: ["Foo"]),
       Target(name: "Baz"),
   ])

To get only the Bar target from the above package, following manifest
could be written:

import PackageDescription
let package = Package(
   name: "FooUser",
   dependencies: [
       .Package(
           url: "../FooLibrary",
           majorVersion: 1,
           targets: ["Bar"])
   ])

Note: In this case since Bar depends on Foo, Foo will be also be
implicitly built and be available.

Any target mentioned in targets and not present in the package should
result in build failure.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#impact-on-existing-code>Impact
on Existing Code

There will be no impact on existing code as these features are additive.

<https://github.com/aciidb0mb3r/swift-evolution/tree/swiftpm-module-access-control#alternatives-considered>Alternatives
Considered
None at this time.

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Ankit Agarwal) #11

1. One major reason for wanting control over which targets are exported
are so packages can control which parts of their API is supported for
external use. This is very important to large projects undergoing active
development while also trying to support a well-defined, semantically
versioned API.

2. In keeping with #1, and the design of the Swift language, I think the
right default is for a module to be private/unexported. This is basically
for the same reasons as `internal` is the default access control modifier
in Swift. This also relates to the discussion below.

I agree keeping things unexported/internal will make sure a certain module
is not being used as a dependency when it shouldn't. I also think the
default behaviour of a dependency package being exposed to every target
should be discontinued with this proposal and each target which wants to
use a certain external dependency should require explicit declaration. This
will make the manifest file clearer as to which target exactly depends on
what.
However it might break all currently packages (which is probably okay) but
it might also get confusing for beginners that why their package is not
compiling after declaring the external package. We can probably provide a
useful warning when an external package is declared but there is no
reference of it in any of the targets.

3. If we are going to add this via a new Target attribute, I suggest we do
so using a `flags` parameter and accompanying enumeration. This will be
equally readable, I think, but more extensible over time.

4. Technically, this proposal is defining two new few features, the

public/private exported/imported part and the ability for packages to
depend on specific targets. They are related problems, but the proposals
(and implementation) are otherwise somewhat orthogonal. I'm fine combining
them into one proposal, but if it appears that one part is going to require
much more design or discussion than the other (or that the combined
discussion is too large) we might consider breaking them up.

I agree.

There is a larger thing we need to consider with regard to the
public/private part of the proposal, and that is its relation to
hypothetical future support for package-level namespaces, whereby I mean
Swift language support for a new level of namespace (and accompanying
access control support in some form) for an entire Package. There are
several reasons I think that we will ultimately need such a thing, the most
major of which is the module-name collision problem (two packages that want
to use a shared common name for a module) that cannot be resolved by
SwiftPM without language support. Since such a feature is purely
hypothetical at this point, I don't think we need to block forward progress
in SwiftPM on it, but I do think we should discuss the consequences.

Some examples of how these things might be related:

1. The choice of `public/private` is probably wrong when viewed as a
language feature, since they already having existing meanings and
`internal` would be a more accurate name within the context of an
individual Package. I think we should include some discussion of the best
names if we reuse modifier names, or if there is a good argument for
finding alternate names ("exported/unexported"?).

if things are going to be private/internal/unexported by default, I think
we only need to define the public/exported name. Exported seems like the
correct name for this behaviour and is well known however I think public is
also equally clear (and simple) here.

2. Suppose Swift added support for a `package` namespace and access
control modifier. This might make it somewhat awkward if we had tied
expectations of what targets in an external dependency were built to a
*manifest*-level construct.

If such a feature is added to swift it would probably require to rethink
the manifest file but I agree we should try to minimize the damage if
possible.

3. We should call out that public/private in this sense ultimately won't
have any enforcement by the compiler. If a public target exposes a type
declared in a private module implicitly (say via a return value), there
won't be any error, even though it breaks the encapsulation this feature
would partially be intended to provide. Similarly, nothing will prevent a
target from importing a private module from an external dependency if was
already a transitive dependency of some other public target.

In the current draft proposal I mentioned private target's dependency can
only be another private target for the same reason but I think the approach
of having dependencies providing one (or more) well maintained exported
target makes more sense. True as of now we cannot enforce this and modules
can and will leak out. Maybe we can implement some form of enforcer or
checker using SourceKit in future.

···

--
Ankit


(Daniel Dunbar) #12

1. One major reason for wanting control over which targets are exported are so packages can control which parts of their API is supported for external use. This is very important to large projects undergoing active development while also trying to support a well-defined, semantically versioned API.

2. In keeping with #1, and the design of the Swift language, I think the right default is for a module to be private/unexported. This is basically for the same reasons as `internal` is the default access control modifier in Swift. This also relates to the discussion below.

I agree keeping things unexported/internal will make sure a certain module is not being used as a dependency when it shouldn't. I also think the default behaviour of a dependency package being exposed to every target should be discontinued with this proposal and each target which wants to use a certain external dependency should require explicit declaration. This will make the manifest file clearer as to which target exactly depends on what.
However it might break all currently packages (which is probably okay) but it might also get confusing for beginners that why their package is not compiling after declaring the external package. We can probably provide a useful warning when an external package is declared but there is no reference of it in any of the targets.

Ya, there are pros/cons here, probably a good start is to just have the proposal articulate each of them and pick the one that looks best after that.

Another choice would be to have some more subtle default (like export single module packages by default, or export all root targets (ones with no dependents) by default, or export the eponymous target). The latter two seem like relatives ok defaults to me, but its much less obvious to have a more complex defaulting strategy.

Yet another choice would be to separate the declaration of the public modules from any individual target, and keep the default that they be all exported, but if a target chooses to explicitly define a list of exported ones, then that project adopts full control. This has the advantage that only larger projects which care about the distinction need worry about the maintenance of the list.

3. If we are going to add this via a new Target attribute, I suggest we do so using a `flags` parameter and accompanying enumeration. This will be equally readable, I think, but more extensible over time.
4. Technically, this proposal is defining two new few features, the public/private exported/imported part and the ability for packages to depend on specific targets. They are related problems, but the proposals (and implementation) are otherwise somewhat orthogonal. I'm fine combining them into one proposal, but if it appears that one part is going to require much more design or discussion than the other (or that the combined discussion is too large) we might consider breaking them up.

I agree.

There is a larger thing we need to consider with regard to the public/private part of the proposal, and that is its relation to hypothetical future support for package-level namespaces, whereby I mean Swift language support for a new level of namespace (and accompanying access control support in some form) for an entire Package. There are several reasons I think that we will ultimately need such a thing, the most major of which is the module-name collision problem (two packages that want to use a shared common name for a module) that cannot be resolved by SwiftPM without language support. Since such a feature is purely hypothetical at this point, I don't think we need to block forward progress in SwiftPM on it, but I do think we should discuss the consequences.

Some examples of how these things might be related:

1. The choice of `public/private` is probably wrong when viewed as a language feature, since they already having existing meanings and `internal` would be a more accurate name within the context of an individual Package. I think we should include some discussion of the best names if we reuse modifier names, or if there is a good argument for finding alternate names ("exported/unexported"?).

if things are going to be private/internal/unexported by default, I think we only need to define the public/exported name. Exported seems like the correct name for this behaviour and is well known however I think public is also equally clear (and simple) here.

I agree picking the public name is somewhat nicer, and could go either way on the name.

2. Suppose Swift added support for a `package` namespace and access control modifier. This might make it somewhat awkward if we had tied expectations of what targets in an external dependency were built to a *manifest*-level construct.

If such a feature is added to swift it would probably require to rethink the manifest file but I agree we should try to minimize the damage if possible.

True.

3. We should call out that public/private in this sense ultimately won't have any enforcement by the compiler. If a public target exposes a type declared in a private module implicitly (say via a return value), there won't be any error, even though it breaks the encapsulation this feature would partially be intended to provide. Similarly, nothing will prevent a target from importing a private module from an external dependency if was already a transitive dependency of some other public target.

In the current draft proposal I mentioned private target's dependency can only be another private target for the same reason but I think the approach of having dependencies providing one (or more) well maintained exported target makes more sense. True as of now we cannot enforce this and modules can and will leak out. Maybe we can implement some form of enforcer or checker using SourceKit in future.

FWIW, my hope is we can eventually get a compiler feature which will allow us to enforce this...

- Daniel

···

On Jul 11, 2016, at 8:30 AM, Ankit Agarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

--
Ankit

_______________________________________________
swift-build-dev mailing list
swift-build-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Ankit Agarwal) #13

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


(Honza Dvorsky) #14

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. :slight_smile:

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

*Public:*
Foo

*Private:*
PrivateBar
ExampleFoo

- Honza

···

On Tue, Jul 12, 2016 at 8:16 PM Ankit Agarwal via swift-build-dev < swift-build-dev@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@swift.org
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Anders Bertelrud) #15

Thanks for taking the initiative for this, Ankit. It's a very welcome improvement.

Comments inline.

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.

I would prefer that this be a boolean parameter rather than a generic `flags` parameter, since it makes the manifest read more naturally, and, importantly, is no less extensible than an enum. Additional parameters with default values can as easily be added as more enum cases can, and in either case, a manifest written to assume the existence of `public` will be equally incompatible with older versions of the package manager.

So, for example:

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary", public: true),
       Target(name: "SampleCLI", dependencies: ["FooLibrary"]),
   ])

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.

I'm a bit wary of magic here. I think it would be clearer to have the manifest declare what is public and what is not. With magic naming conventions it's too easy to accidentally change semantics just by renaming 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.

Since it's the same fundamental kind of dependency in either case, would it be better to have `package` be an optional parameter to Target?

So that `Target(name: "Foo")` is local but `Target(name: "Foo", package: "Bar")` external? That would seem more logical.

Anders

···

On 2016-07-12, at 11.15, Ankit Agarwal via swift-build-dev <swift-build-dev@swift.org> wrote:


(Daniel Dunbar) #16

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

That's my two cents. :slight_smile:

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

···

On Jul 13, 2016, at 1:56 AM, Honza Dvorsky <jan.dvorsky@me.com> wrote:

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@swift.org <mailto:swift-build-dev@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:

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@swift.org <mailto:swift-build-dev@swift.org>
https://lists.swift.org/mailman/listinfo/swift-build-dev


(Ankit Agarwal) #17

Thanks for taking the initiative for this, Ankit. It's a very welcome
improvement.

Comments inline.

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.

I would prefer that this be a boolean parameter rather than a generic
`flags` parameter, since it makes the manifest read more naturally, and,
importantly, is no less extensible than an enum. Additional parameters
with default values can as easily be added as more enum cases can, and in
either case, a manifest written to assume the existence of `public` will be
equally incompatible with older versions of the package manager.

So, for example:

let package = Package(
   name: "FooLibrary",
   targets: [
       Target(name: "FooLibrary", public: true),
       Target(name: "SampleCLI", dependencies: ["FooLibrary"]),
   ])

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.

I'm a bit wary of magic here. I think it would be clearer to have the
manifest declare what is public and what is not. With magic naming
conventions it's too easy to accidentally change semantics just by renaming
a target.

I agree that we should avoid too much magic, I think you missed the
sentence below those examples in the proposal where I mentioned that we
should avoid those instead. However as Daniel mentioned we don't want to
overcomplicate the manifest for simple packages and for beginners, keeping
that in mind we updated the proposed solution which you can find on the
proposal link (
https://github.com/aciidb0mb3r/swift-evolution/blob/swiftpm-module-access-control/proposals/xxxx-swiftpm-target-access-control.md
)

I propose that enum Target.Dependency gains a new case External(package:
String, target: String) to declare dependency on an external package's
target.

Since it's the same fundamental kind of dependency in either case, would
it be better to have `package` be an optional parameter to Target?

So that `Target(name: "Foo")` is local but `Target(name: "Foo", package:
"Bar")` external? That would seem more logical.

I like `Target(name: "Foo", package: "Bar")` but I prefer package name is
stated before the target name `Target(package: "Foo", name: "Bar")` but
then this gets weird and `Target(package:target)` is also weird so I chose
`External(package:target)` instead. However if people prefer
`Target(name:package)` more then I am fine with it.

···

On Wed, Jul 13, 2016 at 9:27 PM, Anders Bertelrud <anders@apple.com> wrote:

On 2016-07-12, at 11.15, Ankit Agarwal via swift-build-dev < > swift-build-dev@swift.org> wrote:

--
Ankit