Laravel : プロジェクトの作成

Laravel : プロジェクトの作成

Laravel プロジェクトの作成方法についてまとめる.

環境

  • macOS Big Sur
    • 11.6.5
  • Docker
    • 20.10.12
  • Laravel
    • 9.21.5
  • PHP
    • 8.1.8
  • Composer
    • 2.3.10
  • MySQL
    • 8.0.29

参考

前提

Docker コンテナ上に Laravel プロジェクトを作成する形で進める.
Docker コンテナ上に作成した Laravel プロジェクトは Sail という Laravel に同梱されたコマンドを使用して操作, 連携する.
Sail を使用することで, コンテナ上への各種環境(PHP, Composer, MySQL)を自動で構築してくれて楽.

環境構築

Docker コンテナ上に Laravel プロジェクトを作成するため, ローカル環境には Docker のインストールだけでよい.

Laravel プロジェクト作成

以下のコマンドで Laravel プロジェクトを作成する. プロジェクト名は sample-project.

$ curl -s "https://laravel.build/sample-project?php=81" | bash

コンテナを起動する

Sail を実行しコンテナ上のサーバーを起動する.

  1. 作成したプロジェクトに移動する
  2. $ ./vendor/bin/sail up を実行する
  3. ブラウザから http://localhost にアクセスする
  4. Web サーバに繋がることを確認する

初回 Sail 実行時は Docker イメージのインストールなどで時間がかかる.
以降は $ ./vendor/bin/sail up をすることでコンテナ上のサーバが起動しブラウザからアクセスできる.

Sail のエイリアスを設定する

いちいち $ ./vendor/bin/sail up を入力するのは手間なのでエイリアスを設定する.
.bashrc などの rc ファイルに以下を追記する.

alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'

# テストコマンドで sail ファイルがカレントにあるかチェックし, あれば `bash sail` を実行.
# なければ `vendor/bin/sail を実行する.

.bashrc 更新後は $ source .bashrc で更新内容を反映する.
以降は sail up で実行可能.

Sail のデーモン起動と終了

  • Sail のデーモン起動
    • $ sail up -d
  • Sail の終了
    • $ sail down

コンテナに入る

  • コンテナに入る
    • $ sail shell
  • コンテナから出る
    • $ exit

コンテナ環境のカスタマイズ

コンテナ設定のための Dockerfile の更新方法について.

カスタマイズ用の Dockerfile の作成

以下のコマンドを実行する

$ sail artisan sail:publish

アプリケーションルート(プロジェクトのトップディレクトリ)に docker/ ディレクトリが作成される.
docker/ ディレクトリ内には各バージョンごとのフォルダ(8.0, 8.1 など)が作られる.
適用されるバージョンは docker-compose.ymlservices.laravel.test.build.context に設定されているバージョンのディレクトリ内 Dockerfile が使用される.

この Dockerfile を変更することでコンテナ環境をカスタマイズできる.

更新した Dockerfile の反映

Dockerfile 更新後は以下のコマンドで Docker イメージを再ビルドする.

$ sail build --no-cache

タイムゾーンを変更する

デフォルトではコンテナのタイムゾーンUTC となっているため日本時間に変更する.
docker/x.x/Dockerfile を以下の通り変更する.

  • 変更前
    • ENV TZ=UTC
  • 変更後
    • ENV TZ='Asia/Tokyo'

更新後は Docker イメージをビルドする.

一旦 sail down, sail up -d して立ち上げ直してから
sail shell でコンテナ内に入り date コマンドで日本時間(JST)になっていることを確認する.

MySQL文字コード変更

日本語での開発に合わせて文字コードの設定を変更する.
x.x は使用しているバージョンに合わせて適宜読み替える.

  • docker/x.x ディレクトリに my.cnf ファイルを以下の通り作成する
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin

[client]
default-character-set = utf8mb4
  • 作成した my.cnf ファイルを MySQL コンテナの /etc/ に配置し設定が反映されるよう docker-compose.yml に以下を追記する
