いこーよの記事をAMP化しました

2016年09月12日
区分
HTML
報告者:
akiyama

こんにちは、akiyamaです。

先日、いこーよの記事をAMP(Accelerated Mobile Pages)化しました。 AMP化にあたりいくつか知見が得られましたのでご紹介します。

AMPとは

AMPはAMP Projectで策定されているモバイルページ高速化の仕組みです。 AMP HTML仕様に沿ったページを作成することによって、モバイル端末での表示パフォーマンスの高速化が期待できます。

AMPの仕様は日本語の情報がSEO関係のページに出回っています。 わりと古めの情報などもありますので、ちゃんとampprojectの一次情報を参照するほうが確実です。

ampbyexampleは実際に動くものが書かれているので便利でした。

AMP化

通常のHTMLをAMP化するためには、次のものが必要です。

  1. <!doctype html>で開始
  2. htmlタグを<html amp>にする
  3. headタグの最初の子タグに <meta charset="utf-8">を書く
  4. <link rel="canonical" href="" />で元記事のURLを指定する
  5. viewportを定義: <meta name="viewport" content="width=device-width,minimum-scale=1">
  6. AMP用JSを読み込み: <script async src="https://cdn.ampproject.org/v0.js"></script>
  7. head内にスタイルを定義
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>

また、AMP規格ではありませんが、google用に構造化データをhead内に書くことが多いと思います。 いこーよの記事ではJSON-LDで記載しています。この内容はページに合わせて変える必要があります。

<script type="application/ld+json">
  {
    "@context":"http://schema.org",
    "@type":"NewsArticle",
    "mainEntityOfPage":"http://iko-yo.net/articles/1671",
    "headline":"親子向け体験コンサート",
    "datePublished":"2016-09-12T03:24:24Z",
    "dateModified":"2016-09-12T03:24:24Z",
    "description":"開館30周年を迎えた...",
    "author":{"@type":"Person","name":"坪内 智子"},
    "publisher":{"@type":"Organization","name":"子供とお出かけ情報「いこーよ」",
    "logo":{
      "@type":"ImageObject",
      "url":"http://iko-yo.net/images/iko-yo_logo_290_50.png",
      "width":290,
      "height":50
    }
  },
  "image":{
    "@type":"ImageObject",
    "url":"http://d2goguvysdoarq.cloudfront.net/system/article_assets/attachments/1812/original.jpg?1473650664",
    "width":640,
    "height":426
  }
}
</script>

JSON-LDで指定するlogoimage(サムネイル画像)にはガイドラインがあります。これについては次の画像の説明で触れます。

これでAMPの枠組みが出来ましたので、body内で通常HTMLから変更が必要な箇所についてやったことなどを書いていきます。

(AMP拡張タグに必要なjavascriptファイルの読み込みなどは冗長なので省略しています)

画像

AMPではimgタグをamp-imgに置き換えたうえ、属性に画像サイズを必ず指定しなければなりません。

いこーよの記事画像はサイズを持っていないため、そのままではAMP化できませんでした。

AMPページをレンダリングするたびに画像サイズを取得するのは、 サイトの負荷を考えると避けたいと考えたため、 今回は記事作成時に画像サイズを保存するように改修しました。

(そのため、過去に作成された記事のAMP化は見送っています)

記事に使われる画像は、内部でアップロードされるものと、外部画像をそのまま使用するものがあります。

外部画像サイズの取得は、アップロード時にJavaScript(CoffeeScript)からImageオブジェクトを作成して取得、

geometry: (src)->
  img = new Image
  img.src = src
  width = img.width
  height = img.height
  ...

内部画像はpaperclipでリサイズされるため、paperclip-metaを使用しています。

サムネイル

AMP化したページはgoogleの検索結果に表示されることがあります。 サムネイルとして表示される画像は、JSON-LDで指定するimageが対応します。

"image": {
  "@type": "ImageObject",
  "url": "https://example.com/thumbnail1.jpg",
  "height": 800,
  "width": 800
},

その際表示される画像は横幅696px以上でなければならないとあります。

The representative image of the article. Only a marked-up image that directly belongs to the article should be specified.

Images should be at least 696 pixels wide. Images should be in .jpg, .png, or. gif format. Image URLs should be crawlable and indexable.

https://developers.google.com/search/docs/data-types/articles#type_definitions

いこーよの場合、サムネイル画像がサイズ要件を満たさないことがあるため、そのような記事ではgoogle検索結果への表示は期待できないかもしれません。

またロゴについても上記ページ内でガイドラインがあります。これは長くなりますのでガイドラインを参照ください。

iframe

いこーよの記事では埋め込みコンテンツとして、google map, youtube, instagramを使用しています。 (instagramはiframe src='http://instagram.com/p/.../embed'形式)

このうち、google map, youtubeはiframeをそのままamp-iframeに置き換えています。 amp-youtubeなどの専用の拡張タグがありますが、実装上AMPではない記事本文を流用しやすいamp-iframeを採用しました。

ただし、google map, youtubeを正常動作させるためには、sandbox="allow-scripts allow-same-origin"の指定が必要でした。 この指定ではsandboxがあまり意味を成さないため、専用タグの方がやはり適切かもしれません。

また、amp-iframeはplaceholderとしてamp-imgを子要素にもつ必要があります。

instagram

当初はinstagramもiframeの使用を考えていましたが、amp-iframeで埋め込んだ場合、一部リンクが開けなくなってしまいました。 そのためinstagramはamp-instagram拡張タグを使用して埋め込んでいます。

