エンジニアと思われるものの備忘録

しがない学生の備忘録です

Gormにおける多層外部キーの定義の仕方とコードの書き方

最近バイトの方でgormの振る舞いとデータベースのテーブル構造のことでハマったのでそれについてメモっておきます

テーブル構造とgoのモデル

f:id:kk_river108:20180825134759p:plain

最初にあったテーブル構造は上の図のような感じ。

今回は例として地域、地域に存在する書店たち、書店の持つ本をマッピングしてみました。

地域と書店の関係はhas many、書店と本もhas manyとなっています。

これをgoのコードにmodelとして書き出すと以下のような感じになりました。

package main

import "time"

type Region struct {
    ID int
    Shops     []Shop `gorm:"foreignkey:ID"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Shop struct {
    ID int
    Name      string
    Books     []Book `gorm:"foreignkey:ShopID"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Book struct {
    ID        int
    ShopID    int
    Name      string
    Price     int
    CreatedAt time.Time
    UpdatedAt time.Time
}

テーブル作成のsqlは次のようになります

CREATE TABLE IF NOT EXISTS region (
        id                   serial  NOT NULL,
    created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT region_id PRIMARY KEY ( id )
);

CREATE TABLE IF NOT EXISTS shop (
        id                   serial  NOT NULL,
        name                 varchar(255) NOT NULL,
    created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT shop_id PRIMARY KEY ( id ),
        CONSTRAINT shop_id_region_id_foreign FOREIGN KEY ( id )
          REFERENCES region( id )
          ON UPDATE NO ACTION
          ON DELETE NO ACTION
);

CREATE TABLE IF NOT EXISTS book (
        id                   serial  NOT NULL,
        shop_id              bigint(20) unsigned NOT NULL,
        name                 varchar(255) NOT NULL,
        price                int NOT NULL,
        created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT book_id PRIMARY KEY ( id ),
        CONSTRAINT book_shop_id_shop_id_foreign FOREIGN KEY ( shop_id )
          REFERENCES shop( id )
          ON UPDATE NO ACTION
          ON DELETE NO ACTION
);

shopは自身のIDは外部キーでregionのidと紐づけています bookはshop idを外部キーでshopのidと紐づけています

gormでinsertする

これらのデータ構造はgormにおいて次のように一括でcreateすることができます。

      r := Region{
        Shops: []Shop{
            Shop{
                Name: "shop1",
                Books: []Book{
                    Book{
                        Name:  "book1",
                        Price: 100,
                    },
                    Book{
                        Name:  "book2",
                        Price: 200,
                    },
                },
            },
        },
    }

    if err := mysql.Create(&r).Error; err != nil {
        log.Fatal(err)
    }

gormがタグに紐づけた外部キーの情報からよしなに親をinsertした後、確定したIDの情報を用いて, 子のテーブルにまでinsertしてくれるのです。

しかし、このデータ構造の場合次のようなエラーが起こります。

/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  [3.76ms]  INSERT INTO `region` (`created_at`,`updated_at`) VALUES ('2018-08-25 13:27:44','2018-08-25 13:27:44')
[1 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  [10.12ms]  UPDATE `shop` SET `name` = 'shop1', `created_at` = '0001-01-01 00:00:00', `updated_at` = '2018-08-25 13:27:44'  WHERE `shop`.`id` = '2'
[0 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  Error 1452: Cannot add or update a child row: a foreign key constraint fails (`debug`.`book`, CONSTRAINT `book_shop_id_shop_id_foreign` FOREIGN KEY (`shop_id`) REFERENCES `shop` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  [5.02ms]  INSERT INTO `book` (`shop_id`,`name`,`price`,`created_at`,`updated_at`) VALUES ('2','book1','100','2018-08-25 13:27:44','2018-08-25 13:27:44')
[0 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  Error 1452: Cannot add or update a child row: a foreign key constraint fails (`debug`.`book`, CONSTRAINT `book_shop_id_shop_id_foreign` FOREIGN KEY (`shop_id`) REFERENCES `shop` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  Error 1452: Cannot add or update a child row: a foreign key constraint fails (`debug`.`book`, CONSTRAINT `book_shop_id_shop_id_foreign` FOREIGN KEY (`shop_id`) REFERENCES `shop` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:27:44]  Error 1452: Cannot add or update a child row: a foreign key constraint fails (`debug`.`book`, CONSTRAINT `book_shop_id_shop_id_foreign` FOREIGN KEY (`shop_id`) REFERENCES `shop` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
2018/08/25 13:27:44 Error 1452: Cannot add or update a child row: a foreign key constraint fails (`debug`.`book`, CONSTRAINT `book_shop_id_shop_id_foreign` FOREIGN KEY (`shop_id`) REFERENCES `shop` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION)

エラーの内容はupdateが走り, それはconstraintに違反しているというものでした(多分)

これの原因はShopの主キーであり、外部キーでもあるIDとRegionの主キーを紐づけていることに起因すると思われます。

正しいテーブル構造

なので、ちゃんとしたテーブル構造は次のようになります

f:id:kk_river108:20180825140200p:plain

sqlはこんな感じ

CREATE TABLE IF NOT EXISTS region (
    id                   serial  NOT NULL,
    created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT region_id PRIMARY KEY ( id )
);

CREATE TABLE IF NOT EXISTS shop (
    id                   serial  NOT NULL,
    region_id            bigint(20) unsigned NOT NULL,
    name                 varchar(255) NOT NULL,
    created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT shop_id PRIMARY KEY ( id ),
    CONSTRAINT shop_region_id_region_id_foreign FOREIGN KEY ( region_id )
        REFERENCES region( id )
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

CREATE TABLE IF NOT EXISTS book (
    id                   serial  NOT NULL,
    shop_id              bigint(20) unsigned NOT NULL,
    name                 varchar(255) NOT NULL,
    price                int NOT NULL,
    created_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    updated_at           timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP  ,
    CONSTRAINT book_id PRIMARY KEY ( id ),
    CONSTRAINT book_shop_id_shop_id_foreign FOREIGN KEY ( shop_id )
        REFERENCES shop( id )
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

goのmodelのコードはこんな感じ

type Region struct {
    ID        int
    Shops     []Shop `gorm:"foreginkey:RegionID"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Shop struct {
    ID        int
    RegionID  int
    Name      string
    Books     []Book `gorm:"foreignkey:ShopID"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Book struct {
    ID        int
    ShopID    int
    Name      string
    Price     int
    CreatedAt time.Time
    UpdatedAt time.Time
}

ShopにRegionIDという外部キー用のカラムを追加しています。

これで先程のcreateを実行してみると

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:18:15]  [2.14ms]  INSERT INTO `region` (`created_at`,`updated_at`) VALUES ('2018-08-25 13:18:15','2018-08-25 13:18:15')
[1 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:18:15]  [6.41ms]  INSERT INTO `shop` (`region_id`,`name`,`created_at`,`updated_at`) VALUES ('2','shop1','2018-08-25 13:18:15','2018-08-25 13:18:15')
[1 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:18:15]  [1.57ms]  INSERT INTO `book` (`shop_id`,`name`,`price`,`created_at`,`updated_at`) VALUES ('1','book1','100','2018-08-25 13:18:15','2018-08-25 13:18:15')
[1 rows affected or returned ]

(/Users/bo0km4n/go/src/github.com/Bo0km4n/dev/mysql_gorm_association/main.go:32)
[2018-08-25 13:18:15]  [3.31ms]  INSERT INTO `book` (`shop_id`,`name`,`price`,`created_at`,`updated_at`) VALUES ('1','book2','200','2018-08-25 13:18:15','2018-08-25 13:18:15')
[1 rows affected or returned ]
main.Region{
  ID:    2,
  Shops: []main.Shop{
    main.Shop{
      ID:       1,
      RegionID: 2,
      Name:     "shop1",
      Books:    []main.Book{
        main.Book{
          ID:        1,
          ShopID:    1,
          Name:      "book1",
          Price:     100,
          CreatedAt: 2018-08-25 13:18:15 Local,
          UpdatedAt: 2018-08-25 13:18:15 Local,
        },
        main.Book{
          ID:        2,
          ShopID:    1,
          Name:      "book2",
          Price:     200,
          CreatedAt: 2018-08-25 13:18:15 Local,
          UpdatedAt: 2018-08-25 13:18:15 Local,
        },
      },
      CreatedAt: 2018-08-25 13:18:15 Local,
      UpdatedAt: 2018-08-25 13:18:15 Local,
    },
  },
  CreatedAt: 2018-08-25 13:18:15 Local,
  UpdatedAt: 2018-08-25 13:18:15 Local,
}

ちゃんと一括でinsertされているのがわかりました!

まとめ

そもそも最初失敗のケースでハマっていたのは親子関係のみの場合はうまく行っていたからです。親子孫のように増えると先程のようなエラーが出て四苦八苦していました。

ちなみに例に上げた親子の関係は1to1でもエラーは起きます。

テーブルの主キーと他のテーブルの主キーを外部キーとして紐付けるのは割とやりがちなケースな気がしますが、しっかり別カラムで紐づけた方が安全な気はします。

今回のサンプルコードは以下に載せてあります

github.com

2018上半期院の授業振り返り

お久しぶりです。 今回は僕が上半期に芝浦工業大学大学院電気電子情報工学専攻で取った授業についてレビューというか感想的なものを書いていこうと思います。

後輩の方々に向けての参考になれば幸いです。

構成はこんな感じで書いていきます。

  • 授業名
    • 概要
    • よかったところ
    • 悪かったところ
    • オススメ度

オススメ度は4段階評価です。

分散システム特論

概要

広域ネットワークを前提とした分散システムを実現するために必要な知識や概念を体系的に学び、実装を通して技術的概要を理解する。 実装に関しては主に5つの課題があります。

  1. TCP/IPスタックの実装
  2. プロセス間通信の演習(pthread, fork)
  3. javaとC間のRPC 実装(javaのオブジェクトシリアライザ、デシリアライザの実装)
  4. 名前付け演習(Linux V6のファイルシステム実装)
  5. 同期演習(Lamportの全順序アルゴリズムの実装)

よかったところ

ファイルシステムjavaシリアライズについての仕様について実装を通して学べる。実装する言語を自分で選べる課題も多くて助かりました。ゴリゴリ実装する授業なので個人的に非常に楽しかったです。

悪かったところ

火曜1限というとこ

オススメ度

  • 3 プログラミング好きな人なら取るべき。そうでないならお勧めしない。

自然言語処理システム特論

概要

自然言語処理システムの基礎となる理論的モデルと処理技術を理解し、 システム作成実験や論文輪講を通じてその実現手法と応用事例について学ぶ。 この授業は主に二つのフェーズに分かれます。

  1. グループでテーマごとに応じた自然言語処理システムの実装
  2. グループでの輪講

僕のグループは単語の類似度計算というテーマでword2vecなどを用いず、共起度とSVD演算を用いて意味の類似度を演算するシステムを実装しました。大変でした。

輪講はこちら

http://www.aclweb.org/anthology/W16-3641

の論文を読みましたが、これは次の研究へつなげるための前座的論文なので実装的なお話は一切ありませんでした。

よかったところ

自然言語処理における、意味解析、構文解析、応用としてのシステムに関しての知識を一通り学べるのはよかったです。

悪かったところ

課題が割とヘヴィということ。二単位でやる感じではない。 グループでの作業分担がしっかりできる人は大丈夫なはず。

オススメ度

  • 2 微妙なラインです。 この時限に他に取りたいのがあるならそちらを取るべきかもしれない。 ただ、最近は自然言語処理機械学習はほぼセット扱いでバズワードなのでやっといて損はないはず。 何にしても他の授業とかと比較してみるのがお勧め

基盤システム特論

実践的な研究開発を通じて、分散システムプラットフォームの本質である並行性、並列性、信頼性についての理解を深める。 この授業は最初は分散システム特論と同じ教科書を使った輪講、その後にグループでのシステム開発になります。 開発するシステムは基本的にIoTデバイス、要はロボットとかセンサが一つ絡んであることが必須条件でそれ以外は特にありません。

よかったところ

自分の実装力を確認できる。

悪かったところ

一言でいうと概要詐欺だということ。 僕がこの授業に最初に想像していたのは 「分散システムを実装してスケールアウトできるか、信頼性などはあるかといった評価をする」ものだと思っていました。 それが蓋をあけてみれば、「どっかの学会に論文出せるようなアイディア重視のシステム作って論文書いて!!」と教授がひたすらしつこく要求してくるものでした。

要は分散システムである必要とかなくてシステムのアイディアが一番重要視されてスケールなんかしなくてもいいんです。 分散システムどこいった。

僕が一生懸命draw.ioで作った下の図は構想発表などでも特に触れられず、他の班の「脳波から感情を推定する」「植物の感情を推定する」とかに教授は嬉々としていました。 アイディアはすごいけどそれ分散システムの体なしてなくない?って感じでした。

なお他の班の多くは一切並列性、並行性、信頼性などに関しては触れていませんでした。システム自体もセンサとサーバ一対一で繋がってるだけ。 なんだこれ。 なんだか必死にスケールできるように!ってアーキテクチャを考えた僕が唐突に恥ずかしくなりました。 f:id:kk_river108:20180728171724p:plain

最初からそういう授業だとわかっていればいいですが、これに関してはあまりにもシラバスで言及がなさすぎます。 あと一つ個人的に言うならインプットが一切なかったということです。

授業なので何かしら新しい知識や技術を期待していましたが全くありませんでした。輪講は分散システム特論と被っているので全く意味がありません。 ただオレオレシステムを実装するだけです。

加えて、授業終了後の夏休み後も論文を学会に提出する人(一応希望制)は引き続き評価とか執筆に勤しみます。

オススメ度

  • -255 悪いところばかり書いたのでお分かりかと思いますが、全くお勧めしません。 時間を無駄にするだけなので他の授業を取ることをお勧めします。 唯一お勧めできるのは「なんかようわからんアイディアもってるけどこれで単位が欲しい!」という人だけです。

データ工学特論

概要

多くのデータから有意な情報を抽出するデータマイニング手法のうち代表的なものを理解し、輪講を通して、基礎的な知識の習得と最新の研究動向の把握を行う。 この授業は先生の講義とグループでの論文輪講が主です。

講義では以下のような手法に関して詳しく解説されます

輪講で僕のグループが読んだ論文はこちらです。

http://db-event.jpn.org/deim2017/papers/64.pdf

セキュリティ系のクラスタリング手法を絡めた研究は実用化が結構大変そうなイメージですね。

よかったところ

各手法に関して基礎的な数学の話も含めて、応用先などを丁寧に解説してくれて非常にわかりやすい授業でした。 必然的に機械学習の手法も多く絡んでくるので聞いてて楽しかったです。

悪かったところ

特にないです。

オススメ度

  • 4 最近のバズワードである機械学習と非常に縁が深い分野の授業なので取っておいて損はないはず。

ソフトウェア構成特論

概要

単純な型システムをもつプログラミング言語を定義し、それについての操作的意味や、記述された式に対して型についての整合性などについての理解を促す。

この授業は

https://www.amazon.co.jp/%E5%9E%8B%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%85%A5%E9%96%80-%E2%88%92%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E8%A8%80%E8%AA%9E%E3%81%A8%E5%9E%8B%E3%81%AE%E7%90%86%E8%AB%96%E2%88%92-Benjamin-C-Pierce/dp/4274069117

の本を噛み砕いて説明、問題の演習を行ってくれています。 この授業を通じて静的型付け言語に対して基礎的な理解を深めることが目的となっています。

よかったところ

書籍をよりわかりやすく、演習問題を通じて丁寧に解説してくれています。 型付きラムダ計算などを代数学?てきに証明するっていうのは解いてて面白かったです。

わるかったところ

特になし

オススメ度

  • 3 4にしたかったんですが分野があまりにもニッチなので一応3にしておきました。 ただプログラミング言語に対してもっと理解を深めたい人にはすごくお勧めできる授業です。

まとめ

以上が僕が今期にとった授業についてです。 とにかく基盤システム特論だけはオススメしません。

他の授業は結構身になるものが多かったので良かったです。 この記事が少しでも誰かの参考になればうれしいです。

GAEにデプロイするとき今のバージョンのzapは使っちゃだめ

GAEでgolangwebサービスをデプロイしようとしたとき

github.com

unique id発行のために上のライブラリを使っていたんですが、内部でzapを使っていました。

github.com

zapは以下のようにgo1.9から追加されたtype aliasを使っています。

package zap

import (
    "fmt"
    "math"
    "time"

    "go.uber.org/zap/zapcore"
)

// Field is an alias for Field. Aliasing this type dramatically
// improves the navigability of this package's API documentation.
type Field = zapcore.Field

// Skip constructs a no-op field, which is often useful when handling invalid
// inputs in other Field constructors.
func Skip() Field {
    return Field{Type: zapcore.SkipType}
}

この場合、GAEにデプロイするときに次のようなエラーでこけます。

File upload done.
Updating service [default]...failed.
ERROR: (gcloud.app.deploy) Error Response: [9] Deployment contains files that cannot be compiled: Compile failed:
/work_dir/go.uber.org/zap/field.go:33: syntax error: unexpected = in type declaration
2018/05/24 22:16:54 go-app-builder: build timing: 72×compile (9m48.835s total), 0×link (0s total)
2018/05/24 22:16:54 go-app-builder: failed running compile: exit status 2

GAEのruntimeが1.8までしかまだ対応してないからですね。

というわけでzap使いたい場合はGAEが1.9に上がるまで待ちましょう。 今回はkatsubushiの代わりにxidを使うことにしました。

github.com

自作OS入門の環境をOS Xで整える

動機

「30日でできる!OS自作入門」を読み実装しようと思ったところ、環境がwindows。おまけにアセンブラやビルド環境がWindows故、そのまま流用できませんでした。

そこで少し色々調べた結果、http://bttb.s1.valueserver.jp/wordpress/blog/2017/11/14/make-os1/ この方のブログが大変シンプルに纏まっており参考にさせていただくことにしました。 このブログの環境はLinuxなので大体は流用することができます。

1日、2日目の内容はアセンブラオンリーなので特に問題はなかったのですが3日目以降はC言語アセンブラをリンクしてビルドする必要がでてきます。 この際、OS Xだと少々一筋縄ではいかない問題がでてきます。

それがgccとldリンカです。OS Xに標準で入っているのは恐らくXcode-command-line-toolsを入れた時に一緒に入るgccです。 gccは別にいいのですがldはGNU ldではないため、リンカスクリプトを指定する-Tオプションがありません。

そこで、本はi386をターゲットにビルドしているのでi386のクロスコンパイル環境を構築することにしました。

以下にその際の手順を示します。

QEMU

brew install qemu

nasm

brew install nasm

i386-elf-gcc, gnu ld

ここからが少し長いです。 基本的に同じコマンドを実行すれば大丈夫なはずです(2018-5現在)

brew install gcc6

export CC=/usr/local/Cellar/gcc@6/6.4.0_2/bin/gcc-6 # ↑の手順でinstallしたGCCのパス
export LD=/usr/local/bin/gcc@6/6.4.0_2/bin/gcc-6

export PREFIX="/usr/local/i386elfgcc"
export TARGET=i386-elf
export PATH="$PREFIX/bin:$PATH"

# binutilsのインストール
mkdir /tmp/src
cd /tmp/src
curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.28.tar.gz
tar xf binutils-2.28.tar.gz
mkdir binutils-build
cd binutils-build
../binutils-2.28/configure --target=$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix=$PREFIX 2>&1 | tee configure.log
sudo make all install 2>&1 | tee make.log

# libiconv最新版をインストールする
cd /tmp/src
curl -O https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz
tar xf libiconv-1.15.tar.g
cd libiconv-1.15
./configure -prefix=/usr/local
make
make install

# クロスコンパイラなGCCをビルド
cd /tmp/src
curl -O https://ftp.gnu.org/gnu/gcc/gcc-6.4.0/gcc-6.4.0.tar.gz
tar xf gcc-6.4.0.tar.gz
mkdir gcc-build
cd gcc-build
../gcc-6.4.0/configure --target=$TARGET --prefix="$PREFIX" --disable-nls --disable-libssp --enable-languages=c --without-headers --with-gmp=/usr/local --with-mpfr=/usr/local --with-mpfr=/usr/local --with-libiconv-prefix=/usr/local
make all-gcc 
make all-target-libgcc 
sudo make install-gcc 
sudo make install-target-libgcc

最終的な環境

テスト

以上の環境構築が終了した場合、

day4/harib01j 内で make run をするとそれっぽいosの画面がqemuで開くはずです。

brew updateしたらなんかvimが動かなくなった

ある日 brew update したらvim

dyld: Library not loaded: /usr/local/opt/lua/lib/liblua.5.2.dylib
  Referenced from: /usr/local/bin/./vim
  Reason: image not found

/usr/local/opt/lua/lib/liblua.5.2.dylib がないと言われた。実際にパスに移動するとなかった。

ということで  bilibili-mac-client/liblua.5.2.dylib at master · typcn/bilibili-mac-client · GitHub からファイルをダウンロードして /usr/local/opt/lua/lib にmvしたら動いた。

それにしてもmacvimをよく止めるね

Contikiにおけるファイル分割してコンパイルの仕方とPROCESSによる並列実行

卒業研究で使ってるWSN用のOS、というより超巨大ライブラリのContikiというものについてメモしておきます。

Why

そもそも日本語のドキュメントは勿論、英語でのドキュメントもサンプルを動かしただけとかばっかで深いところに突っ込むにはソースコード読むしかないという現状だったのでもし他にこの分野に手を付ける人がいたらその助けになればいいなという思いと、自分で忘れないようにするために書いておきます。

Contikiとは

WSN用のOSです。マルチスレッドを標準でサポートしています。何を当たり前のことをと思うかもしれませんが無線センサネットワークとかの界隈ではリソースの都合上結構すごいことだったりする。

このOSで動くプログラムはC言語で書けます。これまた「今時C〜?古くない〜?」と言われそうですが、結構有名なWSN用のOS、TinyOSとかはnesCとかいうドキュメントは英語の本一冊のみという魔の言語で書かなきゃいけなかったりするので恵まれている方です。

あとCoojaというGUIシミュレータが付属しているので基本的には書いたプログラムをこのシミュレータでデバッグしながら開発することができます。 とりあえずContikiで動かすことができるもっとも簡単なプログラムを示します。

#include "contiki.h"
#include "stdio.h"

/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSE(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
  PROCESS_BEGIN();
   
    printf("Hello, Cooja\n");

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/

お決まりのHello world!ですがこれだけでも結構なことをしています。 一行ずつ説明していきます。

  1. PROCESS(プロセス名, プロセス呼び出し時に出力する文字列)

    これは hello_world_process という名前のプロセス構造体を定義しています。第二引数は呼び出し時に標準出力に出力する文字列です。

  2. AUTOSTART_PROCESS(プロセス構造体のアドレス)

    センサー起動時にこのプロセスを自動的に起動します。これがないとセンサを起動しても何も起きません。 プロセスをコード上で任意に起動することもできますがそれは後述します。

  3. PROCESS_THREAD(プロセス構造体, イベント, データ) { ... }

    プロセスの実際の処理を記述する関数です。このPROCESS_THREADは他のPROCESS_THREADとは並列に処理されます。 イベントはセンサ本体のボタンなどが押されたときのシグナルを判定する時に使用します。 データはコード上でプロセスを起動した時に任意のデータポインタを渡すことができます。

  4. PROCESS_BEGIN() ... PROCESS_END()

    この間に書かれた処理が実行されます。 PROCESS_END()が実行されても完全にプログラムの実行が終わったわけではなく, バックグラウンドではセンサ毎に定義されているOSプログラムが存在して, 低電力モードなどへの移行を行っています。あくまでここに定義されたプロセスをベースとなるOS上で実行しているイメージです。

コンパイルの仕様

センサで動かすプログラムをContikiがコンパイルする際には全てMakefileでセンサーごとに独自のバイナリにコンパイルしていきます。 例えば、上の hello_world.cコンパイルする際にはMakefileはこのようになります。

CONTIKI_PROJECT = hello-world
all: $(CONTIKI_PROJECT)

CONTIKI = ../..
include $(CONTIKI)/Makefile.include

makeする際には make TARGET=... でTARGETに動かしたいセンサを指定します。

あとはhello-world.cにガリガリ書いていけばいいのですがある程度コードの規模が大きくなると1ファイルで管理するのは非常に面倒でした。 そこで他のヘッダファイルに定義した関数を別のcファイルで実装し、それを呼び出す場合は

MakefilePROJECT_SOURCEFILES += hoge.c fuga.c と定義します。 この時のディレクトリ構成はこんな感じです。

.
├── Makefile
├── README.md
├── fuga.c
├── fuga.h
├── hello-world.c
├── hoge.c
├── hoge.h
├── symbols.c
└── symbols.h

この時, fuga.c hoge.c を別階層のディレクトに配置すると, コンパイルしたファイルを参照できなくなりエラーが起こります。

別プロセスの呼び出し

では試しに hoge.hhoge_processを定義し, hoge.c に実装し, hello-world.c でボタンが押された時に hoge_process を呼び出す処理を書いてみます

hoge.h

#ifndef _HOGE_H_
#define _HOGE_H_

void start_hoge_process();
#endif

hoge.c

#include "contiki.h"
#include "stdio.h"

PROCESS(hoge_process, "hoge process");
PROCESS_THREAD(hoge_process, ev, data) {
    PROCESS_BEGIN();
    printf("hoge %d\n", data);
    PROCESS_END();
}

void start_hoge_process(void) {
    process_start(&hoge_process, (void *)100);
}

hello-world.c

#include "contiki.h"

#include "dev/button-sensor.h"
#include "dev/leds.h"


#include <stdio.h> /* For printf() */

/*---------------------------------------------------------------------------*/
PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
  PROCESS_BEGIN();
  SENSORS_ACTIVATE(button_sensor);
  printf("Hello, Cooja\n");

  while(1) {
    PROCESS_WAIT_EVENT_UNTIL(ev == sensors_event &&
            data == &button_sensor);
    start_hoge_process();
  }
  PROCESS_END();
}

これによって hello-word.cコンパイルしてcoojaシミュレータで起動(センサーの種別は問いません)し、 センサのボタンをクリックするとログに hoge 100 と出力されるはずです。

ContikiのMakefileには独自のフラグが多くあるのでまた自分が何か見つけたらメモしておこうと思います。 私のContikiで開発中のリポジトリはこちらです。興味があったらContikiでプログラミングしてみてください。 そして私に知見をください。

github.com

contiki本体のリポジトリはこちらになります

github.com

wikiも一応あるにはありますが大体動かしつつソース読むのが手っ取り早いです。

それにしてもWSNの具体的なユースケースや規模がググっても全然出てこないので評価の仕様策定に苦戦中です。 辛い

HighSierraにアプデしたらvimが起動できなかった

macをHigh Sierraにアプデしたんですが、コンソールでvimを起動したらこんなメッセージが

dyld: Library not loaded: /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/libruby.2.0.0.dylib
  Referenced from: /usr/local/bin/vim
  Reason: image not found

ググると先人さまの知恵がありました。

shinogasa.hatenablog.com

swiftfe0.hatenablog.com

とりあえず色々アプデする必要がある模様。

まずpythonをアプデしようとしたら

$ brew upgrade python
Updating Homebrew...
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
==> Auto-updated Homebrew!
Updated 2 taps (homebrew/core, caskroom/cask).
==> New Formulae
avimetaedit           bedtools              cling                 clingo                docker-ls             envconsul             mrboom                raylib                sceptre
==> Updated Formulae
little-cms2 ✔       checkstyle          fail2ban            gitlab-runner       kontena             libuv               nghttp2             pyvim               shfmt               uncrustify
sbt ✔               clhep               firebase-cli        gmime               kotlin              libwps              nmh                 q                   sile                urh
abcm2ps             cmake               fish                gradle-completion   kubernetes-cli      lldpd               node-build          qca                 sip                 vcdimager
angular-cli         conan               fluent-bit          grails              kvazaar             mapnik              nuget               qscintilla2         skinny              wireguard-tools
arangodb            conjure-up          fn                  gx                  ldc                 mediaconch          ocamlbuild          qt                  sslyze              xonsh
armadillo           consul              fonttools           gx-go               libatomic_ops       mgba                opencbm             rancher-cli         stormlib            xxhash
aspcud              convmv              freetds             hana                libcddb             micro               overmind            rebar@3             swi-prolog          youtube-dl
aurora-cli          cppad               fwup                hivemind            libcdio             midnight-commander  paket               remake              swiftformat         zanata-client
awscli              docker              gauge               huexpress           libcouchbase        minio               pandoc              rocksdb             termius             zimg
azure-cli@1         docker-completion   gdnsd               ibex                libdivecomputer     miniupnpc           parallel            roswell             terragrunt
bacula-fd           efl                 gegl                iso-codes           libgosu             mockserver          passenger           rust                tgui
blink1              ejabberd            geoserver           jhipster            libhttpseverywhere  mono-libgdiplus     pegtl               scalariform         thefuck
bmake               erlang              geth                knot                libmaxminddb        mpd                 percona-toolkit     sdl2_mixer          tidy-html5
bwfmetaedit         etsh                ghc                 kobalt              librealsense        multimarkdown       plzip               sfk                 tippecanoe
bzt                 exim                git-annex           kompose             libsass             nativefier          pyqt                shairport-sync      traefik
==> Deleted Formulae
clasp                                                                                                gringo

==> Upgrading 1 outdated package, with result:
python 2.7.14
==> Upgrading python
Error: The following formula:
  python
cannot be installed as a binary package and must be built from source.
Install the Command Line Tools:
  xcode-select --install

なんかこけた

xcodeコマンドラインツールないよって言われたので xcode-select --install 実行 そしたらポップアップでウィンドウが出てきたのでそのままインストール

もう一回実行してちゃんとインストールされたか確認

$ xcode-select --install
xcode-select: error: command line tools are already installed, use "Software Update" to install updates

されてますね。

~ brew upgrade python
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (caskroom/cask).
No changes to formulae.

==> Upgrading 1 outdated package, with result:
python 2.7.14
==> Upgrading python
==> Installing dependencies for python: sqlite, openssl
==> Installing python dependency: sqlite
==> Downloading https://homebrew.bintray.com/bottles/sqlite-3.21.0.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring sqlite-3.21.0.high_sierra.bottle.tar.gz
==> Caveats
This formula is keg-only, which means it was not symlinked into /usr/local,
because macOS provides an older sqlite3.

If you need to have this software first in your PATH run:
  echo 'export PATH="/usr/local/opt/sqlite/bin:$PATH"' >> ~/.bash_profile

For compilers to find this software you may need to set:
    LDFLAGS:  -L/usr/local/opt/sqlite/lib
    CPPFLAGS: -I/usr/local/opt/sqlite/include
For pkg-config to find this software you may need to set:
    PKG_CONFIG_PATH: /usr/local/opt/sqlite/lib/pkgconfig

==> Summary
🍺  /usr/local/Cellar/sqlite/3.21.0: 11 files, 3.0MB
==> Installing python dependency: openssl
==> Downloading https://homebrew.bintray.com/bottles/openssl-1.0.2m.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring openssl-1.0.2m.high_sierra.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the SystemRoots
keychain. To add additional certificates (e.g. the certificates added in
the System keychain), place .pem files in
  /usr/local/etc/openssl/certs

and run
  /usr/local/opt/openssl/bin/c_rehash

This formula is keg-only, which means it was not symlinked into /usr/local,
because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries.

If you need to have this software first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl/bin:$PATH"' >> ~/.bash_profile

For compilers to find this software you may need to set:
    LDFLAGS:  -L/usr/local/opt/openssl/lib
    CPPFLAGS: -I/usr/local/opt/openssl/include
For pkg-config to find this software you may need to set:
    PKG_CONFIG_PATH: /usr/local/opt/openssl/lib/pkgconfig

==> Summary
🍺  /usr/local/Cellar/openssl/1.0.2m: 1,792 files, 12.3MB
==> Installing python
==> Downloading https://homebrew.bintray.com/bottles/python-2.7.14.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring python-2.7.14.high_sierra.bottle.tar.gz
==> /usr/local/Cellar/python/2.7.14/bin/python2 -s setup.py --no-user-cfg install --force --verbose --single-version-externally-managed --record=installed.txt --install-scripts=/usr/local/Cellar/pytho
==> /usr/local/Cellar/python/2.7.14/bin/python2 -s setup.py --no-user-cfg install --force --verbose --single-version-externally-managed --record=installed.txt --install-scripts=/usr/local/Cellar/pytho
==> /usr/local/Cellar/python/2.7.14/bin/python2 -s setup.py --no-user-cfg install --force --verbose --single-version-externally-managed --record=installed.txt --install-scripts=/usr/local/Cellar/pytho
==> Caveats
This formula installs a python2 executable to /usr/local/bin.
If you wish to have this formula's python executable in your PATH then add
the following to ~/.bash_profile:
  export PATH="/usr/local/opt/python/libexec/bin:$PATH"

Pip and setuptools have been installed. To update them
  pip2 install --upgrade pip setuptools

You can install Python packages with
  pip2 install <package>

They will install into the site-package directory
  /usr/local/lib/python2.7/site-packages

See: https://docs.brew.sh/Homebrew-and-Python.html
==> Summary
🍺  /usr/local/Cellar/python/2.7.14: 3,325 files, 45.5MB

成功しました。

続いてvimのアップグレード

 ~ brew upgrade vim
==> Upgrading 1 outdated package, with result:
vim 8.0.1300
==> Upgrading vim --with-override-system-vi --with-python3 --with-lua
==> Installing dependencies for vim: python3
==> Installing vim dependency: python3
==> Downloading https://homebrew.bintray.com/bottles/python3-3.6.3.high_sierra.bottle.tar.gz
######################################################################## 100.0%
==> Pouring python3-3.6.3.high_sierra.bottle.tar.gz
==> /usr/local/Cellar/python3/3.6.3/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python3/3.6.3/bin --install-lib=/usr/local/lib/python3.6/site-pa
==> /usr/local/Cellar/python3/3.6.3/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python3/3.6.3/bin --install-lib=/usr/local/lib/python3.6/site-pa
==> /usr/local/Cellar/python3/3.6.3/bin/python3 -s setup.py --no-user-cfg install --force --verbose --install-scripts=/usr/local/Cellar/python3/3.6.3/bin --install-lib=/usr/local/lib/python3.6/site-pa
==> Caveats
Pip, setuptools, and wheel have been installed. To update them
  pip3 install --upgrade pip setuptools wheel

You can install Python packages with
  pip3 install <package>

They will install into the site-package directory
  /usr/local/lib/python3.6/site-packages

See: https://docs.brew.sh/Homebrew-and-Python.html
==> Summary
🍺  /usr/local/Cellar/python3/3.6.3: 3,359 files, 53MB
==> Installing vim --with-override-system-vi --with-python3 --with-lua
==> Downloading https://github.com/vim/vim/archive/v8.0.1300.tar.gz
==> Downloading from https://codeload.github.com/vim/vim/tar.gz/v8.0.1300
######################################################################## 100.0%
==> ./configure --prefix=/usr/local --mandir=/usr/local/Cellar/vim/8.0.1300/share/man --enable-multibyte --with-tlib=ncurses --enable-cscope --enable-terminal --with-compiledby=Homebrew --enable-luain
==> make
==> make install prefix=/usr/local/Cellar/vim/8.0.1300 STRIP=/usr/bin/true
🍺  /usr/local/Cellar/vim/8.0.1300: 1,424 files, 22.8MB, built in 1 minute 36 seconds

ちゃんと8.xでアップグレードされていますね。 しかし、依存関係で取ってきているのはpython3.6・・・ もしかしてpythonのアップグレード関係なかった・・・?

まぁとりあえず動いているので良しとしましょう。