d.sunnyone.org
sunnyone.org

ページ

2014-12-14

LibreOffice用カラーテーブルの12色版を作った

以前の「LibreOffice用カラーテーブルを作った」のテーブルが、LibreOfficeの新しいバージョンで12色になったことによってまだらになってしまったことを教えて頂いていたのだが、ようやく対応したバージョンを作った。

ダウンロードはこちらから: https://github.com/sunnyone/locolor/raw/master/munsell.soc

今回からmunsell.socにした。使い方は前回の記事参照。


 今回選択したのは、この色達。12色になったことによって、マンセルの各色相の代表色の「5 XX」(10色)が選べるようになったので選んで、それに「10Y」と「10PB」を加えた。



今回更新したのは、「VyOS 1.1のファイルシステム階層を図にした」で使いたかったからなのだけど、どうやって使ったかを参考になればと思って書いておく。
  • 全体の背景は白、文字は黒。
  • 原則箱の塗りつぶしは[1]のトーン(上から1番目)、文字は[8]のトーン、枠線は[5]のトーン。
  • 隣り合う色はできるだけ色相差があるように、各要素別に色相を選択。
なお、LibreOffice 4.4でまだら現象は解消されるらしい。前よりはずっとよくなってる。しかし、好みの問題もあると思うけど、この色はちょっとチカチカして使いにくいなと思うので、きっとこのmunsell.socを使い続けると思う。

ちなみに、前回の「機械的に計算するならHSVのほうがいいんじゃ?」 のやつを実際にやってみたらこうなった。RGBで色相を等分割して、彩度/明度のパーセンテージを決めて割り当てたんだけど、4.4のと似たような結果になる。かなりの調整が要りそうなのでやめた。

2014-12-12

RからMongoDBにアクセスする2つのライブラリ

これは、R Advent Calendar 2014 12日目および MongoDB Advent Calendar 2014 12日目の記事です。

---
すでにJSONファイルがたくさんあるデータをRに食わせようと思って、CSVに変換しようとしたのだけど「こういうときはMongoDBをハブに使えばいいんじゃね?」ということを思いつき、いまさらながらR × MongoDBを試してみた記録である。

データは「Vue.jsを使ってBloggerの最新記事一覧を表示する」の記事にでてくるJSON結果のfeedのentryをバラしてcollectionに入れた。なお、どちらのライブラリもinsertする機能がついているが、その部分は試していない。

rmongodb

インストール

install.packages('rmongodb')

使い方

rmongodbは、RからMongoDBにアクセスするためのライブラリのひとつ。mongodbのCライブラリのラッパーらしい。短い使いかたはこんなかんじ。

mongodb.create で接続を作り、mongodb.findで検索し、cursorで処理する。この場合の処理はmongo.cursor.to.data.frame

data.frameになる前に加工が必要だという場合は、こんな感じ。




Webを見ていると、クエリの組み立てにbson.*を呼んでいたりとかする例が多いのだけど、JSON文字列を渡せたり、listで渡せたりもするので、もうちょっと高レベルな関数ないかなと探してみるのもいいかもしれない。

参考


RMongo

もうひとつRからMongoDBを使うライブラリにRMongoというのがある。こちらはJavaが必要。

インストール

install.packages('RMongo')

使い方





ネストされたところがJSON文字列というのがちょっと。。。

参考

テストケースがわかりやすい。

感想

rmongodbは低レベル操作が目につくのに対し、RMongoのほうはそうでもないので、だいたいうまくやってくれるのかなーと思いきや、ネストされた構造の処理が(より)いまいちなので、rmongodbを使っていればいいのかなという感じだった。

ただ、Rにあまり慣れていないせいもあると思うが、ちょっとでも加工が必要になってくると、かえって冗長な感じになってしまうので、データ構造によってはMongoDB->MongoDBでも、MongoDB->CSVででも、事前処理したほうがさくっと終わりそうだなという印象を受けた。


付録: データ取り込み

RubyでMongoDBに入れた。MongoDBは$始まりのキーは許してくれないので、hash keyを変換するいまいちなコード入り。



2014-12-08

Linux/WindowsがUEFIでブートしているか調べる

この記事は、UEFI Advent Calendar 2014の記事です。ライトですが書けそうなので書きます。
---

Linuxの場合

Linuxの場合は、/sys/firmware/efiの存在を確認する(Ubuntu 14.04で確認)。
$ ls -al /sys/firmware/efi
total 0
drwxr-xr-x  4 root root    0 Dec  2 23:54 .
drwxr-xr-x  5 root root    0 Dec  2 23:54 ..
drwxr-xr-x  2 root root    0 Dec  2 23:54 efivars
-r--------  1 root root 4096 Dec  2 23:54 systab
drwxr-xr-x 27 root root    0 Dec  2 23:55 vars

Macの場合は違うことがあるようだが、未検証。詳しくはArch Wikiをどうぞ。

Windows8 の場合

GUIで調べる

Windows8以降の場合、コントロールパネル>管理ツール>システム情報の「システムの要約」に「BIOSモード」という項目があり、これが「UEFI」か「レガシ」かで判断することが可能。



コマンドラインで調べる

PowerShellの「Get-SecureBootUEFI」コマンドレットの結果を見ることで確認できる(要管理者権限)

BIOSの場合、サポートされていない旨が表示される:
PS C:> Get-SecureBootUEFI -Name SetupMode
Get-SecureBootUEFI : このプラットフォームではサポートされていないコマンドレット: 0xC0000002
発生場所 行:1 文字:1
+ Get-SecureBootUEFI -Name SetupMode
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : NotImplemented: (Microsoft.Secur...BootUefiCommand:GetSecureBootUefiCommand) [Get-SecureBootUEFI], PlatformNotSupportedException
+ FullyQualifiedErrorId : GetFWVarFailed,Microsoft.SecureBoot.Commands.GetSecureBootUefiCommand

UEFIの場合、変数の値に関するメッセージ、あるいは値そのものが表示される:
(値が定義されていない場合)
PS C:> Get-SecureBootUEFI -Name SecureBoot
Get-SecureBootUEFI : 変数は現在定義されていません: 0xC0000100
発生場所 行:1 文字:1
+ Get-SecureBootUEFI -Name SecureBoot
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ResourceUnavailable: (Microsoft.Secur...BootUefiCommand:GetSecureBootUefiCommand) [Get-SecureBootUEFI], StatusException
+ FullyQualifiedErrorId : GetFWVarFailed,Microsoft.SecureBoot.Commands.GetSecureBootUefiCommand

(値が定義されている場合)
PS C:> Get-SecureBootUEFI -Name SetupMode

Name                                    Bytes                                   Attributes
----                                    -----                                   ----------
SetupMode                               {0}                                     BOOTSERVICE ACCESS...

Windows7の場合

いまどきWindows7で調べるか、という感じがするのであまり調べてないが、コントロールパネル>管理ツール>システム情報も、Get-SecureBootUEFIもないので、別の方法を使う必要がある。

次のいずれかの方法を使って、Windows が正しいファームウェア モードでインストールされたことを確かめる
に記述されている「GetFirmwareEnvironmentVariable」関数を呼ぶPowerShellスクリプトがScript Centerにある。

Determine UEFI or Legacy BIOS from PowerShell (Three methods)


bcdeditを実行して、ブートローダーのpathに.efiのファイルがあるかどうか確認する方法も恐らく使える。
(Windows7/BIOS)
C:\>bcdedit | find "path"
path                    \Windows\system32\winload.exe

(以下はWindows8.1/UEFIの出力だが、似たような感じになるのではないかと)
C:\2>bcdedit | find "path"
path                    \EFI\Microsoft\Boot\bootmgfw.efi
path                    \Windows\system32\winload.efi

なお、Webで検索すると、パーティションテーブルがGPTかどうかを確認する方法がよく見つかるが、WindowsでもGPT + BIOSができるようなので、あまり正確でないのでは?と思うが未検証。
Windows 7: Boot Windows 7/8 from GPT on BIOS system : No hybrid MBRs or DUET!




2014-12-07

VyOS 1.1のファイルシステム階層を図にした

この記事は、VyOS Advent Calendar 2014の記事です。
---

VyOSは、ネットワーク機器のように利用できるLinux distribution。Ciscoルータのようにコマンドで設定し、コンフィグを保持して動作する(JunOSと似ているといわれているが、使ったことがないのでわからない)。Brocadeに買収され、公開されなくなってしまったVyattaをコミュニティで保守している。

ファイルシステム階層

このVyOSは、アップグレード等がしやすいように、ディスクにインストールする際も、イメージでインストール(install image)することが推奨されている。install imageでインストールすると、通常の/とswapがあって、必要に応じて/usrや/homeが切れらてて、といったようなパーティションの分割はされず、CDブートするような形になっている。そのため、構造がわからなかったので、追いかけてまとめたのが、以下の図である。


VyOSのファイルシステム階層のまとめ

まとめると、以下のようになる。
  • Read-Onlyのsda1:/boot/1.1.0/1.1.0.squashfsをベースに、Copy-On-Writeでsda1:/boot/1.1.0/live-rwの領域を使うoverlayfsのマウントが基本になっている。
  • 揮発性の/opt/vyatta/configと、永続化される/opt/vyatta/etc/config (/config)がある。
  • /live以下は、このoverlayfsを構成・管理するためにマウントされた領域で、通常は気にしなくて良い。
  • /boot以下もほぼ同様に、live環境と通常環境のつじつまを合わせるためにマウントされている。

各領域の解説

各領域を個別に解説していくと、以下の通りになる。/sysとか/procとかは他と一緒なので省略する。
/live
overlayfsによってCopy-On-Writeが構成されている前の生の領域をつつくためのディレクトリをマウントするディレクトリ。
/live/image
Copy-On-Write領域を含むパーティションをマウントするところ。
/live/cow
Copy-On-Write領域の生のファイル群。
/opt/vyatta/config
揮発性のVyOSの設定を書き込むところ。Ciscoでいうrunning-configのイメージ。
/opt/vyatta/etc/config (=/config)
永続化されるVyOSの設定を書き込むところ。Ciscoでいうstartup-configのイメージ。結果的に/configと同じ。ただし、どの環境でも/configを使えるように、というようなことを言っているので、/configを使うほうがベターか?
/boot
/bootはCopy-On-Writeの構成の前のタイミングから必要なので、別枠で管理される。そのため、個別にマウントされている。
/boot/grub
/bootはイメージ名別だが、grubはブート単位で共通なのでさらにマウントされている。

Debian Liveをベースにしているようなので、おそらくDebian Liveと近いのではないかと思う。

ブートプロセス

上述の説明を作るのに、/proc/mountsを読めばわかるかなーと思っていたが、読んでもわからなかったので、ブートプロセスを追うことになってしまった。もしかしたら参考になる人がいるかもしれないので、マウントに関連する部分について読んだメモを残しておく。
(分割されていないメモのほうが見やすいかもしれないのでここに置いておく)

ブートプロセスのマウント関連部分の概略

  • Grubからinitrdが立ち上がる。
  • initrd内で必要なイメージや領域を探し出し、いくつか「/なんとか」にマウント後、本ブートに必要な分を「/root」以下にぶら下げる(このため、ブート後には見えない謎の/cowとか/live-rw-backingが残る)。
  • 通常のsysvrcのinit処理に移り、/opt/vyatta/configなど、後からでも間に合う分をマウントする。

以下は各処理。

Grubでブート

まずGrubでブートする。/boot/grub/grub.cfgのデフォルトエントリが以下の通りになっているので、initrd=/boot/1.1.0/initrd.imgが起動する。「boot=live」「vyatta-union=/boot/1.1.0」がポイント。

menuentry "VyOS 1.1.0 linux (KVM console)" {
 linux /boot/1.1.0/vmlinuz boot=live quiet vyatta-union=/boot/1.1.0  console=ttyS0,9600 console=tty0
 initrd /boot/1.1.0/initrd.img
}

initrdのinit起動

initrd内のinitスクリプトが起動する。カーネルパラメータbootがBOOT環境変数に入っているため、
. /scripts/${BOOT}    #L215
ではscripts/liveがdot sourceされ、
mountroot   #L218
で、mountroot関数が呼ばれる。

scripts/liveのmountroot関数によるmount処理

必要なイメージを探したりしながら、ブートに必要な部分がマウントされていく。カーネルパラメータvyatta-union=の分岐で必要なパラメータがセットされているのがポイント。
説明しているとしんどいので呼んだと考えられるコマンドと、呼んだ箇所のメモを記載する。