instagramのframeサイズは画像サイズに依存するため、正方形と長方形の画像で異なります。 正方形ならwidth="1" height="1"と適当な固定サイズの指定が可能ですが、正方形以外はきちんとサイズを指定しないといけません。

https://ampbyexample.com/components/amp-instagram/

SNS

twitter, facebookなどSNSの共有ボタンはamp-social-shareで作成可能です。

他のAMPサイトを見るとamp-social-shareを使用しているケースはあまりなく、aタグを使っていることが多かったです。 今回はAMPらしくamp-social-shareを使用して実装しました。

twitter, facebookはpre-configured providersに含まれるので、そのまま使用できます。 (facebookはdata-param-app_idの設定が必要です)

設定されていないSNSを使用する場合は、data-share-endpointを使用して設定します。

lineの場合、data-share-endpoint='http://line.me/R/msg/text/?{パーセントエンコードされたテキスト}'と指定することでシェアボタンが使えるようになります。

endpointにline://msg/text/のようなプロトコルを指定すると、後述するバリデーターに怒られます。 使用できるプロトコルはホワイトリスト化されているものだけになります。

name: "data-share-endpoint"
value_url: {
  allowed_protocol: "ftp"
  allowed_protocol: "http"
  allowed_protocol: "https"
  allowed_protocol: "mailto"
  # Whitelisting additional commonly observed third party
  # protocols which should be safe
  allowed_protocol: "fb-messenger"
  allowed_protocol: "skype"
  allowed_protocol: "snapchat"
  allowed_protocol: "sms"
  allowed_protocol: "tel"
  allowed_protocol: "viber"
  allowed_protocol: "whatsapp"
  allow_relative: false
}

https://github.com/ampproject/amphtml/blob/master/extensions/amp-social-share/0.1/validator-amp-social-share.protoascii

広告

いまいち安定せず、出なかったりすることがあります。 広告業者によっても違うようです。 これについてはまだ調査中です。

javascript

AMPページ内では基本的にjavascriptが使用できません。 いこーよの記事内ではそれほどjavascriptを使用していなかったため大きな問題にはなりませんでした。

動的なコンテンツに対応する場合はamp-listなどの拡張タグを使用する必要があります。 現時点では動的なAMPページをつくるメリットはあまり無いと思います。

バリデーション

作ったAMPページが正しいかチェックするためのバリデーションには https://www.ampproject.org/ja/docs/guides/validate.html で紹介されているブラウザ拡張が便利でした。 バリデーションだけでなく、<link href="/articles/1666/amp" rel="amphtml">とAMPページヘのリンクがあるページでは、機能拡張ボタンからAMPページヘ移動できます。

まとめ

いこーよの記事ページのAMP化を行いました。

特に画像やiframeのサイズ周りの対応に気を使う必要がありました。

それ以外の部分ではあまり手もかからず通常HTMから変換が可能でした。

AMPページの制限は大きいので、あまり凝ったことはせずに、 まずはシンプルなAMPページ化していくのが良いのではないかと思います。

いこーよ開発者のフォントとカラースキーム

2016年05月24日
区分
開発環境
報告者:
akiyama

こんにちは、akiyamaです。 今日はいこーよ開発者のフォントとカラースキームを紹介したいと思います。


akiyama

  • フォント
    • 源ノ角ゴシック Code JP
  • カラースキーム
    • Novel


tahara

フォント

!! アンチエイリアスを使いたくない場合
Emacs.FontBackend: x
Emacs.font: -mplus-*-medium-r-*--14-*-*-*-*-*-*-*

カラースキーム(?)

(set-foreground-color "lightgray")
(set-background-color "black")
(set-cursor-color "yellow")
(set-mouse-color "yellow")


chiba

Emacs

フォント

CPT Font(1970-80年代のMIT Lispマシンの標準フォント) 日本語部分はTerminus-jaを組合せて使っています。

適当にこの色に設定

LispWorks

フォント

たまたまこのフォントになっている

GTk 2.0のMonaというテーマ


honda

  • Xcode
    • フォント
      • menlo regular
    • カラー
      • Dusk
    • イメージ

endo

  • vim
    • フォント
      • Ricty
    • カラースキーム
      • solarized
    • イメージ
  • Atom
    • フォント
      • Ricty
    • カラースキーム
    • 標準
    • イメージ

namikata

  • フォント
    • Mac 標準
  • カラースキーム
    • Monocai


社内エンジニア勉強会 ruby拡張ライブラリ

2016年05月12日
区分
勉強会
報告者:
akiyama

こんにちは、akiyamaです。

社内勉強会担当が回ってきました。

弊社で使用されている言語はrubyが主流なので、 今回はruby拡張ライブラリの書き方について発表しました。 ついでなのでcrystalで書きました。

サンプルとして竹内関数を拡張ライブラリ化しました。

(発表時点でcrsytalのバージョンは0.16.0です。 ここに書かれていることは、将来のバージョンでは使用できなくなる可能性があります)

以下、コードと解説です。

def tarai(x, y, z)
  if x <= y
    y
  else
    tarai(tarai(x - 1, y, z),
          tarai(y - 1, z, x),
          tarai(z - 1, x, y))
  end
end

