[Draft] Package Manager Manifest API Redesign


(Rick Ballard) #1

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

  - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

    These methods neither have well defined semantics nor are used a lot
    (internally or publicly). For e.g., the current implementation of
    `successor()` always just increases the patch version.

    <details>
      <summary>View diff</summary>
      <p>
    ```diff
    struct Version {
    - func successor() -> Version

    - func predecessor() -> Version
    }
    ```
    </p></details>

* Make all properties of `Package` and `Target` mutable.

    Currently, `Package` has three immutable and four mutable properties, and
    `Target` has one immutable and one mutable property. We propose to make all
    properties mutable to allow complex customization on the package object
    after initial declaration.

    <details>
      <summary>View diff and example</summary>
      <p>

      Diff:
    ```diff
    final class Target {
    - let name: String
    + var name: String
    }

    final class Package {
    - let name: String
    + var name: String

    - let pkgConfig: String?
    + var pkgConfig: String?

    - let providers: [SystemPackageProvider]?
    + var providers: [SystemPackageProvider]?
    }
    ```

    Example:
    ```swift
    let package = Package(
        name: "FooPackage",
        targets: [
            Target(name: "Foo", dependencies: ["Bar"]),
        ]
    )

    #if os(Linux)
    package.targets[0].dependencies = ["BarLinux"]
    #endif
    ```
    </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

    According to API design guidelines, everything other than types should be in lowerCamelCase.

    <details>
      <summary>View diff and example</summary>
      <p>

     Diff:
    ```diff
    enum Dependency {
    - case Target(name: String)
    + case target(name: String)

    - case Product(name: String, package: String?)
    + case product(name: String, package: String?)

    - case ByName(name: String)
    + case byName(name: String)
    }
    ```

    Example:
    ```diff
    let package = Package(
        name: "FooPackage",
        targets: [
            Target(
                name: "Foo",
                dependencies: [
    - .Target(name: "Bar"),
    + .target(name: "Bar"),

    - .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
    + .product(name: "SwiftyJSON", package: "SwiftyJSON"),
                ]
            ),
        ]
    )
    ```
    </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

    The associated value `package` in the (enum) case `product`, is an optional
    `String`. It should have the default value `nil` so clients don't need to
    write it if they prefer using explicit enum cases but don't want to specify
    the package name i.e. it should be possible to write `.product(name:
    "Foo")` instead of `.product(name: "Foo", package: nil)`.

    If
    [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
    is accepted, we can directly add a default value. Otherwise, we will use a
    static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

    This enum allows SwiftPM System Packages to emit hints in case of build
    failures due to absence of a system package. Currently, only one system
    package per system packager can be specified. We propose to allow
    specifying multiple system packages by replacing the enum with this struct:

    ```swift
    public struct SystemPackageProvider {
        enum PackageManager {
            case apt
            case brew
        }
    
        /// The system package manager.
        let packageManager: PackageManager

        /// The array of system packages.
        let packages: [String]
    
        init(_ packageManager: PackageManager, packages: [String])
    }
    ```

    <details>
      <summary>View diff and example</summary>
      <p>

     Diff:
    ```diff
    -enum SystemPackageProvider {
    - case Brew(String)
    - case Apt(String)
    -}

    +struct SystemPackageProvider {
    + enum PackageManager {
    + case apt
    + case brew
    + }

···

+
    + /// The system package manager.
    + let packageManager: PackageManager
    +
    + /// The array of system packages.
    + let packages: [String]
    +
    + init(_ packageManager: PackageManager, packages: [String])
    +}
    ```

    Example:

    ```diff
    let package = Package(
        name: "Copenssl",
        pkgConfig: "openssl",
        providers: [
    - .Brew("openssl"),
    + SystemPackageProvider(.brew, packages: ["openssl"]),

    - .Apt("openssl-dev"),
    + SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
        ]
    )
    ```
    </p></details>

* Remove implicit target dependency rule for test targets.

    There is an implicit test target dependency rule: a test target "FooTests"
    implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
    explicitly declare any dependency. We propose to remove this rule because:

    1. It is a non obvious "magic" rule that has to be learned.
    2. It is not possible for "FooTests" to remove dependency on "Foo" while
       having no other (target) dependency.
    3. It makes real dependencies less discoverable.
    4. It may cause issues when we get support for mechanically editing target
       dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
  or a factory method:

    Under this rule, an entity having an identity, will use a type initializer
    and everything else will use factory methods. `Package`, `Target` and
    `Product` are identities. However, a product referenced in a target
    dependency is not an identity.

    This means the `Product` enum should be converted into an identity. We
    propose to introduce a `Product` class with two subclasses: `Executable`
    and `Library`. These subclasses will be nested inside `Product` class
    instead of being a top level declaration in the module. The major
    advantage of nesting is that we get a namespace for products and it is easy
    to find all the supported products when the product types grows to a large
    number. A downside of nesting is that the product initializers will have to
    used with the dot notation (e.g.: `.Executable(name: "tool", targets:
    ["tool"])`) which is a little awkward because we expect factory methods to
    use the dots.

    They will be defined as follow:

    ```swift
    /// Represents a product.
    class Product {
    
        /// The name of the product.
        let name: String

        /// The names of the targets in this product.
        let targets: [String]
    
        private init(name: String, targets: [String]) {
            self.name = name
            self.targets = targets
        }
    
        /// Represents an executable product.
        final class Executable: Product {

            /// Creates an executable product with given name and targets.
            override init(name: String, targets: [String])
        }
    
        /// Represents a library product.
        final class Library: Product {
            /// The type of library product.
            enum LibraryType: String {
                case `static`
                case `dynamic`
            }
    
            /// The type of the library.
            ///
            /// If the type is unspecified, package manager will automatically choose a type.
            let type: LibraryType?
    
            /// Creates a library product.
            init(name: String, type: LibraryType? = nil, targets: [String])
        }
    }
    ```

    <details>
      <summary>View example</summary>
      <p>

    Example:

    ```swift
    let package = Package(
        name: "Foo",
        target: [
            Target(name: "Foo", dependencies: ["Utility"]),
            Target(name: "tool", dependencies: ["Foo"]),
        ],
        products: [
            .Executable(name: "tool", targets: ["tool"]),
            .Library(name: "Foo", targets: ["Foo"]),
            .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]),
        ]
    )
    ```
    </p></details>

* Special syntax for version initializers.

    A simplified summary of what is commonly supported in other package managers:

    > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
    >-----------------|---------------|-------------------------|---------------|
    > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
    > Cargo | Supported | Same as above | Same as above |
    > CocoaPods | Not supported | Same as above | Not supported |
    > Carthage | Not supported | patch and minor updates | Not supported |

    Some general observations:

    * Every package manager we looked at for this supports the tilde `~` operator in some form.
    * The widely accepted suggestion on how to constrain your versions is "use
      `~>`, it does the right thing".
    * It's not clear to us why this has so much traction as "the right thing", as it can
      prevent upgrades that should be compatible (one minor version to next minor version).
    * Most users may not really understand `~`, and just use it per recommendations.
      See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc)
      about this operator for CocoaPods.
    * A lot of people even explicitly set a single exact version simply because
      they don't know better. This leads to "dependency hell" (unresolvable dependencies
      due to conflicting requirements for a package in the dependency graph).
    * The Swift Package Manager will probably have many novice users, because it
      comes built-in to Swift.
    * We think caret `^` has the right behaviour most of the time. That is, you
      should be able to specify a minimum version, and you should be willing to let
      your package use anything after that up to the next major version. This policy
      works if packages correctly follow semantic versioning, and it prevents "dependency
      hell" by expressing permissive constraints.
    * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
      that doesn't require reading a manual for novices to understand, even if that
      means we break with the syntactic convention established by the other package managers which
      support caret `^`.
    * We'd like a convenient syntax for caret `^`, but to still support the use
      case that tilde `~` is used for; but tilde `~` (or a single exact version) should
      be less convenient than caret `^`, to encourge permissive dependency constraints.

    What we propose:

    * We will introduce a factory method which takes a lower bound version and
      forms a range that goes upto the next major version (i.e. caret).

      ```swift
      // 1.0.0 ..< 2.0.0
      .package(url: "/SwiftyJSON", from: "1.0.0"),

      // 1.2.0 ..< 2.0.0
      .package(url: "/SwiftyJSON", from: "1.2.0"),

      // 1.5.8 ..< 2.0.0
      .package(url: "/SwiftyJSON", from: "1.5.8"),
      ```

    * We will introduce a factory method which takes `VersionSetSpecifier`, to
      conveniently specify common ranges.

      `VersionSetSpecifier` is an enum defined as follows:

      ```swift
      enum VersionSetSpecifier {
          case exact(Version)
          case range(Range<Version>)

          /// Creates a specifier for an exact version.
          static func only(_ version: Version) -> VersionSetSpecifier

          /// Creates a specified for a range starting at the given lower bound
          /// and going upto next major version.
          static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

          /// Creates a specified for a range starting at the given lower bound
          /// and going upto next minor version.
          static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
      }
      ```

      Examples:

      ```swift
      // 1.5.8 ..< 2.0.0
      .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

      // 1.5.8 ..< 1.6.0
      .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

      // 1.5.8
      .package(url: "/SwiftyJSON", .only("1.5.8")),
      ```

    * This will also give us ability to add more complex features in future:

      Examples:
      > Note that we're not actually proposing these as part of this proposal.

      ```swift
      .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

      .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

      ```

    * We will introduce a factory method which takes `Range<Version>`, to specify
      arbitrary open range.

      ```swift
      // Constraint to an arbitrary open range.
      .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
      ```

    * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
      arbitrary closed range.

      ```swift
      // Constraint to an arbitrary closed range.
      .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
      ```

    * We will remove all of the current factory methods:

      ```swift
      // Constraint to a major version.
      .Package(url: "/SwiftyJSON", majorVersion: 1),

      // Constraint to a major and minor version.
      .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

      // Constraint to an exact version.
      .Package(url: "/SwiftyJSON", "1.2.3"),

      // Constraint to an arbitrary range.
      .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

      // Constraint to an arbitrary closed range.
      .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
      ```

