Swift Digest
SE-0294 | Swift Evolution

Declaring executable targets in Package Manifests

Proposal
SE-0294
Authors
Anders Bertelrud
Review Manager
Tom Doron
Status
Implemented (Swift 5.4)

01 何が問題だったのか

Swift Package Manager(SwiftPM)では、これまでターゲットが実行可能(executable)かどうかを、パッケージマニフェスト(Package.swift)で宣言する方法がありませんでした。代わりに、ターゲットのソースディレクトリの直下に main という名前のソースファイル(main.swift など)が存在するかどうかを見て、暗黙に推論するしくみになっていました。

この推論方式は、次のような問題を抱えていました。

マニフェストだけではターゲットの意図がわからない

ターゲットが実行可能かどうかは、コンパイラに渡すフラグに影響します(たとえば、ライブラリターゲットには -parse-as-library が渡されますが、実行可能ターゲットには渡されません)。それにもかかわらず、マニフェストだけを見ても executable かどうかが判別できず、ソースファイルの構成に依存してしまっていました。また、実行可能プロダクトに「実行可能ターゲットがちょうど 1 つ含まれているか」といった診断もあいまいになりがちでした。

@main と相性が悪い

SE-0281 で導入された @main を使うと、エントリポイントを持つ型に属性を付けるだけで実行可能モジュールのエントリポイントを指定できます。しかし、SwiftPM が main.swift の有無でしか判別できないと、@main を使うためにわざわざ main.swift という名前の(中身のない、あるいは形ばかりの)ソースファイルを用意する必要が生じ、せっかくの @main の利点が損なわれていました。

main.swift という特別扱いの不便さ

そもそも main という名前のソースファイルを特別視する方式自体にも、古くから不便が指摘されていました(SR-1379 など)。ファイル名のリネームや移動で意図せずターゲットの種類が変わってしまう、といった混乱も起こりえます。

つまり、「このターゲットは実行可能である」という根本的な意図を、ソースファイルの命名ではなくマニフェスト上で明示できるしくみが求められていました。

02 どのように解決されるのか

PackageDescription API に、実行可能ターゲットを宣言するための executableTarget を新しく追加します。パラメータは既存の target と同じですが、ターゲットの種類が「実行可能」であることがマニフェスト上で明示されます。

使い方

Package.swift でこれまで .target(...) と書いていたところを、実行可能ターゲットについては .executableTarget(...) に置き換えます。

// swift-tools-version:5.4
import PackageDescription

let package = Package(
    name: "MyTool",
    targets: [
        .executableTarget(
            name: "MyTool",
            dependencies: ["MyLibrary"]
        ),
        .target(
            name: "MyLibrary"
        ),
    ]
)

.executableTarget として宣言したターゲットでは、ソースファイルの名前に依存せずに実行可能モジュールとしてビルドされます。したがって、エントリポイントは main.swift でも、@main を付けた型でも構いません。

// Sources/MyTool/Entry.swift
@main
struct Entry {
    static func main() {
        print("Hello!")
    }
}

内部的には TargetType 列挙型に .executable ケースが追加されますが、マニフェストを書く側から見える変更は executableTarget の追加だけです。

tools-version による振る舞いの違い

swift-tools-version によって、SwiftPM の挙動が次のように変わります。

  • tools-version が 5.4 より前の場合: 従来どおり、ターゲットのソースディレクトリ直下に main という名前のソースファイル(main.swift / main.m / main.c / main.cpp)があれば実行可能ターゲットとして扱われます。
  • tools-version が 5.4 以降の場合:
    • .executableTarget で宣言されたターゲットは、それだけで実行可能ターゲットとして扱われます。
    • .target で宣言されたターゲットについても、従来どおり main ソースファイルの有無で実行可能かどうかが判定されますが、そう判定された場合には「.executableTarget に書き換えるべき」という警告が出ます(可能なら fix-it 付き)。

1 つのパッケージグラフ内に新旧の tools-version が混在していても問題はなく、各ターゲットの実行可能性はそれを宣言しているパッケージの tools-version に従って判定されます。

将来の tools-version では、この警告はエラー相当の扱いになり、.target で宣言されたターゲットはすべてライブラリターゲットとみなされるようになる予定です。

なお、コンパイラに渡すフラグ自体は従来と同じで、ライブラリターゲットには -parse-as-library が付き、実行可能ターゲットには付きません。main.swift が実行可能モジュールのエントリポイントとして解釈される挙動もそのまま維持されます。また、Swift モジュール全般のルールとして、1 つの実行可能モジュールに @mainmain.swift の両方のエントリポイントを同居させることはできません。

Future Directions(見通し)

実行可能ターゲットが 1 つだけのパッケージでは、プロダクト側(executable プロダクト)とターゲット側(executableTarget)の両方に “executable” という単語が現れ、やや冗長に感じられます。これは、SwiftPM が「対応する executable プロダクトが存在しない場合、executable ターゲットから暗黙に executable プロダクトを作る」挙動を持っているため、実害は小さく抑えられています。将来的にはプロダクトとターゲットの宣言を統合する方向が模索されており、その流れの中でこの重複も解消される見通しです。いずれも speculative な展望であり、実現が約束されたものではありません。