lib Ruby
  $rb_cObject: Void*
  fun rb_define_class(name: LibC::Char*, value: Void*): Void*
  fun rb_define_method(klass: Void*,
                       name: LibC::Char*,
                       func: LibC::Int, LibC::ULong*, Void* -> LibC::ULong,
                       argc: LibC::Int)
  fun rb_fix2int(value: LibC::ULong) : LibC::Long
end

fun ext_tarai(argc: LibC::Int, args: LibC::ULong*, rb_self: Void*) : LibC::ULong
  x = Ruby.rb_fix2int(args[0])
  y = Ruby.rb_fix2int(args[1])
  z = Ruby.rb_fix2int(args[2])
  n = tarai(x, y, z).to_u64
  n << 1 | 0x01
end

fun init = Init_rubyext : Void
  GC.init
  LibCrystalMain.__crystal_main(0, Pointer(Pointer(UInt8)).null)
  klass = Ruby.rb_define_class("RubyExtCrystal", Ruby.rb_cObject)
  Ruby.rb_define_method(klass, "tarai", ->ext_tarai, -1)
end

lib宣言

呼びたいCの関数やグローバル変数をここで宣言する

fun rb_fix2int(value: LibC::ULong) : LibC::Long

は、Cで言うと

extern long rb_fix2int(unsigned long);

と同じ

ext_tarai

ruby -> cブリッジ部分

  • rubyから渡ってきた引数(VALUE)を整数値へと変換する
    • VALUEはRubyの世界で値を意味する型(ruby/ruby.h)
    • typedef uintptr_t VALUE; マシンワードサイズ
    • rubyはsizeof(void*) == sizeof(long) or sizeof(LONG_LONG)の環境でしか動かない
    • crystal内ではULong,Void*として取り扱う
  • tarai関数を呼び出す
    • 次のリターン用に型変換
  • 結果をFIXNUMに変換してリターン
    • INT2NUMなどのCマクロが使用できないので手動変換
    • rubyのFIXNUMは数値を1bit左シフトして最下位ビットを1にしたもの
    • いわゆるタグ付き
    • C:0b0011 (3) -> ruby:0b0111 (7)

Init_rubyext

rubyからload時に呼ばれる初期化関数

  • crystalのGCを初期化してcrystal初期化関数を呼ぶ(呼ばないとSEGV)
  • rb_define_classでrubyの世界にRubyExtCrystalクラスを定義する
  • RubyExtCrystalにtaraiメソッドを定義する
    • ext_taraiをProc化してコールバック関数として登録する
    • 可変長関数を扱うのが面倒なのでarity -1指定して自分で展開する
      • argc, argsに引数が入る

build

crystal build --release --single-module --link-flags="-dynamic -bundle -lruby" -o rubyext.bundle rubyext.cr

rubyから呼び出してベンチマーク

require 'benchmark'
require_relative './rubyext'
ext = RubyExtCrystal.new
def tarai(x, y, z)
  if x <= y
    y
  else
    tarai(tarai(x - 1, y, z),
          tarai(y - 1, z, x),
          tarai(z - 1, x, y))
  end
end
x,y,z = ARGV.map(&:to_i)
Benchmark.bm 10 do |b|
  b.report 'ruby' do
    tarai(x, y, z)
  end
  b.report 'crystal' do
    ext.tarai(x, y, z)
  end
end
$ ruby tarai.rb 18 10 5
                 user     system      total        real
ruby         3.990000   0.010000   4.000000 (  3.999338)
crystal      0.130000   0.000000   0.130000 (  0.132147)

同等のコードをC言語で書く

#include <stdio.h>
#include <stdlib.h>
int tarai(int x, int y, int z)
{
  if (x <= y) {
    return y;
  } else {
    return tarai(
        tarai(x - 1, y, z),
        tarai(y - 1, z, x),
        tarai(z - 1, x, y));
  }
}
int main(int argc, char** argv)
{
  int x, y, z;
  x = atoi(argv[1]);
  y = atoi(argv[2]);
  z = atoi(argv[3]);
  printf("%d %d %d\n", x, y, z);
  printf("%d\n", tarai(x, y, z));
  retturn 0;
}
$ clang -O2 tarai.c
$ time ./a.out 18 10 5
18 10 5
18
./a.out 18 10 5  0.14s user 0.00s system 96% cpu 0.146 total

ほぼ、おんなじでした

アクトインディ技師部隊報告書引越し

2016年04月18日
区分
システム管理
報告者:
akiyama

こんにちは、akiyamaです。

本日、アクトインディ技師部隊報告書をgithub pagesとjekyllに移行しました。

今回はなぜ移行したかと、移行作業について書きます。

・経緯

いままでのアクトインディ技師部隊報告書はCommonLisp で作られており、サーバーはunpyo、 データストアはlepisとなっています。

とてもおもしろい構成なのですが、その構成ゆえにメンテナンスコストが 特定の人にかかりがちになってしまう問題がありました。 そのことがエンジニアチームで行っている振り返りで話題となり、 今後の運用コストを下げるために別システムへ移行することになりました。

github pagesを選んだのは以下の理由からです。

  • サーバーの管理が不要
  • markdwonで書ける
  • gitで記事が管理できる
  • このブログのアイデンティティであるデザインを引き継げそう

なるべく手をかけずに管理でき、移行もプログラマブルに出来るという点を評価し決定しました。

またgithub pagesでホスティングするため、jekyll自体も出来る限り生の状態で使用することにしました。

・移行作業

次に今回の移行で対応が必要だった点を書いていきます。