mysql:
    volumes:
        - './docker/x.x/my.cnf:/etc/my.cnf'
  • $ sail down でコンテナを停止する
  • $ sail up -d でコンテナを起動する
  • MySQL にアクセスして文字コードが変更されたことを確認する
    • $ sail mysql を実行する
    • show variables like '%char%'; を実行する
    • 各パラメータが utf8 系統になっていることを確認する
  • exitMySQL から出る

git clone 後にやること

作成した Laravel プロジェクトを GitHub などで管理し, git clone で ローカル環境に持ってきた後に実施する手順について.

Sail を使えるようにするため以下を実施する.

docker run --rm ¥
    -u "$(id -u):$(id -g)" ¥
    -v $(pwd):/var/www/html ¥
    -w /var/www/html ¥
    laravelsail/php81-composer:latest ¥
    composer install --ignore-platform-reqs

Laravel : まとめ

Laravel : まとめ

Laravel ノウハウまとめ記事.

プロジェクトの作成

Laravel 9 でのプロジェクトの作成方法や初期環境設定についての記事は以下.

Docker + Apache + Laravel で Web アプリケーションつくる

Docker コンテナ上に構築した Apache サーバー上で Laravel アプリケーションを 動かす環境を構築する.

kita127.hatenablog.com

Laravel : Docker + VSCode でデバッガ環境構築

Docker コンテナで動作する Laravel のデバッグ環境構築手順. ステップ実行や変数の状態を確認できるようになりとても便利.

kita127.hatenablog.com

リクエストを受け取り HTML を表示

クラアントから GET リクエストを受け取り HTML を返す一連の流れの構築方法について記載.

MySQL にテーブル作成

Laravel の機能を使用し MySQL データベースにテーブルを作成する方法について.

入力のバリデーション

POST メソッドの実装と入力のバリデーションについて

画像のアップロード

Gist

無分類でのまとめは Gist に

Raspberry Pi と VPN で監視カメラ制作

Raspberry PiVPN で監視カメラ制作

作ったモノ

自宅にいる犬の様子を外出先からでもリアルタイムで見てみたいと思い システムを Raspberry Pi で構築してみました.

Raspberry Pi に犬の様子の撮影と, 動画配信サーバーをやらせます.

動画配信サーバーには MJPG-streamer, ネットワークの構築にはお手軽に VPN を構築できる Tailscale を使用しました.

犬監視システムの構築手順の覚書を全2回に分けて記事にします.

1回目は Raspberry Pi の導入とリモート接続するまでをまとめました.

2回目となる本記事では Raspberry Pi に動画ストリーミングサーバーの構築と Tailscale を使用した VPN ネットワークの構築を説明します.

技術要素とアイテム

手順

動画ストリーミングサーバー構築

Raspberry Pi のカメラ設定はこの記事では扱いません. カメラ接続済みの状態から説明します.

  1. 必要なパッケージを Raspberry Pi にインストール.
$ sudo apt install -y build-essential imagemagick libv4l-dev libjpeg-dev cmake
  1. MJPG-streamer を GitHub から clone し, make する
$ git clone https://github.com/jacksonliam/mjpg-streamer.git
$ cd mjpg-streamer/mjpg-streamer-experimental
$ sudo make
$ sudo make install
  1. make したディレクトリと同じ階層で以下のコマンドを実行し動画ストリーミングサーバーをポート 8080 で起動する
$ sudo ./mjpg_streamer -i "./input_uvc.so -f 10 -r 640x480 -d /dev/video0 -y -n" -o "./output_http.so -w ./www -p 8080" 
  1. PC のブラウザから Raspberry Pi サーバーにアクセスする
http://raspberrypi.local:8080
  1. MJPG-streamer の画面になったら動画は Stream で, 静止画は Static で閲覧可能

動画だけ閲覧する場合は以下の URL でアクセス.

http://raspberrypi.local:8080/stream_simple.html

Tailscale で VPN 構築

ここまでで, Raspberry Pi にブラウザからアクセスしてカメラの情報がリアルタイムで閲覧できるようになりました.

しかし, これではプライベート IP からしかアクセスできないため, 外部からグローバルにアクセスできるようにします.

いくつか方法がありますが, 今回は家族だけで共有したいため, VPN を構築しそこを経由して外部から閲覧できるようにします.

