Swiftで開発環境の情報を取得する

# 概要

- 下記の記事を読んで、開発環境を書こうというモチベーションが起きました。
    - [Qiita記事にさりげなく（けど、わかりやすく）環境を記載する - Qiita](https://qiita.com/zizi4n5/items/c5f0013236fd055c3e02)
- ただ毎回バージョンを確認するのが手間なので、自動で情報を取得できればと思いちょんプロを書きました。
    - （皆さん何かしらの方法で既に楽してそうではあります）
- バッジの形式をカスタムしたい場合は`func badgeText(withLabel label: String, message: String, color: String = "brightgreen") -> String`を変更してください。

# 呼び出し例

```swift
guard let environmentInfo = EnvironmentInfoController.shared.getEnvironmentInfoString() else { return }
print(environmentInfo)
```

- 出力結果

![macOS: Version 10.15.4 (Build 19E287)](https://img.shields.io/badge/macOS-Version 10.15.4 (Build 19E287)-brightgreen) ![Swift: 5.0.2](https://img.shields.io/badge/Swift-5.0.2-brightgreen) ![Xcode: 11.6](https://img.shields.io/badge/Xcode-11.6-brightgreen)

```
![macOS: Version 10.15.4 (Build 19E287)](https://img.shields.io/badge/macOS-Version 10.15.4 (Build 19E287)-brightgreen) ![Swift: 5.0.2](https://img.shields.io/badge/Swift-5.0.2-brightgreen) ![Xcode: 11.6](https://img.shields.io/badge/Xcode-11.6-brightgreen)
```

# 開発環境の取得クラス
- OSバージョンの取得
    - [Check OS version in Swift?](https://stackoverflow.com/questions/24503001/check-os-version-in-swift/25453654)
- プロジェクトのSwiftバージョンの取得
    - [How do I see which version of Swift I'm using?](https://stackoverflow.com/questions/30790188/how-do-i-see-which-version-of-swift-im-using)
    - Stack Overflowの回答を参考にしましたがかなり力技です。他にいい方法があれば良いのですが…。
    - プロジェクト毎にSwiftのバージョンは異なるので、`swift --version`のように一概に取得する手法は取れないですね。
- `Xcode.app`のバージョン取得
    - [How do I get the App version and build number using Swift?](https://stackoverflow.com/questions/25965239/how-do-i-get-the-app-version-and-build-number-using-swift)

```swift
import Foundation

struct EnvironmentInfoController {
    static let shared = EnvironmentInfoController()

    func getEnvironmentInfoString() -> String? {

        guard let swiftVersion = getSwiftVersion() else { return nil }
        guard let osVersion    = getOsVersion()    else { return nil }
        guard let xcodeVersion = getXcodeVersion() else { return nil }

        let environmentText = String(format: "%@ %@ %@",
                                     badgeText(withLabel: "macOS", message: osVersion),
                                     badgeText(withLabel: "Swift", message: swiftVersion),
                                     badgeText(withLabel: "Xcode", message: xcodeVersion)
        )
        
        /*
        // シンプルにテキストで表示したい場合
        var environmentText = ""
        environmentText.append("OS: macOS \(osVersion)")
        environmentText.append("\nSwift: \(swiftVersion)")
        environmentText.append("\nXcode: \(xcodeVersion)")
        */
        
        return environmentText
    }

    // MARK: - Helper Methods
    
    func badgeText(withLabel label: String, message: String, color: String = "brightgreen") -> String {
        return "![\(label): \(message)](https://img.shields.io/badge/\(label)-\(message)-\(color))"
    }
    
    // MARK: - Get EnvironmentInfo Methods

    private func getOsVersion() -> String? {
        let osVersion = ProcessInfo.processInfo.operatingSystemVersionString  // e.g. "Version 10.15.4 (Build 19E287)"
        return osVersion
        
        //        let osVersion = ProcessInfo.processInfo.operatingSystemVersion
        //        return "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
    }

    private func getSwiftVersion() -> String? {
        // 適当に使用しそうなレンジをハードコーディングで設定
        #if swift(>=5.49)
        return "5.4.9"
        #elseif swift(>=5.48)
        return "5.4.8"
        #elseif swift(>=5.47)
        return "5.4.7"
        #elseif swift(>=5.46)
        return "5.4.6"
        #elseif swift(>=5.45)
        return "5.4.5"
        #elseif swift(>=5.44)
        return "5.4.4"
        #elseif swift(>=5.43)
        return "5.4.3"
        #elseif swift(>=5.42)
        return "5.4.2"
        #elseif swift(>=5.41)
        return "5.4.1"
        #elseif swift(>=5.40)
        return "5.4.0"
        #elseif swift(>=5.39)
        return "5.3.9"
        #elseif swift(>=5.38)
        return "5.3.8"
        #elseif swift(>=5.37)
        return "5.3.7"
        #elseif swift(>=5.36)
        return "5.3.6"
        #elseif swift(>=5.35)
        return "5.3.5"
        #elseif swift(>=5.34)
        return "5.3.4"
        #elseif swift(>=5.33)
        return "5.3.3"
        #elseif swift(>=5.32)
        return "5.3.2"
        #elseif swift(>=5.31)
        return "5.3.1"
        #elseif swift(>=5.30)
        return "5.3.0"
        #elseif swift(>=5.29)
        return "5.2.9"
        #elseif swift(>=5.28)
        return "5.2.8"
        #elseif swift(>=5.27)
        return "5.2.7"
        #elseif swift(>=5.26)
        return "5.2.6"
        #elseif swift(>=5.25)
        return "5.2.5"
        #elseif swift(>=5.24)
        return "5.2.4"
        #elseif swift(>=5.23)
        return "5.2.3"
        #elseif swift(>=5.22)
        return "5.2.2"
        #elseif swift(>=5.21)
        return "5.2.1"
        #elseif swift(>=5.20)
        return "5.2.0"
        #elseif swift(>=5.19)
        return "5.1.9"
        #elseif swift(>=5.18)
        return "5.1.8"
        #elseif swift(>=5.17)
        return "5.1.7"
        #elseif swift(>=5.16)
        return "5.1.6"
        #elseif swift(>=5.15)
        return "5.1.5"
        #elseif swift(>=5.14)
        return "5.1.4"
        #elseif swift(>=5.13)
        return "5.1.3"
        #elseif swift(>=5.12)
        return "5.1.2"
        #elseif swift(>=5.11)
        return "5.1.1"
        #elseif swift(>=5.10)
        return "5.1.0"
        #elseif swift(>=5.09)
        return "5.0.9"
        #elseif swift(>=5.08)
        return "5.0.8"
        #elseif swift(>=5.07)
        return "5.0.7"
        #elseif swift(>=5.06)
        return "5.0.6"
        #elseif swift(>=5.05)
        return "5.0.5"
        #elseif swift(>=5.04)
        return "5.0.4"
        #elseif swift(>=5.03)
        return "5.0.3"
        #elseif swift(>=5.02)
        return "5.0.2"
        #elseif swift(>=5.01)
        return "5.0.1"
        #elseif swift(>=5.00)
        return "5.0.0"
        #elseif swift(>=4.49)
        return "4.4.9"
        #elseif swift(>=4.48)
        return "4.4.8"
        #elseif swift(>=4.47)
        return "4.4.7"
        #elseif swift(>=4.46)
        return "4.4.6"
        #elseif swift(>=4.45)
        return "4.4.5"
        #elseif swift(>=4.44)
        return "4.4.4"
        #elseif swift(>=4.43)
        return "4.4.3"
        #elseif swift(>=4.42)
        return "4.4.2"
        #elseif swift(>=4.41)
        return "4.4.1"
        #elseif swift(>=4.40)
        return "4.4.0"
        #elseif swift(>=4.39)
        return "4.3.9"
        #elseif swift(>=4.38)
        return "4.3.8"
        #elseif swift(>=4.37)
        return "4.3.7"
        #elseif swift(>=4.36)
        return "4.3.6"
        #elseif swift(>=4.35)
        return "4.3.5"
        #elseif swift(>=4.34)
        return "4.3.4"
        #elseif swift(>=4.33)
        return "4.3.3"
        #elseif swift(>=4.32)
        return "4.3.2"
        #elseif swift(>=4.31)
        return "4.3.1"
        #elseif swift(>=4.30)
        return "4.3.0"
        #elseif swift(>=4.29)
        return "4.2.9"
        #elseif swift(>=4.28)
        return "4.2.8"
        #elseif swift(>=4.27)
        return "4.2.7"
        #elseif swift(>=4.26)
        return "4.2.6"
        #elseif swift(>=4.25)
        return "4.2.5"
        #elseif swift(>=4.24)
        return "4.2.4"
        #elseif swift(>=4.23)
        return "4.2.3"
        #elseif swift(>=4.22)
        return "4.2.2"
        #elseif swift(>=4.21)
        return "4.2.1"
        #elseif swift(>=4.20)
        return "4.2.0"
        #elseif swift(>=4.19)
        return "4.1.9"
        #elseif swift(>=4.18)
        return "4.1.8"
        #elseif swift(>=4.17)
        return "4.1.7"
        #elseif swift(>=4.16)
        return "4.1.6"
        #elseif swift(>=4.15)
        return "4.1.5"
        #elseif swift(>=4.14)
        return "4.1.4"
        #elseif swift(>=4.13)
        return "4.1.3"
        #elseif swift(>=4.12)
        return "4.1.2"
        #elseif swift(>=4.11)
        return "4.1.1"
        #elseif swift(>=4.10)
        return "4.1.0"
        #elseif swift(>=4.09)
        return "4.0.9"
        #elseif swift(>=4.08)
        return "4.0.8"
        #elseif swift(>=4.07)
        return "4.0.7"
        #elseif swift(>=4.06)
        return "4.0.6"
        #elseif swift(>=4.05)
        return "4.0.5"
        #elseif swift(>=4.04)
        return "4.0.4"
        #elseif swift(>=4.03)
        return "4.0.3"
        #elseif swift(>=4.02)
        return "4.0.2"
        #elseif swift(>=4.01)
        return "4.0.1"
        #elseif swift(>=4.00)
        return "4.0.0"
        #endif

        return nil
    }

    private func getXcodeVersion() -> String? {
        guard let xcode = Bundle(path: "/Applications/Xcode.app") else { return nil }
        guard let xcodeVersion = xcode.infoDictionary?["CFBundleShortVersionString"] as? String else { return nil }

        return xcodeVersion
    }

    // MARK: - DEBUG Methods
    
    private func printAllSwiftVersion() -> Double? {
        for majorVerion in (1...5).reversed() {
            for minorVersion in (0..<5).reversed() {
                for buildVersion in (0..<10).reversed() {
                    print("#elseif swift(>=\(majorVerion).\(minorVersion)\(buildVersion))")
                    print("return \"\(majorVerion).\(minorVersion).\(buildVersion)\"")
                }
            }
        }
        
        return nil
    }
}
```

# その他

- 外部ライブラリを使用していてそのバージョンを取得したい場合は、下記のライブラリのロジックを見れば参考になるでしょうか。(詳しく見れてないです)
    - [mono0926/LicensePlist](https://github.com/mono0926/LicensePlist)