・デザイン

ある種の人たちから絶賛を浴びているデザインをそのまま引き継ぐことが大前提でした。

元のシステムのソースを元に、_layout, _includeなどのファイルへ構造を移す作業でした。 ここが一番面倒だったと思います。

画像などはpublicフォルダへコピーするだけだったので簡単でした。

移行作業中にどちらの表示を見ているのかわからなくなることがあり、多少混乱しましたが、 ほぼコピーできたと思います。

・過去記事

元のシステムは先にも書きましたがCommonLisp製でデータも独自形式なので、 そのままでは扱いづらいのため、CLからJSONで出力します。

本番サーバーにデータのダンプがあるのでそれをコピーしてきます。 https://github.com/actindi-dev/tech-blog をローカルで動かし、 データベースからJSONで引っこ抜きます。

(defun to-h (post)
 (let ((h (make-hash-table)))
  (setf (gethash "title" h) (post-title post))
  (setf (gethash "author" h) (post-author post))
  (setf (gethash "date" h) (post-date post))
  (setf (gethash "category" h) (post-category post))
  (setf (gethash "body" h) (post-body post))
  h))

(defun export-json ()
  (unless *db*
    (setf *db* (open-db (merge-pathnames "lepis/" *default-directory*))))
  (setf posts
    (loop for post in (zrang :entries 0 nil :from-end t) collect (to-h post)))
  (with-open-file (out "/tmp/conv.json" :direction :output :if-does-not-exist :create :if-exists :overwrite)
    (format out "~A~%" (json:encode-json-to-string posts))))

出力したJSONをmarkdownに変換します。記事の数もありますので変換スクリプトをrubyで書きます。

require 'json'

def decode_universal_time(n)
  Time.at(Time.utc(1900,1,1).to_i + n)
  end

json = JSON.parse(open('/tmp/conv.json').read)

json.each do |post|
  date = decode_universal_time(post['date'])
  fname = "_posts/#{date.strftime('%Y-%m-%d')}-#{post['date']}.markdown"
  open(fname, 'w') do |out|
    out.puts('---')
    out.puts('layout: post')
    out.puts("date: #{date.to_s}")
    out.puts("title: #{post['title']}")
    out.puts("author: #{post['author']}")
    out.puts("categories: #{post['category']}")
    out.puts("permalink: /:slug")
    out.puts('---')
    out.puts(post['body'].encode(universal_newline: true))
  end
end

気をつける点として、外部から過去記事にはられたリンクがあるので、permalinkは維持する必要がありました。

以前のシステムではpermalinkは投稿されたUniversal time になっています。 記事のUniversal timeを permalink: /:slug でpermalinkに指定すれば期待したpermalinkを出力してくれます。

あとはこのスクリプトでmarkdownを生成すれば過去記事の移行は完了です。

・投稿者ページ

そのほかに対応が必要だったのは投稿者ごとのページです。 こちらの 回答を参考に投稿者ごとのStatic pageを作りました。

・paginate

jekyll3ではjekyll-paginateがgemに分離されたため、標準ではpaginateできません。 なるべく追加gemは使用しない方針でしたが、さすがにpaginateだけはインストールして使用しています。

・シンタックスハイライト

待望のシンタックスハイライト対応です。 (いままではシンタックスハイライトしたhtmlをvimなどで出力して書いていました)

_config.ymlに設定を追加して使用しています。

kramdown:
  input: GFM
  syntax_highlighter: rouge

現在は syntax.css のCSSを使用していまが、 ちょっと色味がきつい気がするので調整したいです。

・やっていないこと

移行しなかった部分としてはアクセスカウンターと日付の年号表記です。 年号はこのブログの雰囲気を醸し出すパーツとしては重要なのですが、 ちょっと手間が掛かりそうなので今回見送りました。

・懸念

記事ファイルが_posts/にフラットな状態で並んでしまうので、 この先件数が増えた時に管理しづらくなりそうな気がしています。

・まとめ

CommonLisp製ブログシステムからgithub pagesに移行しました。 1日程度の作業で移行できるgithub pagesはとても便利です。 後々を考えると細かいところはあまりこだわらずjekyllのデフォルトに従うほうが 将来的には楽になりそうです。

しばらく運用してみてチームメンバーの感想も聞いてみたいと思います。

google formsへ自動投稿

2016年02月05日
区分
googleforms
報告者:
akiyama

こんにちは、akiyamaです。

google formsはさくっとformを作ることができて楽なのですが、回答するためにはブラウザから送信しないといけないのが面倒です。
ということで、さっそくCLIから投稿しましょう。

まず該当のformを調べます。
新しくなったformsでは入力項目の後ろに

<input type="hidden" name="entry.1185209320" jsname="L9xHkb">
のような箇所があります。
以前のformsの場合はinputにそのままキーがあります。
entry.nnnnnnnnの部分がリクエストパラメーターのキーになります。

キーがわかればあとは送信するだけです。
リクエストを送るURLの最後のviewformとなっている箇所をformResponseへ変更します。

curl -sSf https://docs.google.com/xxxx/formResponse -d ifq --data-urlencode 'entry.1185209320=test' --data-urlencode "submit=Submit"

これで投稿できるようになったので、あとはメールやslackと連携できます。いろいろ楽になりそうです。Happy Hacking!

買うことが目的

2016年01月22日
区分
技術書
報告者:
akiyama

こんにちは、akiyamaです。