Tailscale を使用すれば手軽に VPN を構築し外部から自宅にあるサーバーに簡単アクセスできます.
しかも, VPN 接続のため, セキュリティ的にもポートフォワーディングよりも安心感があります.

Tailscale のアカウントを取得する

Tailscale はアカウントを取得し, アカウントに登録した端末間で VPN を構築します. そのためまずは以下の Tailscale のウェブサイトにアクセスし, アカウントを取得します.

googleGitHub のアカウントがあれば, そこから取得できます.

http://city.takarazuka.hyogo.jp

PC にクライアントソフトをインストールする

  1. Tailscale にログイン
  2. Download を押下
  3. 自分の PC 用のクライアントソフトをダウンロードし, インストールする

Raspberry Pi に Tailscale クライアントをインストールする

Raspberry PiVPN ネットワークに参加させるためにクライアントソフトをインストールします.

公式の Raspberry Pi へのインストール手順はこちら.
https://tailscale.com/download/linux/rpi

  1. Tailscale のインストール
$ sudo apt-get install apt-transport-https
$ curl -fsSL https://pkgs.tailscale.com/stable/raspbian/buster.gpg | sudo apt-key add -
$ curl -fsSL https://pkgs.tailscale.com/stable/raspbian/buster.list | sudo tee /etc/apt/sources.list.d/tailscale.list
$ sudo apt-get update
$ sudo apt-get install tailscale
  1. Tailscale に Raspberry Pi をコネクト
$ sudo tailscale up

URL が表示されるためコピーしてブラウザにアクセスする.
Authorization successful が表示されれば OK.

  1. Tailscale のマイページにアクセスし Raspberry Pi の IP アドレスを確認する

確認した IP アドレスで Raspberry Pi にアクセスできることを確認する.

http://<Raspberry PiのIP>:8080

終わり

以上で MJPG-streamer と Tailscale を使用した Raspberry Pi 監視カメラシステムが完成です.

Raspberry Pi 環境構築

Raspberry Pi 環境構築

記事の内容

自宅にいる犬の様子を外出先からでもリアルタイムで見てみたいと思い システムを Raspberry Pi で構築してみました.

Raspberry Pi に犬の様子の撮影と, 動画配信サーバーをやらせます.

動画配信サーバーには MJPG-streamer, ネットワークの構築にはお手軽に VPN を構築できる Tailscale を使用しました.

犬監視システムの構築手順の覚書を全2回に分けて記事にします.

1回目は Raspberry Pi の導入とリモート接続するまでをまとめました.

モノ

環境構築手順

Raspberry Pi を単体で起動

  • SD カードを装着する
  • Raspberry Pi に ディスプレイ, マウス, キーボードを接続
  • Raspberry Pi に USB-C AC アダプタを接続し電力を供給

Raspberry Pi のデフォルトのユーザー名は pi

VNCリモートデスクトップ設定

Raspberry Pi 側の設定

Raspberry Pi にはデフォルトでリモートデスクトップソフトであるVNC(Virtual Network Computing)が搭載されている. そのため, VNC の設定を有効にして, リモートデスクトップ接続の準備をする.

  1. スタートメニューをクリック
  2. 「設定」を選択
  3. Raspberry Pi の設定」を選択
  4. 「インターフェース」タブを選択
  5. VNC を有効にする

また, VNC のセキュリティ設定を変更しておかないと MacVNCクライアントから接続できないため, そちらも設定変更しておく.

以下を参考にさせていただきました.
https://qiita.com/karaage0703/items/9650e7aeceb6e1b81612#comment-467f53a421bea472cf81

  1. ラズパイ画面右上に表示されている V2 をクリック
  2. ハンバーガーメニューをクリックし Option を選択する
  3. Security を選択し以下の設定にする
    • Encryption
      • Prefer off
    • Authenticatior
  4. Users & Permissionsで、Standard userをダブルクリック
  5. パスワードを設定する

Raspberry Pi の IP アドレスを確認する. terminal から ip addr などで Raspberry Pi の IP アドレスを調べておく.

PC(Mac) から VNC リモート接続

以下で Mac から Raspberry Piリモートデスクトップ接続できる.

  • Finder を起動
  • 「移動」タブを選択
  • 「サーバーへ接続」をクリック
  • vnc://raspberrypi.local を入力
  • 「接続」を押下

