SSブログ

Swift3でアプリケーションバンドルの中の、埋め込みコマンドを探して実行する。 [プログラミング]

Macでコマンドラインアプリケーションをラップして、GUIで使いたいとか思いません? でも、配る時にコマンドラインツールをHomeBrewで入れてね、というのはスマートじゃないですよね。だからHomeBrewで入れたツールのバイナリを奪ってきて、Xcodeで作るGUIな.appの中にぶち込もうという計画。

わかっている人にはそんなに難しいことではないのだろうけど、アプリケーションバンドルって何?から始めようとするとキツい案件になる。でも、コマンドを打ちたくないから安易にできるようになった方がいいんですよねって話だし、そう考えるのはものぐさか、コマンドラインの初心者だと思うんだよね。だから、できるという状態まで持って行ってあげようというのが趣旨です。


それまでライブラリを.appの中にコピーして使ったということはあった。mailpeeper-tlsではOpenSSLの.dylibを使っていたので、そこいらのところはObjective-Cで習得済みだった。基本的にXcodeの操作なのですが、MacPortsなりHomeBrewなりで生成できるバイナリのライブラリをもってきて、実行時にリンクさせるようにするのでした。今回はリンク話の直パスということになります。

今回はSwiftで独立したバイナリのコマンドラインアプリケーションをコピーしてきて、そのバイナリを実行しようというものです。基本的に次のことができればいい。

・バイナリを.app内に取り込む。
・.app内のバイナリの位置を特定して実行する。

この二つだけで大体問題は解決します。やってみたらそんなに難しくなかった。今回は例えばgitを使いたいなと思います。そもそもGitなんて使えんのかいなということなんだけど、実際Xcodeでも使っているので問題ないと思ったのです。

Xcode.appのバンドルの中を検索してみると
/Applications/Xcode.app/Contents/Developer/usr/libexec/git-core/git
にあることがわかります。別にソースごとXcodeの中に埋め込んでいるわけではなく、独立したコマンドのバイナリが入っているのですね。これには少しホッとしました。

ただリソースの場所としてはずいぶん自由な場所に入れてあるので、探すのが面倒になりそうだなと思っていたら最終的にはそんな気苦労は無用でした。そういうのを前提に関数が用意されているので、結構自由にバンドルの中に放り込むことができそうです。




さて実際にやっていきましょうか。最初にバイナリをバンドルの中に入れ込むのですが、HomeBrewから入れ込むにしろ、他のツールから入れ込むにしろ、whichコマンドで場所を特定してコピーしていきます。自分で作ったツールで./a.outして使ってたら直接でいいですなんて説明はいいですよね。とにかくプロジェクトのフォルダーにコピってきます。

そしてXcodeのProject Navigatorに突っ込んでおきます。どこでもいいです。とりあえず取り込むために登録するだけなので、gitに登録できるような状態にしておけばいいです。今回はgitコマンドなのでgitにgitを登録するような感じになりますがややこしいのは置いといて。こんな感じ。GUIでFinderからドロップするだけでもよかったかな。とにかく登録する。

pngit.png

Productsじゃないだろと言うツッコミはさておきw、実際はProductsにも入ってないノンカテゴリのフォルダなしだったりします。普通はこういう適当なことはやめておきましょうw。でもできることは確かです。


.appの中に入れるのはいくつか方法はあると思いますが、Projectの設定のBuildPhasesのCopy Bundle Resourcesに入れてしまうのが面倒がないと思います。苦労して入れたい方は他にも方法があると思うので探してやってみてください。実際Xcodeでやっているからできると思います。昔はcpコマンドとかを埋めてコピーしていた気がします。今回はとりあえずリソースとして入れこんでみることにします。

copybundle.png

実際にはこういう風にコピーされているのを確認することができる。

innnerbundle.png

まぁ探してくれはするのでどこでもいいのですが…。


さて実際のサンプルソースを見てみましょう。

ほぼ
https://gist.github.com/Seasons7/836d3676884a40c8c98a
なんですが、ちとふるいので少し改変しないと動きません(NSBundleとかがBundleになっていたり)。でもコンパイラが指事してくれるから問題ないです。

今回は前にやったディレクトリを取ってきた部分と絡めて実装する。ボタンとか押すと発動するようにしてみてください(実際そうしてる)。

@IBAction func addGit(_ sender: Any) -> Void {
	//フォルダ指定
	let openPanel = NSOpenPanel()
	openPanel.canChooseDirectories = true	// ディレクトリを選択
	openPanel.canCreateDirectories = false	// ディレクトリを作成できない
	openPanel.canChooseFiles = false		// ファイルを選択できない
	
	var path :String=""
	
	let num = openPanel.runModal()
	if num == NSFileHandlingPanelOKButton{
		for fileURL in openPanel.urls {
			let filePath = fileURL.path
			path = filePath
		}
		//下のコマンド呼んでる。実際にgitを発動しておる
		callGitCommand(path, "init")
		
	}else if num == NSFileHandlingPanelCancelButton{
		NSLog("Canceled")
		return
	}
}

func callGitCommand (_ path :String , _ param :String){
	let task = Process()
	let pipe = Pipe()
	
	task.launchPath = Bundle.main.path(forResource: "git", ofType: nil)
	task.arguments = ["-C", "\(path)", "\(param)" ]
	task.standardOutput = pipe
	task.launch()
	
	let handle = pipe.fileHandleForReading
	let data = handle.readDataToEndOfFile()
	let result_s = String(data: data, encoding: String.Encoding.utf8)
	NSLog(result_s!)
}


冗長なところとコメントはいらないから消したつもりなんだけど残ってるかな。あっても許してね、一応動くし。

http://d.hatena.ne.jp/Watson/20100324/1269427861
のように、NSTask関係では、コマンドラインは相対パスだと動かないから、
/bin/sh -c をかますといいとありますが、結局

Bundle.main.path(forResource: "git", ofType: nil)

でフルパスというか絶対パスが出てきてしまうので気にしないでいいです。パラメータに渡すパスとかも絶対パスを渡せば動作的に問題ないです。

パスのところは空白文字が入るとダメかもしれないので、ダブルクォーテーションとかで括ったほうがいいのかもしれません。そこいらへん試してないけど、一応日本語パスとかもいけたんで大丈夫かもしれません。というか、それくらいテストしろw。そういう面倒が嫌なので、そもそも空白文字のあるディレクトリは作らないようにしているんですけどね。

とにかく、.appの中に入れ込んだコマンドラインアプリケーションでも実行することができたのでした。しかし、実行環境が必要な場合もあると思うので、その場合はそれも持ってきて実行パスとかに置かないといけないのかもしれないなと思ったり。というかgitだってHomeBrewがない環境で動くかどうかって試してないから、HomeBrewのコンパイルリンクの方法が別のライブラリに頼っていた場合は、それも一緒に持ってこないとダメだよなぁ。少なくとも依存パッケージを入れられた場合は、それらは必要となるから結構面倒な話になるかもしれない。

ということを気にするとHomeBrewがない環境が必要になっちゃうじゃないか。面倒くさいなぁもう。

タグ:Swift3
コメント(0) 
共通テーマ:パソコン・インターネット

コメント 0