* Adjust order of parameters on `Package` class:

    We propose to reorder the parameters of `Package` class to: `name`,
    `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

    The rationale behind this reorder is that the most interesting parts of a
    package are its product and dependencies, so they should be at the top.
    Targets are usually important during development of the package. Placing
    them at the end keeps it easier for the developer to jump to end of the
    file to access them. Note that the compatibleSwiftVersions property will likely
    be removed once we support Build Settings, but that will be discussed in a separate proposal.

    <details>
      <summary>View example</summary>
      <p>

    Example:

    ```swift
    let package = Package(
        name: "Paper",
        products: [
            .Executable(name: "tool", targets: ["tool"]),
            .Libary(name: "Paper", type: .static, targets: ["Paper"]),
            .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
        ],
        dependencies: [
            .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
            .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
            .package(url: "http://some/other/lib", .only("1.2.3")),
        ]
        targets: [
            Target(
                name: "tool",
                dependencies: [
                    "Paper",
                    "SwiftyJSON"
                ]),
            Target(
                name: "Paper",
                dependencies: [
                    "Basic",
                    .target(name: "Utility"),
                    .product(name: "CHTTPParser"),
                ])
        ]
    )
    ```
    </p></details>

* Eliminate exclude in future (via custom layouts feature).

    We expect to remove the `exclude` property after we get support for custom
    layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

    Adding variadic overload allows omitting parenthesis which leads to less
    cognitive load on eyes, especially when there is only one value which needs
    to be specified. For e.g.:

        Target(name: "Foo", dependencies: "Bar")

    might looked better than:

        Target(name: "Foo", dependencies: ["Bar"])

    However, plurals words like `dependencies` and `targets` imply a collection
    which implies brackets. It also makes the grammar wrong. Therefore, we
    reject this option.
    
* Version exclusion.
    
    It is not uncommon to have a specific package version break something, and
    it is undesirable to "fix" this by adjusting the range to exclude it
    because this overly constrains the graph and can prevent picking up the
    version with the fix.

    This is desirable but it should be proposed separately.

* Inline package declaration.

    We should probably support declaring a package dependency anywhere we
    support spelling a package name. It is very common to only have one target
    require a dependency, and annoying to have to specify the name twice.

    This is desirable but it should be proposed separately.


(David Hart) #2

Was looking forward to this :slight_smile: here are my comments:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

···

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org> wrote:


(Ankit Aggarwal) #3

Hi David,

Thanks for the feedback! Comments inline:

Was looking forward to this :slight_smile: here are my comments:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a
Package.swift manifest API redesign for the Package Manager. We'll welcome
comments or discussion at this time. My hope is that we can get this
polished up and ready for evolution within the next week or so, but we'll
see how the conversation goes!

You can see the proposal in progress at https://github.com/
aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/
proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also
including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs
provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and
their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity.
The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties,
and
   `Target` has one immutable and one mutable property. We propose to make
all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should
be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo",
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an
optional
   `String`. It should have the default value `nil` so clients don't need
to
   write it if they prefer using explicit enum cases but don't want to
specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/
blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will
use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this
struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl",
"libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every
SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target
"FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests"
doesn't
   explicitly declare any dependency. We propose to remove this rule
because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing
target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an
initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this
case? I'm confused.

This is similar to the rule we use to decide if something should be a
struct or a class. If you're forming a concrete object, that would be an
identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The
target Bar is being referred in Foo's dependencies so that uses a factory
method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory
method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type
initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is
easy
   to find all the supported products when the product types grows to a
large
   number. A downside of nesting is that the product initializers will
have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods
to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will
automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Library(name: "Foo", targets: ["Foo"]),
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]),
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases
and static members. Using it with a type means that the capitalization
looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization
following it looks out of place here but we really think that the products
should be under a namespace because the types supported product might grow
to a substantial number in future. Adding namespace using nesting solves
this issue nicely.

Another advantage would be getting free autocomplete support for products
in IDEs or text editors which supports SourceKit. Right now there is none
which supports autocomplete for the manifest file but since SourceKit is
cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package
managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret
(`^`) |
   >-----------------|---------------|----------------------
---|---------------|
   > npm | Supported | Allows patch-level changes if a
minor version is specified on the comparator. Allows minor-level changes if
not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as
above |
   > CocoaPods | Not supported | Same as above | Not
supported |
   > Carthage | Not supported | patch and minor updates | Not
supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~`
operator in some form.
   * The widely accepted suggestion on how to constrain your versions is
