Skip to content

Instantly share code, notes, and snippets.

@junkor-1011
Last active May 13, 2023 08:34
Show Gist options
  • Save junkor-1011/bbcb2fd302c0d7edb2a53bd55ef864d2 to your computer and use it in GitHub Desktop.
Save junkor-1011/bbcb2fd302c0d7edb2a53bd55ef864d2 to your computer and use it in GitHub Desktop.
LT資料: Makefile as a task runner
root = true
[*]
end_of_line = lf
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
python-example
go-example

セットアップ用ノート

スライドショー立ち上げ:

# http://localhost:8000
make slideshow

# portを変更 (http://localhost:XXXX)
make slideshow PORT=<XXXX>

サンプルプロジェクト用ディレクトリ生成:

# 全部生成
make
# or
make all

# pythonサンプルのみ
make python-example

# goサンプルのみ
make go-example

参考

.venv
*.py[cod]
__pycache__/
*$py.class

Note for go-example

Goは機械語にコンパイルされた実行用バイナリファイルをビルドする点でCやC++に近いため、 GoのプロジェクトでMakefileが利用されるケースもそれなりに多い模様なので参考として作成

主な操作:

  • make build
    • 実行用のバイナリファイルをビルドする
    • 既にビルド済みの場合、ソースコードが変更されない限り再ビルドはされない
  • make run
    • go runコマンドを利用してコンパイル無しでソースコードを実行する
  • make clean
    • ビルドされたバイナリファイルを削除する
MAKEFLAGS += --warn-undefined-variables
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := build
.PHONY: build run clean help
go-example: go.mod main.go
go build
build: go-example ## build binary file
run: ## run app
@go run main.go
clean: ## cleanup binary
rm go-example
help: ## Print this help
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
module go-example
go 1.20
<!DOCTYPE html>
<html>
<head>
<title>GNU Make as TaskRunner</title>
<meta charset="utf-8">
<style>
@page {
size: 1210px 681px;
margin: 0;
}
@media print {
.remark-slide-scaler {
width: 100% !important;
height: 100% !important;
transform: scale(1) !important;
top: 0 !important;
left: 0 !important;
}
}
body { font-family: sans-serif; }
h1, h2, h3 {
font-weight: normal;
}
.remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; }
</style>
</head>
<body>
<textarea id="source">
class: center, middle
# Title
---
# Agenda
1. Introduction
2. Deep-dive
3. ...
---
# Introduction
</textarea>
<script src="https://remarkjs.com/downloads/remark-latest.min.js">
</script>
<script>
var slideshow = remark.create({
sourceUrl: 'slides.md',
});
</script>
</body>
</html>
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
"""
FastAPI sample-app
original: https://fastapi.tiangolo.com/ja/tutorial/first-steps/
"""
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
MAKEFLAGS += --warn-undefined-variables
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := all
# 作成するサンプルプロジェクト
PYTHON_EXAMPLE := "python-example"
GO_EXAMPLE := "go-example"
# all targets are phony
.PHONY: all
all: python-example go-example
python-example: ## create python-example
@echo 'Start to create $@'
@mkdir $(PYTHON_EXAMPLE)
@cp python-example.mk $(PYTHON_EXAMPLE)/Makefile
@cp python-example.md $(PYTHON_EXAMPLE)/README.md
@cp pre-commit-config-python-example.yaml $(PYTHON_EXAMPLE)/.pre-commit-config.yaml
@cp gitignore_python-example $(PYTHON_EXAMPLE)/.gitignore
@cp main.py $(PYTHON_EXAMPLE)
@cp requirements.txt $(PYTHON_EXAMPLE)
@echo 'Finished to create $@'
go-example: ## create go-example
@echo 'Start to create $@'
@mkdir $(GO_EXAMPLE)
@cp go-example.mk $(GO_EXAMPLE)/Makefile
@cp go-example.md $(GO_EXAMPLE)/README.md
@cp main.go $(GO_EXAMPLE)
@cp go.mod $(GO_EXAMPLE)
@echo 'Finished to create $@'
# taskB: ## executes task B
# @echo 'Starting $@'
# sleep 1
# @echo 'Finished $@'
clean:
rm -rf $(PYTHON_EXAMPLE)
rm -rf $(GO_EXAMPLE)
help: ## Print this help
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
PORT = 8000
slideshow: ## start localserver for slideshow: ex)make slideshow PORT=xxxx
@python3 -m http.server --bind 127.0.0.1 ${PORT}
# example from https://pre-commit.com/#2-add-a-pre-commit-configuration
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black

Note for python-example

注意:

  • 予めpre-commitをインストールして使えるようにしておくこと
    • 例えばpython3 -m pip install -U --user pre-commitなど
    • PEP668(Status: Accepted)によれば--userオプション付きでも仮想環境外でのpipの使用はNGらしいので、 代わりにpipx install pre-commitなどとする

ポイント:

  • 趣旨通り、よくやりそうな操作をタスク化している
  • その上で、Targetの依存関係を利用して以下のような工夫を施している:
    • venv仮想環境のセットアップ・ライブラリのインストールなどは基本的に初回のみ(.venvディレクトリが無いとき)に実行される、といった形で無駄なタスクが走らないようにしている
    • 必要な初期セットアップを他のタスクの依存関係に含めることで、コマンド1回でプロジェクトの実行をできるようにしていたり、git hooksの追加などのやり忘れを防止している

