Shibuya.tracでJenkinsについて発表してきましたよ

先週金曜日に、Shibuya.trac 第13回勉強会でお話してきました。きれいな会場を提供してくださったパソナ様、発表を聞いてくださった参加者の皆様、ありがとうございます。


発表のはじめに

  1. Jenkinsを知らない/使ったことがない人
  2. Jenkinsをちょっと使ったことがある人
  3. Jenkinsを実プロジェクトで使っている人

というようなアンケートをとったのですが、今回は2番目の層がターゲットでした。「色々Jenkinsの情報はあるけど、この辺を見ていけば効率よく情報収集できるよ」っていうのが、テーマです。Jenkinsをがっつり使い倒している人にとっては、ちょっと物足りなかったかもしれませんね。

Shibuya.tracなのにTracやBTSと絡めた話ができなかったのは、ちょっと残念でした。まあ、ネタを考える時間もなかったので、仕方ないということで。「何か小ネタを挟め」と上司からパワハラを受けたので、泣く泣くデスノートを挟んだのはナイショの話です・・・

次は年明けて2月に、大阪遠征してJenkins勉強会で発表させていただきます。今回はJenkinsだけではなかったので少し軽めのテーマだったのですが、大阪ではJenkins勉強会ということもあって運用事例も含めてちょっと踏み込んだ内容を話せればと思ってます。大阪近辺の方は、ぜひよろしくお願いします。

Groovy Eclipse Pluginのあれこれ

この記事は、G* Advent Calendar 8日目の記事です。

http://atnd.org/event_images/0004/2777/g_ecosystem.001_original.jpg


最近Groovyを一番書いているエディタは、Jenkinsのスクリプトコンソールなikikkoです、こんにちは。Groovyはあまりコアな使い方はしていないので、ライトな記事でいかせていただきます。ということで、GroovyのEclipseプラグインについて。

Eclipseプラグイン

Groovy用のEclipseプラグイン、Groovy - Eclipse Pluginです。主な機能はこんな感じ。公式サイトから転載して、適当な日本語に訳してます。

そういや、Groovy-Eclipseプラグインの設定については、以前こんなのも書いてました。

リファクタリング

多分Eclipse JDTの最も優れている機能の一つであるリファクタリングのサポート。さすがにJDTほどではないですが、Groovyプラグインもちょいちょいサポートしています。

機能的には、

  • ローカル変数/メソッド/クラス/フィールドのリネーム
  • メソッド/ローカル変数/定数に抽出
  • スーパークラスにプルアップ/サブクラスにプッシュダウン

といった、まだまだ基本的な機能ばかり。

ただ、Ctrl+1(cmd+1)のquick fixがあまりサポートされてないのがちょっと残念。使う頻度の高いであろう変数名のリネームもquick fixではサポートされてなく、"コンテキストメニュー > Refactor > Rename"のように遷移しなければなりません*1

Java<=>Groovy間でのリファクタリング

で、機能面が若干不足していることはとりあえず脇に置いておいて。

少し前にテストをプロダクトコードと別言語で書くことに関するTL - Togetterが話題になりました。(Javaに関しての)結論的な意見としては、大体↓な感じかなと。


ですが、Groovy EclipseならJava<=>Groovyの2言語間でリファクタリングが追随されるんです*2!これで、テストコードは気軽にGroovyで書きつつプロダクトコードはJavaで書いても、どちらでリファクタリングしてもちゃんと整合性が取れます。やったねたえちゃん!

https://cacoo.com/diagrams/EvOLBDFqjtfCszMe-35E79.png

ちなみに、他のIDEは試してません キリッ。まあ、Groovyサポートが手厚いIntelliJとかなら、この辺よろしくやってくれるんじゃないかなと勝手に思っています。


それでは、みなさんよいGroovyライフを!

*1:ショートカットキーは割り当てられています