"use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right
thing", as it can
     prevent upgrades that should be compatible (one minor version to next
minor version).
   * Most users may not really understand `~`, and just use it per
recommendations.
     See e.g. how Google created a [6-minute instructional video](
https://www.youtube.com/watch?v=x4ARXyovvPc)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply
because
     they don't know better. This leads to "dependency hell" (unresolvable
dependencies
     due to conflicting requirements for a package in the dependency
graph).
   * The Swift Package Manager will probably have many novice users,
because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is,
you
     should be able to specify a minimum version, and you should be
willing to let
     your package use anything after that up to the next major version.
This policy
     works if packages correctly follow semantic versioning, and it
prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer
a syntax
     that doesn't require reading a manual for novices to understand, even
if that
     means we break with the syntactic convention established by the other
package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the
use
     case that tilde `~` is used for; but tilde `~` (or a single exact
version) should
     be less convenient than caret `^`, to encourge permissive dependency
constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version
and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`,
to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower
bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) ->
VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower
bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) ->
VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").
excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to
specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes
`ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`,
`compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of
a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package.
Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property
will likely
   be removed once we support Build Settings, but that will be discussed
in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order
is insignificant. While ordering feels important for me when calling a
function or initializer with few arguments (like .package(url: "", from:
"1.4.5")), the arguments to the Package function seem like a list of
configuration options and shouldn't have a fixed order. My suggestion was
to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the
properties will be made mutable. However I think if majority of packages
uses the initializer and thus have a consistent ordering, it will be easier
for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest
itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for
custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description
v4
library. The v4 runtime library will release with Swift 4 and packages
will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/
master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests
from v3
to v4. To indicate the replacements of old APIs, we will annotate them
using
the `@unavailable` attribute where possible. Unfortunately, this will not
cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be
adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also
include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need
to
stay on the v3 manifest API and support the Swift 3 language version for
its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/
master/proposals/0151-package-manager-swift-language-
compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need
to bump its
minimum tools version to 4.0 or later using the command `$ swift package
tools-version
--set-current`, and then modify the manifest file with the changes
described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which
needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a
collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something,
and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one
target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency
in leading dots and capitalization. Should a novice (or an expert) have to
remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a
package author point of view, it's not obvious why Target is capitalized,
why package is lowercase with a leading dot and why Library is capitalized
with a leading dot. It looks confusing and inconsistent, which makes it
less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with
a leading dot? It does force us to introduce a Target factory method, to
revert SystemPackageProvider back to an enum, to revert products back to an
enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we
think this is a good rule to create a distinction between constructing
things and referring to things. A downside of lowercasing everything would
be: it might seem like the references support all the parameters that
constructor supports. e.g. .target(name: "Foo", dependencies: [
.target(name: "Bar", dependencies: ["Baz"]) ])

···

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev < swift-build-dev@swift.org> wrote:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution < > swift-evolution@swift.org> wrote:


(Daniel Dunbar) #4

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

···

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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


(David Hart) #5

Hi David,

Thanks for the feedback! Comments inline:

Was looking forward to this :slight_smile: here are my comments:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

Makes sense. And I would 100% agree with you if this was any other library. But I see the Package API as a kind of DSL which should not necessarily have the same priorities as other libraries. While scalability and extensibility is important, I think conciseness and clarity at the point of use become more important. So how about this:

struct SystemPackageProvider {
    enum PackageManager {
        case apt
        case brew
    }
    
    let packageManager: PackageManager
    let packages: [String]

    init(_ packageManager: PackageManager, packages: [String]) {
        self.packageManager = packageManager
        self.packages = packages
    }
    
    static func apt(_ packages: String...) -> SystemPackageProvider {
        return SystemPackageProvider(.brew, packages: packages)
    }
    
    static func brew(_ packages: String...) -> SystemPackageProvider {
        return SystemPackageProvider(.brew, packages: packages)
    }
}

The variadics allows a fairly concise syntax:

let package = Package(
    name: "Copenssl",
    pkgConfig: "openssl",
    providers: [
        .brew("openssl"),
        .apt("openssl", "libssl-dev")
    ]
)

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

Makes sense now!

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

I don’t follow the identity rule to the conclusion that product types should be subclasses. As long as Product is a class, and that its created with an initialiser, it seems to follow the rule.

Concerning the syntax, I understand some of the advantages, but my opinion is that its not worth it. Swift newcomers will be confused because they usually see the dot syntax with enums, and Swift experts will be put off by the stylistic anti-pattern.

I would strongly prefer keeping the current syntax:

products: [
    Product(name: “tool”, type: .executable, targets: ["tool"]),
    Product(name: “Foo”, type: .library(.automatic), targets: ["Foo"]),
    Product(name: "FooDy", type: .library(.dynamic), targets: ["Foo"]),
]

The advantages I see:

It is consistent with the other initialisers like Package and Target
It still follows the identity rule
It does have the dot syntax stylistic wart

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

Fine by me.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

You have me convinced. Now that I understand the identity rule, I’m happy with it. As I said above, the only problem I have left is with the Product syntax.

···

On 27 Feb 2017, at 11:21, Ankit Aggarwal <ankit_aggarwal@apple.com> wrote:
On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Daniel Dunbar) #6

Oh, and to your later email:

The reason why we wanted product type to use subclasses, and not a type field, is that we imagine product types growing to encompass other things for which it does not make sense to share the fields across all other products. Right now the Product type looks very normalized because it only supports things with names and modules, but we imagine other types for which that is not true (e.g., flat sets of files, archives, frameworks, bundles, etc.).

- Daniel

···

On Feb 27, 2017, at 4:50 PM, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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


(David Hart) #7

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Sounds great! When you send it through proposal, can you include an example Package with all properties set so we can see the new API in use?

Thanks!

···

On 28 Feb 2017, at 01:50, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

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


(Jens Nerup) #8

Hello Daniel,

In general I’m really happy with the changes in the proposal and especially after you have incorporated the comments from David (Thanks David). In the proposal it is stated that the exclude section may be eliminated as soon as we have custom layouts. My question is: would pkgConfig be a candidate for removal as soon as we have support for custom build configurations?