slack導入以来、いろいろな通知や連携ができて便利さを実感しています。
個人的に助かってるのが技術書の新刊情報や安売り情報RSSでslackのroomに流せること です。
一人でRSSリーダーで読むよりチャットしながら買う方が積むときの罪悪感がなぜか少くなく感じます。
みんなで積めば怖くないということでしょうか。

購読しているRSSはこんなかんじです。

  • O'Reilly Japan - New & Upcomming http://www.oreilly.co.jp/catalog/soon.xml
  • O'Reilly Japan Ebook Store - New Release http://www.oreilly.co.jp/ebook/new_release.atom
  • O'Reilly Deal of the Day http://feeds.feedburner.com/oreilly/ebookdealoftheday
  • O'Reilly Media, Inc. New Titles http://feeds.feedburner.com/oreilly/newbooks
  • O'Reilly Media, Inc. Upcoming Titles http://feeds.feedburner.com/oreilly/upcomingbooks
  • オーム社eBook Storeのブログ http://ohmshaebookstore.hatenablog.com/feed
  • 達人出版会日記 http://d.hatena.ne.jp/tatsu-zine/rss
  • The Leanpub Blog http://feeds.feedburner.com/leanpub
  • No Starch Press - New Books https://www.nostarch.com/feeds/newbooks.xml
  • No Starch Press - Coming Soo https://www.nostarch.com/feeds/comingsoon.xml
  • Packt Publishing https://www.packtpub.com/rss.xml
  • Apress.com eBook Daily Deal http://www.apress.com/index.php/dailydeals/index/rss


日本の出版社はあんまり機械可読な情報がないし、発信頻度も低いのでちょっと残念です。

海外は積極的な値引きが多くて嬉しいです。この前も年末に発売されたばかりの Common Lisp Recipesのebookが40%offになっていて、買ったばかりのchibaがショックを受けていました。
apressはDaily Dealが$10なのでついつい積んでしまいます。

カバーできてない技術書関係のRSSなどありましたら、ぜひ教えて下さい。

KLICのビルド

2015年12月10日
区分
klic
報告者:
akiyama

2016/9/13追記

version 3.01が出ていました。この記事は不要になりました。 http://www.ueda.info.waseda.ac.jp/software-j.html


こんにちは、akiyama です。

アクトインディ Advent Calendar 2015 の10日目の記事です。

弊社のchibaからGHCの本を借りたので、実際に動かしてみたいと思いKLICをビルドしました。
ビルド環境はMacOSX 10.10.5 Apple LLVM version 7.0.0 (clang-700.1.76)です。

(余談ですがKLICをなぜかKL1Cだと読み待ちがえたまま覚えていて、 口頭でKLICと聞いたときに同じものだと認識できていませんでした)

まず、 http://www.ueda.info.waseda.ac.jp/~kaneki/klic/klic_top.html より klic-3.005-extio-shared.tar.gzをダウンロードします。
展開してディレクトリに移動するとConfigureという実行可能ファイルがあるので、これを実行してみます。

# This is version of KLIC contains its sequential core,
# a distributed parallel implementation and a shared memory parallel
# implementation that works either on Sparc-based system with Solaris
# 2.X or Alpha-based system with DEC OSF/1 with gcc

などと歴史を感じるメッセージが表示され感慨深いものがあります。

そのまま質問に答えていくと、次のメッセージとともにアボートします。

!!! Couldn't locate libc.a in directories  /usr/lib /usr/local/lib !!!
Aborting KLIC configuration script

Configureを読んでみるとこのエラーはlibcを見つけられなかったりnmで抽出したシンボルの検出に失敗して出ているようなのでConfigureを修正します。

diff --git a/Configure b/Configure
index 7fc15e5..e43babd 100755
--- a/Configure
+++ b/Configure
@@ -799,7 +799,7 @@ esac
 KLICLDFLAGS="$KLICLDFLAGS $XLDFLAGS"

 LIBPATH=""
-for dir in /lib /usr/lib /usr/local/lib; do
+for dir in /lib /usr/lib /usr/local/lib /usr/lib/system; do
     if test -d $dir; then
  LIBPATH="$LIBPATH $dir"
     fi
@@ -829,7 +829,7 @@ echo ' '

 LIBNAMES=""
 locate_file libc_s.a f $LIBPATH &amp;&amp; LIBNAMES="$LIBNAMES libc_s"
-( locate_file libm.a f $LIBPATH || locate_file libm.so f $LIBPATH ) \
+( locate_file libm.a f $LIBPATH || locate_file libm.so f $LIBPATH || locate_file libm.dylib f $LIBPATH) \
  &amp;&amp; LIBNAMES="$LIBNAMES libm"
 ( locate_file libelf.a f $LIBPATH || locate_file libelf.so f $LIBPATH ) \
  &amp;&amp; LIBNAMES="$LIBNAMES libelf"
@@ -846,6 +846,7 @@ echo ' '
 locate_file libc.a f $LIBPATH ||
 locate_file libc_s.a f $LIBPATH ||
 locate_file libc.so f $LIBPATH ||
+locate_file libsystem_c.dylib f $LIBPATH ||
 give_up_config "Couldn't locate libc.a in directories $LIBPATH"
 LIBFILES="$where"
 echo $n "# Using library file(s):"$c
@@ -857,6 +858,9 @@ for lib in $LIBNAMES; do
 # for SGI IRIS IRIX 5.2
     LIBFILES="$LIBFILES $where"
     echo $n " $where"$c