SSH でリモート接続

SSH でもリモート接続できることを確認しておく.

terminal から ssh pi@raspberrypi.local で接続. pi はユーザー名.

HDMI接続なしでもデスクトップ起動に設定

このままだと, Raspberry Pi がディスプレイに接続された状態でないと VNC 接続できないため, ディスプレイに接続されていない状態でも接続できるように設定を変更する.

以下を参考にさせていただきました.
https://algorithm.joho.info/raspberry-pi/cannot-currently-show-the-desktop-raspberry-pi/

  1. Raspberry Pi で以下のコマンドを実行し設定ファイルを開く
    • sudo nano /boot/config.txt
      • エディタは vi とかもある
  2. #hdmi_force_hotplug=1コメントアウトをはずし hdmi_force_hotplug=1 に変更する

以上で Raspberry Pi をディスプレイに接続していない状態でも VNC 接続できるようになる.

解像度の設定をする

このままだと, 解像度があっていないため変更する. Raspberry Pi 側で以下の設定をする.

  1. terminal を起動
  2. raspi-cofig を入力
  3. Diaplay Option
  4. Resoltion
  5. DMT Mode 82 1920x1080
  6. sudo nano /boot/config.txt
  7. 以下コードの設定となっていることを確認する
  8. 解像度の変更
    1. Raspberry Pi のデスクトップ画面で
    2. メニュー ー> 設定 ー> Screen Configuration
    3. Configure -> Screens -> HDMI-1 -> 解像度 -> 1920x1080
    4. Configure -> 適用
# uncomment if hdmi display is not detected and composite is being output
hdmi_force_hotplug=1

# uncomment to force a specific HDMI mode (this will force VGA)
hdmi_group=2
hdmi_mode=82

環境構築完了

ここまでの設定で Raspberry PiVNCSSH で接続できる環境が整う.

次回, 動画ストリーミングサーバーの構築と VPN ネットワークの構築.

電気素人のプルアップ回路解説

電気素人のプルアップ回路解説

ブログの内容

プルアップ回路についてざっくり以下のように暗記していますが正直その原理が不明でした.

  • スイッチが OFF 時にマイコン入力状態が ON になる
  • スイッチが ON 時にマイコン入力状態が OFF になる

というのも, 例えば以下のような 5V 電源の回路の場合, スイッチが OFF 時マイコンの入力端子には 5V が印加される...というのは理解できます. スイッチが ON の場合はマイコン入力端子には 0V が印加される...なぜ?電源と回路的につながっているため 5V が印加されるのでは?と考えてしまいます.

f:id:kita127:20210725145127p:plain

このあたりについて, あまりどのブログにも原理が書かれておらず, おそらくは基本的な内容のためわざわざ書いていないのではないかと思われます. そこまで基本的な原理であれば恐らくは義務教育で習う範疇の知識ではないかと思ったので中学2年の理科の教科書をやりなおした後, プルアップ回路についてリベンジしてみました.

その結果, 一応, 自分なりには結論を導き出せた気分なので, それを本ブログにまとめます.

なお, プルアップ回路については以下の「プルアップ・プルダウン抵抗とは?電子回路に必須の考え方」を参考にさせていただきました. 本ブログは以下のページを読んだ上で, 電気まわりで不明点がある場合に読んでいただくと良いかと思います.

https://voltechno.com/blog/pullup-pulldown/

プルアップ回路とは

ここではプルアップ回路の利用意図について説明しますが詳細については参考サイトの「プルアップ抵抗・プルダウン抵抗とは?電子回路に必須の考え方」を参照してください.

マイコンの入力端子は常に電源側かグランド側に接続しなければならず, どちらにも接続されていない浮いた状態(ハイインピーダンス)になってはいけません. プルアップ, プルダウン回路はマイコンの入力端子が浮いた状態にならないようスイッチが ON/OFF どちらの状態でも マイコン入力端子を Hi/Lo いずれかに倒すための回路です.

スイッチ ON 時の原理

それではプルアップ回路でのスイッチ ON 時にマイコンの入力端子に 0V が印加される自分なりの考察を説明します.

f:id:kita127:20210725145241p:plain