Regards,

Jens

···

On 28 Feb 2017, at 01.50, Daniel Dunbar via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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

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


(Joseph Heck) #9

Hey Daniel, Ankit -

I think the updated package manager spec is great, especially with the naming updates. I'd also like to echo and encourage the pattern of providing examples in the proposals and relevant documentation, as it makes the consumption of the spec so much more effective, which was done fairly prolifically in this update. More please!

- joe

···

On Feb 27, 2017, at 5:12 PM, Daniel Dunbar via swift-build-dev <swift-build-dev@swift.org> wrote:

Oh, and to your later email:

The reason why we wanted product type to use subclasses, and not a type field, is that we imagine product types growing to encompass other things for which it does not make sense to share the fields across all other products. Right now the Product type looks very normalized because it only supports things with names and modules, but we imagine other types for which that is not true (e.g., flat sets of files, archives, frameworks, bundles, etc.).

- Daniel

On Feb 27, 2017, at 4:50 PM, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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


(Daniel Dunbar) #10

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Sounds great! When you send it through proposal, can you include an example Package with all properties set so we can see the new API in use?

Good idea!

- Daniel

···

On Feb 27, 2017, at 10:24 PM, David Hart <david@hartbit.com> wrote:
On 28 Feb 2017, at 01:50, Daniel Dunbar <daniel_dunbar@apple.com <mailto:daniel_dunbar@apple.com>> wrote:

Thanks!

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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) #11

Hello Daniel,

In general I’m really happy with the changes in the proposal and especially after you have incorporated the comments from David (Thanks David). In the proposal it is stated that the exclude section may be eliminated as soon as we have custom layouts. My question is: would pkgConfig be a candidate for removal as soon as we have support for custom build configurations?

I don't think so, I don't think custom build configurations will be a replacement for the pkgConfig functionality, which is trying to gather settings from *outside* the package.

I agree with the sentiment that it is an awkward thing to have the system module map package initializer, but unfortunately I think we may have to live with it for the time being. We have briefly discussed making it be a target not a package thing, and perhaps the move to using `.target()` should actually encourage us to do that, but that is probably something best done as a follow on to the manifest API redesign (as with exclude) given its limited scope.

- Daniel

···

On Feb 28, 2017, at 12:28 AM, Jens Nerup <jens@makecph.com> wrote:

Regards,

Jens

On 28 Feb 2017, at 01.50, Daniel Dunbar via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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

_______________________________________________
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


(Jens Nerup) #12

Hi Daniel,

Thanks for the reply. Sorry I wasn't clear - by removal I actually meant remove pkgConfig from the Package, and introduce it in a custom build configuration (associated with a Target). I think pkgConfig and custom build configurations may fit nicely together. And you are absolutely right this is probably best suited for a follow on to the current manifest API redesign. Let's discuss that later and once again thank you for your quick reply.

Regards,

Jens

···

On 28 Feb 2017, at 17.05, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

On Feb 28, 2017, at 12:28 AM, Jens Nerup <jens@makecph.com <mailto:jens@makecph.com>> wrote:

Hello Daniel,

In general I’m really happy with the changes in the proposal and especially after you have incorporated the comments from David (Thanks David). In the proposal it is stated that the exclude section may be eliminated as soon as we have custom layouts. My question is: would pkgConfig be a candidate for removal as soon as we have support for custom build configurations?

I don't think so, I don't think custom build configurations will be a replacement for the pkgConfig functionality, which is trying to gather settings from *outside* the package.

I agree with the sentiment that it is an awkward thing to have the system module map package initializer, but unfortunately I think we may have to live with it for the time being. We have briefly discussed making it be a target not a package thing, and perhaps the move to using `.target()` should actually encourage us to do that, but that is probably something best done as a follow on to the manifest API redesign (as with exclude) given its limited scope.

- Daniel

Regards,

Jens

On 28 Feb 2017, at 01.50, Daniel Dunbar via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this was already something we weren't really happy about, but had chosen to live with (using the "identity" rule to determine what was a type and what wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be reasonable and consistent with a user model of thinking of these like enums (even though they won't actually be in practice, we will use factory functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we ever ended up needing to make the Targets array polymorphic (not something we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot + lowercase for everything (except Package), including reverting SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev <swift-build-dev@swift.org <mailto:swift-build-dev@swift.org>> wrote:
Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for a Package.swift manifest API redesign for the Package Manager. We'll welcome comments or discussion at this time. My hope is that we can get this polished up and ready for evolution within the next week or so, but we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md. I'm also including the current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md <http://xxxx-package-manager-manifest-api-redesign.md/>)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/), and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity. The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties, and
   `Target` has one immutable and one mutable property. We propose to make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo", 
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an optional
   `String`. It should have the default value `nil` so clients don't need to
   write it if they prefer using explicit enum cases but don't want to specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager 

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager 
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl", "libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target "FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests" doesn't
   explicitly declare any dependency. We propose to remove this rule because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo" while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this case? I'm confused.

This is similar to the rule we use to decide if something should be a struct or a class. If you're forming a concrete object, that would be an identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The target Bar is being referred in Foo's dependencies so that uses a factory method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses: `Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is easy
   to find all the supported products when the product types grows to a large
   number. A downside of nesting is that the product initializers will have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name <http://self.name/> = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]), 
           .Library(name: "Foo", targets: ["Foo"]), 
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]), 
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases and static members. Using it with a type means that the capitalization looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization following it looks out of place here but we really think that the products should be under a namespace because the types supported product might grow to a substantial number in future. Adding namespace using nesting solves this issue nicely.

Another advantage would be getting free autocomplete support for products in IDEs or text editors which supports SourceKit. Right now there is none which supports autocomplete for the manifest file but since SourceKit is cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret (`^`) |
   >-----------------|---------------|-------------------------|---------------|
   > npm | Supported | Allows patch-level changes if a minor version is specified on the comparator. Allows minor-level changes if not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as above |
   > CocoaPods | Not supported | Same as above | Not supported |
   > Carthage | Not supported | patch and minor updates | Not supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~` operator in some form.
   * The widely accepted suggestion on how to constrain your versions is "use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right thing", as it can
     prevent upgrades that should be compatible (one minor version to next minor version).
   * Most users may not really understand `~`, and just use it per recommendations.
     See e.g. how Google created a [6-minute instructional video](https://www.youtube.com/watch?v=x4ARXyovvPc <https://www.youtube.com/watch?v=x4ARXyovvPc>)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply because
     they don't know better. This leads to "dependency hell" (unresolvable dependencies
     due to conflicting requirements for a package in the dependency graph).
   * The Swift Package Manager will probably have many novice users, because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That is, you
     should be able to specify a minimum version, and you should be willing to let
     your package use anything after that up to the next major version. This policy
     works if packages correctly follow semantic versioning, and it prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd prefer a syntax
     that doesn't require reading a manual for novices to understand, even if that
     means we break with the syntactic convention established by the other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support the use
     case that tilde `~` is used for; but tilde `~` (or a single exact version) should
     be less convenient than caret `^`, to encourge permissive dependency constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes `VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) -> VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").excluding("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes `ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`, `compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package. Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property will likely
   be removed once we support Build Settings, but that will be discussed in a separate proposal.

I would have liked this proposal to suggest modifying the API so the order is insignificant. While ordering feels important for me when calling a function or initializer with few arguments (like .package(url: "", from: "1.4.5")), the arguments to the Package function seem like a list of configuration options and shouldn't have a fixed order. My suggestion was to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the properties will be made mutable. However I think if majority of packages uses the initializer and thus have a consistent ordering, it will be easier for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON <http://github.com/SwiftyJSON>", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib <http://some/other/lib>", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description v4
library. The v4 runtime library will release with Swift 4 and packages will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/master/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests from v3
to v4. To indicate the replacements of old APIs, we will annotate them using
the `@unavailable` attribute where possible. Unfortunately, this will not cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4 tools
will by default to use the v4 manifest. It will be possible to switch to v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need to
stay on the v3 manifest API and support the Swift 3 language version for its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/master/proposals/0151-package-manager-swift-language-compatibility-version.md).