#Lxxxは特に記述がなければscripts/liveのもの。括弧書きはブート後の/proc/mountsの対応行。

mount -t ext4 -o ro,noatime /dev/sda1 /live/image

-> livefs_root=$(find_livefs ${i})
  # $i: 0...60 (timeoutカウンタ)
  -> check_dev "${dev}" # L1546
    # $dev: "/dev/sda1"  #L1531,L1533,L1544の結果からおそらく
    -> mount -t ${fstype} -o ro,noatime "${devname}" ${mountpoint}  #L1449
       # $fstype: "ext4"  #L1445のget_fstypeの結果からおそらく
       # $devname: "/dev/sda1"  #L1423の結果からおそらく
       # $mountpoint: "/live/image" #L10
       ===> mount -t ext4 -o ro,noatime /dev/sda1 /live/image (後の/dev/sda1 /live/image ext4 rw,relatime,data=ordered 0 0)
  # livefs_rootには/live/imageが入る

mount -t squashfs -o ro,noatime /dev/loop0 /1.1.0.squashfs

-> mount_images_in_directory "${livefs_root}" "${rootmnt}" "${mac}"                    # L1700
  -> setup_unionfs "${directory}/${LIVE_MEDIA_PATH}" "${rootmnt}" "${adddirectory}"     # L609
     # $directory: $livefs_root
     # $LIVE_MEDIA_PATH: "live" #L11
    -> mount -t "${fstype}" -o ro,noatime "${backdev}" "${croot}/${imagename}"          # L1178
       # $backdev: backdev=$(get_backing_device "${image}" "-r") #L1161
           # $(setup_loop "${1}" "loop" "/sys/block/loop*" '0' "${LIVE_MEDIA_ENCRYPTION}" "${2}") #L569
       # $croot: "/" #L1071
       # $imagename: imagename=$(basename "${image}") #L1142
       ===> mount -t squashfs -o ro,noatime /dev/loop0 /1.1.0.squashfs  (/dev/loop0 /1.1.0.squashfs squashfs ro,noatime 0 0)

mount -o remount,rw /dev/sda1 /live/image

-> # cowprobe=$(find_cow_device "${root_persistence}") #L1223
         # $root_persistence: "live-rw" #L13
         -> find_cow_device  #scripts/live-helpers:L333
            -> if ! try_mount "${devname}" "${cow_backing}" "rw"
               # $cow_backing: cow_backing="/${pers_label}-backing" #script/live-helpers:L339
               # $pers_label: $root_persistence  #script/live-helpers:L338
               # つまり、$cow_backingは"/live-rw-backing"
               # $devname: 探索の結果から/dev/sda1 (のはず)
               -> mount -o remount,"${opts}" "${dev}" "${old_mountp}"
                   # $opts: "rw" #L291
                   # $dev: "/dev/sda" #L289
                   # $old_mountp: "/live/image"
                   ===> mount -o remount,rw /dev/sda1 /live/image (後の/dev/sda1 /live/cow ext4 rw,relatime,data=ordered 0 0)

mount -o bind /live/image /live-rw-backing

-> mount -o bind "${old_mountp}" "${mountp}" #script/live-helpers:L314
                   # $old_mountp: おそらく"/live/image" #L294
                   # $mountp: /live-rw-backing
                   ===> mount -o bind /live/image /live-rw-backing
                        (/dev/sda1 /live-rw-backing ext4 rw,relatime,data=ordered 0 0)
            ## find_cow_deviceはecho "${pers_fpath}"を返す #script/live-helpers:L389
               # $pers_fpath: ${cow_backing}/${PERSISTENT_PATH}/${pers_label}  #script/live-helpers:L346
                 # $PERSISTENT_PATH: "$LIVE_MEDIA_PATH" #L482
                   # $LIVE_MEDIA_PATH: "${ARGUMENT#vyatta-union=}" #L481
                   #  grubのvyatta-union=は/boot/1.1.0
                 # すなわち、$pers_fpathは"/live-rw-backing/boot/1.1.0/live-rw"
            # find_cow_deviceは${cow_backing}/${PERSISTENT_PATH}/${pers_label}である
            #    "/live-rw-backing/boot/1.1.0/live-rw" を返している

mount -o bind /live-rw-backing/boot/1.1.0/live-rw /cow

-> mount -o bind ${cowdevice} /cow #L1297
       ===> mount -o bind /live-rw-backing/boot/1.1.0/live-rw /cow (後の/dev/sda1 /live/cow ext4 rw,relatime,data=ordered 0 0)
       # $cowdevice: ${cowprobe}  #L1249

mount -t overlayfs -o noatime,lowerdir=//1.1.0.squashfs,upperdir=/cow overlayfs /root