まず以下の前提があります.

  • 電源電圧は 5V
  • プルアップ抵抗とマイコン入力端子の抵抗(Rm)ではマイコン入力端子の抵抗のほうが遥かに大きい
  • スイッチが閉じられた時のスイッチの抵抗はほぼ 0Ω とする

この図では抵抗は以下の3つが存在します.

  • プルアップ抵抗
  • 閉じられたスイッチ
  • マイコンの入力端子抵抗
    • 抵抗 Rm とする

ここで求めたいこととしてはスイッチ ON 時の Rm にかかる電圧になります.

Rm とスイッチは並列回路のためスイッチと Rm のそれぞれにかかる電圧は等しくなります. そのため, スイッチにかかる電圧を求めると Rm の電圧がわかります.

f:id:kita127:20210725145300p:plain

なぜ並列回路のそれぞれの抵抗にかかる電圧が等しくなるかのメカニズムまでは私にはわかりません. とりあえず中学2年の理科の教科書でそのように書いているためそうとしています. ただ実地で考えてもテスタなどで電圧を計測する際は並列につないで計測対象の電圧を計るところから, 現実的にそうなることはテスタを使ったことがある人は体験していると思います.

話はそれましたが, スイッチにかかる電圧を求めれば Rm の電圧もわかるため, スイッチの電圧を求めます. 求め方はオームの法則を使用します. オームの法則は以下の式です.

電圧(V) = 電流(I) × 抵抗(R)

スイッチの抵抗はほぼ 0Ω のため

V = スイッチに流れる電流 × 0Ω = 0V

となり, 抵抗が 0Ω なのでスイッチにかかる電圧は 0V となります. そのため Rm の電圧も 0V となります.

以上によりプルアップ回路でスイッチを ON した時にはマイコンの入力端子には 0V が印加されます.

スイッチ OFF 時の原理

次はプルアップ回路でのスイッチ OFF 時にマイコンの入力端子に 5V が印加される理由の自分なりの考察です.

f:id:kita127:20210725145319p:plain

前提はスイッチ ON 時の前提と同じです.

スイッチ OFF の場合プルアップ抵抗とマイコン入力端子の抵抗は直列回路となります.

直列回路の場合「プルアップ抵抗にかかる電圧 + Rm の電圧 = 電源電圧(5V)」となります. また, プルアップ抵抗と Rm それぞれにかかる電圧の比は抵抗の大きさに比例します. (このあたりも中学2年理科の範疇です)

f:id:kita127:20210725145334p:plain

前提より抵抗値は Rm の方がプルアップ抵抗より遥かに大きいため Rm 側に 5V ほぼすべての電圧が印加されます.

そのためスイッチ OFF 時の Rm は 5V となります.

Node.js + Express + Sequelize で Web アプリ作成手順

Node.js + Express + Sequelize で Web アプリ作成手順

本記事の目的

Node.js + Express + Sequelize で Web アプリケーション作成のための環境構築から手順までをまとめる.

本記事は以下の書籍「作りながら学ぶWebプログラミング実践入門」の内容を自分なりに消化, 改修した内容となっている.

https://book.mynavi.jp/ec/products/detail/id=112778

今回作成したアプリケーションのリポジトリは以下

https://github.com/kita127/todo-app

各技術要素の概略

Sequelize と Mocha は「作りながら学ぶWebプログラミング実践入門」には登場しない要素.

環境構築

Node.js の導入

まずはメイン言語となる Node.js の導入.

nodebrew の導入

Node.js はバージョンの更新が目まぐるしいため, 通常は直接インストールせずバージョンマネージャをインストールして 任意のバージョンに切り替えつつ使用するのが一般的らしい.

Mac 環境では nodebrew というバージョンマネージャがよく使われるらしいのでそちらを導入する.

https://github.com/hokaccha/nodebrew

curl でインストールする

$ curl -L git.io/nodebrew | perl - setup

.bashrc 等の設定ファイルの PATH の設定に nodebrew のパスを追加する.

$ export PATH=$HOME/.nodebrew/current/bin:$PATH

設定ファイルをリロードする.

$ source ~/.bashrc

nodebrew のコマンドが使用できることを確認する.