An existing package which wants to use the new v4 manifest APIs will need to bump its
minimum tools version to 4.0 or later using the command `$ swift package tools-version
--set-current`, and then modify the manifest file with the changes described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to less
   cognitive load on eyes, especially when there is only one value which needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something, and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency in leading dots and capitalization. Should a novice (or an expert) have to remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a package author point of view, it's not obvious why Target is capitalized, why package is lowercase with a leading dot and why Library is capitalized with a leading dot. It looks confusing and inconsistent, which makes it less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with a leading dot? It does force us to introduce a Target factory method, to revert SystemPackageProvider back to an enum, to revert products back to an enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we think this is a good rule to create a distinction between constructing things and referring to things. A downside of lowercasing everything would be: it might seem like the references support all the parameters that constructor supports. e.g. .target(name: "Foo", dependencies: [ .target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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

_______________________________________________
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


(Karl) #13

The pkgConfig functionality could be generalised, though. For example, OSX’s system modulemap (found at: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/module.modulemap) does not provide any linker information. Instead, you need to manually include those weird “.tbd” files in Xcode's “Link Libraries” panel — try a package with "import Darwin.ncurses” and you won’t be able to link.

Either OSX’s system modulemap gets better (and linux’s too, I suppose), or we need the ability to include that linker information in the package manifest for more formats than just pkg-config.

- Karl

···

On 28 Feb 2017, at 17:05, Daniel Dunbar via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 28, 2017, at 12:28 AM, Jens Nerup <jens@makecph.com <mailto:jens@makecph.com>> wrote:

Hello Daniel,

In general I’m really happy with the changes in the proposal and especially after you have incorporated the comments from David (Thanks David). In the proposal it is stated that the exclude section may be eliminated as soon as we have custom layouts. My question is: would pkgConfig be a candidate for removal as soon as we have support for custom build configurations?

I don't think so, I don't think custom build configurations will be a replacement for the pkgConfig functionality, which is trying to gather settings from *outside* the package.

I agree with the sentiment that it is an awkward thing to have the system module map package initializer, but unfortunately I think we may have to live with it for the time being. We have briefly discussed making it be a target not a package thing, and perhaps the move to using `.target()` should actually encourage us to do that, but that is probably something best done as a follow on to the manifest API redesign (as with exclude) given its limited scope.

- Daniel


(Ankit Aggarwal) #14

Hi,

Thank you all for the feedback so far. I have updated the proposal:
https://github.com/aciidb0mb3r/swift-evolution/blob/manifest-api-redesign/proposals/xxxx-package-manager-manifest-api-redesign.md

The major changes are:

* Drop SystemPackageProvider struct upgrade.
* Drop the identity rule.
* Targets and Products will use factory methods.
* Rename VersionSetSpecifier to Requirement (because we also support
revision and branches and not just versions).
* Add a section on example manifests.

Note that there are collapsed examples and diffs in each proposed change.
Click "view example" to expand the examples.

···

On Wed, Mar 1, 2017 at 3:37 AM, Jens Nerup via swift-build-dev < swift-build-dev@swift.org> wrote:

Hi Daniel,

Thanks for the reply. Sorry I wasn't clear - by removal I actually meant
remove pkgConfig from the Package, and introduce it in a custom build
configuration (associated with a Target). I think pkgConfig and custom
build configurations may fit nicely together. And you are absolutely right
this is probably best suited for a follow on to the current manifest API
redesign. Let's discuss that later and once again thank you for your quick
reply.

Regards,

Jens

On 28 Feb 2017, at 17.05, Daniel Dunbar <daniel_dunbar@apple.com> wrote:

On Feb 28, 2017, at 12:28 AM, Jens Nerup <jens@makecph.com> wrote:

Hello Daniel,

In general I’m really happy with the changes in the proposal and
especially after you have incorporated the comments from David (Thanks
David). In the proposal it is stated that the exclude section may be
eliminated as soon as we have custom layouts. My question is: would
pkgConfig be a candidate for removal as soon as we have support for custom
build configurations?

I don't think so, I don't think custom build configurations will be a
replacement for the pkgConfig functionality, which is trying to gather
settings from *outside* the package.

I agree with the sentiment that it is an awkward thing to have the system
module map package initializer, but unfortunately I think we may have to
live with it for the time being. We have briefly discussed making it be a
target not a package thing, and perhaps the move to using `.target()`
should actually encourage us to do that, but that is probably something
best done as a follow on to the manifest API redesign (as with exclude)
given its limited scope.

- Daniel

Regards,

Jens

On 28 Feb 2017, at 01.50, Daniel Dunbar via swift-build-dev < > swift-build-dev@swift.org> wrote:

Hi David,