*2:環境によってはファイル名のリファクタリングがうまく効かなかったりするみたい。Eclipse本体/Pluginとも最新にしておけば多分大丈夫だとは思いますが、念のため確認しておいてね。

スクリプトコンソールのススメ

この記事は、Jenkins Advent Calendar 2日目の記事です。

http://jenkins-ci.org/sites/default/files/jenkins_logo.png


Jenkinsには、外部から操作するための口としてスクリプトコンソールCLIRemote Access APIなどが用意されています。ここでは、僕が一番使い慣れている(けど多分あまりメジャーでない)スクリプトコンソールについて紹介します。

スクリプトコンソールとは

ブラウザ上からGroovyコードを記述して、そのコードをサーバ上で実行することができます。トラブルシューティングやジョブの一括編集などに力を発揮します。スクリプトコンソールを使うには、[Jenkinsの管理 > スクリプトコンソール]から、もしくは http://localhost:8080/script を直接入力してみてください。

f:id:ikikko:20111202233315p:image

僕は今の現場では100超のジョブをお守りしているのですが、一つ一つGUI上からポチポチ編集していくと拉致があかないので、大体はこれを使って一括編集しています。

サンプルコード

以下、簡単なサンプルコードを交えて、どんなことができるかを説明していきます。興味を持たれた方は、お使いのJenkins上でコードをコピペして試してみてください。なお、スクリプトコンソール上では"Ctrl+Enter"(Mac上では"Cmd+Enter")でサブミットできるようなショートカットが用意されています。

ジョブ数のカウント

以下のコード1行で、Jenkins上のジョブ数が即座に分かります。下半分は結果です。

jenkins.model.Jenkins.instance.items.size()

--

Result: 6

画面上の例では"println(hudson.model.Hudson.instance.pluginManager.plugins)"のようにクラス名がHudsonですが、Jenkinsでも構いません。

GroovyではJavaの記法が大体通るので、こんな感じでJavaで書くこともできます。Javaコードを見てのとおり、Groovyでは"getHoge()"を"hoge"でアクセスできるんですね。

jenkins.model.Jenkins.getInstance().getItems().size()
ビルドキューにたまっているジョブをクリア

全てがイヤになって 優先して実行したいジョブがあるけど、キューにジョブがいっぱいたまっててビルドが回ってくるまでに時間がかかるような場合に使えるコマンド。なお、このコマンドではすでに実行しているジョブはキャンセルされません*1

jenkins.model.Jenkins.instance.queue.clear()

これを推し進めて、以下のコードをブックマークレットとして保存しておくと、ブラウザ上から1クリックでビルドキューをクリアできるようになります。コード中の”localhost:8080”は適宜変更してください。

javascript:(function(u,o){var w=window,d=document,f=d.createElement('form'),e,i;(location=='about:blank'?w:open()||w).document.body.appendChild(f);f.action=u;f.method='POST';for(i in o){e=d.createElement('input');e.name=i;e.value=o[i];e.type='hidden';f.appendChild(e);}f.submit();})('http://localhost:8080/script',{'script':'jenkins.model.Jenkins.instance.queue.clear()'});

まあ、CLI使ってもほぼ同じことができるのですが*2、僕があまりCLIに慣れていないのと、ブックマークレット化しておくとマウスだけでできるので楽っちゃ楽です。

ジョブの情報を参照・編集

ジョブの情報を1行ずつ表示するには、下記のコード1行+αでいけます。

jenkins.model.Jenkins.instance.items.each { println it }

null

--

hudson.model.FreeStyleProject@7e1eb84f[helloArtifactory]
hudson.model.FreeStyleProject@12d68b39[helloArtifactoryGradle]
hudson.model.FreeStyleProject@434e54d8[helloArtifactoryIvy]
hudson.maven.MavenModuleSet@4ba4536d[helloArtifactoryMavenRelase]
hudson.model.FreeStyleProject@11613fe7[helloNcss]
hudson.model.FreeStyleProject@7aa5f9b[helloArtifactExtractor]

