EagleLand

Macのアプリにバイナリをバンドルする

Published at 2015-03-21

WebPonizeには画像をWebPフォーマットに変換するために、WebM Projectにて配布されているWebPのライブラリ群をバンドルする必要があった。

cwebpをバンドルして実行するアプローチ

実行可能形式のcwebpをプロジェクトに配置して、Swiftから実行する。バンドルされたcwebpへの参照はNSBundleを、実行はNSTaskを使えばできる。以下の様な雰囲気。

func execute() -> String {
    let task = NSTask()
    let pipe = NSPipe()

    task.launchPath = bundle.pathForResource("cwebp", ofType: "")
    task.currentDirectoryPath = "~"
    task.arguments = "-q 80 input.png -o output.webp"
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    return NSString(data: data, encoding: NSUTF8StringEncoding)!
}

iTunes Connectにsubmitできない

いざMacのストアにsubmitしようとすると、同梱物に実行可能ファイルがあるので怒られる。厳密に言えば、その実行可能ファイルにも自分(ここでは私)が書いたプログラムの一部だということをセキュリティ的に証明する必要がある。よくよく考えてみれば正体不明な実行可能ファイルをバンドルできてしまったら大変なことになりそうだ。納得。

自分で書いているSwiftのコード(というかコンパイルした成果物)に対してはCode Signと呼ばれるMacのディベロッパーとしてiTunes Connectに登録すると得られる証明書が、Xcodeがビルド時に付与される。

コンパイル済バイナリへのCode Sign

cwebpに自分のCode Signを付与することが良い事か悪い事かは一旦置いておいて、できるかどうか試した。

$ codesign --entitlements ./cwebp.entitlements -s "登録してあるディベロッパー名" ./cwebp
$ codesign --display --entitlements - ./cwebp

無事に(?)付与できた。

cwebpにCode Signが付与された状態でMacのストアにsubmitできるかどうか試したが、できなかった。今回はcwebpがコンパイル済のもので試したけど、ソースコードからmakeする途中でやってもたぶん同じことになると思う。予想だけど。

バイナリとCode Signの関係については、ImageAlphaやImageOptimも同じことが該当していると予想している。ImageAlphaはpngquant、ImageOptimはoptipngやらjpegoptimその他をバンドルしているが、それらのバイナリはMacにバンドルされる想定のObjective-C / Swiftのプログラムというわけではない。

ちなみに、ストアへのsubmitができなかっただけで.app形式へのエクスポートは可能。この場合、表面上は正体不明のバイナリを含んでいるアプリということになる。WebPへのコンバート(cwebpの実行)も正常に行われることから、ImageAlphaやImageOptimはWebからのダウンロードによる配布形式をとっているんだと思う。予想だけど。

libwebp.aをリンクして実行するアプローチ

cwebpは実行可能形式だけど、ライブラリとして利用するためのlibwebp.aとヘッダーファイル群(encode.hdecode.hなど)も配布されている。

これらのファイルをXcodeのプロジェクトに追加したあとは、ヘッダーファイルを作成し、encode.hdecode.hをインポートする。libwebp.aへのリンクは自動で行われた。

#ifndef SampleApp_libwebp_h
#define SampleApp_libwebp_h

#import "libwebp/encode.h"
#import "libwebp/decode.h"

#endif

作成したら、プロジェクト設定の Build SettingsSwift Compiler - Code GenerationObjective-C Bridging Header に作成したヘッダーファイルのパスを指定。

先程のようにコマンドラインっぽく実行するわけではなく、ヘッダーファイルで定義されている構造体に沿ってWebPへの変換作業をする。

let image: CGImage? = getCGImage(image)
let provider: CGDataProviderRef = CGImageGetDataProvider(image)
let bitmap: CFDataRef = CGDataProviderCopyData(provider)

let rgb: UnsafePointer<UInt8> = CFDataGetBytePtr(bitmap)
let width: Int32 = Int32(image.size.width)
let height: Int32 = Int32(image.size.height)
let stride: Int32 = Int32(CGImageGetBytesPerRow(image))
let qualityFactor: Float = Float(80)

var webp: NSData
var output: UnsafeMutablePointer<UInt8> = nil
var size: size_t = WebPEncodeRGBA(rgb, width, height, stride, qualityFactor, &output)

webp = NSData(bytes: output, length: Int(size))
webp.writeToFile(self.saveFilePath, atomically: true)
free(output)

これならば手元でのコンパイルでCode Signが付与される。ストアへのsubmitもできた。

タイトルと URL をコピーしました