-> mount -t ${UNIONTYPE} -o noatime,lowerdir=${rofsstring},upperdir=/cow overlayfs "${rootmnt}" #L1345
        # $UNIONTYPE: "overlayfs" #/etc/live.conf
        # $rofsstring: "${croot}/${imagename}${roopt}:${rofsstring}" #L1178
           #  $croot: "/" #L1071
           #  $imagename: imagename=$(basename "${image}") #L1142
           #  $roopt: ""  #L1087
           #  $rofsstring: "" #L1074
           # rofsstring=${rofsstring%:} #L1183
        # $rootmnt: "/root"  #L62, #init:L51
        ==> mount -t overlayfs -o noatime,lowerdir=//1.1.0.squashfs,upperdir=/cow overlayfs /root
            (overlayfs / overlayfs rw,relatime,lowerdir=//1.1.0.squashfs,upperdir=/cow 0 0)

mount -t tmpfs tmpfs /root/live

-> mount -t tmpfs tmpfs ${rootmnt}/live #L1364
        ==> mount -t tmpfs tmpfs /root/live  (tmpfs /live tmpfs rw,relatime 0 0)

mount -o move /cow /root/live/cow

-> mount -o move /cow "${rootmnt}/live/cow" #L1411
        ==> mount -o move /cow /root/live/cow (/dev/sda1 /live/cow ext4 rw,relatime,data=ordered 0 0)

mount --move /live/image /root/live/image

run_scripts /scripts/live-bottom  #L1708
-> live-bottom/05mountpoints  https://github.com/vyos/live-initramfs/blob/47cb65a9b94ca48696e8e0255c921167ddcfb49b/scripts/live-bottom/05mountpoints
  -> mount --move /live/image /root/live/image  #scripts/live-bottom/05mountpoints:L33
    ===> mount --move /live/image /root/live/image (/dev/sda1 /live/image ext4 rw,relatime,data=ordered 0 0)

mount -o bind /root/live/cow/config /root/opt/vyatta/etc/config

-> live-bottom/50vyatta
https://github.com/vyos/build-iso/blob/4ddda254c76d2bfb807ed7c2c5ea992037638bf7/livecd/config.vyatta/chroot_local-includes/usr/share/initramfs-tools/scripts/live-bottom/50vyatta
    -> mount -o bind /root/live/cow/config /root/opt/vyatta/etc/config  #L51
    ===> mount -o bind /root/live/cow/config /root/opt/vyatta/etc/config (/dev/sda1 /opt/vyatta/etc/config ext4 rw,relatime,data=ordered 0 0)

あとは、以下で通常のinit処理が起動する。
exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console #L303

/etc/init.d/vyatta-router

ここでもマウント処理がある。

mount -o nosuid,nodev,mode=775,nr_inodes=0 -t tmpfs none /opt/vyatta/config

-> start #L159
  -> mount -o $tmpfs_opts -t tmpfs none ${vyatta_configdir} #L169
    # $tmpfs_opts: nosuid,nodev,mode=775,nr_inodes=0  #L168
    # $vyatta_configdir: "/opt/vyatta/config"  # /etc/default/vyattaより
    ===> mount -o nosuid,nodev,mode=775,nr_inodes=0 -t tmpfs none /opt/vyatta/config (none /opt/vyatta/config tmpfs rw,nosuid,nodev,relatime,nr_inodes=0,mode=775 0 0)

(mount --bind /opt/vyatta/etc/config /config)

if文で結果的に走ってないのだけど、VyOS的に大事な行なので。
-> mount_slashconfig #L175
    -> mount --bind /opt/vyatta/etc/config /config #L154
    ===> このbind mountはlive環境でもそうでないときでも/configが存在するようにという配慮らしい(L139)

mount --bind /live/image/boot/$image_name /boot

-> bind_mount_boot #L193   #ツール類が正しく操作できるようにという配慮(L112)
    -> mount --bind /live/image/boot/$image_name /boot  #L123
    ===> mount --bind /live/image/boot/1.1.0 /boot (/dev/sda1 /boot ext4 rw,relatime,data=ordered 0 0)

mount --bind /live/image/boot/grub /boot/grub

-> mount --bind /live/image/boot/grub /boot/grub    #L130
    ===> mount --bind /live/image/boot/grub /boot/grub (/dev/sda1 /boot/grub ext4 rw,relatime,data=ordered 0 0)

感想

/proc/mountsと/etc/mtabを追えばわかるだろうなと思って始めたけど、それだけじゃ全然わからなかった。ディスクを読むのに使ってたSystemRescueCdも似たような/livemntがあるので、LiveCD系は似たような構成なんだろうなと思う。そういう意味でLiveブートの構造がわかったのはよかった。

2014-12-04

Vue.jsを使ってBloggerの最新記事一覧を表示する

これは、Vue.js Advent Calendar 2014の記事です。

---
Webサイトのトップページだとかに、ブログの最新記事一覧へのリンクを表示することがある。今回はGoogleのブログサービスである「Blogger」で記事を書いていた場合に、その一覧を、データバインディングを行うJavaScriptライブラリであるVue.jsを使って表示する方法を説明する。

サンプル

実際のコードを見るほうが簡単なので、このブログを表示する例を出すと、以下の通りとなる。

動作例:http://jsfiddle.net/zvrakxuv/

短く書けるのがわかると思う。

解説

さて、以下で解説していく。

BloggerのAPIからのJSONの取得

まずデータを得るが、BloggerにはJSONPで記事の情報を返すURLがあるので、これを利用する。具体的には、このようなURLにアクセスすればよい。
http://blogname.blogspot.com/feeds/posts/default?alt=json-in-script&callback=myFunc
(参考: Simple example of retrieving JSON feeds from Blogger Data API)

すると、以下のようなデータが返ってくるので、JSONPの要領でデータを読み込めばよい(データはあまりにも多いので、jqコマンドで整形後適宜省略)。
myFunc({
  "feed": {
    "entry": [
      {(略)
        "published": {
          "$t": "2014-11-11T22:14:00.000+09:00"
        }, (略)
        "title": {
          "$t": "「アセットを抽出」の出力は油断するとボケる (Photoshop CC 2014)",
          "type": "text"
        }, (略)
        "link": [
          (4つ省略),
          {
            "title": "「アセットを抽出」の出力は油断するとボケる (Photoshop CC 2014)",
            "href": "http://d.sunnyone.org/2014/11/photoshop-cc-2014_67.html",
            "type": "text/html",
            "rel": "alternate"
          }
        ],
        "author": [(略)]
      },(略)});
詳しくは実際に叩いてみるか「Using JSON in the Google Data Protocol」を参照。

この他にも、サムネイルのURLや本文など、いろいろ入っているので、他にも状況に応じて使える。ただ、その分ブラウザに返すデータ量が多い。この時点でmax-results=5のように指定して数を限定するのがおすすめである。

Vue.jsによるデータバインディング

あとはcallbackに指定した関数でデータを受けて表示すればよいので、jQueryなんかで書いてももちろんよいが、ここではVue.jsを使う。

Vue.jsは、データバインディングを行うライブラリで、他のメジャーどころで言えばKnockout.jsの立ち位置に近いが、DSLがもう少し簡略化されており、さらっと書けるので、今回のようなケースには向いていると思う。(基盤に徹するBackbone.jsや、詰め込みまくりなAngular.jsとは異なる。)

バインド対象となる要素の記述

まずテンプレートとなる要素を記述する。今回の例は以下。


今回のポイントとなるのは以下の通り。

バインド対象となるidの記述

あとでCSSセレクタ記法で指示するので書き方はなんでもよいのだが、なんらかわかるように印をつける。今回はidとした。

繰り返しを意味するv-repeat

Vue.jsでは、各種v-なんとか(デフォルトではv-だが、data-vのように変更も可能)という属性(Directiveと呼ぶらしい)が用意されている。

このDirectiveのうち、まずはv-repeatを利用する。このv-repeat属性に「変数名: 対象」としておくと、対象を回して要素の中身を繰り返してくれる。

なお、dtとddをセットにしたいのでtemplate要素を利用したが、ul/liだったらliにv-repeatをつける感じになる。templateは、非対応ブラウザを考慮してdisplay: noneをつけたほうがいいかもしれない。

v-シリーズの指定方法は、概ねKnockout inspiredな感じである。詳しくは、Guideの「Directives」およびAPI Referenceの「Directives」を参照。

属性をセットするv-attr

v-attrに「属性名: 式」と記述するとその値を要素の属性にセットできる。

属性にも後述の{{ }}の記法が使えるのだが、テンプレートの段階でブラウザに解釈されると悲しい属性(imgのsrc)があるので、属性には基本的にこのv-attrを使うほうがベターだと思う。

値を出力する{{ }}記法

mustacheテンプレートライクな (Angularライクというべきか) {{ }}記法を使うと、値を記述することができる。
.でたどることもできるし、[n]も使える。| を使うとFilterを呼び出すこともできる。

フィルタ関数の定義

| に使うフィルタはカスタムに定義することができる。

定義するには、値と引数を受け取り、値を返す関数を用意し、Vue.filter()で設定する。
Vue.filter('first', function(value, length) {
  return value.substr(0, length);
});

フィルタについては、Guideの「Filters」およびAPI Referenceの「Filters」を参照。

データバインドの適用

あとは、先ほどcallback=に指定した関数でデータを受け取り、Vueオブジェクトを生成するだけ。今回は、elにバインド対象を、dataにバインドするデータを渡している。
function showFeedArticles(json) {
  new Vue({
    el: '#feed-article-list',
    data: json
  });
}

今回のケースではこれでおしまい。あとは、Guide をさらっと読めば一通り使えるようになると思う。

2014-11-30

1本では実がならない?果樹を植える時に注意すべき受粉の性質について

この果物の木は実がならないなぁと感じたことがないだろうか?実は、果樹は1本で実がなるものとならないものがある。気づかないで植えたりすると桃栗三年柿八年というように長い期間を無駄にしてしまうので、果樹を育てるならこの話は知っておいて損はないと思う。



果樹を育てる話をするときは結構な頻度でこの話をするのだけど、言葉を知らなかったので思い出したこのタイミングでまとめた。

果樹には自家受粉できるものとできないものがある

自家受粉できるとは、同じ株の花で受粉できること。つまり、自家受粉ができないということは、1本では実が成らないということになる(例外あり)。庭に植えるような場合では、スペースに限りがあるので、これによって果樹の選択を変える場合もでてくるだろう。

まれにしかないのであればあまり気にしないのだけど、果樹には自家受粉できないものは結構多いので、 知らないものであれば育てる前に調べないといけない。

自家受粉できないケースにはどんな例があるか

雌花が咲く株と雄花が咲く株が異なる(雌雄異株)

もっともわかりやすいのがこれ。雌花しか咲かない木と雄花しか咲かない木では、1本だけで成らないのは想像しやすいと思う。 例としては、キウイがある。

雄の花と雌の花のタイミングが異なる(雌雄異熟)

同じ株の花で受精できる特性を持っていても、タイミングがズレている種類がある。 例としては、アボカドやクルミがある。

同一品種では受粉しにくい特性(自家不和合性)がある

わかりにくいのがこれで、同じ株の花の花粉が、同じ株についても受精が避けられる仕組みになっている種類がある。 例としては、梨や栗がある。

同じような意味で自家(不)結実性という語も使われている。果物に関しては、自家結実性と呼ぶことが 多いようなので、これで調べるのが一番調べやすいと思う。

自家結実性について注意すべきポイント

品種のレベルで異なる場合がある

やっかいなことに、果物のレベルで必ず同じかというとそうでもなく、自家結実性のある品種とそうでない品種があったりする。

しやすい/しにくいという1/0でないことがある

必ずしも「1本では絶対に無理」のデジタルな話ではなく、「1本では成りにくい」ということもある。 特に気にせず植えている場合で「他と比べるとウチの木はなんか成らないなぁ」というケースでは 自家不結実性の話を疑ってもいいかもしれない。

受粉しなくても実になることがある(単為結果)

先に「例外あり」と書いたのは、 花が咲いて受粉して、というプロセスでない形で実になるケースがある。わかりやすいのがイチジク。

参考になるページ

もっと詳しく知りたい場合は、他の情報元を見てほしい。参考になりそうだったページをいくつか紹介する。

基礎から覚える 果樹の受粉マニュアル | 種・苗・球根・ガーデニング用品・農業資材の通販サイト【タキイネット通販】
自家結実性のほかにも、カキやクリなど著名な果物の育て方が図入りでわかりやすく書いてある。

果樹の自家結実性について:4月の栽培ガイド
果樹の種類が多く書いてあるのでざっと把握したいとき向きに。

自家受粉と他家受粉
今回の話。一番分類が読みやすかった。

雌雄異熟(5)
アボカドとクルミの雌雄異熟の特性について詳しく書いてある。興味があれば。

2014-11-11

「アセットを抽出」の出力は油断するとボケる (Photoshop CC 2014)

現在のPhotoshopには「アセットを抽出」という機能があり、レイヤー名やグループ名にfilename.pngのような名前(オプション引数あり)をつけておくと、スライスを使わずに「ファイル>アセットを抽出」で一気に出力することができる。


さらに、出力ダイアログに倍率を設定するボタンがついており、2xのようなスケールした画像を同時に生成することができ、Retina対応に便利な機能もついている。非常に便利な機能なのだが、このスケーリングが曲者であり、油断するとボケる。

スケールでボケるのはどんなときか?

「大きくしてるんだからボケるのは当たり前じゃないか?」と思われる方も居ると思うが、例えばシェイプや文字で構成したレイヤーを2x出力してもボケない。以下の通り。


これができなければ2xなんて用意しないと思うので、当然といえば当然である。

しかし、これはいけると思って「埋め込みを配置」やドラッグアンドドロップで埋め込んだ写真などのラスタ画像の2xは以下のようにボケてしまう。(当然ながら、埋め込んだ画像は2x拡大するだけの解像度は持っているものとする)


「アセットを抽出」の2x(200%)がボケる状態でも、「イメージ>画面解像度」で幅/高さを2倍にしてもボケないので、Photoshopはボケずに拡大する頭は持っているにもかかわらず、だ。

どうすればボケないか?

2xしか使わないのであれば、一番簡単なのは、そもそも2xの解像度で作っておき、1x側を0.5x出力するという方法。1x解像度で作っておき、出力直前に画面解像度を拡大するという手もありだと思う。

しかし、どうしても1xから2x, 3xを出したい、というのであれば、画像の取り込み時に以下の手順を踏めばいけるということがわかったので紹介する。
(参考:Photoshop generator not working with smart object & scaling.

1. 新しい画像として埋め込む画像を読み込む

「ファイル>開く」でもいいし、埋め込みにならない場所にドラッグアンドドロップでもいい。


2. レイヤーをスマートオブジェクトに変換する

レイヤーを右クリックし「スマートオブジェクトに変換」を選択する。


3. レイヤーを埋め込みたい画像にコピーする

レイヤーを右クリックし「レイヤーを複製」で埋め込みたい画像にコピーする。




あとは適切にリサイズすればOK.

以下のように、ボケてない状態で出力することができる。


要は、大きな状態で明示的にスマートオブジェクトを構成すれば、なんとかしてくれる、という話であるので、「埋め込みを配置」時にキャンバスサイズに小さくされるのを100%, 100%に戻し、明示的に「スマートオブジェクトに変換」することでも対応できる。

しかし、そもそも「埋め込みを配置」で埋め込まれるのはスマートオブジェクトなので、さらにスマートオブジェクトに変換すると、コンテンツの編集で出てくるのがスマートオブジェクトという状態になっているので、ちょっとどうなんだろうという感じなので、手順としては上述にした。

上記のフォーラムでも話があったが「埋め込みを配置」でできるのだってスマートオブジェクトなのだから、やれよ!と思う。上述の手順は正直面倒だし、OKなスマートオブジェクトなのか、NGなスマートオブジェクトかを区別するのが難しくて怖すぎるので現実的には拡大側は使えないと思う。機能としては便利だと思うので、早く直って欲しいものだ。

2014-10-30

色を選びやすくするツール「pcolors」を公開

d3.jsの練習がてら、色を選ぶためのツール(ページ)「pcolors」を作った。色をマウスオーバーすると、いつもの16進のRGBの色がでてくるので、コピーすればOK。


使い方としては例えば、「ドミナントカラー」で「暖色系」かな、となったらじゃあ色相は2で統一、トーンはvとp+、じゃあv2とp2+かな、となると「#e60044」と「#fad5cf」か、みたいな感じで使う。詳しくはPCCSのことが書いてある本を読めば書いてあるはず(私は「配色&カラーデザイン ~プロに学ぶ、一生枯れない永久不滅テクニック~」で入った)

まぁこれは見ての通りPCCSトーンマップなのだけど、WebにあるやつはPCCSの主張と見た目がどうもあってない気がしたり(紙であれば納得できる雰囲気になるのだと思う)、ページの構成がいまいちだったりしたので、自分で作った。

ひとつひとつ埋めていったので、どこか間違っている色があるかも。こういう機能があれば便利、みたいなのも気が向けば実装するかもしれない。

2014-10-12

PythonでGIMPのスクリプトを書く(Python-Fuの書き方)

これはGIMPでスクリプトを使おう!と思ったものの久々のScheme (Script-Fu)にめげて「GIMPってPythonも普通に使えるんじゃね?」ってことで試してみたら罠もあったけどいい感じだったよって記事である。Linuxではもちろん、Windowsでもインストーラから入れればPythonが同梱されているので、気軽に使える。

もちろんGIMPを普段から使うのであれば、マクロ的に使ってもいいと思うのだけど、GIMPを使ってなくても「UIの必要な画像処理」には便利だと思う。画像の表示、範囲選択とか、そういうのが本筋じゃないプログラムにさらっと提供できる。たとえば、自前のOpenCVを使った画像処理ライブラリのフロントエンドとかね。

枝葉の処理が多いのでこのスクリプト自体は解説しないが、自分が作ろうと思って作ったもの(後述)はこちら


PythonでのGIMPのスクリプトの書き方

基本的には、developerworksの記事「Python を使用して GIMP 用のプラグインを作成する」がわかりやすいので、詳しいチュートリアル的なものが必要であればそちらのほうで。こちらは、だいたいどんなことをすればできあがるのかなというのをイメージできるようざっくりと書くことにする。

1. 雛形を持ってきて配置

雛形があれば余計なところに時間がかからないので、作ったのがこちら。


これを、~/.gimp-2.8/plug-ins (WindowsであればC:\Users\{username}\.gimp-2.8\plug-ins)に適切なファイル名をつけて配置する。なお、Unix系では実行権が必要なのでつけておく。
$ chmod a+x helloworld.py

2. register関数を書く

まずプラグインをGIMP側に登録するregister関数の引数を埋めていく。
詳しくは先のdeveloperworksの記事、あるいはGIMP Python Documentationを参照のこと。

基本的には見ての通りのところを直していけばいいのだが、引数と書いた部分の配列に、項目を設定しておくと、スクリプト起動時にダイアログが現れて選択させることができる。例えば、文字列を受け取りたければ、以下のタプルを配列に入れる。
(PF_STRING, "string", "説明", 'デフォルト値')
PF_STRINGのような型にどんなものがあるかは、やはりGIMP Python DocumentationのPlugin Frameworkを参照。

ここに入れた項目は、設定した関数名の関数に渡るので、入れた項目を覚えておく。

実体の関数(雛形のplugin_main)を書く

plugin_mainとregister関数で指定してあるので、この関数に処理を実装していく。

引数は、現在のイメージ、レイヤーに続いて、先ほどregister関数で指定した引数を受け取るようにする。

中の実装に関しては、大きく分けて以下の2つの方法で関数を呼び出していく形になる。

A. pdb.~で、プロシージャデータベースの関数を呼び出す

GIMP内部にプラグインの処理を登録しておくプロシージャデータベース(Procedual Database, pdb)というものが存在し、この関数をpdb.なんとかのような形で呼び出すことができる。

例えば、このような形である。
pdb.gimp_image_scale(img, 640, 480)

どのようなプロシージャが存在するかは「ヘルプ>プロシージャーブラウザー」よりプロシージャーブラウザーを起動して検索することができる。


B. gimpモジュールに含まれる関数を呼び出す

レイヤーを作成したりといったベーシックな処理であれば、gimpモジュールにも関数が準備されている。

例えば、以下のようにするとメッセージを表示することができる。
gimp.message("Hello, World")

実装されている関数についてはGIMP Python Documentationの「GIMP Module Procedures」に一覧がある。


あるいは、フィルター>Python-Fu>コンソールで、Python Consoleが動くので「help(gimp.Image)」などとしても良い。

Pythonで文法チェック

書き終わったら、そのスクリプトをpythonコマンド(Windowsではインストールパス\Python\pythonにある)で実行してみる。実行したとき、gimpfuモジュールがない旨のメッセージが出ればひとまずOK。これで文法的なミスがないか確認する。

>"C:\Program Files\GIMP 2\Python\python" C:\Users\yoichi\.gimp-2.8\plug-ins\helloworld.py
Traceback (most recent call last):
  File "C:\Users\yoichi\.gimp-2.8\plug-ins\helloworld.py", line 5, in 
    from gimpfu import *
ImportError: No module named gimpfu

起動

ここでGIMPを起動し、メニュー項目に指定した項目があり、実行できれば成功である。

丁寧にチュートリアル通り作ればいいが、動けばいいやと雰囲気で適当に作ったらはまった。

(Unixでは)実行権を与える必要がある

忘れがちであるが、Unix系ではplug-insに置いたスクリプトに実行権が必要である。

main()を忘れない

register()のあとにmain()があるが、これがないと動作しない。詳しくは追いかけていないが、実行権が必要であるところからして、このスクリプトは単体のプロセスとして動作し、main()の中でGIMP本体と通信して動作しているのだと思う。

image, layer引数が必要

引数の配列で宣言した項目のほかに、現在のイメージとレイヤーを受け取っておく必要がある。

Windowsではデバッグしにくい

Unixではコンソールからgimpコマンドで起動すればエラーが出てくるが、Windowsではコンソールはデフォルトではない。そのため、困っているときは以下のように起動してみる。
gimp --console-messages 

また、関数部分のみ実行されるようにしてPythonコンソールからimportして呼び出してみたり、sys.stderrをopen("file/path", "a")で差し替えてみたりといったデバッグテクニックがあるので、GIMP Forumの「Debugging python-fu scripts 」の記事を参照されたい。

Windows版GIMPではメッセージボックスが出ない(ことがある?)

どっかの変数のデフォルト値の違いだと思うが、デフォルトではWindowsでインストールしたGIMPではgimp.messageでメッセージボックスが出なかった。
そういうときは、ウィンドウ>ドッキング可能なダイアログ>エラーコンソールで、リスト形式でエラーメッセージを見ると良い。

参考

今までのリンクの他にも「Writing Python script for Gimp 」のブログ記事もわかりやすい。

こちらの「Beginning GIMP: From Novice to Professional」にあるいくつかのサンプルスクリプトを読むともっとイメージがつきやすいと思う。

また、GIMP Plugin Registryの「python」タグでもPythonでの記述についてのサンプルが得られる。

何に使ったか?

ということで無事に書けるようになったわけだが、そもそもなぜ書くことになったか?

先日、画像のピクセル数が見た目サイズの2倍の画像をベタなHTML+CSSに入れ込んでいくという作業があり、物理電卓をバチバチ叩きながら1/2サイズを作っていった。

「これは機械にやらせたほうがいい」と思って「画像... 自動化... UI作るの面倒... ここはGIMPじゃね?」ということで久々にScript-Fuを書こうとしたところ、どうもScheme脳に頭が切り替わらない。「Pythonも使えるんじゃね?Windowsも普通に入ってるわ」ということでPythonでGimpマクロ的なものを書いてみた次第である。

結果、SchemeによるScript-Fuに比べると素直じゃない感はあるものの、仕組みを掴んでしまえば、Lispを普段書かない自分には圧倒的にサクサク書くことができた。そのギャップから、普段Pythonは書けないのに「俺ひょっとしてPython書けるんじゃね?」という謎の錯覚が生まれた。

なお補足しておくと、どっちかっていうと今回は「これは覚えるチャンス」的な意味が大きくて、このケースでは実際にはCompassにサイズ計算させるとか、とか、sh+ImageMagickやPowerShellでディレクトリに置いたやつを一気に作る、とかのほうが現実的なアプローチだとは思うので、そちらを検討されたほうがよろしいかと。

2014-09-26

Linux版HipChatで日本語入力できないときの対処(Ubuntu + 野良Qt5アプリで日本語入力)

Linux版HipChatで日本語入力ができなくて、最初そんなもんかと思ったけど追っかけてみたら直せた。
結論的にはださいがこれでいけた:

今回はこうだったが、実際は環境によっていろいろだと思うので、進め方を残しておく。

環境

Distribution
Ubuntu 14.04 Japanese Remix (amd64)
Desktop 環境
Cinnamon →第342回 デスクトップ環境Cinnamonを使用する:Ubuntu Weekly Recipeを参照

Qt5での日本語入力に必要なこと

Qt5でbuildされた QtCreatorで日本語入力をできるようにする(@Linux環境) によると、どうやら2つのことが必要らしい。
  • 環境変数QT_IM_MODULEの設定
  • platforminputcontextsプラグインの配置(or 環境変数でプラグインパスの変更)

対象アプリケーションの構成の確認

HipChatのインストール方法はHipChat - Downloadsに書いてある通り、apt-lineを追加してapt-get install。つまり、公式ではない野良アプリケーション。

以下のコマンド出力からわかるように、自前でQt5ライブラリを抱えており、シンボリックリンク"/usr/bin/hipchat"からのbash script "/opt/HipChat/bin/hipchat"からの/opt/HipChat/lib/hipchat.binで起動するようになっている。
$ dpkg -L hipchat
/usr
/usr/bin
/usr/share
/usr/share/applications
/usr/share/applications/hipchat.desktop
/usr/share/icons
/usr/share/icons/hicolor
/usr/share/icons/hicolor/256x256
/usr/share/icons/hicolor/256x256/apps
/usr/share/icons/hicolor/256x256/apps/hipchat.png
(略)
/opt
/opt/HipChat
/opt/HipChat/bin
/opt/HipChat/bin/linuxbrowserlaunch
/opt/HipChat/bin/hipchat
/opt/HipChat/bin/HipChatNowPlaying.rb
/opt/HipChat/lib
/opt/HipChat/lib/libvorbis.so.0.4.5
/opt/HipChat/lib/libvorbisfile.so.3.3.4
(略)
/usr/bin/hipchat
(略)
/opt/HipChat/lib/hipchat.bin
(略)
/opt/HipChat/lib/libQt5Network.so.5
/opt/HipChat/lib/libvorbis.so
/opt/HipChat/lib/libQt5Sql.so.5
/opt/HipChat/lib/libdbusmenu-qt5.so.2
$ file /usr/bin/hipchat
/usr/bin/hipchat: symbolic link to `/opt/HipChat/bin/hipchat' 
$ file /opt/HipChat/bin/hipchat 
/opt/HipChat/bin/hipchat: Bourne-Again shell script, ASCII text executable
$ tail -1 /opt/HipChat/bin/hipchat 
exec -a "$0" $commandtorun "$hipchatRoot/lib/hipchat.bin"  $arguments

環境変数QT_IM_MODULEの設定

QT_IM_MODULEが設定されていることを確認する。

$ env | grep IM_
CLUTTER_IM_MODULE=xim
QT_IM_MODULE=fcitx
QT4_IM_MODULE=fcitx
GTK_IM_MODULE=fcitx

fcitxに設定されているようだ。

platforminputcontextsプラグインの確認

先ほどの記事によると、platforminputcontextsプラグインが重要らしい。ということでパッケージを調べてみると

$ dpkg -L hipchat | grep platforminput
/opt/HipChat/lib/plugins/platforminputcontexts
/opt/HipChat/lib/plugins/platforminputcontexts/libibusplatforminputcontextplugin.so
/opt/HipChat/lib/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so

ということで、fcitxはない。ibusはあるので、おそらくibusだったらそのまま入力できたのだろう。

Ubuntu側にあればもらってこようと思って調べたら、
$ dpkg -S fcitxplatform
fcitx-frontend-qt5:amd64: /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so
ということで、存在していた。なければapt-get install fcitx-frontend-qt5で入りそうである。

そのため、以下のようにシンボリックリンクを張ることで日本語入力を行うことができるようになった。
$ sudo ln -s /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so \
 /opt/HipChat/lib/plugins/platforminputcontexts/
それぞれビルドされた環境が異なるし、いつまでこれができるかわからないので、いっそcpでコピーしたほうがいいかもしれない。

番外:環境変数QT_PLUGIN_PATHの設定

先ほどの記事からするとQT_PLUGIN_PATHを/usr/lib/x86_64-linux-gnu/qt5/pluginsに向ければいけそうであるが、そうはいかない。というのも、/opt/HipChat/bin/hipchatがunsetしているからである。

/opt/HipChat/bin/hipchat抜粋:
thisfile="`readlink -f "$0"`"
thisdirectory="`dirname "$thisfile"`"
hipchatRoot=$thisdirectory/../
export HIPCHAT_LD_LIBRARY_PATH=$LD_LIBRARY_PATH
export HIPCHAT_QT_PLUGIN_PATH=$QT_PLUGIN_PATH
export LD_LIBRARY_PATH=$hipchatRoot/lib
unset QT_PLUGIN_PATH
exec -a "$0" $commandtorun "$hipchatRoot/lib/hipchat.bin"  $arguments

自前のほうに向けないとあらぬプラグインを読んだりするかもしれないものね。こういうケースもあるので、野良アプリケーションでは起動するときの流れを確認されたい。

2014-09-20

WindowsアプリのUI自動操作をUI Automation PowerShell Extensionで行う

UI Automation PowerShell Extensionは、.NET Frameworkに付属するUI オートメーションライブラリのPowerShellラッパーで、UI自動操作(UIオートメーション)をPowerShellで記述できる。これにより、GUIしかないアプリの定型作業をスクリプト化したり、UIテストを行ったりすることができる。だいぶ前に書こうと思ったのだけど、思い出したのでようやく書くことにした。

まずは例として、Windowsに付属する「電卓」を使って1 + 2を計算してみるスクリプトを書くと以下のようになる。

$process = Start-Process calc -PassThru
$window = Get-UiaWindow -ProcessId $process.Id
$window | Get-UiaButton -Name '1' | Invoke-UiaButtonClick | Out-Null
$window | Get-UiaButton -Name '加算' | Invoke-UiaButtonClick | Out-Null
$window | Get-UiaButton -Name '2' | Invoke-UiaButtonClick | Out-Null
$window | Get-UiaButton -Name '等号' | Invoke-UiaButtonClick | Out-Null


結構簡単に操作できることがわかると思う。ではここから使い方を見ていく。

準備

まずは準備として、http://uiautomation.codeplex.com/ に行き、Downloadボタンを押してzipをダウンロード、どこかに展開する。

本当はこの中身をDocuments\WindowsPowerShell\Modules\UIAutomationに入れるのだけど、お試しなのでとりあえず
Import-Module .\UIAutomation.dll
とする。

Get-Moduleして以下のようにでてくればOK。

PS > Get-Module UIA*

ModuleType Version    Name                                ExportedCommands
---------- -------    ----                                ----------------
Binary     0.8.7.79   UIAutomation                        {Add-UiaBannerText...


作り方

作り方は、「UI要素を探す」「UI要素をコントロールする」を繰り返すだけ。

UI要素を探す

UIをポイントするとUI要素を表示してくれるSpyツール「UIAutomationSpy.exe」が付属しているので、これを使ってコマンドを生成することができる。

UIAutomationSpy.exeを起動すると、ボタンが2つとテキストボックスがいくつかある画面が起動するので「Start」ボタンを押してから、マウスカーソルに適当なウィンドウに持っていくと、テキストボックスにコマンドが表示される。



欲しい部品が見つかったら、素早くStopボタンを押す。

一番下のテキストボックスにホバーしたUI要素を探すためのコマンドが書かれているが、基本的にはこれをそのまま使うのではなく、不要な部分を削ったり、ちょっと加えたりする。例えばさっきの電卓の例であれば、採取したタイミングでは以下のようになっている。

Get-UiaWindow -Class 'CalcFrame' -Name '電卓' | `
Get-UiaPane -Class 'CalcFrame' | `
Get-UiaPane -Class '#32770' | `
Get-UiaButton -AutomationId '81' -Class 'Button' -Name 'クリア'

しかし、Get-Uiaシリーズは基本的に再帰的に要素を探してくれるので、ウィンドウを取得できたら、あとは特定できるのであれば詳しく書く必要はない。上記の例では、以下でもOK.

Get-UiaWindow -Class 'CalcFrame' | Get-UiaButton -Name 'クリア'

なお、Idというだけあって、通常はAutomationIdを使うのがおすすめ。上記の例では適切なものがついていないが、WPFアプリケーションならx:NameやNameに設定したものがついている。

UI要素をコントロールする

上記のGet-~で得られたオブジェクトを、コントロールするコマンドレットにパイプで渡せばコントロールすることができる。例えば、ボタンをクリックするなら以下の通りになる。

$button | Invoke-UiaButtonClick

コントロールするコマンドレットは自分で探すしかないが、基本的にSet-UiaやInvoke-Uiaがついているので、補完でだいたい探せる。例えば「Invoke-UiaWindowPattern -PatternName Close」や「Set-UiaTextBoxText」などである。

見つからないときは「Get-Command *uia*」とすれば一覧がでてくるので、これから探す。なお、テキストを取得するGet-UiaTextTextなど、取得する系のコマンドレットもあるので、テストなんかには活用されたし。

上記を組みわせていけば自動操作するスクリプトができる。

もっと詳しく

ほとんど上記で行えているが、歯抜けではあるがドキュメントがあるので、こちらも参考にできる。

また、このライブラリはSystem.Windows.Automationのラッパーでしかないので、.NETのUI オートメーションの情報、例えばMSDNのドキュメント(UI オートメーションの概要)や既存の記事なども参考にできる。コマンドレットで不足があれば、.NETのAPIを得られたオブジェクトに対して実行してしまっても動作する。

なお、試してみてはいないが、Metro UIなんて記載もあるので、Windowsストアアプリも操作できそうである。興味があればぜひ。

Tips

UI自動操作は対象によってTips的なものが使うほどにでてくるが、最初から大事そうなものを挙げておく。

ウィンドウをプロセスで絞り込む

ウィンドウはプロセスを決めなくても検索できるが、自分で起動している場合など、限定できる場合は-ProcessIdで絞っておくと安全である。

Get-UIAWindow -ProcessId $proc.Id -AutomationId hogehoge

UI要素をガイドする赤枠を外す

デフォルトでは選択した要素には赤枠がつくようになっている。しかし、通常は邪魔なので以下で外すことができる。

[UIAutomation.Preferences]::Highlight = $false

タイムアウトを変更する

既定でもUI要素が出てくるまである程度待ってくれるが、足りない場合は-Secondsで延長できる。

Get-UIAWindow -AutomationId hogehoge -Seconds 60

以上、Happy UI Automation Lifeを。

2014-09-19

一部のSSDとext4でWriteが激しく遅い問題に対処する

結論:dbench 5 -D .で3MB/s程度しか出ないときは、barrier=0マウントオプションを検討する。

背景

Windows8.1の上のVirtualBoxにUbuntuを乗せて開発をしていたのだけど、開いたウィンドウがフルスクリーンの裏/表に回っちゃうとか、3D性能が下がるせいでウィンドウマネージャが限定されて効率が悪いといった不満があったので、今のご時勢にデュアルブートにすることにした。

それ自体はまぁいつもの感じでできて、グラフィックがサクサクになって期待通りの感じだったのだけど、ディスクI/Oが異常に遅い。apt-getすると「Unpacking~」で数十分とか待たされて、apt-get upgradeが全然終わらない。

これでは使い物にならないので、なんとかすることにした。
 

対処

使っているSSDはcrucialの「CT256V4SSD2」。ファームウェアが更新されていて、変更内容がパフォーマンス改善だったので、更新してみるものの、改善されない(なお、ディスクは消去されるのでインストールし直した)。

遅くなる事例がないかなと思って調べてみると、どうやら廉価なモデルらしく、painfully slowとか言われていて、機種のせいかとあきらめかけたら、まさかの日本のAmazonレビューに気になる記述を発見。

※2013/2/14追記
公式フォーラムでの情報を参考に、Linux ext4ファイルシステム環境において
ほぼ完全な動作を得られましたので一応追記しておきます。

mountオプションとしてnoatime,nodiratime,data=writeback,barrier=0,commit=180,nobhを指定することでフリーズを回避できるようです。
CD/USBブート後当SSDに対してtune2fs -o journal_data_writeback /dev/sd**を実行、
fstabのオプションフィールドに上記パラメータを追加することで、ようやく完全にSSDとしての性能を発揮するようになりました。
http://www.amazon.co.jp/product-reviews/B0092LHA7Y

この中ではbarrier=0が気になったのでmount -o remount,rw,barrier=0 /でマウントしたらapt-getのひっかかりがなくなった。

計測

もう体感では明らかに速くなっているのでこれでもいいのだけど、検索してみると同じ症状ではないかと思われる人がベンチマークを取っている(ext4のボトルネック除去:(SSDの)命懸けベンチマーク編)ので真似して「dbench 5 -t 30 -D .」で1回だけざっくり測ってみた結果がこちら。

Crucial CT256V4SSD2 / dbench 5 -t 30 -D . / 1回
Throughputmax_latency
未設定3.29658 MB/sec1222.288 ms
barrier=0106.397 MB/sec841.859 ms
barrier=13.29504 MB/sec1100.531 ms

体感と一致するありえないぐらいの遅さが数字に出ていた。3MB/sって光回線より遅いよ...

barrier=1がいつでもここまでインパクトを与えるかというとそうではなく、ずっと使っているノートPCのSSD(Crucial C300-CTFDDAC128M)では以下の通り。

Crucial C300-CTFDDAC128M / dbench 5 -t 30 -D . / 1回
Throughputmax_latency
未設定118.525 MB/sec52.287 ms
barrier=0144.024 MB/sec29.145 ms
barrier=1118.909 MB/sec44.737 ms

確かに遅くはあるのだけど、桁が変わるほどではない。max_latencyを見ると、さっきのSSDは投げ捨てろって感じだけどね...

ついでなので、SSDにTRIMコマンドを発行するようになる「discard」オプションをつけて測ってみたが、特に変化はない様子。

結局

上記の結果と、たいして重要なデータを置いていない作業用PCだということを踏まえて「barrier=0,discard」を/etc/fstabにマウントオプションとして追記することにした。

大事なデータを扱っている機械なら、こんな個体に当たったら使うのをやめるべきと思う。

2014-09-04

localhostへのリンクがFiddler経由になるようレスポンスを書き換える(スマホ開発用)

iPhone から開発マシンの localhost にアクセスする(Windows)のようにFiddlerの設定を変更し、「Allow remote computers to connect」をオンにすることで、iPhoneやAndroidのスマートフォンから開発者のPCにアクセスしてデバッグできるようになる。しかし、HTML内のURLにlocalhostや127.0.0.1が含まれている場合があり、そのリソースにはアクセスできない。

そんなときは、FiddlerでResponse bodyを置き換えることで、アクセスすることができる。

RulesメニューのCustomize Rulesから編集できるCustomRules.jsのOnBeforeResponse関数に以下を記述する。



もっと書き換えるべきヘッダがあったり、書き換えすぎちゃうものがあったりするかもしれないけど、そのとき適宜スクリプト書き換える方向で。

参考:Modifying a Request or Response

2014-08-29

LXDE (Lubuntu) でマルチディスプレイ設定を維持する

Lubuntu (LXDE) でのマルチディスプレイ設定画面を探したのだけど見つからなかったので、やりかたを書いておく。XubuntuやGNOMEだと画面があるのだけど。

そのセッション限定でマルチディスプレイ設定を行う

LXDEに限った話ではないが、xrandrコマンドを使うとマルチディスプレイの設定ができるので、このコマンドで設定できる。

まず、xrandr -qでモニタの一覧を出す。
$ xrandr -q
Screen 0: minimum 64 x 64, current 3840 x 1080, maximum 16384 x 16384
VBOX0 connected 1920x1080+0+0 0mm x 0mm
   1920x1080      60.0*+
   1440x1050      60.0  
   1280x960       60.0  
   1024x768       60.0  
   800x600        60.0  
   640x480        60.0  
VBOX1 connected 1920x1080+1920+0 0mm x 0mm
   1920x1080      60.0*+
   1440x1050      60.0  
   1280x960       60.0  
   1024x768       60.0  
   800x600        60.0  
   640x480        60.0  

モニタの名前がわかったら、--outputと--right-ofや--left-ofを使って、モニタの位置を指示する。
xrandr --output VBOX1 --right-of VBOX0

このあたりの操作は、ArandrというXrandrコマンドのフロントエンドがあるらしいので、Arandrを使うとGUIでできるのだと思うが、後述の自動起動のためにコマンドで行っておく。

参考:https://help.ubuntu.com/community/Lubuntu/MultiDisplay

マルチディスプレイ設定を維持する


上記のxrandrコマンドによる設定は、そのセッション限定なので、Xを使うたびに設定する必要がある。ただ、それは面倒なので、自動起動することにする。

~/.config/autostart/ に.desktopファイルを置くと、Xセッション起動時に自動起動が走るので、これを活用する。

具体的には、~/.config/autostart/multidisplay.desktopファイルとして、以下の内容を記述する。

[Desktop Entry]
Name=multidisplay
Exec=xrandr --output VBOX1 --right-of VBOX0
Type=Application

参考:Autostarting (日本語) - ArchWiki


これで自動でマルチディスプレイ設定が毎回行われるようになる。

2014-08-14

ドメイン移転

いつかやろうと思ってた独自ドメイン化を完了しました。 このブログは http://d.sunnyone.org/ になります(もとのhttp://sunnyone41.blogspot.jpからはリダイレクト)

h2が大文字化されるとか、邪魔なスタイルがあったので、一緒にスタイルをがっつり変更。今までより見やすくなったと思いますがどうでしょう。

2014-07-06

VyOS と L2TPv3 でハイパーバイザ内部ネットワーク同士をブリッジ接続する

前回ようやく仮想マシン(VM)達を移設するためのPCの準備ができたのだけど、移設する元のPCには、内部のみのネットワーク(VirtualBoxで言うところのHost-Only Network)があり、そこにいくつかVMがぶら下がっているので、これらも移行しないといけない。

一気に移せば何の問題もないのだけど、徐々に移そうとすると、内部のみのネットワークなので、旧ハイパーバイザに残ったVMと新ハイパーバイザに移し終わったVMで通信ができなくなってしまう。

どうしたらいけるかなと考えた結果、「イーサネットフレームを中継するVMを用意すればよくね?」ということで、それを可能にするL2TPv3と、L2TPv3を簡単に使えそうなVyOS (Vyattaのfork)を使って試してみた。

今回の話を図にすると、以下のとおり。

環境 / 利用ソフトウェア

  • 仮想マシンハイパーバイザ 2台。
  • VyOS v1.1 Helium (開発版なので注意:今回はVyOS-virt-livecd-1407022200-7e8544a-i386.isoを利用)

設定方法

VyOS インストール

まず、それぞれのハイパーバイザにVyOS をインストールする必要があるが、これはISOイメージでブートしてvyos / vyosでログイン、install imageと打てば指示に従うだけでOK。

こちらのブログが絵が張ってあってわかりやすそうなので参考に→『Vyatta』からフォークした『VyOS』をインストールしてみた | 俺的備忘録 〜なんかいろいろ〜

あとはホストとしての設定をする必要があるが、とりあえずこんな感じだけやればSSHでログインできる(ブリッジするだけのホストとして考えているので、インターネットに出していくのは想定していない)。
$ configure
# set interfaces ethernet ethX address '192.168.XXX.YYY/24'  (あとで出てくるので値は省略)
# set system host-name vybridge1    (あるいはvybridge2)
# set service ssh port 22
# commit
# save
以下、saveは省略するが、適宜保存する。

vybridge1の設定

まずeth0インタフェースにIPアドレスを振る。

$ configure
# set interfaces ethernet eth0 address '192.168.100.241/24'

次に「l2tpeth0」インタフェースを構成する。トンネルID/セッションIDは好きに決めてOK.
# set interfaces l2tpv3 l2tpeth0 
# set interfaces l2tpv3 l2tpeth0 local-ip 192.168.100.241
# set interfaces l2tpv3 l2tpeth0 remote-ip 192.168.100.242
# set interfaces l2tpv3 l2tpeth0 tunnel-id 41
# set interfaces l2tpv3 l2tpeth0 peer-tunnel-id 42
# set interfaces l2tpv3 l2tpeth0 session-id 1
# set interfaces l2tpv3 l2tpeth0 peer-session-id 2
# set interfaces l2tpv3 l2tpeth0 source-port 5000
# set interfaces l2tpv3 l2tpeth0 destination-port 5001

いったん適用して様子を見る。
# commit
# exit
$ show interfaces l2tpv3 detail
l2tpeth0:  mtu 1488 qdisc pfifo_fast state UNKNOWN group default qlen 1000
    link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet6 XXXX::XXXX:XXXX:XXXX:XXXX/64 scope link
       valid_lft forever preferred_lft forever

    RX:  bytes    packets     errors    dropped    overrun      mcast
             0          0          0          0          0          0
    TX:  bytes    packets     errors    dropped    carrier collisions
           738          7          0          0          0          0
l2tpeth0ができていることを確認する。

br0を構成し、eth1とブリッジする。
$ configure
# set interfaces bridge br0
# set interfaces ethernet eth1 bridge-group bridge br0
# set interfaces l2tpv3 l2tpeth0 bridge-group bridge br0
# commit
# exit
$ show interfaces
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface        IP Address                        S/L  Description
---------        ----------                        ---  -----------
br0              -                                 u/u
eth0             192.168.100.241/24                u/u
eth1             -                                 u/u
l2tpeth0         -                                 u/u
lo               127.0.0.1/8                       u/u
                 ::1/128
$ show bridge br0
bridge name     bridge id               STP enabled     interfaces
br0             0000.xxxxxxxxxxxx       no              eth1
                                                        l2tpeth0
br0ができていて、eth1とl2tpeth0が入っていることがわかる。
なお、eth1にはIPを振っていない。

これで、vybridge1の準備は終了。

vybridge2の設定

vybridge2の設定もほぼ同様。

$ configure
# set interfaces ethernet eth0 address '192.168.100.242/24'

vybridge2の「l2tpeth0」インタフェースには、基本的にはvyatta1と対照にする。
# set interfaces l2tpv3 l2tpeth0 
# set interfaces l2tpv3 l2tpeth0 local-ip 192.168.100.242
# set interfaces l2tpv3 l2tpeth0 remote-ip 192.168.100.241
# set interfaces l2tpv3 l2tpeth0 tunnel-id 42
# set interfaces l2tpv3 l2tpeth0 peer-tunnel-id 41
# set interfaces l2tpv3 l2tpeth0 session-id 2
# set interfaces l2tpv3 l2tpeth0 peer-session-id 1
# set interfaces l2tpv3 l2tpeth0 source-port 5001
# set interfaces l2tpv3 l2tpeth0 destination-port 5000

br0を構成し、eth1とブリッジする。
$ configure
# set interfaces bridge br0
# set interfaces ethernet eth1 bridge-group bridge br0
# set interfaces l2tpv3 l2tpeth0 bridge-group bridge br0
# commit

これでおしまい。あとは、新ハイパーバイザのVMから、旧ハイパーバイザのVMに通信してみて通信できればOK。

備考

実際のところ、今回の要件では一気に移行すればいいだけだったので、こんなことは必要ない。必要な場合であっても、Production環境でこのような使い方をするのであれば、L2VPNはオーバーヘッドが大きいのでパフォーマンスの検証をすべきと思う。(そもそも今のところVyOSの開発版についている機能のようなので、安定版では使えないのだけど)

なお、今回の構成では暗号化はされないので、もし必要ならIPSecの上に乗せるなど、別途考える必要がある。

しかしながら、かなりお手軽にL2ブリッジを構成できるので覚えておいて損はないと思う。

参考



2014-07-03

KVMの仮想マシンをWebブラウザから管理する

自分用にLinux KVM (Kernel-based Virtual Machine) で検証用他の仮想マシン達を立てている。その仮想マシンの管理に、今まではvirshコマンドと仮想マシンマネージャー (virt-manager)を使っていたが、各VMの画面が見たいとなると、VNCでXにログインして... という感じで煩わしかった。そこで、ハイパーバイザとなるPCのリプレースを契機に、Web管理ツールのWebVirtMgrを使って、Webブラウザから管理できるようにしてみた。

こんな感じの画面がWebブラウザから使えるようになる。


環境/前提

  • Ubuntu 14.04 Server
  • libvirtでKVMの仮想マシンを起動できる状態
    • (BIOS設定他+apt-get install qemu-kvm libvirt-bin的な状態)

Web側の設定


WebVirtMgrはPython + Djangoで書かれたWebアプリケーションなので、それが動くようにする。基本的には、https://github.com/retspen/webvirtmgr/wiki/Install-WebVirtMgr に書かれている手順の通り。このページには、RedHat/CentOS/Debianなんかのときの方法も書いてある。

Python他のインストール

apt-getで入れる。

$ sudo apt-get install git python-pip python-libvirt python-libxml2 novnc supervisor nginx 

アプリケーションのダウンロードと設定

gitで落としてきて、いくつか設定する。
$ git clone git://github.com/retspen/webvirtmgr.git
$ cd webvirtmgr

ここで本来の手順は「sudo pip install -r requirements.txt」なのだけど、パッケージ管理されないファイルが増えてしまう。requirements.txtを見ると、書いてあるライブラリがUbuntu 14.04のバージョン的にいけそうだったのでパッケージで入れることにした。
$ sudo apt-get install python-django gunicorn python-lockfile

スクリプトで初期設定をする。
$ ./manage.py syncdb
You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'ユーザ名'): 
Email address: user@example.org
Password: パスワード
Password (again): パスワード
Superuser created successfully.
$ ./manage.py collectstatic

ディレクトリをまるっと移動する。
$ sudo mkdir /var/www
$ cd ..
$ sudo mv webvirtmgr /var/www/
$ sudo chown -R www-data:www-data /var/www/webvirtmgr

バックグラウンド起動の設定

バックグラウンドで起動するようにsupervisorを設定する。
/etc/supervisor/conf.d/webvirtmgr.confに以下の内容を書く。
[program:webvirtmgr]
command=/usr/bin/python /var/www/webvirtmgr/manage.py run_gunicorn -c /var/www/webvirtmgr/conf/gunicorn.conf.py
directory=/var/www/webvirtmgr
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/webvirtmgr.log
redirect_stderr=true
user=www-data

supervisorのリスタート(これで8000/tcpで立ち上がる)
$ sudo service supervisor restart 

フロントのWebサーバの設定

フロントに立つnginxを設定する。プロキシしているだけなので、Apacheでもいいと思うが、サンプルがこうなっているのでそうした。

/etc/nginx/sites-available/webvirtmgrに以下を記述する。
server {
    listen 8008 default_server;

    server_name $hostname;
    #access_log /var/log/nginx/webvirtmgr_access_log; 

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-for $proxy_add_x_forwarded_for;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Forwarded-Proto $remote_addr;
    }
}

$ sudo ln -s /etc/nginx/sites-available/webvirtmgr /etc/nginx/sites-enabled/webvirtmgr
$ service nginx restart 

これで http://hostname:8008/ にアクセスすると画面が見えるようになる。

novncの設定

ブラウザからコンソールを使うためのnovncの設定。この設定ファイルもwebvirtmgrに入っているので、使うだけでOK.

$ cd /var/www/webvirtmgr
$ sudo cp conf/initd/webvirtmgr-novnc-ubuntu /etc/init.d/webvirtmgr-novnc
$ sudo service webvirtmgr-novnc start
$ sudo update-rc.d webvirtmgr-novnc defaults

(コメントにnova-novncproxyとか書いてあったのでOpenStack Novaから持ってきたのであろう...)

libvirtの設定

WebVirtMgrはlibvirtdにTCP接続するので、その設定をする(なので、ハイパーバイザは別ホストでもOK)
ここの設定は https://www.webvirtmgr.net/docs/ を参考にした(証明書切れてる...)。

/etc/default/libvirt-binのlibvirtd_optsの行に-lを足して、tcpでlistenするようにする。
libvirtd_opts="-d -l"

/etc/libvirt/libvirtd.conf を以下のように編集する。今回はハイパーバイザが同一ホストなので127.0.0.1でlistenさせる。
listen_tls = 0
listen_tcp = 1
listen_addr = "127.0.0.1"

libvirt用のパスワードを設定する。
$ sudo apt-get install sasl2-bin
$ sudo saslpasswd2 -a libvirt ユーザ名
Password: 
Again (for verification): 
$ sudo sasldblistusers2 -f /etc/libvirt/passwd.db 
ユーザ名@ホスト名: userPassword

libvirtdを再起動する。
$ service libvirt-bin restart

以下のようにして接続できることを確認する。

$ virsh -c qemu+tcp://127.0.0.1/system nodeinfo
Please enter your authentication name: ユーザ名@ホスト名
Please enter your password: 
CPU model:           x86_64
CPU(s):              8
CPU frequency:       800 MHz
CPU socket(s):       1
Core(s) per socket:  4
Thread(s) per core:  2
NUMA cell(s):        1
Memory size:         16318576 KiB

Web画面にログインしての設定

http://hostname:8008/ にアクセスすると、ログイン画面が表示されるので、最初の「syncdb」を実行したときのユーザ/パスワードでログインする。

あとは画面ベースなので詳細は省略するが、以下のように設定すれば使えるようになる。

  • Servers List画面で127.0.0.1を追加する。
  • Storage Pools画面で、仮想マシン用のディレクトリorLVMプールとISOを配置するディレクトリを追加する。
  • Networks画面でネットワークを追加する。
  • Instances画面でインスタンスを追加する。

感想

OpenStackをこのようにVMを管理するために使おうと検証してみたのだけど、LVMのストレージやOpen vSwitchのブリッジを自前でIDを振って管理したりしていて、トラブルがあったときに追いづらくなるデメリットのほうが大きそうだったのでやめた。一方で、このWebVirtMgrはlibvirtやLVMをそのまま見せる設計思想なので、何かあったときにも追いやすく、1台~数台の管理ならかなり向いていそうだった。

2014-07-01

GPT パーティションテーブルを別のディスクにコピーする

sdaからsdbにパーティションテーブルをコピーするには、sgdiskを使うのが簡単。コマンドを先に書くとこう。不用意に実行するとさっくり壊れるので注意。
# sgdisk -R=/dev/sdb /dev/sda
# sgdisk -G /dev/sdb
sgdisk --backup sda.table /dev/sdaとかしておいたほうが安心かも?
参考: http://askubuntu.com/questions/57908/how-can-i-quickly-copy-a-gpt-partition-scheme-from-one-hard-drive-to-another

sgdiskがないとき

GPT版のfdiskであるところのgdiskをインストールする。
# apt-get install gdisk
cfdisk, sfdiskがあるように、cgdisk, sgdiskも入ってくる。ただし、コマンドラインオプションは違うので注意。
正直デフォルトで入っていて欲しい...

コマンドの説明

前提として、こんなディスクが二つあるとする。
$ sudo sgdisk -p /dev/sda
Disk /dev/sda: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 1519615B-B82D-4CA6-9849-28F3198A90E7
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 3102653 sectors (1.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048            4095   1024.0 KiB  EF02
   2            4096        13676543   6.5 GiB     FD00

$ sudo sgdisk -p /dev/sdb
Creating new GPT entries.
Disk /dev/sdb: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 65906D3D-B672-4373-AB55-F0C473EC52ED
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 16777149 sectors (8.0 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name

ここで以下を実行することでパーティションテーブルをコピーできる。
$ sudo sgdisk -R=/dev/sdb /dev/sda
The operation has completed successfully.

これだけでコピーされているのだが、ディスクとパーティションに振られた固有IDまでコピーされてしまっている。

以下はディスク本体と2番目のパーティションの状態。
$ sudo sgdisk /dev/sda -p -i 2
Disk /dev/sda: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 1519615B-B82D-4CA6-9849-28F3198A90E7
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 3102653 sectors (1.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048            4095   1024.0 KiB  EF02
   2            4096        13676543   6.5 GiB     FD00
Partition GUID code: A19D880F-05FC-4D3B-A006-743F0F84911E (Linux RAID)
Partition unique GUID: CEA27178-6FB4-4358-B921-C75352CE33D7
First sector: 4096 (at 2.0 MiB)
Last sector: 13676543 (at 6.5 GiB)
Partition size: 13672448 sectors (6.5 GiB)
Attribute flags: 0000000000000000
Partition name: ''

$ sudo sgdisk /dev/sdb -p -i 2
Disk /dev/sdb: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): 1519615B-B82D-4CA6-9849-28F3198A90E7 ←同じ
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 3102653 sectors (1.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048            4095   1024.0 KiB  EF02
   2            4096        13676543   6.5 GiB     FD00
Partition GUID code: A19D880F-05FC-4D3B-A006-743F0F84911E (Linux RAID)
Partition unique GUID: CEA27178-6FB4-4358-B921-C75352CE33D7 ←同じ
First sector: 4096 (at 2.0 MiB)
Last sector: 13676543 (at 6.5 GiB)
Partition size: 13672448 sectors (6.5 GiB)
Attribute flags: 0000000000000000
Partition name: ''

そこで、sgdisk -Gで振りなおす。

$ sgdisk -G /dev/sdb

そうすると、IDが新しくなっていることがわかる。
$ sudo sgdisk /dev/sdb -p -i 2
Disk /dev/sdb: 16777216 sectors, 8.0 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): CCC50747-7D41-4662-9FFE-93E7EE7644A3 ←変わっている
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 16777182
Partitions will be aligned on 2048-sector boundaries
Total free space is 3102653 sectors (1.5 GiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048            4095   1024.0 KiB  EF02
   2            4096        13676543   6.5 GiB     FD00
Partition GUID code: A19D880F-05FC-4D3B-A006-743F0F84911E (Linux RAID)
Partition unique GUID: 29394950-7433-4913-8BA9-64E36B979394 ←変わっている
First sector: 4096 (at 2.0 MiB)
Last sector: 13676543 (at 6.5 GiB)
Partition size: 13672448 sectors (6.5 GiB)
Attribute flags: 0000000000000000
Partition name: ''

2014-06-14

Haswell Refreshで新しいPCを組んだ

KVMでVMをいくつもホストしている自宅のPCが壊れてしまった(といってもおそらくファンだけなのだけど)ので、ケースを捨てるのはたいへんだという理由で中身を入れ替えることにした。備忘の意味で構成を記録しておく。

CPUとHDD以外の部品は秋葉原のツクモの店員さんに相談しながら決めた。アキバのPCショップの店員さんの知識は信じられないくらいマニアックでおもしろいので、荷物は重くなるけれど、通販はしないことにしている。

CPU
Core i7 4790S (Haswell Refresh)
M/B
GIGABYTE GA-Z87X-UD3H
Memory
CFD ELIXIR W3U1600HQ-8GC11 (8GBx2)
HDD
ST3000DM001 x2
電源
Corsair HX650 CP-9020030-JP (650W)
CPU Cooler
Thermalright TRUE Spirit 120M(BW)


ATXマシンを作るのは壊れたこのマシンを作ったとき以来で、壊れたマシンを作ったのはPhenom初代なのでPhenom IIが出る2008年より前、つまり4年以上前になる。そしてずっとAMDでやってきたので、Intelに戻ってきたのはCoppermine以来、つまり10年以上前、下手すると15年くらい前。時が経つのはおそろしい…

このPCを新しいVMの親玉にすべくOpenStackを調べているのだけど、どうもそんなチープな世界を想定したプロダクトではないらしく、おそろしく仰々しい。見ていると時代の進化を追えておもしろいのだけど、たぶん本番はもっとローレベルなものになるはず。

2014-06-03

PSCmdletでもasync/awaitを使いたい

PowerShellコマンドレットをC#で作ること自体は簡単で、単にSystem.Management.Automationアセンブリを参照してCmdletかPSCmdletクラスを継承したクラスを含むクラスライブラリを作るだけなのだけど(このへんを参照→How to Write a Simple Cmdlet) うっかりasync/awaitしたあとにWriteObjectでオブジェクトを出力しようとしたりすると、以下のように怒られてしまう。

Get-SampleCmdlet : WriteObject メソッドと WriteError メソッドは、BeginProcessingメソッド、ProcessRecord メソッド、および EndProcessing メソッドの上書きの外側から呼び出すことはできず、同じスレッド内からだけ呼び出すことができます。コマンドレットで呼び出しが正しく作成されていることを確認するか、または Microsoft カスタマー サポート サービスにお問い合わせください。

Microsoft カスタマー サポート サービスにお問い合わせしたらこんなことも教えてくれるの?と思うところだけど、まぁこういう状況ではSynchronizationContextを提供してあげればよくね?ということでオレオレSynchronizationContextを準備して提供するようにしたのがこのAwaitablePSCmdletクラス

使い方は簡単で、PSCmdletクラスの代わりにこのAwaitablePSCmdletクラスを継承して、BeginProcessing/ProcessRecord/EndProcessingの代わりにAsyncをつけたBeginProcessingAsync/ProcessRecordAsync/EndProcessingAsyncを実装すればいいだけ。
例えばこんな感じ。


本当はStopProcessingにCancellationTokenまわりとか実装しないといけないのだろうけど、たぶんあとは微調整できるレベルだと思うので、必要に応じて。

2014-05-11

MSILでわかるC# のラムダ式

そういえばLINQ to Objectsでコードを書いたときに裏でどういう動きになっているのかなぁ、とILでイメージができなかったので読んでみたら、単にC#のラムダ式がどうなっているかという話だけだったのでまとめる。

長いのでサマリ:
  • ラムダ式を使うと、基本的に入れ子クラスが作られ、ラムダ式が使う変数をとっておかれる。
  • ラムダ式の中身に書いたものは、基本的に入れ子クラスのメソッドとして定義される。
  • 入れ子クラスは不要なときには作られない。

評価スタックでピンとこない方は「C#でHelloWorldプログラムを作成する」を読んでおくのをおすすめする。

今回の説明用のサンプルコードはこちら。「.Count(x => x == val)」がどうなっていくのか、というお話。
namespace ConsoleApplicationLinq
{
    class Program
    {
        static void Main(string[] args)
        {
            int val = 100;
            var format = "Count of {0}: {1}";
            
            var array = new int[0];
            var count = array.Count(x => x == val);

            System.Console.WriteLine(format, val, count);
        }
    }
}

サイズ0のint配列に100がいくつあるか調べてWriteするというコード(0に決まっている)。説明の都合、順番などが不自然な感じになっている。

このソースをILに逆アセンブルした結果がこちら。
https://gist.github.com/sunnyone/0d3ed8285b5d91495399
別ウィンドウで開きながら説明を見るといいかも。

さて、これからILを見ていく。書いた部分がコンパイルされているであろうMainメソッドの実装を見ようとすると、そのMainメソッドの前に見知らぬ「<>c__DisplayClass1」という入れ子クラスが作られていることがわかる。

そのクラスの内容はこうなっている。

.class auto ansi sealed nested private beforefieldinit '<>c__DisplayClass1'
         extends [mscorlib]System.Object
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .field public int32 val // valフィールド
    
    (ここにコンストラクタの定義があるが省略)
    
    // <Main>b__0メソッドの定義
    .method public hidebysig instance bool 
            '<Main>b__0'(int32 x) cil managed
    {
      .maxstack  8
      
      // xをロード(評価スタックにpush)
      IL_0000:  ldarg.1
      
      // valフィールドをロード
      IL_0001:  ldarg.0
      IL_0002:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
      
      // 二つの値=xとvalの値を比較し、同じなら1・異なるときは0
      IL_0007:  ceq
      
      // return
      IL_0009:  ret
    } // end of method '<>c__DisplayClass1'::'<Main>b__0'

  } // end of class '<>c__DisplayClass1'

このクラスは要は「x => x == val」の部分を<Main>b__0というメソッドに実装し、加えてvalフィールドを持っている。

次に本体のMainメソッド。Mainメソッドはこの入れ子クラスを活用して動作する。

まずメソッドとローカル変数の定義。format, array, countのほかに、先ほどの<>c__DisplayClass1が「CS$<>8__locals2」として用意されているのがわかる。valはないことに注意。
.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  3

    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count,
             [3] class ConsoleApplicationLinq.Program/'<>c__DisplayClass1' 'CS$<>8__locals2')

ここから処理開始だが、記述したC#コードに対応する部分に先立って、<>c__DisplayClass1がnewされ、ローカル変数に入る。
// <>c__DisplayClass1クラスのインスタンスを生成してローカル変数3番目にセット
    IL_0000:  newobj     instance void ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::.ctor()
    IL_0005:  stloc.3

次に「int val = 100;」の部分。ここがポイント。
C#コード上ではローカル変数に見えているが、実際には内部の<>c__DisplayClass1クラスのフィールドになっている。
入れ子になったクラスにあるラムダ式の実体がこの変数を使うためにフィールドに入れている。
// CS$<>8__locals2と「100」をロードして、CS$<>8__locals2のvalにセット
    IL_0006:  ldloc.3
    IL_0007:  ldc.i4.s   100 
    IL_0009:  stfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val

次に「var format = "Count of {0}: {1}";」の部分。このように、ラムダ式と関係ない部分はふつうのローカル変数になる。
// 説明省略
    IL_000e:  ldstr      "Count of {0}: {1}"
    IL_0013:  stloc.0

次に「var array = new int[0];」だが、ここもローカル変数に入れるだけ。
// 説明省略
    IL_0014:  ldc.i4.0
    IL_0015:  newarr     [mscorlib]System.Int32
    IL_001a:  stloc.1

次に実際のラムダ式が登場する「var count = array.Count(x => x == val);」の部分。
先ほどvalのために生成したDisplayClassのメソッドを使ってFuncオブジェクトを生成し、Countメソッドに渡している。

// ローカル変数1番目:arrayをロード
    IL_001b:  ldloc.1
    
    // <Main>b__0メソッドのポインタをロード
    IL_001c:  ldloc.3
    IL_001d:  ldftn      instance bool ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::'<Main>b__0'(int32)
    
    // <Main>b__0メソッドのポインタを使って、Funcオブジェクトを生成
    IL_0023:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
                                                                                        
    // System.Linq.Enumerable::CountメソッドにarrayとFuncオブジェクトを渡す
    IL_0028:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)

    // ローカル変数2番目にセット
    IL_002d:  stloc.2

最後に「System.Console.WriteLine(format, val, count);」の部分。
ポイントはvalを使うのにラムダ式用の<>c__DisplayClass1を使っているところ。
// ローカル変数0番目: formatをロード
    IL_002e:  ldloc.0
    
    // ローカル変数3番目: CS$<>8__locals2のvalフィールドをロード、intなのでboxingする
    IL_002f:  ldloc.3
    IL_0030:  ldfld      int32 ConsoleApplicationLinq.Program/'<>c__DisplayClass1'::val
    IL_0035:  box        [mscorlib]System.Int32
    
    // ローカル変数2番目: countをロード, boxing
    IL_003a:  ldloc.2
    IL_003b:  box        [mscorlib]System.Int32
    
    // WriteLine
    IL_0040:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0045:  ret
  } // end of method Program::Main

このように、ラムダ式は、必要な変数を入れ子クラスのインスタンスにとっておいて、ラムダ式に書いた内容のメソッドが実行される、という形で実装されている。
しかし、ラムダ式があれば必ず入れ子クラスが作られるかというとそうではなく、変数をとっておく必要がない場合、違う形にコンパイルされる。

たとえば、先のコードのint val = 100;にconstをつけてconst int val = 100;にするだけで、ILはこうなってしまう。

.class private auto ansi beforefieldinit ConsoleApplicationLinq.Program
       extends [mscorlib]System.Object
{
  // ProgramクラスそのものにFuncのフィールドが用意される
  .field private static class [mscorlib]System.Func`2<int32,bool> 'CS$<>9__CachedAnonymousMethodDelegate1'
  
  (CompilerGeneratedAttributeの部分は省略)
  
  .method private hidebysig static void  Main(string[] args) cil managed
  {
    // ローカル変数の定義などなど
    .entrypoint
    .maxstack  3
    .locals init ([0] string format,
             [1] int32[] 'array',
             [2] int32 count)
    IL_0000:  ldstr      "Count of {0}: {1}"
    IL_0005:  stloc.0
    IL_0006:  ldc.i4.0
    IL_0007:  newarr     [mscorlib]System.Int32
    IL_000c:  stloc.1
    IL_000d:  ldloc.1
    
    // CS$<>9__CachedAnonymousMethodDelegate1をロードして、存在すればこの先の処理まで飛ばす
    IL_000e:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0013:  brtrue.s   IL_0026

    // なければProgramクラスに定義された<Main>b__0メソッド(「x => x == val」の実装)を使ってFuncオブジェクトを作る
    IL_0015:  ldnull
    IL_0016:  ldftn      bool ConsoleApplicationLinq.Program::'<Main>b__0'(int32)
    IL_001c:  newobj     instance void class [mscorlib]System.Func`2::.ctor(object,
                                                                                        native int)
    // 作ったらフィールドに格納して、ロードしておく
    IL_0021:  stsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    IL_0026:  ldsfld     class [mscorlib]System.Func`2 ConsoleApplicationLinq.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
    
    // 以下同じ
    IL_002b:  call       int32 [System.Core]System.Linq.Enumerable::Count(class [mscorlib]System.Collections.Generic.IEnumerable`1,
                                                                                 class [mscorlib]System.Func`2)
    IL_0030:  stloc.2
    IL_0031:  ldloc.0
    IL_0032:  ldc.i4.s   100
    IL_0034:  box        [mscorlib]System.Int32
    IL_0039:  ldloc.2
    IL_003a:  box        [mscorlib]System.Int32
    IL_003f:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object,
                                                                  object)
    IL_0044:  ret
  } // end of method Program::Main

  (コンストラクタの定義は省略)
  
  .method private hidebysig static bool  '<Main>b__0'(int32 x) cil managed
  {
    (CompilerGeneratedAttributeの部分は省略)
    
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldc.i4.s   100 // 「100」はここに埋め込まれるので、フィールドから取る必要がない
    IL_0003:  ceq
    IL_0005:  ret
  } // end of method Program::'<Main>b__0'

} // end of class ConsoleApplicationLinq.Program

こっちのコードだとご丁寧にもFuncオブジェクトをstaticフィールドにキャッシュしている。

ちなみに、valをローカル変数ではなくフィールドに持つようなクラスを作った場合も上述のconstの形に近くなり、入れ子クラスは作られない(Funcオブジェクトのキャッシュはしなくなる)。

まぁ、この差異はプログラム全体からしたらたいしたことはないと思うので、書くときに意識することはないと思うが、知っておいても悪くない…かな?
(一応、array.Count(x => x == val)を10000000回実行したら、valがconst/フィールド/ローカル変数それぞれの場合でStopwatchクラス読みで240ms/240ms/300msだった)

こうしてILコードを見ていると、ラムダ式がどういうものなのかしっくりきたのだけど、みなさまはどうだろうか?

2014-04-24

MSBuild Launcher 0.2.3 リリース

MSBuild Launcher 0.2.3をリリースしました。

今回の大きな変更点の一つ目はパフォーマンス改善です。生産性が上がるというレベルの変化なのでぜひ上げてください。処理待ちよりもログ出力待ちのほうが多いなんてビルドをされている方には、体感でわかるレベルで違うはずです。PowerShellで適当に出力して確認していたら、2倍ぐらい速くなっていました。


確認に使っていたビルドファイル:


もうひとつはキャンセルをするときに再帰的にプロセスを停止するようにしたことです。今まではエンジン用プロセスだけ停止していたので、Execで起動するようなケースではプロセスが残ってしまったりしたのですが、子供から殺すのでいなくなるはずです。

2014-01-04

Jenkins入門 (LibreOfficeをビルドするまで)

新年あけましておめでとうございます。今年もよろしくおねがいします。
今年はJenkinsさんの記事でスタートです。この記事は主にLibreOfficeの開発者や翻訳者に向けて作られています…が、Jenkinsの部分はLibreOfficeに限った話ではないので、なんでも使えます。

Jenkinsを使いはじめる上で一番難しいのは、機能や手順を調べたりすることではなく「どうやって使ってみたらいいのか」を認識することだと思っているので、その点について掴んでもらえれば、この記事の目指すところはおしまいです。

環境はUbuntu(記事を書いたときに試したのは12.04)を想定していますが、Windowsでもインストーラがあるし、ほとんどがWeb UIでの操作なので読みかえは容易だと思います。

Jenkinsとは

JenkinsはCI, Continuous Integration「継続的インテグレーション」をするためのツールです(以前は Hudson という名前でした)。聞いたことない人にとっては、なんか恐そうな感じですが、Jenkinsに関してはだいたい「Web UIのついたすごいcron」だと思ってもらっていいです。まぁそれじゃ本筋じゃないので一応書いとくと、開発者が独自に個別に部品を作ってどんどん進んじゃうと、いよいよ結合だーバーンってやったらそりゃあひどいことになるよね、よくないよねと。結合するフェーズがやってきたらくっつけるんじゃなくて、毎日とか、数時間ごととか、コミットごととかに、ビルドしたりテストかけたりして、継続的に合体できて動くことを確認しましょうね、っていうのを実現するためのツールです。

なので、本当はもっといろいろなことができるわけですが、今回はそのJenkinsのビルド(しかも手動)という側面だけとりあげて書きます。


今回の構成

今回の構成を絵にすると以下の通りです。upstream以外、すべてローカルPCを想定しています。


upstreamのgit repositryから、pullしてきて、そこでなんだかんだ作業します。そのなんだかんだ作業したローカルのレポジトリをJenkinsさんにcloneしてもらって、ビルドして成果物を作ってもらいます。

(ここは翻訳の確認のためのビルドという使い方を想定しているので、普通のCI環境とは違うところだと思います。"upstream"にあたる共有の作業レポジトリを定期的にビルドしてもらうというのがCIという意味では多いと思います。)



手順

LibreOfficeを普通にビルドできるようにする

まずはビルドする環境で、Jenkinsなしで対象としたいプロダクトがビルドできるように準備します。LibreOfficeをUbuntuでビルドするケースでは簡単です。ほとんどWikiの「How_to_Build」や「How_to_Build/localized」に書かれている通りにするだけ。

sudo apt-get build-dep libreoffice
sudo apt-get install translate-toolkit dos2unix

これでビルド環境の準備はOK. あとは、絵の中段の作業レポジトリをcloneして作ります。今回の作業対象となる「libreoffice-4-1」ブランチをcheckoutしておきます。
(話を簡単にするためothersも読めるホームディレクトリ直下にcheckoutすることを想定していますが、(あとで作られる)jenkinsユーザも読めるディレクトリであればどこでもいいです。)
git clone git://anongit.freedesktop.org/libreoffice/core libo
cd libo
git checkout libreoffice-4-1

念の為、普通にビルドできることも確認しましょう。
./autogen.sh --with-lang="ja"
make

instdir/program/sofficeから起動できましたね?

Jenkinsをインストールする


さて本題のJenkinsです。こちらも簡単で、手順JenkinsのWikiの「Installing Jenkins on Ubuntu」に書かれているので、その通りやればOK. apt-lineが用意されているので、ちょちょいと追加してapt-get installするだけです。

wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins

おわり。あとは/etc/init.d/jenkins startで上がってくるので、
http://127.0.0.1:8080/
にブラウザからアクセスすれば画面を見ることができます。

が、LibreOfficeはレポジトリがでかいので、gitのcloneがローカル内なのにデフォルトタイムアウトの10分を越えてしまいました(VMのせいかな?)。なので、startする前に以下の通り/etc/default/jenkinsファイルを修正して、タイムアウトを延長しておくのをおすすめします。
JAVA_ARGS="-Dorg.jenkinsci.plugins.gitclient.Git.timeOut=120"
「O」は大文字なので気をつけてください。
参考:https://issues.jenkins-ci.org/browse/JENKINS-20387

Jenkinsを設定する(Gitプラグインの追加)

Jenkinsを設定するために、まずはトップ画面(http://127.0.0.1:8080/)を表示します。


「新しいジョブを作成してください」と言っていますが、デフォルトではGitが使えないので、まずはプラグインを入れます。

 プラグインを追加するには、画面左の「Jenkinsの管理」を表示したあと、「プラグインの管理」をクリックします。
 (ただ、セキュリティを設定の警告が出ている通り、現在のJenkinsは誰でもアクセスできてしまう危険な状態なので、自分だけしかいないネットワークじゃなければ、プラグインを入れる前に、セキュリティを設定をクリックして設定しておくのがいいと思います。ここでは割愛します。)

そこで利用可能のタブを選ぶと、使えるプラグインの一覧が出てきます。その中に「Git Plugin」があるので、チェックして「ダウンロードして再起動後にインストール」をクリックします。


するとインストールがはじまるので、「インストール完了後、ジョブがなければJenkinsを再起動する」にチェックを入れれば、Jenkinsが再起動されてプラグインのインストールは完了します。

Jenkinsの設定(ジョブの追加)

いよいよ、ジョブを追加していきます。ジョブを追加するには、左側の「新規ジョブ作成」をクリックします。そうすると、入力画面がでてくるので、ここでは「Build-LibreOffice」などの好きなジョブ名を入力し、「フリースタイル・プロジェクトのビルド」を選択して「OK」をクリックします。

 そうすると、ジョブの設定画面がでてきます。名称などの基本的な設定が一番上にあり、その次にでてくるのが「ソースコード管理」です。

これはなにかというと「成果物を作成するためのソースをどこからもってくるのか」という設定です。「Git」のラジオボタンを選択して、レポジトリの場所を「Repositry URL」に、ビルドしたいブランチを「Branches to build」に入力します。さきほどの下準備通りであれば場所は「/home/USERNAME/libo」、ブランチは「libreoffice-4-1」みたいな感じになっているでしょう。もちろん「git://anongit.freedesktop.org/libreoffice/core」にしても、ビルドはできます。


スクリーンショットでは選択していませんが、Additional Behavioursで「Clean after checkout」を選んでおくほうがいいかもしれません。

次はこのジョブの肝、 ビルド処理とビルド後の処理を設定します。まずは「ビルド手順の追加」をクリックし「シェルの実行」を選ぶと、「シェルスクリプト」の入力欄がでてきます。ここに、プロダクトをビルドするコマンドを入力します。

ただし、成果物をあとで取得しやすくするために、tarでまとめるコマンドを追加しています。

./autogen.sh --with-lang="ja"
make
tar zcf libreoffice.tar.gz instdir

あとは「ビルド後の処理の追加」をクリックし「成果物を保存」を選ぶと、「保存するファイル」欄がでてきます。これはなにかというと、ビルドの結果としてJenkinsが残しておきますというファイルです(Webからダウンロードできます)。*とかも使えますが、ここでは先程tarした「libreoffice.tar.gz」を入力します。

あとは保存を押せば設定終了です。

Jenkinsジョブをビルドする


ビルドする方法はWeb画面からでもいくつかあるのですが、普段一番触ると思われるトップからやってみます。

トップにジョブの一覧があるので、右側にある時計のマークをクリックするとビルドがはじまります。


あとは左下にある「ビルド実行状態」にビルド中表示が出るので、ここの#ビルド番号 のリンクをクリックすると、ビルド状況がわかります(「コンソール出力」でビルドログをリアルタイムに表示)。今、上の画面で灰色になっている○が、青くなれば成功です!赤くなったら失敗です…


完了すると、ジョブの画面の「最新成功ビルドの成果物」からlibreoffice.tar.gzをダウンロードすることができます。


あとは何度もビルド実行をすれば、がんばってビルドしてくれます。


使い方はイメージつきましたでしょうか?本当は、コミットを検知してビルドを開始したり、ビルドが終わったらメールを送ったりすることができますが、たぶんここまでこれればあとは検索で戦えるでしょう。

自分が開発しなくても、upstreamを追いかけるなどで何度もビルドするような方は便利だと思います。作業時間を覚えてて推定残り時間をプログレスバーに出してくれたり、ログを残しておいたりしてくれるので、単なるバッチ管理に使うのも便利です。


Tips - どこでビルドされているのか?


成果物にしていなかったものでも、前回ビルド時のファイル群を見てみたいことがあります。そんなときは、/var/lib/jenkins/jobs/{ジョブ名}/workspaceを見に行くと便利です。ここで作業が行なわれています。