(屬原創! 轉載請表明出處。)
BASE := /opt/python
SHELL := /bin/bash
PYTHON := ${BASE}/bin/python
CYTHON := ${BASE}/bin/cython
CY_OPTS := -2 -D --lenient
CYBUILD := build_ext --inplace
CC := gcc
C_OPTS := -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing
INC := -I${BASE}/include/python2.7
LIB := -L${BASE}/lib/python2.7
ifeq ("$(wildcard $(FILE))","")
.SUFFIXS: .c .py .o .so
all: pytolib2
pytolib2: $(FILE).so
@strip -s $(FILE).so
@if [ -f $(FILE).so ]; \
then \
echo 'compile completed.'; \
else \
echo 'compile failure!'; \
fi;
$(FILE).c: $(FILE).py
@$(CYTHON) $(CY_OPTS) $(FILE).py
$(FILE).so: $(FILE).c
@$(CC) $(C_OPTS) $(INC) $(LIB) $(FILE).c -o $@
@rm -f $<
.PHONY: all clean
clean:
@rm -f $(FILE).{c,so}
else
err:
@echo ""
@echo "Usage: make -f {specify_makefile_path+file} FILE={python_file_name(attached .py is except)}"
@echo " Example has an logs.py one want compile to library(.so), and Makefile in current path."
@echo ""
@echo " The good command >>> make -f Makefile FILE=logs"
@echo " result >>> compile completed."
@echo ""
@echo ""
endif
=============== 使用範例 ================
Example 1: (這是一個靜態的使用方式)要編譯一個 logs.py,在當前路徑下,有 logs.py、logs.conf、logs.txt。
` make -f Makefile FILE=logs `
=============== 動作分析 ================
- 首先 [all] tag 依關聯去尋找 [pytolib2] tag。
- [pytolib2] 指示它要有 $(FILE).so 檔案存在。
- $(FILE).so 的存在要求須有 $(FILE).c 為前提。
- $(FILE).c 存在的要求則先具備 $(FILE).py 檔案存在。
- 系統判斷存在 $(FILE).py ==> logs.py
logs.c 的產生則執行 $(CYTHON) $(CY_OPTS) $(FILE).py 此動作。 - 有了 logs.c,則開始產生 logs.so 的命令
$(CC) $(C_OPTS) $(INC) $(LIB) $(FILE).c -o $@ - 完成動作。
Sample 2: (測試 Makefile 部分)
...
%.c: %.py
@echo $<
@echo $*
@echo $@
@echo $?
....
command: ` make logs `
result:
logs.py
logs
logs.c
logs.py
============== 分析 =============
它可以這麼理解:$@ 取得了 Tag_Name: %.c (為 logs.c)
$< 、 $? 取得了相依性 %.py (為 logs.py)
$* 取得當前第一個資訊
接下來,我要將上面的 Makefile 做調整 ...
========== 以下為調整的部分 ==========
...# 檢閱當前路徑下 所有的 .py 檔
SOURCE = $(wildcard *.py)
# Makefile 有幾個可方便使用的命令,詳情參考
# https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html
# 其中
# $(notdir $var) 去除路徑結構 ex: '/a/b/c.py' notdir is 'c.py'
# $(suffix $var) 取附檔名稱 ex: 'c.py' suffix is '.py'
# 故以下判斷式:當取得的 FILE 變數是為 (.pyx) 時,則觸發 ...
ifeq ("$(suffix $(notdir $(wildcard $(FILE))))",".pyx")
# $(dir $var) 取得路徑結構 ex: '/a/b/c.py' dir is '/a/b'
FDIR = $(dir $(FILE))
TARGET = $(basename $(notdir $(wildcard $(FILE))))
# 運用 shell script 取得特定內容
SOURCEFILE := $(shell awk '/ext_modules/ { print gensub(/^.*\"(.*)\"\,.*$+/, "\\1", ""); exit }' $(FILE))
# 當 FILE 變數 內容不為空 時
# Makefile 專用的判斷式,內容的每一行需位於行首。
ifneq ("$(FILE)","")
# TARGET 則取 FILE 變數指定的名稱 (採用 basename,附檔名將排除)
TARGET = $(basename $(FILE))
# 否則判斷 SOURCE 變數內容不為空 時
else ifneq ("$(SOURCE)","")
# TARGET 將取自 SOURCE 變數找到的名稱列表 (附檔名將排除)
TARGET = $(basename $(SOURCE))
endif
# 預設將執行 TARGET tag
all: $(TARGET)
# 設計功能 pytolib2 指定相依性
pytolib2: $(TARGET)
# 設計功能 pytolib
pytolib:
@$(PYTHON) $(FILE) $(CYBUILD)
@echo "SOURCE FILE: "$(SOURCEFILE)
@if [ -f "$(SOURCEFILE).so" ]; \
then \
strip -s $(SOURCEFILE).so; \
rm -rf build/ $(SOURCEFILE).c; \
echo "Successfully compile "$(SOURCEFILE)".py to "$(SOURCEFILE)".so"; \
else \
echo "Compile "$(SOURCEFILE)".py failure!"; \
fi;
# TARGET 變數可能是一個檔案列表,此時可指定 %
# 來針對它關聯的每一個檔案(%.c) 來進行運作。
# $^ 是顯示先決條件都符合的檔案名稱 (即 ?.c)
$(TARGET): % : %.c
@$(CC) $(C_OPTS) $(INC) $(LIB) $^ -o $@.so
@strip -s $@.so
@if [ -f $@.so ]; \
then \
echo "Successfully compile "$^ "to "$@.so; \
else \
echo 'compile '$^ 'to '$@.so' failure!'; \
fi;
# 此段告訴 make,為每個 .py 的檔案,依底下動作產生對應檔名的 .c 檔。
%.c: %.py
@$(CYTHON) $(CY_OPTS) $^
@echo "Successfully compile "$< "to "$@
# 宣告此 clean 不是存在實體的環境中,為虛擬的。
.PHONY: pytolib clean help
help:
@echo ""
@echo "Usage: make -f {specify_makefile_path+file} {Functions} {FILE={python_file_name(attached .py is except)}}"
@echo " Functions: "
@echo " pytolib: ........ Used by cython build_ext (first be create the (.pyx) File)"
@echo " pytolib2: ....... This is an normal compile"
@echo ""
@echo " Example has an logs.py one want compile to library(.so), and Makefile in current path."
@echo ""
@echo " specify compile once file >>> make -f Makefile FILE=logs"
@echo " result >>> compile completed."
@echo ""
@echo " compile to all >>> make -f Makefile"
@echo " [Notice] default is used by this pytolib2 function."
@echo ""
@echo " compile function use 'pytolib' >>> make -f Makefile pytolib FILE=setup/log.pyx"
@echo " [Notice] Use the 'pytolib' function, must specify (.pyx) path and File."
@echo " [Notice] 'pytolib' function cannot compile to all!"
@echo ""
@echo ""
@echo " --- Author: Baron. Wan ---"
@echo ""
# 當我們執行刪除動作時,我們可以運用 makefile 的特性
# wildcard 命令來確認當前環境下,應該刪除的檔案是否尚存在 ?
clean:
@rm -f $(wildcard *.c *.so)
# Makefile 內部命令都會先被預執行,所以判斷上無法即時判斷檔案是否已刪除成功,
# 此時可以採取如下的做法,當再次執行 `make msg` 時,即能正確判斷結果。
ifeq ("$(wildcard *.c *.so)","")
MSG := "Remove completed"
else
MSG := "Cannot remove: "$(wildcard *.c *.so)
endif
msg:
@echo $(MSG)
=========== 執行 ===========
cmd: ` make `會進行當前路徑下,所有 .py 的編譯動作。
cmd: ` make FILE=logs `
則只對於 logs.py 進行編譯。