主な操作:

  • make setup
    • venv仮想環境のセットアップと、gitのpre-commit hookの設定
    • 他のタスクの依存関係に含まれるため、スキップしても必ず1回は実行されてやり忘れがない
    • 逆に、一度セットアップすると以後は余分に実行されることが無い
  • make dev
    • いわゆるwatchモード(ソースコードに変更があると再読み込み・再起動する)でFastAPIアプリを起動する
    • setupを行っていない場合は先に自動で実行する
  • make start
    • 本番に近い設定で設定FastAPIアプリを起動する
    • setupを行っていない場合は先に自動で実行する
  • make clean
    • 不要なファイルやディレクトリ(__pycache__など)および、仮想環境を削除してクリーンアップする
MAKEFLAGS += --warn-undefined-variables
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := setup
PYTHON = python3
VENV = .venv
# all targets are phony
# .PHONY: $(shell egrep -o ^[a-zA-Z_-]+: $(MAKEFILE_LIST) | sed 's/://')
.PHONY: setup dev start clean help
.git:
[[ ! -d .git ]] && git init
.git/hooks/pre-commit: .git
[[ -d .git ]] && pre-commit install
$(VENV): requirements.txt
$(PYTHON) -m venv $(VENV)
$(VENV)/bin/python3 -m pip install --upgrade -r requirements.txt
@touch $(VENV)
setup: $(VENV) .git .git/hooks/pre-commit ## setup project
dev: setup ## start FastAPI app (with reload)
source $(VENV)/bin/activate; uvicorn main:app --reload
start: setup ## start FaastAPI app
source $(VENV)/bin/activate; uvicorn main:app
clean: ## cleanup project
rm -rf $(VENV)
find . -type f -name *.pyc -delete
find . -type d -name __pycache__ -delete
help: ## Print this help
@echo 'Usage: make [target]'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
# install fastapi[all] & uvicorn[default]
anyio==3.6.2
certifi==2023.5.7
click==8.1.3
dnspython==2.3.0
email-validator==2.0.0.post2
fastapi==0.95.1
h11==0.14.0
httpcore==0.17.0
httptools==0.5.0
httpx==0.24.0
idna==3.4
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.2
orjson==3.8.11
pydantic==1.10.7
python-dotenv==1.0.0
python-multipart==0.0.6
PyYAML==6.0
sniffio==1.3.0
starlette==0.26.1
typing_extensions==4.5.0
ujson==5.7.0
uvicorn==0.22.0
uvloop==0.17.0
watchfiles==0.19.0
websockets==11.0.3

class: center, middle

GNU makeをタスクランナーとして使う


背景(タスクランナーの有用性)

プロジェクトのセットアップ、ビルド、実行、デプロイとしった開発作業のライフサイクル中で頻繁、 あるいは確実に行う操作をスクリプト化しておいてポータビリティ性を確保することは開発の効率化やミスの低減につなげることになり、 そのためにいわゆるタスクランナーと言われるツールの活用が有効である。

nodejsのnpm-scriptsなど言語・ランタイムに初めからタスクランナー機能が組み込まれていれば良いが、 一般的にはタスクランナー機能を別途用意する必要があり、その選択肢のの一つとしてGNU make(Makefile)の活用が挙げられる。


タスクランナーとしてのGNU make

  • GNU makeは一般的にCやC++プロジェクトなどのビルドやインストールなどに使われることが多い
  • しかし、C, C++に限らず任意の開発言語で使えるタスクランナーとしても有用
    • プロジェクトのセットアップ、ビルド、実行、デプロイといった一連の操作をタスクとして定義することができる
    • タスク間依存関係も簡単に記述可能(タスクBは必ずタスクAを実行してからやる、など)
  • 3rdパーティー製のタスクランナー用ツール(例えばGulpなど)に比べると、 余計なものをほとんどインストールしなくて良い点で導入ハードルが非常に低い利点がある
    • 大抵のLinuxディストリビューションやmacOSを始めとするほとんどの*nix系OSで標準的に使うことができる
    • 枯れている技術なので、ツールのアップデートへの追随なども不要

インストール

そもそもデフォルトで入っていることが多い。

一方、Dockerイメージなどパッケージが削られているケースでは入っていないケースもあるが、そのような場合でも

# debian系
sudo apt install make

# redhat系
sudo dnf install make

など、ディストリビューション標準のパッケージマネージャーからほぼ確実にインストールできる


使い方について

Makefile、あるいは拡張子.mkのテキストファイルを作成し、 そのファイルが置かれているディレクトリでmakeコマンドを実行して使う

文法などの基本事項は「参考」で挙げているリンクなどを参照のこと

今回は適当な例を2つ作成している(makeコマンドで生成できる):

  • python-example
  • go-example

注意点など

  • (makeに限った話ではないが、) あまりにも自由にタスク・コマンドを作っていくと本来想定されるmakeの使い方から大きく外れて収集がつかなくなる可能性がありそう
  • コマンドに引数を与える機能は貧弱なので、ユースケースによっては機能不足になる可能性はある

参考


class: center, middle

End

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment