読者です 読者をやめる 読者になる 読者になる

euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

超かんたんなSwift on Linux用のMakefile

Linux Swift

あると便利

gist.github.com

こんなもので良ければどうぞ。

使い方

$ make
# make buildに同等。ビルドします。
# コンパイルオプションで分けたりしてもいいと思います。
# make buildでデバッグ版、make build_releaseでリリース版とか。

$ make update
# swift package updateに同等。単にタイプ数少なくてすむ

$ make run
# パスは適宜替えてください。

自明ですがどうぞ。

まとめ

風邪を引きました。みなさんもどうぞご自愛ください。

Python Redisでredis.exceptions.ConnectionError: Error 99 connecting to ~~~.com:XXXX. Cannot assign requested address.

Python Redis

が出た

多分大量の接続を作っては切ってを繰り返している。OSがあなたが要らなくなったポートやコネクションを開放するより早くあなたが新しいのを作ろうとしているので足らなくなっている。

対処法

全部おんなじDBへのアクセスならいちいち繋いで切ってを繰り返さない。コネクションプールみたいなものを作って再利用する。そのほうが全体の処理も早くなる。

Linux上でSwift Package Managerを使ってビルドするときに躓いたこと

Swift Linux

けっこう茨の道です

なんかまだ「初心者でも鼻くそほじりながら日曜日にさらっとね」って感じではないです。なにせコンパイラ、Xcode、Swift Package Manager(以下SPM)すべてが日進月歩でバージョンアップしているのでウェブに書いてあることそのまま試すと全く動かなかったりします。

このブログの内容は2016年11月6日の内容です。Swiftはversion 3.0.1 (swift-3.0.1-RELEASE)を使いUbuntu 14.04上でやりました。

f:id:euphonictechnologies:20161106185709p:plain

まずは

github.com

公式ドキュメント。必読です。私はここを読まずにたくさんの時間を無駄にしました。アホですね。

CとSwift混ぜるとき

モジュールを分ける

SPMはSources下のディレクトリ単位でモジュール扱いをします。

CとSwiftのコードを同じモジュールに混在させるとバリデーションに引っかかります。素直に分けましょう。私のオセロライブラリGrapheneの場合

(Project root)
├── Graphene.xcodeproj
... xcodeprojの中身がドバ~っと
├── LICENSE
├── Package.swift
├── README.md
├── Sources
│   ├── Graphene
│   │   ├── Board.swift
│   │   ├── BoardBuilder.swift
│   │   ├── BoardMediator.swift
... ファイルがドバ~っと
│   └── Intrinsics
│       ├── Intrinsics.c
│       └── include
│           ├── Graphene-Bridging-Header.h
│           ├── Intrinsics.h
│           └── Swift_Bridging_Header.h
└── Tests
    ├── GrapheneTests
    │   └── GrapheneTests.swift
    └── LinuxMain.swift

こんな感じになっています。SwiftコードはGrapheneというパッケージ名と同じモジュールにまとめてあります。CのコードはIntrinsicsというモジュールにしました。Cモジュールにはinclude(すべて小文字)が必要です。モジュール名と同じヘッダファイルが第一に使われます。他のbridging headerは多分必要ないのですが、とりあえず残してあります。

Package.swiftにtargetを追加してdependencyを明示する

オフィシャルのドキュメントに書いてあるのですが、dependencyを明示するためにモジュールにPackage.swiftにtargetをきちんと書く必要があります。つまり

import PackageDescription

let package = Package(
    name: "Graphene",
    targets: [
        Target(name: "Graphene", dependencies: ["Intrinsics"]),
    ]
)

こんな感じです。これはCのコードからなるIntrinsicsモジュールを先にビルドするためです。

@_silgen_nameはとりあえず外す

なんかCのシンボルがリンク時に見つからないとか言うし@silgen_nameアトリビュートがあるファイルにimport Intrinsicsと書くと今度はシンボルが曖昧とか言われるしわけわかんないのでとりあえず@silgen_nameは消してimport Intrinsicsを残すことにしました。つまり

#if os(Linux)
import Intrinsics
#else
@_silgen_name("_bitScanForward")
    func _bitScanForward(_: UInt64) -> UInt
    
@_silgen_name("_bitPop")
    func _bitPop(_: UInt64) -> UInt
#endif