We discussed the leading-dot & capitalization issue today again... this
was already something we weren't really happy about, but had chosen to live
with (using the "identity" rule to determine what was a type and what
wasn't). However, as we talked it over more we:
1. Felt that for the product types, using .library vs .Library would be
reasonable and consistent with a user model of thinking of these like enums
(even though they won't actually be in practice, we will use factory
functions on Product to make the dot work and keep the space extensible).
2. Realized that using .target would be a useful change to make now if we
ever ended up needing to make the Targets array polymorphic (not something
we plan to do now, but it never hurts to have it be extensible).
so we decided to go ahead and revise to a model where we use leading-dot +
lowercase for everything (except Package), including reverting
SystemPackageProvider to the `.brew(...)` style syntax.

Thanks for the feedback!
- Daniel

On Feb 27, 2017, at 2:21 AM, Ankit Aggarwal via swift-build-dev < > swift-build-dev@swift.org> wrote:

Hi David,

Thanks for the feedback! Comments inline:

On Sun, Feb 26, 2017 at 5:08 AM, David Hart via swift-build-dev < > swift-build-dev@swift.org> wrote:

Was looking forward to this :slight_smile: here are my comments:

On 25 Feb 2017, at 01:35, Rick Ballard via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi all,

Ankit, Daniel, Anders, Boris and I have a draft proposal in progress for
a Package.swift manifest API redesign for the Package Manager. We'll
welcome comments or discussion at this time. My hope is that we can get
this polished up and ready for evolution within the next week or so, but
we'll see how the conversation goes!

You can see the proposal in progress at https://github.com/aciidb0m
b3r/swift-evolution/blob/manifest-api-redesign/proposal
s/xxxx-package-manager-manifest-api-redesign.md. I'm also including the
current version inline in this email.

Thanks,

   - Rick

# Package Manager Manifest API Redesign

* Proposal: [SE-XXXX](xxxx-package-manager-manifest-api-redesign.md)
* Author: [Ankit Aggarwal](https://github.com/aciidb0mb3r)
* Review Manager: TBD
* Status: **Discussion**

## Introduction

This is a proposal for redesigning the `Package.swift` manifest APIs
provided
by Swift Package Manager.
This proposal only redesigns the existing public APIs and does not add any
new functionality; any API to be added for new functionality will happen
in
separate proposals.

## Motivation

The `Package.swift` manifest APIs were designed prior to the [API Design
Guidelines] (https://swift.org/documentation/api-design-guidelines/),
and their
design was not reviewed by the evolution process. Additionally, there are
several small areas which can be cleaned up to make the overall API more
"Swifty".

We would like to redesign these APIs as necessary to provide clean,
conventions-compliant APIs that we can rely on in the future. Because we
anticipate that the user community for the Swift Package Manager will grow
considerably in Swift 4, we would like to make these changes now, before
more packages are created using the old API.

## Proposed solution

Note: Access modifier is omitted from the diffs and examples for brevity.
The
access modifier is `public` for all APIs unless specified.

* Remove `successor()` and `predecessor()` from `Version`.

   These methods neither have well defined semantics nor are used a lot
   (internally or publicly). For e.g., the current implementation of
   `successor()` always just increases the patch version.

   <details>
     <summary>View diff</summary>
     <p>

   struct Version {
   -    func successor() -> Version

   -    func predecessor() -> Version
   }

   </p></details>

* Make all properties of `Package` and `Target` mutable.

   Currently, `Package` has three immutable and four mutable properties,
and
   `Target` has one immutable and one mutable property. We propose to
make all
   properties mutable to allow complex customization on the package object
   after initial declaration.

   <details>
     <summary>View diff and example</summary>
     <p>

     Diff:

   final class Target {
   -    let name: String
   +    var name: String
   }

   final class Package {
   -    let name: String
   +    var name: String

   -    let pkgConfig: String?
   +    var pkgConfig: String?

   -    let providers: [SystemPackageProvider]?
   +    var providers: [SystemPackageProvider]?
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(name: "Foo", dependencies: ["Bar"]),
       ]
   )

   #if os(Linux)
   package.targets[0].dependencies = ["BarLinux"]
   #endif

   </p></details>

* Change `Target.Dependency` enum cases to lowerCamelCase.

   According to API design guidelines, everything other than types should
be in lowerCamelCase.

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   enum Dependency {
   -    case Target(name: String)
   +    case target(name: String)

   -    case Product(name: String, package: String?)
   +    case product(name: String, package: String?)

   -    case ByName(name: String)
   +    case byName(name: String)
   }

   Example:

   let package = Package(
       name: "FooPackage",
       targets: [
           Target(
               name: "Foo",
               dependencies: [
   -                .Target(name: "Bar"),
   +                .target(name: "Bar"),

   -                .Product(name: "SwiftyJSON", package: "SwiftyJSON"),
   +                .product(name: "SwiftyJSON", package: "SwiftyJSON"),
               ]
           ),
       ]
   )

   </p></details>

* Add default parameter to the enum case `Target.Dependency.product`.

   The associated value `package` in the (enum) case `product`, is an
optional
   `String`. It should have the default value `nil` so clients don't need
to
   write it if they prefer using explicit enum cases but don't want to
specify
   the package name i.e. it should be possible to write `.product(name:
   "Foo")` instead of `.product(name: "Foo", package: nil)`.

   If
   [SE-0155](https://github.com/apple/swift-evolution/blob/
master/proposals/0155-normalize-enum-case-representation.md)
   is accepted, we can directly add a default value. Otherwise, we will
use a
   static factory method to provide default value for `package`.

* Upgrade `SystemPackageProvider` enum to a struct.

   This enum allows SwiftPM System Packages to emit hints in case of build
   failures due to absence of a system package. Currently, only one system
   package per system packager can be specified. We propose to allow
   specifying multiple system packages by replacing the enum with this
struct:

   public struct SystemPackageProvider {
       enum PackageManager {
           case apt
           case brew
       }

       /// The system package manager.
       let packageManager: PackageManager

       /// The array of system packages.
       let packages: [String]

       init(_ packageManager: PackageManager, packages: [String])
   }

   <details>
     <summary>View diff and example</summary>
     <p>

    Diff:

   -enum SystemPackageProvider {
   -    case Brew(String)
   -    case Apt(String)
   -}

   +struct SystemPackageProvider {
   +    enum PackageManager {
   +        case apt
   +        case brew
   +    }
   +
   +    /// The system package manager.
   +    let packageManager: PackageManager
   +
   +    /// The array of system packages.
   +    let packages: [String]
   +
   +    init(_ packageManager: PackageManager, packages: [String])
   +}

   Example:

   let package = Package(
       name: "Copenssl",
       pkgConfig: "openssl",
       providers: [
   -        .Brew("openssl"),
   +        SystemPackageProvider(.brew, packages: ["openssl"]),

   -        .Apt("openssl-dev"),
   +        SystemPackageProvider(.apt, packages: ["openssl",
"libssl-dev"]),
       ]
   )

   </p></details>

Why not keep the enum and change the associated type to a String array?

True, we could do that but we'd be repeating that information in every
SystemPackager we add. Converting to a struct makes it easier to scale.

* Remove implicit target dependency rule for test targets.

   There is an implicit test target dependency rule: a test target
"FooTests"
   implicity depends on a target "Foo", if "Foo" exists and "FooTests"
doesn't
   explicitly declare any dependency. We propose to remove this rule
because:

   1. It is a non obvious "magic" rule that has to be learned.
   2. It is not possible for "FooTests" to remove dependency on "Foo"
while
      having no other (target) dependency.
   3. It makes real dependencies less discoverable.
   4. It may cause issues when we get support for mechanically editing
target
      dependencies.

* Introduce an "identity rule" to determine if an API should use an
initializer
or a factory method:

Could you explain this rule in more detail. What is an identity in this
case? I'm confused.

This is similar to the rule we use to decide if something should be a
struct or a class. If you're forming a concrete object, that would be an
identity. Consider these two examples:

1. Target and its dependencies:

    Target(name: "Foo", dependencies: [.target(name: "Bar")])

Here the target Foo is being constructed, so an initializer is used. The
target Bar is being referred in Foo's dependencies so that uses a factory
method.

2. Product and product dependencies in targets:

When constructing the product, the initializer should be used:
    .Library(name: "FooLib", targets: ["Foo", "Utility"])

And while referring to the product, like in target dependency, factory
method should be used:
    Target(name: "Foo", dependencies: [.product(name: "FooLib")])

   Under this rule, an entity having an identity, will use a type
initializer
   and everything else will use factory methods. `Package`, `Target` and
   `Product` are identities. However, a product referenced in a target
   dependency is not an identity.

   This means the `Product` enum should be converted into an identity. We
   propose to introduce a `Product` class with two subclasses:
`Executable`
   and `Library`. These subclasses will be nested inside `Product` class
   instead of being a top level declaration in the module. The major
   advantage of nesting is that we get a namespace for products and it is
easy
   to find all the supported products when the product types grows to a
large
   number. A downside of nesting is that the product initializers will
have to
   used with the dot notation (e.g.: `.Executable(name: "tool", targets:
   ["tool"])`) which is a little awkward because we expect factory
methods to
   use the dots.

   They will be defined as follow:

   /// Represents a product.
   class Product {

       /// The name of the product.
       let name: String

       /// The names of the targets in this product.
       let targets: [String]

       private init(name: String, targets: [String]) {
           self.name = name
           self.targets = targets
       }

       /// Represents an executable product.
       final class Executable: Product {

           /// Creates an executable product with given name and targets.
           override init(name: String, targets: [String])
       }

       /// Represents a library product.
       final class Library: Product {
           /// The type of library product.
           enum LibraryType: String {
               case `static`
               case `dynamic`
           }

           /// The type of the library.
           ///
           /// If the type is unspecified, package manager will
automatically choose a type.
           let type: LibraryType?

           /// Creates a library product.
           init(name: String, type: LibraryType? = nil, targets: [String])
       }
   }

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Foo",
       target: [
           Target(name: "Foo", dependencies: ["Utility"]),
           Target(name: "tool", dependencies: ["Foo"]),
       ],
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Library(name: "Foo", targets: ["Foo"]),
           .Library(name: "FooDy", type: .dynamic, targets: ["Foo"]),
       ]
   )

   </p></details>