+  elif locate_file $lib.dylib f $LIBPATH; then
+    LIBFILES="$LIBFILES $where"
+    echo $n " $where"$c
   else
     give_up_config "Couldn't find $lib.a in $LIBPATH"
   fi
@@ -945,7 +949,7 @@ try_sed()
     return
 }

-try_sed 's/^.* [ATDSIW]  *[_.]*//p' 's/^.* [ATDSIW] //p' ||
+try_sed 's/^.* [ATDSIW]  *[_.]*//p' 's/^.* [ATDSIW] //p' 's/ (.*)$//p' ||
 try_sed 's/^__*//' 's/^\([a-zA-Z_0-9$]*\).*xtern.*/\1/p' ||
 try_sed '/|UNDEF/d' '/FUNC..GL/s/^.*|__*//p' ||
 try_sed 's/^.* D __*//p' 's/^.* D //p' ||

自前でlibフィイルからシンボル検索して使えるか判断してるあたり時代を感じます。

Configureが実行できるようになったので設定します。

ここで重要なのは DEF_BUILD_DLL=’no’ です。ついこれをYESにしてだいぶはまりました。 後でわかったことですが、KLICはkl1ファイルをCに変換してコンパイルします。
そのさいに、module_mainという関数が定義されruntimeから呼ばれます。
runtime自体にはmodule_mainが含まれていないため、 dllにしてしまうとruntimeのリンク時にundefined symbolになってしまいビルドができません。
なのでstatic linkでビルドし、kl1ファイルコンパイル時に埋め込む形になります。
(dllで動く方法ありましたら教えてください)

その他の設定はこんなかんじです。とりあえずはまりそうなパラレルなどは無効にしています。

DEF_PARALLEL='no'
DEF_CC='gcc'
DEF_OPTLEVEL='2'
DEF_DEBUGFLAGS='-g'
DEF_UOPTFLAGS='-fomit-frame-pointer'
DEF_KLICCFLAGS='none'
DEF_FORT='none'
DEF_USECC_AS_LD='yes'
DEF_TYPE='linux'
DEF_BUILD_DLL='no'
DEF_LN='ln -s'
DEF_ASYNCIO='yes'
DEF_DIRPREFIX='/usr/local'
DEF_USE_DEFAULT_SETTING='yes'
DEF_LIBPATH=' /usr/lib /usr/local/lib /usr/lib/system'
DEF_INCLUDEDIR='/usr/include'
DEF_LIBNAMES=' libm'
DEF_KLICINCLUDE='/usr/local/include'
DEF_SPKLICINCLUDE='yes'
DEF_INSTPARALLEL='0'

Configureが終わればMakefileが出力されるので一気にビルドしたいところですが、 K&R Cということもありclangではコンパイルエラーが起こりました。
大体がインクルード時に大域変数の重複宣言なってしまっていたり、未定義シンボルだったりするので一つずつ潰していきます。
DLL YESしてしまってmodule_main(とそれを保持するpredicate_main_xmain_0)の参照エラーさえなければ簡単だと思います。
ちょっと長くなりますが、最終的な差分は次の通りになります。

diff --git a/Configure b/Configure
index 7fc15e5..e43babd 100755
--- a/Configure
+++ b/Configure
@@ -799,7 +799,7 @@ esac
 KLICLDFLAGS="$KLICLDFLAGS $XLDFLAGS"

 LIBPATH=""
-for dir in /lib /usr/lib /usr/local/lib; do
+for dir in /lib /usr/lib /usr/local/lib /usr/lib/system; do
     if test -d $dir; then
  LIBPATH="$LIBPATH $dir"
     fi
@@ -829,7 +829,7 @@ echo ' '

 LIBNAMES=""
 locate_file libc_s.a f $LIBPATH &amp;&amp; LIBNAMES="$LIBNAMES libc_s"
-( locate_file libm.a f $LIBPATH || locate_file libm.so f $LIBPATH ) \
+( locate_file libm.a f $LIBPATH || locate_file libm.so f $LIBPATH || locate_file libm.dylib f $LIBPATH) \
  &amp;&amp; LIBNAMES="$LIBNAMES libm"
 ( locate_file libelf.a f $LIBPATH || locate_file libelf.so f $LIBPATH ) \
  &amp;&amp; LIBNAMES="$LIBNAMES libelf"
@@ -846,6 +846,7 @@ echo ' '
 locate_file libc.a f $LIBPATH ||
 locate_file libc_s.a f $LIBPATH ||
 locate_file libc.so f $LIBPATH ||
+locate_file libsystem_c.dylib f $LIBPATH ||
 give_up_config "Couldn't locate libc.a in directories $LIBPATH"
 LIBFILES="$where"
 echo $n "# Using library file(s):"$c
@@ -857,6 +858,9 @@ for lib in $LIBNAMES; do
 # for SGI IRIS IRIX 5.2
     LIBFILES="$LIBFILES $where"
     echo $n " $where"$c
+  elif locate_file $lib.dylib f $LIBPATH; then
+    LIBFILES="$LIBFILES $where"
+    echo $n " $where"$c
   else
     give_up_config "Couldn't find $lib.a in $LIBPATH"
   fi
@@ -945,7 +949,7 @@ try_sed()
     return
 }