こんな感じです。当然macOSでXcode上でビルドするためには_silgen_nameが必要なので、こんなふうに分岐させてみました。Linux向け特殊処理が必要なときは今のところこうやってさっさと回避するのが吉のようです。

こうすることでLinux上のコンパイラはIntrinsics内のシンボルをリンクしようとするし、Xcode上では@_silgen_nameアトリビュートを使ってビルドしてくれます。ここらへんは多分ちゃんとしたやり方があると思うのですが、今はこれが精一杯。

ちゃんとstdヘッダをインクルードする

uint64_tのためにCのコードに#include <stdint.h>が必要です。Xcodeでビルドするときはどっかでインクルードされてるから必要なかったりして、意外にハマりました。他にもimplicitに依存しているstdライブラリはちゃんと明示する必要があります。明示しておくほうがお行儀も良いですしね。

これらがうまくいくと

~/projects/Graphite$ swift build
warning: minimum recommended clang is version 3.6, otherwise you may encounter linker errors.
Compile Intrinsics Intrinsics.c
Linking Intrinsics
Compile Swift Module 'Graphene' (27 sources)
<module-includes>:1:1: warning: umbrella header for module 'Intrinsics' does not include header 'Graphene-Bridging-Header.h'
#include "/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Intrinsics/include/Intrinsics.h"
^
<module-includes>:1:1: warning: umbrella header for module 'Intrinsics' does not include header 'Swift_Bridging_Header.h'
#include "/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Intrinsics/include/Intrinsics.h"
^
/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/EdaxProtocol.swift:28:13: warning: variable 'bb' was never mutated; consider changing to 'let' constant
        var bb = SimpleBitBoard()
        ~~~ ^
        let
/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/NegaAlphaSearch.swift:81:26: warning: result of call to 'put(_:x:y:guides:)' is unused
                newBoard.put(turn, x: px, y: py, guides: false)
                         ^  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/BoardMediator.swift:22:9: warning: result of call to 'updateGuides' is unused
        updateGuides(.black)
        ^           ~~~~~~~~
/home/ubuntu/projects/Graphite/Packages/Graphene-1.9.0/Sources/Graphene/SimpleProofSolver.swift:281:33: warning: result of call to 'put(_:x:y:guides:returnChanges:)' is unused
            newBR.boardMediator.put(node.whosTurn, x: px, y: py)
                                ^  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compile Swift Module 'Graphite' (1 sources)
Linking ./.build/debug/Graphite

と、GraphiteというGrapheneパッケージを利用した実行ファイルがビルドできました。CモジュールIntrinsicsが先にコンパイルされているのがわかります。

そのほかSwift on Linuxのメモ

Ubuntu 14.04でやる

RHELとかCentOSでもビルドできたという報告がググルと出ますが、どうも更に茨の道のようです。Ubuntu 14.04が一番鉄板です。16も私のところではライブラリ周りでうまくいかなくて諦めました。ヘタレです。

arc4randomはない

知らなかったのですが、arc4randomはFonudationの中ではなくてBSDのライブラリファンクションなんですね。

developer.apple.com

というわけで当然Linuxのglibcにはないので、代替品を用意する必要があります。私はこんな感じにしました。

#if os(Linux)
    import Glibc
    import SwiftShims
#else
    import Darwin
#endif

func cs_arc4random_uniform(upperBound : UInt32 = UINT32_MAX) -> UInt32 {
    #if os(Linux)
        return _swift_stdlib_cxx11_mt19937_uniform(upperBound)
    #else
        return arc4random_uniform(upperBound)
    #endif
}

こういうときもさっさとLinuxだけ別にしちゃいましょう。_swift_stdlib_cxx11_mt19937_uniformはSwiftShimsにあります。CSPRNGの必要あるのかって気はしますが。他のパターンもSwiftShimsで結構カバーできるような気がします。

まとめ

github.com

とりあえずLinux上でビルドができるようになりました。というわけでLinux上で開発ができるようにEmacsの環境を整えようと思います。

Xcodeで書きたいのはやまやまなのですが、SwiftのコンパイラのバージョンがmacOS用のツールチェーンだと微妙に古いのでうまくいかず、かといってオフィシャルのベータをインストールしたりしてぐちゃぐちゃにするとiOS用のビルド環境がなくなっちゃうのでLinux上で頑張ることにします。