スクリプトコンソール上では、最後に評価された式を結果として出力します。今回はeachのクロージャの中でprintlnして出力しているので、最後に無駄に評価されて出力しないように、nullを追加しています。

情報を参照するだけでなく、ジョブを変更することもできます。例えば、ビルドエラー時の通知メールで、全ジョブに特定の宛先を追加したくなった場合。この例では、"foo@example.com"をメール通知の設定がされているジョブ全部に追加します。

jenkins.model.Jenkins.instance.items.findAll {
  it.publishersList.get(hudson.tasks.Mailer.class) != null
}.each { 
  println it
  
  def mailer = it.publishersList.get(hudson.tasks.Mailer.class)
  mailer?.recipients += ' foo@example.com'
}

null

--

hudson.model.FreeStyleProject@7e1eb84f[helloArtifactory]
hudson.model.FreeStyleProject@12d68b39[helloArtifactoryGradle]

実際のコーディングの進め方

僕も上記のコードを最初から間違いなく記述できるわけではありません。スクリプトコンソールのいいところは、実際に動いているものを確認しながら少しずつコードを作っていけるところです。こんなときは、groovyのdump()メソッドを使ってどんなプロパティがあるのか確かめつつ、JenkinsのJavadocも参考に進みます。

実際に、上記の「通知メールに宛先を追加したい場合」を例にあげてみましょう。まずは、"items"でジョブ一覧が取れるけど、実際にジョブの中身がどうなってるかを確認します。

jenkins.model.Jenkins.instance.items[0].dump()

--