$ nodebrew help
nodebrew 1.1.0

Usage:
    nodebrew help                         Show this message
    nodebrew install <version>            Download and install <version> (from binary)
    nodebrew compile <version>            Download and install <version> (from source)
    nodebrew install-binary <version>     Alias of `install` (For backward compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for `list`

nodebrew から Node.js のインストール

今回は最新のバージョンを導入する.

$ nodebrew install latest

意図したバージョンの Node.js がインストールされたことを確認する.

$ node -v
v16.0.0

Express の導入

Express はプロジェクトの雛形を生成するツール express-generator をグローバル環境に導入する.

$ npm install express-generator -g

プロジェクトの雛形を作成する. 以下のコマンドでテンプレートファイルに ejs を指定し, project-name で Express プロジェクトの雛形を作成する.

$ express -v ejs project-name

作成したプロジェクトフォルダに移動すし, 依存パッケージをインストールする.

$ cd ./project-name
$ npm install

作成したプロジェクトを試しに動かしてみる.

$ npm start

ブラウザから http://localhost:3000 にアクセスする. 以下が表示されることを確認する.

Express
Welcom to Express

Express の説明

Express の使い方

routes/ フォルダに任意の url にアクセスした際の制御を記述する.

以下はrouter/hoge.js を作成し http://localhost:3000/hoge にアクセスした際の制御. res.render() の第一引数にレンダリングする ejs ファイルを指定する. 第二引数には ejs ファイルに渡すオブジェクトを指定する.

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
    res.render('hoge', { title: 'hoge' });
});

module.exports = router;

レンダリングする ejs ファイルを view フォルダに作成する.

view/hoge.ejs を以下の通り作成.

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Welcome to <%= title %></p>
  </body>
</html>

app.js に以下の設定を追加する. 以下の設定で作成した hoge.js が URL /hoge と紐づく. 以下の設定により /hogehoge.js におけるルートとなるため router.get('/', ....) の記述で http://localhost:3000/hoge にアクセスした場合の処理の記述となる.

// 作成したルータ hoge.js を require する
var hogeRouter = require('./routes/hoge');

    .
    .
    .

// 作成した hogeRouter に URL を紐付ける
app.use('/hoge', hogeRouter);

Sequelize

Sequelize を導入する. Sequelize は Node.js 用の OR マッパー.

導入

$ npm install sequelize

sequelize-cli の導入

sequelize を便利に使用するための CLI ツールを導入する.

$ npm install sequelize-cli

sequelize を初期化する

$ npx sequelize-cli init

以下のフォルダが作成されることを確認する.

  • config
    • 設定情報管理
  • models
    • データベースアクセスに使う「モデル」というオブジェクトを定義する

config.json に設定をする

SQLite3 を使用する場合は以下のような感じで設定する.

{
  "development": {
    "database": "db-development",
    "dialect": "sqlite",
    "storage": "seq-todo.sqlite3"
  },
  "test": {
    "database": "db-test",
    "dialect": "sqlite",
    "storage": "seq-todo.sqlite3"
  },
  "production": {
    "database": "db-product",
    "dialect": "sqlite",
    "storage": "seq-todo.sqlite3"
  }
}

モデルを作成する

データベースのテーブルにアクセスするためのオブジェクトであるモデルを作成する. モデルの作成は sequelize-cli のコマンドで行う.

以下は users というテーブルを作成, account, password, name, role といったカラムを持つ.

$ npx sequelize-cli model:generate --name users --attributes account:string,password:string,name:string,role:string

以下が生成されることを確認する.

  • models/users.js
  • migrations/yyyymmddxxxxxxxx-create-users.js

マイグレーションを実行する

データベースの内容を変更した場合にその差分をデータベースに適用することをマイグレーションと呼ぶ.

今回は新たにモデルを作成したため、その変更をデータベースに反映する. これによりモデルを作成した users テーブルが seq-todo.db に作成される.

$ npx sequelize-cli db:migrate --env development

シーディングによりレコードを作成する

seq-todo.db に users テーブルを作成したが, レコードは何もない. そのため, あらかじめレコードを作成する. こうしたはじめに用意しておくデータをシードと呼ぶ.