-try_sed 's/^.* [ATDSIW]  *[_.]*//p' 's/^.* [ATDSIW] //p' ||
+try_sed 's/^.* [ATDSIW]  *[_.]*//p' 's/^.* [ATDSIW] //p' 's/ (.*)$//p' ||
 try_sed 's/^__*//' 's/^\([a-zA-Z_0-9$]*\).*xtern.*/\1/p' ||
 try_sed '/|UNDEF/d' '/FUNC..GL/s/^.*|__*//p' ||
 try_sed 's/^.* D __*//p' 's/^.* D //p' ||
diff --git a/include/klic/basic.h b/include/klic/basic.h
index ac92038..c12cbd5 100644
--- a/include/klic/basic.h
+++ b/include/klic/basic.h
@@ -35,10 +35,12 @@

 typedef void voidfn ();

+#ifndef IN_DEBUG_C
 extern NoReturn voidfn fatal;
 extern NoReturn voidfn fatalf;
 extern NoReturn voidfn fatalp;
 extern void debug_printf();
+#endif

 /*
   PARALLEL flag is DIST || SHM.
diff --git a/include/klic/g_basic.h b/include/klic/g_basic.h
index 223a156..56dcbb0 100644
--- a/include/klic/g_basic.h
+++ b/include/klic/g_basic.h
@@ -133,9 +133,10 @@ extern q** make_larger_stack();
   external functions
 */

+#ifndef DEFINE_GLOBAL
 #ifdef __GNUC__
 extern Volatile voidfn G_error;
 #else
 extern voidfn G_error;
 #endif
-
+#endif
diff --git a/include/klic/options.h b/include/klic/options.h
index 9a27441..3b87e9b 100644
--- a/include/klic/options.h
+++ b/include/klic/options.h
@@ -20,8 +20,6 @@ enum argtype {
     ARG_NOT_USED
 };

-extern Const struct opttable opttable[];
-
 char *parse_opts();

 /* not copied when spawned */
diff --git a/include/klic/timing.h b/include/klic/timing.h
index 6553f31..d722334 100644
--- a/include/klic/timing.h
+++ b/include/klic/timing.h
@@ -24,6 +24,10 @@
 #include <sys/times.h>
 #include <sys/param.h>

+#ifndef HZ
+#define HZ 60
+#endif
+
 #define	 tick2msec(n)	((n)*1000.0/HZ)

 #define	 timerstruct	struct tms
diff --git a/runtime/bb.c b/runtime/bb.c
index d3e1187..c041219 100644
--- a/runtime/bb.c
+++ b/runtime/bb.c
@@ -260,7 +260,7 @@ q bblt_fix_2(x)

  type_error:
   builtin_2_type_error(x,
-    predicate_floating__arithmetics_xfloating__point__to__integer_2,
+    &amp;predicate_floating__arithmetics_xfloating__point__to__integer_2,
           "fix/2");
  suspend_x:
   return suspend_builtin_2(x,
diff --git a/runtime/debug.c b/runtime/debug.c
index fa8fa8f..1978bf3 100644
--- a/runtime/debug.c
+++ b/runtime/debug.c
@@ -8,6 +8,7 @@
 #include <setjmp.h>
 #include <ctype.h>
 #include <errno.h>
+#define IN_DEBUG_C
 #include <klic/basic.h>
 #include <klic/struct.h>
 #include <klic/primitives.h>
diff --git a/runtime/generic.c b/runtime/generic.c
index 93418f2..c55194a 100644
--- a/runtime/generic.c
+++ b/runtime/generic.c
@@ -5,6 +5,7 @@
 %       (Read COPYRIGHT-JIPDEC for detailed information.)
 ----------------------------------------------------------- */

+#define DEFINE_GLOBAL
 #include <klic/gdobject.h>
 #include <klic/functorstuffs.h>
 #include <klic/atomstuffs.h>
diff --git a/runtime/print.c b/runtime/print.c
index 91ab677..a8a9533 100644
--- a/runtime/print.c
+++ b/runtime/print.c
@@ -15,18 +15,11 @@
 #include <klic/gobj.h>
 #include <klic/susp.h>

-print_partially(x, depth, length)
-     q x;
-     unsigned long depth, length;
-{
-  fprint_partially(stdout, x, depth, length);
-}
-
 #ifdef DEBUGLIB
 int verbose_print;
 #endif

-fprint_partially(stream, x, depth, length)
+void fprint_partially(stream, x, depth, length)
      FILE *stream;
      q x;
      unsigned long depth, length;
@@ -156,6 +149,13 @@ fprint_partially(stream, x, depth, length)
   return;
 }

+print_partially(x, depth, length)
+     q x;
+     unsigned long depth, length;
+{
+  fprint_partially(stdout, x, depth, length);
+}
+
 fprint(stream, x)
      FILE *stream;
      q x;
diff --git a/runtime/unify.c b/runtime/unify.c
index 06b008d..8008033 100644
--- a/runtime/unify.c
+++ b/runtime/unify.c
@@ -41,7 +41,7 @@ extern struct predicate predicate_unify__term__dcode_xunify__goal_2;

 /* Resume a goal with the same priority as current */