This API looks very weird: the leading dog is usually used for enum cases
and static members. Using it with a type means that the capitalization
looks very out of place.

Yes, as mentioned in the proposal we think the dot and capitalization
following it looks out of place here but we really think that the products
should be under a namespace because the types supported product might grow
to a substantial number in future. Adding namespace using nesting solves
this issue nicely.

Another advantage would be getting free autocomplete support for products
in IDEs or text editors which supports SourceKit. Right now there is none
which supports autocomplete for the manifest file but since SourceKit is
cross platform, it should be possible to create a plugin in future.

* Special syntax for version initializers.

   A simplified summary of what is commonly supported in other package
managers:

   > Package Manager | x-ranges | tilde (`~` or `~>`) | caret
(`^`) |
   >-----------------|---------------|----------------------
---|---------------|
   > npm | Supported | Allows patch-level changes if a
minor version is specified on the comparator. Allows minor-level changes if
not. | patch and minor updates |
   > Cargo | Supported | Same as above | Same as
above |
   > CocoaPods | Not supported | Same as above | Not
supported |
   > Carthage | Not supported | patch and minor updates | Not
supported |

   Some general observations:

   * Every package manager we looked at for this supports the tilde `~`
operator in some form.
   * The widely accepted suggestion on how to constrain your versions is
"use
     `~>`, it does the right thing".
   * It's not clear to us why this has so much traction as "the right
thing", as it can
     prevent upgrades that should be compatible (one minor version to
next minor version).
   * Most users may not really understand `~`, and just use it per
