2015年5月7日 星期四

為 python 程式碼 產生完整二進制可執行檔

Cython 可為 python 產生一個可攜式,可跨平台的 binary code,一般都將其編譯成 library (.so)。但是會有特殊的需求,需要編譯成 binary execute file。最早大都採用 freeze.py ;可惜在 python 的日新月異下,它變得越來越不敷使用。(許多 function 都不支援了!),網路上都建議採用 cx_Freeze 來產生 binary execute file 。但是它產生出來會帶著 "一堆" 的函式庫,對於個人的專案上實在產生不小的困擾 !

既然 Cython 可以將程式碼編譯成 C-code,然後再轉換成 library,本身產生出來的 code 又很穩定,是不是能夠利用它將 python source code 編譯成 binary execute file ? 答案是可行的。當然可能要用非官方提供的編譯方式,先為 python source code  編譯時添加 '--embed' ,然後再使用 gcc 編譯成 object (.o) 檔,最後使用 gcc,指定 python 的 library 位置 '-L /usr/lib/python/lib' -L/usr/lib/python/lib/python2.7/config',然後添加參數:-lpthread -ldl -lutil -lm -Xlinker -export-dynamic











學習 PYTHON

Q. 為執行的 shell command 顯示進度 progress bar (經過時間)
A. 使用 threading module 可監控背景正在執行的 shell command, 使用 progress.bar.Bar() 來顯示進度。

【範例】

import sys,os
from subprocess import Popen, PIPE
import time
from progress.bar import Bar
from threading import Thread

def reader(th):
    ## Read from the queue
    bar = Bar('processing', fill='#')
   
    while True:
        if th.is_alive() is False:
            break
          
        bar.next()
        time.sleep(1)
   
    bar.finish()
   

def work():
    p = Popen({My_Shell_Command}, shell=True, stdout=PIPE, stderr=PIPE)
    s,f = p.communicate()
    print '\nResult : '
    print s,f

def main():
    th = Thread(target=work)
    th.start()
    reader(th)

if __name__=='__main__':
    main()


2015年3月26日 星期四

Custom Makefile (python to library (.so))


(屬原創! 轉載請表明出處。)


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 `

=============== 動作分析 ================

  1.    首先 [all] tag 依關聯去尋找 [pytolib2] tag。
  2.    [pytolib2] 指示它要有 $(FILE).so 檔案存在。
  3.    $(FILE).so 的存在要求須有 $(FILE).c 為前提。
  4.    $(FILE).c 存在的要求則先具備 $(FILE).py 檔案存在。
  5.    系統判斷存在 $(FILE).py ==> logs.py
              logs.c 的產生則執行 $(CYTHON) $(CY_OPTS) $(FILE).py 此動作。
  6.    有了 logs.c,則開始產生 logs.so 的命令
          
    $(CC) $(C_OPTS) $(INC) $(LIB) $(FILE).c -o $@ 
  7.    完成動作。

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 進行編譯。