-Inline void resume_same_prio(gp)
+void resume_same_prio(gp)
      struct goalrec *gp;
 {
   declare_globals;
@@ -60,7 +60,7 @@ Inline void resume_same_prio(gp)
   by the unification with `y'
 */

-Inline q *resume_goals(allocp, x, y)
+q *resume_goals(allocp, x, y)
      q * allocp;
      q x;
      q y;

ビルドが通ったらテストを実行します。

make tests
...
...
...
echo 'Seq. impl.: OK' >tests.log

無事テストも完走しました。これでKLICを実行できるようになりました。
actindiの公用プログラミング言語 にKL1を追加できるよう使ってみたいと思います。

アクトインディ Advent Calendar 2015

2015年12月03日
区分
advent-calender
報告者:
akiyama

アクトインディ Advent Calendar 2015 に参加してます!
まだ枠が空いているので、参加したい方がいらっしゃいましたら今すぐ入社。まだ間に合います!

SQLアンチパターン読書会

2015年11月30日
区分
読書会
報告者:
akiyama

こんにちは、技術書収集好き akiyama です。

弊社では定期的にエンジニア勉強会を開催しています。
勉強会では持ち回り担当者が興味があることについて発表をする形式でやっています。
たとえばこんなかんじです。

今回、勉強会とは別にSQLアンチパターン読書会を開催しました。
弊社はRails技術者が多く、RDBMSを直接触った経験が少ない人もいます。 かねてより、ORMとRDBとのギャップを埋めたいという声や、もっと技術者同士の知識交換もしたいという声があり、よい機会だということで、SQLアンチパターンを選んでの開催となりました。

読書会の進め方は

章ごとに各自黙読
質問があれば随時
読み終わった後にまとめ的議論

というふうに進めました。

1時間で1、2章分ほどを想定していましたが、わりとサクサク進み3章分読み進めて終了。 はじめての読書会なので、これがよいやり方だったか判断は難しいですが、 参加者には好評のようで続くことになりました。おそらく本が良かったのでしょう。

実際に開催してみて、参加者が、「このスキーマ見たことある」という人と、「なんでこんなことやるんだ?」と疑問を持つ人に別れたかんじがあり 相互理解にはよかったんではないかと思います。
アンチパターン集ということだけあって一部のRDB経験者のトラウマを刺激してしまうパターンもあり嗚咽が流れることもありました。
開催者としては、各人の読書速度や前提知識に違いがあるなかで、一部置いてけぼりなところがあったかなと反省しています。

というようなヒヨコ読書会ですが、アクトインディではいっしょに勉強会や読書会やってみたい エンジニアとかプログラマとか募集中です!

PCの放熱対策

2015年11月10日
区分
放熱
報告者:
akiyama

こんにちは、akiyama です。

先週から風邪を患ってしまい、本日は咳もでるため自宅からリモート勤務を行っています。
弊社の勤務形態の自由度が高いおかげで社内に風邪をバラまかずに済んでいます。
そんなアクトインディですが自由度高めで働きたいエンジニア募集中です。

先日、社内でちょっとNotePCの放熱対策が話題になりました。
いこーよのtestを全て流すとかなりの時間がかかるのでtest-queueを使用して並列で流していますが、 その時にNotePCがかなり熱くなってしまい困りものです
ですので、自宅ではクールひんやりアルミボード(ペット用)を使用してNotePCの下に敷いています。
↓これ

アルミボードの利点としては

  • ファンレスでうるさくない
  • 何もしないより効果がある
  • 家にいない間は猫が涼める
欠点は
  • 室温高いとあんまり意味がない
  • 夏場は猫と取り合いになる(負ける)
といったところでしょうか。

実際に使っている写真を載せようかと思いましたが、このblogは画像アップロード機能がないので諦めました。
弊社技術blog(CommonLisp製)を進化させてくれるエンジニアとかプログラマとか募集中です!

チケットとコミットログを連携させる

2015年08月24日
区分
git
報告者:
akiyama

はじめまして、6月入社のakiyamaと申します。
社内二人目のkinesisユーザです。 自宅ではDucky Yellow Keyboard使っています。

アクトインディではgitコミットログの一行目にredmineのチケット情報を入れることになっています。
またgitブランチ名にチケット番号を含める運用です(フォーマットはd/NNNN-branch-name)。
となれば、.git/hooks/prepare-commit-msgを使って自動挿入が楽そうです。

 1 #!/usr/bin/env ruby
 2 
 3 require 'json'
 4 require 'open-uri'
 5 require 'openssl'
 6 
 7 REDMINE_API_KEY=''
 8 REDMINE_URL = 'http://'
 9 
10 exit 0 if ARGV.size > 1
11 
12 branch = `git rev-parse --abbrev-ref @` || exit
13 branch =~ %r|^d/(\d+)| || exit
14 issue_no = $1
15 
16 open("#{REDMINE_URL}/issues/#{issue_no}.json?key=#{REDMINE_API_KEY}",
17      'r',
18      ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE) do |f|
19 
20   issue = JSON.parse(f.read)['issue']
21   open(ARGV[0], 'r+') do |f|
22     text = f.read
23     f.rewind
24     f.puts "#refs ##{issue['id']} #{issue['subject']}"
25     f.print text
26   end
27 end
28 

コミットをキャンセルしやすいようにチケット情報をコメントアウトしています。
先頭の#を消して3行目以降にコミットログを書けばさくさくコミットできて快適です。

初回から小ネタですが、今後ともよろしくお願いします。

技師部隊からの
お知らせ

【求人】エンジニア募集しています。

本頁の来客数
八十七万千百七十六名以上(計測停止中)

メンバー一覧

アクトインディ技師部隊員名簿

アクトインディ技師部元隊員

アクトインディへ

カテゴリー

アクトインディ

aaaa