kanta's spike

PDFで公開されているMake本をマージする | kanta’s spikeで、公開されているGNU Make 第3版のPDFをマージするシェルスクリプトを作成したことを紹介しました。

折角なので、この『Make本』を読んで学びたいと思います。 そして、勉強の成果として、PDFで公開されているMake本をマージする | kanta’s spikeで作成したシェルスクリプトをmakefileに置き換えたいと思います。

Makeについてはこれまで、メタプログラミング · the missing semester of your cs educationで学んだり、コピペでツールインストール用に使用してきました。1

この機会に、ちゃんと make について学びたいと思います。

ターゲットと依存関係の整理

makefileの基本構成は以下になるようです。

target: prereq1 prereq2
    commands

用語の意味は以下になります。

  • target: ファイルもしくは何か作り出されるもの
  • prereq: 必須項目(または依存項目)。ターゲットが作成される前に必要なもの
  • commands: 必須項目からターゲットを作るためのコマンド

以前作成したシェルスクリプトをもとに、『Make本』のマージ版PDFを作成するための、依存関係を整理します。

target prereq メモ
マージ版目次付きpdf2 マージ版PDF3 make_book_pdf/03_add_toc.shから整理
目次情報ファイル4
マージ版PDF3 ダウンロードした章別PDF make_book_pdf/02_merge_pdf.shから整理
改訂情報PDF5
ダウンロードした章別PDF 章別PDFのURL一覧6 make_book_pdf/01_download_pdf.shから整理
PDFダウンロード用ディレクトリ7
改訂情報PDF5 改訂情報PDFの原本8 cpで作成
PDFダウンロード用ディレクトリ7 mkdir -pで作成
目次情報ファイル4 手動で作成
章別PDFのURL一覧6 手動で作成
改訂情報PDFの原本8 手動で作成

疑問点の整理

前項でシェルスクリプトからターゲットと依存関係を整理しました。 しかし、いくつか疑問があります。

「Make本」を読んで、疑問点と対策案をまとめてみました。

疑問 対策案 参照
ダウンロードした章別のPDF群は、どのようにターゲットを定義するのか? $(TOUCH) build/downloaded でダミーファイルとして対応する? 11.1.4 ソーステキストの検証
commandsとしてwhile文などの数行あるシェルスクリプトを記載できるのか? 1行のスクリプトを\で改行することで対応できそう 5.1.1 長いコマンド
curlgitなどのコマンドを利用しているが、それらのコマンドの存在チェックは必要か? コマンドを変数で定義して、環境に応じて再定義が必要なことをREADMEに記載しておく 7章 ポータブルなmakefile

Makefileの作成

これまでの整理を踏まえて作成したMakefileが以下になります。

BUILD_DIR := ./build
DOWNLOAD_DIR := $(BUILD_DIR)/pdf
OUTLINE_TOOL_DIR := $(BUILD_DIR)/pdfoutline
URL_LIST := ./URL.txt
TOC_FILE := ./toc.txt
ADDITIONAL_PDF := ./additional_info.pdf
COMBINED_PDF := $(BUILD_DIR)/_make.pdf
OUTPUT_PDF := make_merged.pdf
DOWNLOAD_INTERVAL_SEC := 15

# for debug
QUIET = @

# commands
TOUCH = touch
CURL = curl
GS = gs
GIT = git
PYTHON3 = python3

.PHONY: all
all: $(OUTPUT_PDF)


# add toc to combined pdf
$(OUTPUT_PDF): $(TOC_FILE) $(OUTLINE_TOOL_DIR) $(COMBINED_PDF)
	$(PYTHON3) $(OUTLINE_TOOL_DIR)/pdfoutline.py $(COMBINED_PDF) $(TOC_FILE) $@

# clone tool
$(OUTLINE_TOOL_DIR):
	$(GIT) clone https://github.com/yutayamamoto/pdfoutline.git $@

# combine pdf
$(COMBINED_PDF): $(BUILD_DIR)/downloaded $(ADDITIONAL_PDF)
	cp -p $(ADDITIONAL_PDF) $(DOWNLOAD_DIR)/025.pdf
	$(GS) -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=$@ -dBATCH $(DOWNLOAD_DIR)/*.pdf

# download from URL_LIST
$(BUILD_DIR)/downloaded: $(URL_LIST) $(DOWNLOAD_DIR)
	$(QUIET) idx=1;							\
	while read line || [ -n "$${line}" ];				\
	do								\
		output_path=$(DOWNLOAD_DIR)/`printf "%02d" $$idx`0.pdf;	\
		echo "download $${line} to $${output_path} ...";	\
		$(CURL) $${line} -o $${output_path};			\
		idx=$$(($$idx+1));					\
		echo "sleep $(DOWNLOAD_INTERVAL_SEC)sec...";		\
		sleep $(DOWNLOAD_INTERVAL_SEC);				\
	done < $<
	$(TOUCH) $@

# make download dir
$(DOWNLOAD_DIR):
	mkdir -p $(DOWNLOAD_DIR)


# clean
.PHONY: clean
clean:
	rm -rf $(DOWNLOAD_DIR)
	rm $(COMBINED_PDF)
	rm $(BUILD_DIR)/downloaded
	rm $(OUTPUT_PDF)

.PHONY: clean-all
clean-all:
	rm -rf $(BUILD_DIR)
	rm $(OUTPUT_PDF)

まとめ

生成物(ターゲット)が依存するファイルが新しくなったら、makeすると自動的に更新されるので、便利になりました。

作成済の他ツールのMakefileを修正しながら、もっとmakeになれていきたいと思います。

学んだ事

  • シェルスクリプトと組合せる場合、for文などは1行で記載し、\で改行する 9

    例えば、このようなfor文の場合を考える。

    for i in 1 2 3
    do
    	echo hello
    	echo world
    done
    

    1行で記載するとこのようになる。

    for i in 1 2 3; do echo hello; echo world; done
    

    Makefile内には、コマンドに1行のシェルスクリプトとして記載できる。

    $(ターゲット):
    	for i in 1 2 3; do echo hello; echo world; done
    

    1行だと行が長すぎる場合は、\を使って改行する。

    $(ターゲット):
    	for i in 1 2 3; \
    	do \
    	 echo hello; \
    	 echo world; \
    	done
    
  • シェルスクリプト内の変数は、makeの変数と区別するため、$$変数名に変更する必要がある10 11

    MSG := hello
    x := world
    $(ターゲット):
    	for i in 1 2 3; \
    	do \
    	 echo $(MSG) $x $$i; \
    	done
    

参考

作成日: 2023/02/09