シーディング作成用のスクリプトファイルを生成する.

$ npx sequelize-cli seed:generate --name sample-users

seeders/yyyymmddxxxxxxxx-sample-users.js が生成されているためその up に生成するレコードの情報を記述する.

up: async (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('users', [
        {
            account: 'admin.com',
            password: 'admin',
            name: 'admin',
            role: 'admin',
            createdAt: new Date(),
            updatedAt: new Date()
        }
    ]);
    /**
     * Add seed commands here.
     *
     * Example:
     * await queryInterface.bulkInsert('People', [{
     *   name: 'John Doe',
     *   isBetaMember: false
     * }], {});
    */
},

シーディングを実行する.

$ npx sequelize-cli db:seed:all

Mocha

ユニットテスト用のフレームワークとして Mocha を導入する.

# mocha をインストール
$ npm install mocha --save-dev

# プロジェクト直下の test ディレクトリ内にある .js ファイルを対象に mocha はテストする
$ mkdir test

# テストモジュール作成
$ touch ./test/test-sample.js

テストモジュールに以下のようにテストを記述する.

describe('TEST SAMPLE', () => {
    it('test 1', (done) => {
        if ('aaa' === 'aaa') {
            done();
        }
        else {
            done('失敗');
        }
    });
});

テスト実行

$ npx mocha

C ソースをパースしてシンボルを抽出する Go ライブラリ symc を作った

C ソースをパースしてシンボルを抽出する Go ライブラリ symc を作った

注意 このライブラリは個人的な問題を解決するために作成したツールのため、かなり適当です. ご使用の場合はご注意ください.

C ソースをパースして変数, 関数の定義や使用箇所を抽出する Go ライブラリを作成した.

https://github.com/kita127/symc1

作った背景

普段 C ソースを相手に仕事をしてるが, ソースを静的に解析する機会など多い. ソース内にある識別子がどこで定義されどこで使用されるかなどの情報がそういった解析時に欲しくなる時がある. ソースを解析して識別子情報だけを手軽に抽出するライブラリを作成すれば, 応用して色々なツールが作れるのではないかと考えた. ツール類は Go で作成する機会が多いため, Go ライブラリとして作成.

機能

主な機能は以下.

  • プリプロ展開後の C ソースを入力とする
    • プリプロ解析からやるのは環境による差異など色々困難なため妥協
    • プリプロ展開していないソースを入力した場合, エラーで死にます
  • 解析したモジュールに対して以下の情報を抽出する
    • 定義している変数
    • 定義している関数
    • extern 宣言している変数
    • プロトタイプ宣言している関数
    • 関数内で参照している変数
    • 関数内でコールしている関数の情報

使い方

使い方は以下のとおり.

package main

import (
    "fmt"

    "github.com/kita127/symc"
)

func main() {

    cSrc := `
int variable;

int extFunc( int a );

int func( void ){

    variable++;

    return extFunc( variable );
}

`

    module := symc.ParseModule(string(cSrc))
    fmt.Println(module)
}
$go build && ./main
Module : Symbols={ VariableDef : Name=variable, PrototypeDecl : Name=extFunc, FunctionDef : Name=func, Params=[], Symbols=[Assigne : Name=variable CallFunc : Name=extFunc, Args=[RefVar : Name=variable]] }

github.com/kita127/symc をインポートして, symc.ParseModule() に C ソースの文字列を渡せば, そのソースの識別子情報を AST の構造体として返してくれる.

上記では C ソース解析後, そのソースの情報を持つ AST である symc.Module 構造体のポインタを取得している.

取得した symc.Module 構造体のポインタは 変数の定義VariableDef や関数の定義FunctionDef 情報などを保持してる.

関数の定義情報の中にはその関数内で参照している変数などの情報も取得できる.

その他の機能

PrettyString()

解析した情報を少し見やすくする Pretty string 機能.

package main

import (
    "fmt"
    "io/ioutil"
    "os"

    "github.com/kita127/symc"
)

    cSrc := `
int variable;

int extFunc( int a );

int func( void ){

    variable++;

    return extFunc( variable );
}

`


