Swift PackageをZIP圧縮するときはswift package archive-source を使おう

## TL;DR
Swift Packageを`zip -r`で圧縮すると、解凍後にビルドエラーが発生することがあります。
`swift package archive-source`コマンドを使うとシンボリックリンクが正しく保持されたり、.buildディレクトリも自動的に除外されるなど、配布に適した形で圧縮されるためおすすめです。

```bash
cd swift-package-directory
swift package archive-source --output output/path/package-name.zip
```

# はじめに
Swift Packageの配布や移動のためにZIP圧縮することは珍しくありませんが、通常の`zip`コマンドを使うと思わぬビルドエラーに遭遇することがあります。本記事では、realm-swiftを例にその原因と解決法を解説します。

## 通常のZIP圧縮を使うとどうなる？

例として、realm-swiftをZIP圧縮・解凍してビルドしてみます。

```bash
git clone https://github.com/realm/realm-swift.git && cd realm-swift
swift build
...
Build complete! (70.11s)
```

もちろん、クローン時点では`swift build`は成功します。
では、続けてrealm-swiftを一度ZIP圧縮・解凍してビルドしてみます。

```bash
zip -r realm-swift.zip realm-swift # 圧縮
rm -rf realm-swift # 元のディレクトリを削除
unzip realm-swift.zip && cd realm-swift # 解凍
swift build 
...
realm-swift/Realm/RLMNetworkTransport.h:24:34: error: redefinition of 'RLMHTTPMethod'
   24 | typedef RLM_CLOSED_ENUM(int32_t, RLMHTTPMethod) {
      |                                  ^  
...
fatal error: too many errors emitted, stopping now [-ferror-limit=]
2 warnings and 20 errors generated.
[34/309] Compiling timestamp_logger.cpp
```

はい、ビルドに失敗しました。

## 原因はシンボリックリンクの消失

解凍後にgit statusでリポジトリの状態を確認すると、いつのまにか差分が出ていることがわかります。

```bash
git status

On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	typechange: Realm/ObjectServerTests/include/RLMSyncTestCase.h
	typechange: Realm/ObjectServerTests/include/RLMUser+ObjectServerTests.h
	typechange: Realm/Tests/Swift/RLMSupport.swift
	typechange: RealmSwift/RealmSwift-Info.plist
	typechange: RealmSwift/Tests/RealmSwiftTests-Info.plist
	...
	typechange: include/Realm/RLMNetworkTransport.h
	...

no changes added to commit (use "git add" and/or "git commit -a")
```

typechangeとはなんでしょうか。
[こちらのQiita記事](https://qiita.com/itochan/items/0a714f2b18fa6a41c8a6)によると、ファイルの種類が変更されたことを示しており、通常はシンボリックリンクが通常ファイルに変わったり、その逆の場合に発生します。

今回エラーの原因となったファイル
https://github.com/realm/realm-swift/blob/master/include/Realm/RLMNetworkTransport.h
の中身は、
```
../../Realm/RLMNetworkTransport.h
```
となっており、もともとシンボリックリンクだったことがわかります。

realm-swiftのような複雑なライブラリでは、ヘッダーファイルの重複を避けるためにシンボリックリンクが使用されることがあります。`zip`コマンドはデフォルトでシンボリックリンクを実ファイルに展開してしまうため：

1. 同じ内容のファイルが複数箇所に実体として配置される
2. C/C++コンパイラが重複定義エラー（redefinition error）を報告する
3. ビルドが失敗する

ような事態が発生してしまったようです。

もちろん、すべてのSwift Packageがシンボリックリンクを使っているわけではないので、ビルドエラーが必ず起きるわけではないです。
ただ、あらかじめ予防できるのであればそれに越したことはないでしょう。

## 解決策 swift package archive-sourceの使用
そこでタイトルに戻りますが、ZIP圧縮する際は、`swift package archive-source`を使うとシンボリックリンクが正しく保持されるため、先ほどのようなビルドエラーを避けることができます。
さらには、.buildディレクトリなどの再配布に不必要なファイルも自動的に除外されます。
```bash
swift package archive-source -h
OVERVIEW: Create a source archive for the package

USAGE: swift package archive-source [--output <output>]

OPTIONS:
  -o, --output <output>   The absolute or relative path for the generated source archive
  --version               Show the version.
  -h, -help, --help       Show help information.
```

では、`swift package archive-source`を使ってrealm-swiftをZIP圧縮して解凍してビルドしてみます。

```bash
swift package archive-source --output ../realm-swift.zip
cd .. && rm -rf realm-swift # 元のディレクトリを削除
unzip realm-swift.zip && cd realm-swift # 解凍
swift build
...
Build complete! (75.28s)
```

無事にビルドが成功しました。

ちなみにrealm-swiftの場合、除外されたファイルは以下の通りでした。
- .git
- .build
- .swiftpm

## 代替策 zip -yオプション
ちなみに、今のディレクトリをありのままの状態でZIP圧縮したい場合は、以下のように`-y`オプションを使えばシンボリックリンクを保持したまま圧縮できるため、先の例のようなビルドエラーを避けつつ、`zip`コマンドを使うこともできます。

```bash
zip -r -y realm-swift.zip realm-swift
# -y オプション:  store symbolic links as the link instead of the referenced file
# 訳: シンボリックリンクを参照先のファイルではなく、リンクとして保存する
```
## まとめ

`swift package archive-source`を使うことで、シンボリックリンクの問題を回避しつつ、再配布に適した形でSwift PackageをZIP圧縮できることがわかりました。
Swift PackageをZIP圧縮する際は、ぜひ`swift package archive-source`を使うことをおすすめします。