recommendations.
     See e.g. how Google created a [6-minute instructional video](
https://www.youtube.com/watch?v=x4ARXyovvPc)
     about this operator for CocoaPods.
   * A lot of people even explicitly set a single exact version simply
because
     they don't know better. This leads to "dependency hell"
(unresolvable dependencies
     due to conflicting requirements for a package in the dependency
graph).
   * The Swift Package Manager will probably have many novice users,
because it
     comes built-in to Swift.
   * We think caret `^` has the right behaviour most of the time. That
is, you
     should be able to specify a minimum version, and you should be
willing to let
     your package use anything after that up to the next major version.
This policy
     works if packages correctly follow semantic versioning, and it
prevents "dependency
     hell" by expressing permissive constraints.
   * We also think caret `^` is syntactically non-obvious, and we'd
prefer a syntax
     that doesn't require reading a manual for novices to understand,
even if that
     means we break with the syntactic convention established by the
other package managers which
     support caret `^`.
   * We'd like a convenient syntax for caret `^`, but to still support
the use
     case that tilde `~` is used for; but tilde `~` (or a single exact
version) should
     be less convenient than caret `^`, to encourge permissive dependency
constraints.

   What we propose:

   * We will introduce a factory method which takes a lower bound version
and
     forms a range that goes upto the next major version (i.e. caret).

     ```swift
     // 1.0.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.0.0"),

     // 1.2.0 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.2.0"),

     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", from: "1.5.8"),
     ```

   * We will introduce a factory method which takes
`VersionSetSpecifier`, to
     conveniently specify common ranges.

     `VersionSetSpecifier` is an enum defined as follows:

     ```swift
     enum VersionSetSpecifier {
         case exact(Version)
         case range(Range<Version>)

         /// Creates a specifier for an exact version.
         static func only(_ version: Version) -> VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower
bound
         /// and going upto next major version.
         static func uptoNextMajor(_ version: Version) ->
VersionSetSpecifier

         /// Creates a specified for a range starting at the given lower
bound
         /// and going upto next minor version.
         static func uptoNextMinor(_ version: Version) ->
VersionSetSpecifier
     }
     ```

     Examples:

     ```swift
     // 1.5.8 ..< 2.0.0
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8")),

     // 1.5.8 ..< 1.6.0
     .package(url: "/SwiftyJSON", .uptoNextMinor("1.5.8")),

     // 1.5.8
     .package(url: "/SwiftyJSON", .only("1.5.8")),
     ```

   * This will also give us ability to add more complex features in
future:

     Examples:

Note that we're not actually proposing these as part of this proposal.

     ```swift
     .package(url: "/SwiftyJSON", .uptoNextMajor("1.5.8").exclud
ing("1.6.4")),

     .package(url: "/SwiftyJSON", .only("1.5.8", "1.6.3")),

     ```

   * We will introduce a factory method which takes `Range<Version>`, to
specify
     arbitrary open range.

     ```swift
     // Constraint to an arbitrary open range.
     .package(url: "/SwiftyJSON", "1.2.3"..<"1.2.6"),
     ```

   * We will introduce a factory method which takes
`ClosedRange<Version>`, to specify
     arbitrary closed range.

     ```swift
     // Constraint to an arbitrary closed range.
     .package(url: "/SwiftyJSON", "1.2.3"..."1.2.8"),
     ```

   * We will remove all of the current factory methods:

     ```swift
     // Constraint to a major version.
     .Package(url: "/SwiftyJSON", majorVersion: 1),

     // Constraint to a major and minor version.
     .Package(url: "/SwiftyJSON", majorVersion: 1, minor: 2),

     // Constraint to an exact version.
     .Package(url: "/SwiftyJSON", "1.2.3"),

     // Constraint to an arbitrary range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..<"1.2.6"),

     // Constraint to an arbitrary closed range.
     .Package(url: "/SwiftyJSON", versions: "1.2.3"..."1.2.8"),
     ```

I'm ver happy with the versioning part of this proposal :slight_smile:

Great!

* Adjust order of parameters on `Package` class:

   We propose to reorder the parameters of `Package` class to: `name`,
   `pkgConfig`, `products`, `dependencies`, `targets`,
`compatibleSwiftVersions`.

   The rationale behind this reorder is that the most interesting parts
of a
   package are its product and dependencies, so they should be at the top.
   Targets are usually important during development of the package.
Placing
   them at the end keeps it easier for the developer to jump to end of the
   file to access them. Note that the compatibleSwiftVersions property
will likely
   be removed once we support Build Settings, but that will be discussed
in a separate proposal.

I would have liked this proposal to suggest modifying the API so the
order is insignificant. While ordering feels important for me when calling
a function or initializer with few arguments (like .package(url: "", from:
"1.4.5")), the arguments to the Package function seem like a list of
configuration options and shouldn't have a fixed order. My suggestion was
to remove all arguments but the name and adds a configuration closure:

let package = Package(name: "paper") {
    $0.products = [...]
    $0.dependencies = [...]
}

It will be possible to avoid using the initializer because all the
properties will be made mutable. However I think if majority of packages
uses the initializer and thus have a consistent ordering, it will be easier
for other developers to read manifests on Github (or similar).

PS: The closure syntax can also be added using extension in the manifest
itself if someone really wants to use it.

   <details>
     <summary>View example</summary>
     <p>

   Example:

   let package = Package(
       name: "Paper",
       products: [
           .Executable(name: "tool", targets: ["tool"]),
           .Libary(name: "Paper", type: .static, targets: ["Paper"]),
           .Libary(name: "PaperDy", type: .dynamic, targets: ["Paper"]),
       ],
       dependencies: [
           .package(url: "http://github.com/SwiftyJSON", from: "1.2.3"),
           .package(url: "../CHTTPParser", .uptoNextMinor("2.2.0")),
           .package(url: "http://some/other/lib", .only("1.2.3")),
       ]
       targets: [
           Target(
               name: "tool",
               dependencies: [
                   "Paper",
                   "SwiftyJSON"
               ]),
           Target(
               name: "Paper",
               dependencies: [
                   "Basic",
                   .target(name: "Utility"),
                   .product(name: "CHTTPParser"),
               ])
       ]
   )

   </p></details>

* Eliminate exclude in future (via custom layouts feature).

   We expect to remove the `exclude` property after we get support for
custom
   layouts. The exact details will be in the proposal of that feature.

## Impact on existing code

The above changes will be implemented only in the new Package Description
v4
library. The v4 runtime library will release with Swift 4 and packages
will be
able to opt-in into it as described by
[SE-0152](https://github.com/apple/swift-evolution/blob/mast
er/proposals/0152-package-manager-tools-version.md).

There will be no automatic migration feature for updating the manifests
from v3
to v4. To indicate the replacements of old APIs, we will annotate them
using
the `@unavailable` attribute where possible. Unfortunately, this will not
cover
all the changes for e.g. rename of the target dependency enum cases.

All new packages created with `swift package init` command in Swift 4
tools
will by default to use the v4 manifest. It will be possible to switch to
v3
manifest version by changing the tools version using `swift package
tools-version --set 3.1`. However, the manifest will needed to be
adjusted to
use the older APIs manually.

Unless declared in the manifest, existing packages automatically default
to the Swift 3 minimum tools version; since the Swift 4 tools will also
include
the v3 manifest API, they will build as expected.

A package which needs to support both Swift 3 and Swift 4 tools will need
to
stay on the v3 manifest API and support the Swift 3 language version for
its
sources, using the API described in the proposal
[SE-0151](https://github.com/apple/swift-evolution/blob/mast
er/proposals/0151-package-manager-swift-language-compatibility-version.md
).

An existing package which wants to use the new v4 manifest APIs will need
to bump its
minimum tools version to 4.0 or later using the command `$ swift package
tools-version
--set-current`, and then modify the manifest file with the changes
described in
this proposal.

## Alternatives considered

* Add variadic overloads.

   Adding variadic overload allows omitting parenthesis which leads to
less
   cognitive load on eyes, especially when there is only one value which
needs
   to be specified. For e.g.:

       Target(name: "Foo", dependencies: "Bar")

   might looked better than:

       Target(name: "Foo", dependencies: ["Bar"])

   However, plurals words like `dependencies` and `targets` imply a
collection
   which implies brackets. It also makes the grammar wrong. Therefore, we
   reject this option.

* Version exclusion.

   It is not uncommon to have a specific package version break something,
and
   it is undesirable to "fix" this by adjusting the range to exclude it
   because this overly constrains the graph and can prevent picking up the
   version with the fix.

   This is desirable but it should be proposed separately.

* Inline package declaration.

   We should probably support declaring a package dependency anywhere we
   support spelling a package name. It is very common to only have one
target
   require a dependency, and annoying to have to specify the name twice.

   This is desirable but it should be proposed separately.

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

One thing that still really bothers me about the API is the inconsistency
in leading dots and capitalization. Should a novice (or an expert) have to
remember the following different writings:

Target(name: "Foo", dependencies: ["Utility"])
.package(url: "http://github.com/SwiftyJSON", from: "1.2.3")
.Library(name: "Paper", type: .static, targets: ["Paper"])

I understand the arguments brought forward in the proposal. But from a
package author point of view, it's not obvious why Target is capitalized,
why package is lowercase with a leading dot and why Library is capitalized
with a leading dot. It looks confusing and inconsistent, which makes it
less pleasant to read and harder to remember.

Could we push for more consistency by having everything b lowercased with
a leading dot? It does force us to introduce a Target factory method, to
revert SystemPackageProvider back to an enum, to revert products back to an
enum. But I think it's worth it.

It is true that it might not be obvious when to use what initially, but we
think this is a good rule to create a distinction between constructing
things and referring to things. A downside of lowercasing everything would
be: it might seem like the references support all the parameters that
constructor supports. e.g. .target(name: "Foo", dependencies: [
.target(name: "Bar", dependencies: ["Baz"]) ])

_______________________________________________
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

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