func main() {
    module := symc.ParseModule(string(cSrc))
    fmt.Println(module.PrettyString())
}
>go build && ./main
DEFINITION variable
PROTOTYPE extFunc
FUNC func() {
    ASSIGNE variable
    extFunc(variable)
}

Inspect()

引数に解析済みの AST と処理関数を受け取り, AST を深さ優先で走査しながら引数の関数を適用する.
go 標準の ast パッケージにも同様の機能がありそちらを参考にした.

以下は AST を走査し, 変数定義の場合は変数名を大文字に変更する例.

引数で渡す関数が false を返すとそのタイミングで走査をやめることもできる. 以下の例は常に true を返すため, AST を走査し終わるまで走査をやめない.

symc.Symbol は変数定義や関数定義などシンボル情報が実装しているインターフェースであり, AST を構成している型は全て実装している. そのため Inspect() に渡す値は *Module 以外の *VariableDef*FunctionDef 等でも良い.

package main

import (
    "fmt"
    "strings"

    "github.com/kita127/symc"
)

func main() {

    cSrc := `
int g_var;

void func(int a)
{
    int hoge;
    char fuga;

    fuga = cal( hoge );
}
`
    module := symc.ParseModule(cSrc)

    // To uppercase only variable definitions.
    symc.Inspect(module, func(s symc.Symbol) bool {
        if v, ok := s.(*symc.VariableDef); ok {
            v.Name = strings.ToUpper(v.Name)
        }
        return true
    })

    fmt.Println(module)

}
$ go build && ./main
Module : Symbols={ VariableDef : Name=G_VAR, FunctionDef : Name=func, Params=[VariableDef : Name=A], Symbols=[VariableDef : Name=HOGE VariableDef : Name=FUGA 
Assigne : Name=fuga CallFunc : Name=cal, Args=[RefVar : Name=hoge]] }

ReduceLocalVar()

ローカル変数の情報は不要だったり, 冗長だったりする場合もあるため, 削除するメソッドを実装.

以下の例では, module の情報を生成後, ReduceLocalVar() メソッドでローカル変数の定義, 参照, 代入の情報のみ削除している.

package main

import (
    "fmt"
    "github.com/kita127/symc"
)

func main() {

    cSrc := `
int g_var;
static int s_var;
extern short ext_var;

void func(int arg)
{
    int i;
    int l_var;

    for(i = 0; i < 10; i++){
        g_var++;
    }

    l_var = ext_var;
    l_var += s_var;
    cal( l_var );

    g_var = l_var;
}
`

    module := symc.ParseModule(cSrc)

    fmt.Println("before reducing")
    fmt.Println(module.PrettyString())

    // Reducing local variable infomation.
    fmt.Println("after reducing")
    module.ReduceLocalVar()
    fmt.Println(module.PrettyString())
}
$ go build && ./main
before reducing
DEFINITION g_var
DEFINITION s_var
DECLARE ext_var
FUNC func(DEFINITION arg) {
    DEFINITION i
    DEFINITION l_var
    ASSIGNE i
    i
    ASSIGNE i
    ASSIGNE g_var
    ASSIGNE l_var
    ext_var
    ASSIGNE l_var
    s_var
    cal(l_var)
    ASSIGNE g_var
    l_var
}


after reducing
DEFINITION g_var
DEFINITION s_var
DECLARE ext_var
FUNC func(DEFINITION arg) {
    ASSIGNE g_var
    ext_var
    s_var
    cal()
    ASSIGNE g_var
}

注意点

Macgcc(clang?) でしか確認していないため他のコンパイラプリプロ展開したソースは試していない. モダンな C 言語の構文には対応していない. 多分, C90 くらいまでの構文はいけると思う(適当).

また冒頭の注意でも記載している通り, 個人的な問題を解決するために作成したライブラリのため, さほど厳格には作っていない. ご使用の場合はその辺りを留意した上でお願いします.

反省点とその他

ライブラリ作成途中に goyacc という Go 用のパーサジェネレータの存在を知った. 本来であればこういった構文解析系のライブラリは既存のパーサジェネレータ等を使うべきだが, かなり作ってから知ったのでそのまま作り切った. (ちなみに自前で字句解析・構文解析している)

一応, ユニットテストをこまめに作りながら作成を進めたので大体いけているとは思う.