Result: <hudson.model.FreeStyleProject@7e1eb84f builders=hudson.util.DescribableList@1759e38d publishers=hudson.util.DescribableList@e6a49f5 buildWrappers=hudson.util.DescribableList@641d7b37 scm=hudson.scm.SubversionSCM@4b848b3a pollingBaseline=null builds=[10:helloArtifactory #10, 9:helloArtifactory #9, 8:helloArtifactory #8, 7:helloArtifactory #7, 6:helloArtifactory #6, 5:helloArtifactory #5, 4:helloArtifactory #4, 3:helloArtifactory #3, 2:helloArtifactory #2, 1:helloArtifactory #1] quietPeriod=null scmCheckoutRetryCount=null assignedNode=null canRoam=true disabled=false blockBuildWhenDownstreamBuilding=false blockBuildWhenUpstreamBuilding=false jdk=null authToken=null triggers=[] transientActions=[org.jenkinsci.plugins.all_changes.AllChangesAction@a01330f, org.jfrog.hudson.action.ArtifactoryProjectAction@54bf22ea] concurrentBuild=false customWorkspace=null lastBuildStartTime=0 nextBuildNumber=11 holdOffBuildUntilSave=false logRotator=null cachedBuildHealthReportsBuildNumber=null cachedBuildHealthReports=null keepDependencies=false properties=hudson.util.CopyOnWriteList@36c8c63e name=helloArtifactory description= parent=hudson.model.Hudson@4d12ee4f actions=[]>

実行結果を見て、ビルド実行後のアクションを表すのがpublishersではないかと当たりをつけます。で、FreeStyleProjectのjavadocを見て、publishersを扱っているメソッドがいくつか有りますが、試行錯誤の末getPublishersList()が望みのものだと分かりました。

jenkins.model.Jenkins.instance.items[0].publishersList.each {println it}

null

--

hudson.tasks.junit.JUnitResultArchiver@336215d4
hudson.tasks.JavadocArchiver@362e3cb1
hudson.tasks.ArtifactArchiver@4e5db277
hudson.tasks.Mailer@2a6cd712

上記の結果を見ると、"hudson.tasks.Mailer"というクラスがメール関係っぽいですね。publishersListの型であるDescribableのJavadocを見ると、get()の引数でクラスを与えると、そのクラスが取得できるとあります。

jenkins.model.Jenkins.instance.items[0].publishersList.get(hudson.tasks.Mailer.class).dump()

--

Result: <hudson.tasks.Mailer@48c12420 recipients=hoge@example.com dontNotifyEveryUnstableBuild=false sendToIndividuals=false>

ここまで来ると、recipientsがメール通知の宛先を表しているというのは何となく分かりますね。あとは、サンプルコードにあるように、recipientsを編集してやればいいということになります。

編集時の注意点

メール通知の宛先の場合はrecipientsプロパティを直接編集できましたが、場合によってはfinalが付けられていて後から編集することができないこともあります。そのような場合には、インスタンスを自分で生成して入れ替えるようにしてください。

jenkins.model.Jenkins.instance.items.findAll {
  it.publishersList.get(hudson.tasks.Mailer.class) != null
}.each { 
  println it

  def origMailer = it.publishersList.get(hudson.tasks.Mailer.class)

  def mailer = new hudson.tasks.Mailer()
  mailer.recipients = origMailer.recipients + ' foo@example.com'
  mailer.dontNotifyEveryUnstableBuild = origMailer.dontNotifyEveryUnstableBuild
  mailer.sendToIndividuals = origMailer.sendToIndividuals

  it.publishersList.replace(mailer)
}

null


それでは、みなさんよいJenkinsライフを!

*1:すでに実行しているジョブをキャンセルすると、ビルド結果が灰色のAbortedになって履歴に残りますしね

*2:CLIからでもスクリプトコンソールからでも、ソースの同じところが呼び出される

リポジトリ管理ツール:Artifactoryの紹介

何か驚くほどに(日本語の)情報が少ない。めぼしいものといえば、ぐらい?なので、ちょっと書いてみます。


概要

リポジトリ管理ツールとは

リポジトリ管理ツールとは、Mavenなどのビルド結果の生成物(アーティファクト)を突っ込むためのリポジトリ(インハウスリポジトリ)を簡単に立てることができるツールです。まあぶっちゃけインハウスリポジトリWebDAVでも構わないので、Apacheとか使うと簡単に構築することはできます。ただ、ツールとして作られている以上、単純なWebDAVにはない機能も備えているわけでして。

いくつかあげると、

  • セントラルリポジトリをはじめとした、リモートリポジトリのキャッシュ
  • リポジトリの分割・分割されたリポジトリの仮想的な統一
  • デプロイされているアーティファクトの検索
  • Jarの内部を参照可能
  • セキュリティをロールに応じて細かく設定可能

のような、細かいながらも便利な機能を備えています。

https://cacoo.com/diagrams/U0u5Kn5wshzbt0eS-40E30.png

インストールも簡単です。大抵のプロダクトはWarファイルが提供されてるのでコンテナにつっこめば動きますし、スタンドアロンでも動かすこともできます(大体は裏でJettyが起動します)。商用版と合わせてフリー版も用意しているプロダクトも多いです。

比較

JFrogが提供しているArtifactoryや、Sonatypeが提供しているNexusなど、いくつか類似のプロダクトがあります。比較は以下の表を参考に。

ざっと眺めたところ、

  • Eclipseとの連携はNexusの方がよさそう
    • M2Eを開発してたSonatypeが開発元ですしね。
    • 2011/11/08リリースされた最新のArtifactoryでは、EclipseのP2 Repositoryのサポートがあるみたい。Proだけなので、試せてないけど。
  • Ivy, GradleやJenkinsとの連携はArtifactoryがよさそう
  • Archivaは・・・どこがいいのか分からない
    • 使ったことないのもあるし、使ってるところも見たことがないので・・・。

というわけで、Ivy/Gradleユーザの私はArtifactoryを選択したので、Artifactoryについてちょっと取り上げてみます。

Artifactory

Artifactoryでも、上記で取り上げた基本的な機能はあります。細かいところはユーザガイドWikiを見たり、公開されているリポジトリ(例:Gradleリポジトリ)を触ってみて使い心地を確かめてみてください。

Jenkinsとの連携

僕は使ってないのですが、Jenkinsとの連携もできます。この辺詳しいことは、@さんに聞けば分かると思います |ω・`)チラ。まあそれだけじゃあんまりなので、一応自分でも試して、簡単にスクリーンショットを。

まずは、Jenkins/Artifactoryの各画面の遷移図の説明。Jenkinsのジョブ(図左上)に対応して、Artifactoryではそのジョブに該当するビルド一覧(図右上)が表示されます。もちろん、Jenkinsの各ビルド(図左下)に対応して、Artifactoryでもビルドが記録されます(図右下)。ここではJenkins => Artifactoryへの遷移だけを示していますが、Artifactory => Jenkinsへのリンクももちろんたどることができます。
https://cacoo.com/diagrams/U0u5Kn5wshzbt0eS-11ED3.png

ビルド時に記録される情報はこんな感じ。これ以外にも環境変数なども記録しておくことができます。
https://cacoo.com/diagrams/U0u5Kn5wshzbt0eS-D4EC6.png

なお、Artifactoryにはアーティファクトをディレクトリ階層構造で見ることができるビューもあるのですが、フリー版(OSS版)ではそこからJenkinsとの連携機能は無効化されていました。実際には、「アーティファクトをディレクトリ階層からたどって検索して、そこからこのアーティファクトはどのビルドで生成されたものか」というのを確認したいことが多いと思うので、この機能が使えないのはちょっと残念ですね。
https://cacoo.com/diagrams/U0u5Kn5wshzbt0eS-43CDF.png


あればものすごい便利というわけでもないけど導入自体にそこまで手間はかからないので、ぜひ入れておきたい部類ですね。

ついにねんがんのJenkins実践入門を献本してもらったぞ

f:id:ikikko:20111109224306p:image

@さんをはじめとした著者の方々、@さん含め技術評論社の方々、お疲れさまでした&ありがとうございます。「献本ほしいなう」としつこくつぶやいて、スミマセンでした><

献本していただけると聞いたときから、はじめての「献本」で慌てないために覚えておくといいかもしれないこと : ゼロスタートの広報ブログを見ながら、今か今かとお待ちしてましたよw


ざっと目を通して思った感想です。週末にでもまたちゃんと読み込んでみます。

Jenkinsをこれから導入する人に

説明用のスクリーンショットが多く、分かりやすいです。とりあえず8章あたりまで本書通りに進めれば、基本的な使い方はマスターしているでしょう。

Jenkinsのはじめの一歩的なものとして、同じく技評さんのサイトで掲載されている特集:Hudsonを使ったアジャイルな開発入門|gihyo.jp … 技術評論社があります。大枠は変わってないとは言え、3年前に書かれたものでちょっと古いので、時間を節約したい方はこの本を買って読み進めることをオヌヌメします*1

Jenkinsをすでに導入していて、よりよい使い方を模索している人に

9章以降はちょっと高度な説明がされています。ここ読んでおけば、Jenkinsレベルが何段階か上がるかと思います。忘れがちだけど実際には避けることができない運用周りの話も、ちゃんと説明されているのがいいですね。

そういや、Reverse Proxy Auth Pluginはこんな感じで使えるんですね。以前ユーザ認証周りを検討していたときに、ちょうど出てきたばかりということもあってこれはスルーしてて、代わりにScript Security Realmを使って独自の認証処理を組み込んだんですよね。これはこれで、柔軟性が高くて便利なのが分かったのがよかったですが。


あと、これは本には直接は関係しないのですが、ここ最近でJenkins関連でイイねと思った発言を取り上げてみました。実際にJenkinsを使ってる方の感想なので、重みがありますね。





最後に超蛇足。

バグ管理システムとの連携もこの本の中で触れられていますが、とりあげられているのがTrac/Redmine/JIRAの3つで、僕がメンテしているBacklog Pluginが入っていないです。悲しいです>< ちゃんと要望も聞いてアクティブにメンテしてるので、Backlog+Jenkins環境の方はぜひ検討してみて何かあればコンタクトください。

以上、宣伝でした。

*1:3年前の結婚記念日で宿泊したホテルで、このサイトを見ながらHudsonを学んだのはいい思い出ですw

ビルドツールのEclipseプラグインを試してみたよ

Wiiを買ったはいいものの、付属のソフト以外何も買ってなくてあっさり飽きたので新しいソフトを探していたら、「JUST DANCE Wii」というなかなか面白そうなものが見つかったのでさっそくAmazonで予約しつつも、手元に届くまでYouTubeを見ながらヘビーローテーションの振り付けを覚えようとしているikikkoです、こんにちは。


概要

今回は、ビルドツールのEclipseプラグインをそれぞれ試してみました。機能紹介と簡単な感想付きで買いてみます。取り上げたのは、以下の4つ。

  • M2E (Maven)
  • IvyDE (Ivy)
  • Gradle Plugin (Gradle)
  • Gradle STS Support (Gradle)
感想

先に全体の感想を書いておくと、

  • ビルドツールのライブラリ依存性管理機能と、Eclipseのビルドパス設定がスムーズに連携できるか
  • Eclipse内からスムーズに(ターゲット/ゴール/タスク)が実行できるか

Eclipseプラグインの肝になると感じました。

前者がスムーズにできないと、ライブラリの依存を更新して改めてEclipse側のビルドパスを設定する必要があります。扱うライブラリが多く、かつ頻繁に変更がある状態だと、片方だけやって片方は対応し忘れるといった設定漏れが発生します。

後者ができないと、結局ターミナルなどを別途開いて、そちらでビルドツールを起動しないといけなくなります。2つの画面を行ったり来たりしてるとやっぱり集中が途切れますし、画面間で実行ログを参照するなどもちょっとやりにくい。

M2E

f:id:ikikko:20111011010953p:image

開発元
機能
  • pomエディタ
  • pom.xmlと同期した、Eclipseのビルドパスの自動設定
  • Runコマンドの拡張
感想

もともとSonatypeでM2Eclipseとして開発されていたのですが、Eclipse 3.7からM2EとしてEclipseにプロジェクトが移りました。が、移ったあとの方が前よりしょぼくなってる気が。リポジトリやプロファイルの設定など、Advanced Tabsでできてたことが軒並みできなくなってます。最悪、XMLを直接編集すればいいのですが、それだとプラグインを使う意味があまりないですしね。。。

Dependency Hierarchyで、落とされてきたJarがどのアーティファクトに紐付けられているかが分かるので、これは相変わらずいいですね。あとは、追加されたDependencyを自動検知して、Eclipseのビルドパスに反映してくれるやつ。

Maven側でもmaven-eclipse-pluginがあるのですが、こちらだとpom.xmlの更新を自動検知してくれる機能はありません。なので、dependencyなどを修正した場合は再度maven-eclipse-plugin(mvn eclipse:clean eclipse:eclipse)を叩く必要があります。

両方を使った感じ、maven-eclipse-pluginの方が安定してそう。その場合でもPOMエディタとして使えるので、M2Eを入れておいて損はないかと思います。

あと、Jenkinsの開発時(や、↓の例だとSpring Dataのチュートリアル時でも)にエラーが出ることがあります。あまり細かくは追っていませんが、M2Eでのmaven lifecycleの扱いが以前と変わっているみたいです。下記リンクを参考に。

IvyDE

http://ant.apache.org/ivy/ivyde/history/latest-milestone/images/completion4.jpg

開発元
機能
  • ivy.xmlの補完
  • ivy.xml修正のタイミングで、自動resolve & ビルドパス反映
  • retrieve対応(プロジェクト直下にJar配備)
  • Eclipseのプロジェクト参照(未検証)
  • Reverse Dependency Explorer(依存性のビュー表示)
感想

ivy.xmlに同期した自動resolve&ビルドパス反映はいい感じ。漏れがなくなるし、ivy.xmlの修正時の確認が楽。これだけのためにいれといてもいいかな。

Dependency Explorerは推移依存までは見れないみたい。推移依存まで見れたら、どのartifactがどのmoduleと紐付いているのかが分かりやすくなるのですが。この点はM2Eの方が優れてますね。

Gradle Plugin

f:id:ikikko:20111011013747p:image

機能
  • Runコマンドからタスク実行
感想

使い方を記したサイトなどは見つかりませんでした。多分、YouTubeにあがってるスクリーンキャストぐらい。

作者自身「まだexperimentalだよ」と言っているのもあって、色々機能不足だし不安定。この記事書いてるときも、さっきまでできてたことがNPEが発生してできなくなったりするし。ま、experimentalにあまり過度の期待を抱いても・・・ですね。開発も2010年3月から止まってるし。

Gradle STS Support

http://static.springsource.org/sts/docs/2.7.0.M1/reference/html/gradle/img/run-as-menu.png

開発元
機能
  • マルチプロジェクトのサポート
  • インポートウィザード
  • プロジェクトの依存性管理とEclipseビルドパスへの反映
  • Runコマンドからタスク実行
感想

上のGradle Pluginよりは将来性がありそう。SpringSourceが開発しているということもありますし。ただ、STSと切り離してインストールできるようになると嬉しいな。今はSTSの上でしか動きません、多分*1

機能的には、マルチプロジェクトをちゃんと動的に読み込んでくれるなど、なかなかの高機能。そのかわり、build.gradleの補完などはないので、別途入れたgroovy eclipse pluginの方に任せる感じかな。build.gradleをGroovyファイルとして読み込めば、Gradle独特の機能は無理にしろ、Groovyとしての補完は有効になるので。

ちなみに、チュートリアルではSpring Integrationのプロジェクトを使っていますが、サイト内に記載されているリポジトリパスが違っていますので注意を。

git clone --recursive git://git.springsource.org/spring-integration/spring-integration.git

ではなくて、正しくは

git clone --recursive git://github.com/SpringSource/spring-integration.git

です。

*1:一応、素のEclipseの上にGradle Tooling APIだけインストールしてみたけど、案の定動きませんでした

CodeMirrorでショートカットキーを追加する、あるいはJenkins Coreへの初めてのコミット

ブログの出落ちが好評との噂ですが、そのプレッシャーゆえにネタ探しに疲れてきているikikkoです、こんばんは。

http://serif.hatelabo.jp/images/cache/7acc714e78ac1911acd9a7c74b7db66ca3eb26e1/1bd1cfc82cb42540b01e5da7c1e363394ca2fb06.gif


今回、CodeMirrorをちょっと触ったので、自分の理解がてら触った範囲をまとめてみます。

概要

CodeMirrorというJSのライブラリをご存知でしょうか。テキストエリアをシンタックスハイライト可能な要素に変えてくれるライブラリです。ハイライト可能な言語はC, JavaRuby/PythonからHTML/CSS/JavaScriptなど、幅広く対応しています。

原理的には、↓な感じ。用意されたテキストエリアをCodeMirrorが提供するシンタックスハイライト付きのIFrameに置き換えます。元のテキストエリアはdisplay:noneにして非表示に。ユーザはIFrameに対してテキストを編集していき、submit時に元のテキストエリアに戻すという仕組みになっています。

https://cacoo.com/diagrams/jOKLDCwnkbRzt7jo-56185.png

一番単純な使い方はこのような感じ。シンタックスハイライトさせたいテキストエリアの要素をわたしてやります。

CodeMirror.fromTextArea(document.getElementById("textarea"),{
  mode:"text/x-java",
}

拡張:ショートカットキーの追加

他のライブラリ同様、CodeMirrorではいくつかの設定によって拡張することができます。対応言語なども増やすことができるのですが、ここでやったのはキーイベントを取得して特定の動作を行う、いわゆるキーボードショートカットを追加してみました。具体的には、Windows/LinuxではCtrl+Enter、MacではCommand+Enterでサブミットを行うというもの。

CodeMirrorでキーイベントを取得するには、onKeyEvent()をCodeMirror生成時のオプションで指定します。これでkeydown/keyup/keypressイベントを取得することができます。なお、戻り値にtrueを返すことによって、後続のイベントを全部キャンセルすることができます。サブミット後にはイベントは基本なくてもいいはずなので、trueを指定することになります。

CodeMirror.fromTextArea(document.getElementById("textarea"),{
  mode:"text/x-java",
  onKeyEvent: function(editor, event){
    console.log(event);
    ...
    return true;
  }
}

で、あとはCtrl or Command+Enterにフックしてsubmitすればいいだけです。が、macのCommandはCtrlキーと違って、メタキーとしてキーイベントの取得ができません。仕方ないので、keydown/keyupにひっかけて、Commandキーが押されているかどうかを判定しました。

完成コードのJS部分を抜粋したものがこちら。

(function() {
  var cmdKeyDown = false;
  
  CodeMirror.fromTextArea(document.getElementById("textarea"),{
    mode:"text/x-java",
    onKeyEvent: function(editor, event){
      function isGeckoCommandKey() {
        return Prototype.Browser.Gecko && event.keyCode == 224
      }
      function isOperaCommandKey() {
        return Prototype.Browser.Opera && event.keyCode == 17
      }
      function isWebKitCommandKey() {
        return Prototype.Browser.WebKit && (event.keyCode == 91 || event.keyCode == 93)
      }
      function isCommandKey() {
        return isGeckoCommandKey() || isOperaCommandKey() || isWebKitCommandKey();
      }
      function saveAndSubmit() {
        editor.save();
        document.form1.submit();
        event.stop();
      }
      
      // Mac (Command + Enter)
      if (navigator.userAgent.indexOf('Mac') > -1) {
        if (event.type == 'keydown' && isCommandKey()) {
          cmdKeyDown = true;
        }
        if (event.type == 'keyup' && isCommandKey()) {
          cmdKeyDown = false;
        }
        if (cmdKeyDown && event.keyCode == Event.KEY_RETURN) {
          saveAndSubmit();
          return true;
        }
        
      // Windows, Linux (Ctrl + Enter)
      } else {
        if (event.ctrlKey && event.keyCode == Event.KEY_RETURN) {
          saveAndSubmit();
          return true;
        }
      }
    }
  })
})();

どこで使ってる?

Jenkinsのスクリプトコンソールで使ってます。いつの頃からか、Jenkins上のスクリプトコンソールはCodeMirrorが採用されていますが、このおかげでちょっと困ったことになっていました。具体的には、サブミットにマウスを使用しないといけなくなったということです。

http://twitter.com/#!/ikikko/status/90756645495382016:twitter:detail:left

http://twitter.com/#!/kohsukekawa/status/90815059693019136:twitter:detail:right

http://twitter.com/#!/ikikko/status/90816199021170688:twitter:detail:left

CodeMirror採用前は、TabキーでSubmitボタンに遷移することができ、そこでSpaceキーを押せばキーボードだけで操作することができました。しかし、CodeMirror採用後はTabキーはそのままTabが入力されるようになったため、このテクニックが使えなくなったのです。スクリプトコンソールは結構頻繁に使っていたため、コンソール上ので操作が結構面倒になりました。

シンタックスハイライトが導入されて感じたことをふと思い出して、ショートカットキーを実装してプルリクエストした次第です。この対応は、Ver 1.432 (2011/09/25) 以降に入っています。スクリプトコンソールを使う方は、頭の片隅にでも留めておいてもらえると、ちょっと楽になるかもですね。


ちなみに。

CodeMirrorのユーザマニュアル見ていると、自動補完などもできるみたい。簡単にできそうなら、これも対応してみようかな。簡単にできそうなら。。。