My code works, I don’t know why.

國王的耳朵是驢耳朵

[Debian套件打包] 設定好debian目錄後的打包

| Comments

這邊探討打包的工具。有dpkg-buildpackage, debuild, pbuilder, 和git-buildpackage等工具,分別討論如下:

如果單獨要rebuild套件,可以使用下面的指令。 fakeroot debian/rules binary fakeroot debian/rules build


dpkg-buildpackage

從Autotools tarball打包deb套件: 不嚴謹style提到產生並設定好debian後,可以透過dpkg-buildpackage自動打包成deb檔案。而dpkg-buildpackage會做

  • 套件大掃除 (debian/rules clean)
  • 整理source檔案如apply patch (dpkg-source -b)
  • 編譯 (debian/rules build)
  • 安裝產生的檔案到虛擬 root (fakeroot debian/rules binary)並打包
    • 如/usr/bin/xxx,/usr/share/doc/xxx, 等
  • 使用gpg sign .dsc 檔案
  • 產生並sign .changes 檔案

debuild

除了dpkg-buildpackage外,也有更嚴謹的套件產生工具debuild可以使用。之前的測試用程式,用debuild全部會失敗,因為他提供了更多的檢查。列舉一些錯誤訊息如下。

1
2
3
4
5
6
7
E: testautotools: copyright-contains-dh_make-todo-boilerplate
W: testautotools: readme-debian-contains-debmake-template
E: testautotools: description-is-dh_make-template
E: testautotools: maintainer-address-malformed unknown <user@unknown>
E: testautotools: section-is-dh_make-template
W: testautotools: superfluous-clutter-in-homepage <insert the upstream URL, if relevant>
W: testautotools: bad-homepage <insert the upstream URL, if relevant>

和dpkg-buildpackage一樣,自幹可以下-uc -us跳過sign的步驟。另外你也可以自訂一些初始參數,存到/etc/devscripts.conf或是~/.devscripts下面。手冊建議可以加入:

1
2
DEBSIGN_KEYID=你的_GPG_keyID
DEBUILD_LINTIAN_OPTS="-i -I --show-overrides"

pbuilder

pbuilder透過pbuilder套件中的image以及chroot,使用乾淨的環境來產生並測是套件。如此一來可以確認是否debian目錄下面的設定是否真的可以在這些乾淨的環境被編譯和安裝。

照手冊整理使用方式:我全部沒測過,當作以後的課題好了。

  • 環境設定
  • 建立image
  • 使用pbuilder打包source package (套件名稱.orig.tar.gz, 套件名稱.debian.tar.gz, 套件名稱.dsc)
  • sign

透過版本控制版本來 buildpackage

  • git-buildpackage
  • svn-buildpackage
  • cvs-buildpackage

Autobuilder

Autobuilder是Debian提供的服務,會自動rebuild原始套件,產生不同平台如X86, ARM專用的套件。

參考資料

[Debian套件打包] Debian目錄初探 (2)

| Comments

[Debian套件打包] debian目錄初探 (1)我整理了debian目錄下面主要的幾個檔案。主要是參考Debian New Maintainers’ Guide的第五章。

dh_make產生的範本除了產生control, copyright, changelog以及rules這個四檔案以外,還會產生deb的工具(debhelper)使用的一些設定檔範本。這些設定檔範本一般來說副檔名為ex。有些設定檔範本的檔名prefix會是你的原始套件名稱。舉例來說,我們要產生的套件為testautotools,那debian下面可以看到

  • testautotools.cron.d.ex
  • testautotools.default.ex
  • testautotools.doc-base.EX 這些檔案。

另外請注意dh_make沒有產生所有debhelper會用到的設定檔,如果真的需要這些檔案,使用者必須自己寫一個出來。

要使用這些設定檔,要做的是:

  • 如果設定檔範本副檔名為ex或是EX,把檔名中.ex或是.EX刪除。
  • 如果設定檔範本prefix是你的原始套件名稱,請確認該名稱和最後release的套件名稱一致。
  • 更動設定範例檔。
  • 幹掉沒在用的設定範例檔。
  • 視情況更動control和rules檔。

設定檔範本說明

  • README.Debian: 和原始套件無關但是和Debian套件有關的可以放這邊,沒用到就幹掉。
  • compat: debhelper相容性,手冊建議設成9。
  • conffiles: 處理升級套件時config file(如/etc下的設定檔)衝突的問題。有興趣可以參考這邊這邊
  • dirs: 在Makefile內make install不會建立但是又需要的目錄。基本上這有點邏輯矛盾,通常應該是要回頭看Makefile。這個算是一個workaround吧?
  • docs: 其他要工具安裝的文件如README或是自訂的文件檔案。
  • emacsen-*: emacs相關檔案,跳過。
  • install: 原本套件的build system沒有安裝的檔案,但是產生的deb套件需要加料安裝的就放在這邊。
  • {package.,source/}lintian-overrides: 看不懂,跳過
  • menu: 如果是GUI的應用程式,可以設定選單位置。詳細語法在這邊
  • NEWSTODO: 工具會自動安裝該檔案。
  • postinst, preinst, postrm, prerm: dpkg安裝、更新、移除套件時會呼叫的scripts。一般建議菜鳥不要亂動,很容易GG的。如果真的非要更動的話,請確認測試過您套件install,upgrade, remove, purge等操作。細節說明在這邊
  • watch: uscan這個程式會根據watch的資訊看upstream是否有新的release。
  • source/format: 顯示原始套件是原裝的(native)或是需要透過quilt管理patch。詳細說明可以man dpkg-source
  • source/local-options:提供選項讓協助管理產生patch,例如請工具忽略autotool產生的configure.sub檔案。
  • patches/*: 透過quilt產生的patch放在這邊。
  • manpage.*: 原始套件應該要付manpage,如果沒有dh_make會生一些範本 。
    • manpage.1.ex: 使用manpage標準語法,1的部份需要根據您的套件性質更動。如一般API就需要改成3。詳細可以man man找section查詢數字代表的section。
    • dh_make也提供其他格式的manpage如xml和SGML,有興趣的請自行查詢手冊
  • 套件名稱.manpages: 告訴工具要安裝原始套件中的哪些manpage。
  • 套件名稱.symbols: 不建議菜鳥把包函式庫,一般來說函式庫應該要包含這個檔案。
  • 套件名稱.cron.*: 套件定期需要執行的事。
  • 套件名稱.examples: 如果需要工具安裝範例請寫在這邊。
  • 套件名稱.init: 開機需要自動從/etc/init.d啟動的套件的啟動script。
  • 套件名稱.default: 從/etc/init.d啟動的套件的啟動script會參考的變數放在這邊。
  • 套件名稱.info: info 指令會顯示出來的文件說明
  • 套件名稱.link: 在套件build system要額外建立symbolic link可在這邊指定。不過目前想不通什麼時機會用到。
  • 套件名稱.doc-base: 描述套件文件要放在系統的位置。範例如下:
testautotools.doc.EX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Document: testautotools
Title: Debian testautotools Manual
Author: <insert document author here>
Abstract: This manual describes what testautotools is
 and how it can be used to
 manage online manuals on Debian systems.
Section: unknown

Format: debiandoc-sgml
Files: /usr/share/doc/testautotools/testautotools.sgml.gz

Format: postscript
Files: /usr/share/doc/testautotools/testautotools.ps.gz

Format: text
Files: /usr/share/doc/testautotools/testautotools.text.gz

Format: HTML
Index: /usr/share/doc/testautotools/html/index.html
Files: /usr/share/doc/testautotools/html/*.html

參考資料

[Debian套件打包] Debian目錄初探 (1)

| Comments

debian目錄存放描述deb套件的行為和資訊。也是從原始套件(通常是tarball)中產生deb套件檔案的重要資料。

Autotools tarball打包deb套件: 不嚴謹style有提到透過執行dh_make在解壓縮目錄下面產生debian目錄。這次的主題是debian目錄下面的資訊,主要是參考Debian New Maintainers’ Guide的第四章。

Debian套件強制規定debian目錄下面要有control, copyright, changelog三個檔案以及rules這個執行檔。分別討論說明如下。

目錄


debian/control檔

Autotools tarball打包deb套件: 不嚴謹style產生的control內容為:

control
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Source: testautotools
Section: unknown
Priority: extra
Maintainer: unknown <user@unknown>
Build-Depends: debhelper (>= 8.0.0), autotools-dev
Standards-Version: 3.9.2
Homepage: <insert the upstream URL, if relevant>
#Vcs-Git: git://git.debian.org/collab-maint/testautotools.git
#Vcs-Browser: http://git.debian.org/?p=collab-maint/testautotools.git;a=summary

Package: testautotools
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: <insert up to 60 chars description>
 <insert long description, indented with spaces>

詳細檔案規範可以參考:Debian Policy Manual Chapter 5 - Control files and their fields。這邊只列出幾點說明

  • Source: 套件名稱
  • Section: Debian套件分類有兩層
    • 第一層
      • main : 自由軟體,一般來說沒有特別指定的預設值。也就是說如果是main/devel直接使用devel就可以了。
      • non-free
      • contrib : 基於non-free軟體衍生的軟體
    • 第二層
      • admin
      • devel
      • doc
      • libs
      • mail
      • net
      • x11
  • Priority:上傳時管理者有權限修改套件的優先權。
    • required
    • important
    • standard
    • optional: 新的套件不和上面優先權套件衝突時通常使用該權限。
    • extra: 新的套件和上面優先權套件衝突時使用該權限。
  • Maintainer: Bug tracking system發生錯誤時聯絡的信件。
  • Build-Depends: 從原始碼編譯成binary時需要的套件,例如libtool或是autoconf等。
  • Standards-Version: 遵循的Debian Policy Manual版本
  • Homepage:不解釋
  • Package: 套件名稱
  • Architecture:
    • any: 任何平台皆可,通常是需要編譯的套件。
    • all: 套件和平台無關,可能是文件、圖片會是直譯式的語言套件。
  • Description: 80字元寬度,總字數不建議超過60字元。
  • 套件原始碼VCS: 以git為例
    • Vcs-Git: Git repository URL
    • Vcs-browser: 瀏覽器可瀏覽的Git repository URL

和其他相關套件的關係描述

  • Depends: Must have.
  • Recommends: Nice to have. 安裝套件程式如apt-get或是dpkg會出現提示訊息詢問使用者是否安裝Recommends的套件。
  • Suggests: apt-get或是dpkg不會提示這些套件,但是aptitude會詢問。
  • Pre-Depends: 少用的關係描述,比Depends強。
  • Conflicts: 除非衝突的套件有被反安裝,否則不與安裝。
  • Breaks: 安裝該套件將會把Breaks裏面的套件搞爛。通常表示Breaks裏面的套件該被更新到新的版本。
  • Provides: 因為版本相容考量或是其他因素,一個系統中可以由不同套件提供相同功能。這就是Virtual packages概念。不同的套件可以透過Provides提供類似的功能。
  • Replaces
  • 和其他相關套件的關係表示法為:
  • <<: 低於
  • <=: 低於或等於
  • = : 等於
  • >=: 高於或等於
  • >>: 高於
  • 範例:
control
1
2
3
4
5
Depends: foo (>= 1.2), libbar1 (= 1.3.4)
Conflicts: baz
Recommends: libbaz4 (>> 4.0.7)
Suggests: quux
Replaces: quux (<< 5), quux-foo (<= 7.6)

  • 另外 ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}這樣的描述是給debhelper呼叫相對的計算相依性程式。有興趣的人可以man dh_shlibdeps或是man dh_perl等取得更多資訊。

debian/copyright檔

原始套件的版權聲明。除了版權聲明外,還有一些maintain資訊。maintainer可以直接修改dh_make產生的範本。

常見的版權可以透過dh_make --copyright 版權(ex: gpl2, bsd)自動產生debian/copyright檔。如果沒有原本套件的版本,可以搜尋/usr/share/common-licenses/下面是否有符合的版權宣告。再沒有就必須手動修正該檔了。

dh_make產生的範本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Format: http://dep.debian.net/deps/dep5
Upstream-Name: testautotools
Source: <url://example.com>

Files: *
Copyright: <years> <put author's name and email here>
           <years> <likewise for another author>
License: <special license>
 <Put the license of the package here indented by 1 space>
 <This follows the format of Description: lines in control file>
 .
 <Including paragraphs>

# If you want to use GPL v2 or later for the /debian/* files use
# the following clauses, or change it to suit. Delete these two lines
Files: debian/*
Copyright: 2014 unknown <user@unknown>
License: GPL-2+
 This package is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 .
 This package is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 .
 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>
 .
 On Debian systems, the complete text of the GNU General
 Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".

# Please also look if there are files or directories which have a
# different copyright/license attached and list them here.

debian/changelog檔

dpkg或是其他套件管理工具會參考這檔案取得版本號碼等資訊。另外這個檔案會被存放到/usr/share/doc/套件名稱/changelog.Debian.gz

詳細規範可以參考Debian Policy Manual, 4.4 “debian/changelog”。簡單說明如下

  • 第一行提供下面資訊:
    • 套件名稱。
    • 套件版本。
    • 套件應該上傳到FTP的dists的哪個目錄,如unstable或是testing,新的套件通常是unstable。
    • 緊急度:新的套件通常是low 。
  • 接下來是以開頭的更動說明。第一次的更動,可以看到ITP。ITP是Intent To Package,新的套件會在Debian bug tracking system發出需求。所以會有對應的issue number。詳細可以參考Work-Needing and Prospective Packages需要maintainer或是期望的新套件認領流程。
dh_make產生的範本
1
2
3
4
5
testautotools (0-1) unstable; urgency=low

  * Initial release (Closes: #nnnn)  <nnnn is the bug number of your ITP>

 -- unknown <user@unknown>  Tue, 10 Jun 2014 12:42:30 +0800

debian/rules執行檔

這是dpkg-buildpackage會呼叫到的script,這些script本質上就是Makefiles。需提供下面的target:

  • 必備target
    • clean
    • build
    • build-arch
    • build-indep
    • binary
    • binary-arch
    • binary-indep
  • 可有可無的target
    • install
      • get-orig-source

很恐怖嗎?基本上dh_make完在debian就會順便幫你產生一個預設的rules執行檔範例。基本上就是呼叫dh執行檔,列出如下

debian/rules
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.

# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1

%:
  dh $@

dh是一個wrapper,會根據參數呼叫其他的script如dh_testdir, dh_auto_clean, dh_clean等。另外產生binary時手冊上面使用fakeroot模擬root權限,主要是將要安裝到系統的執行檔、函式庫、設定檔等安裝到假的root filesystem然後打包成deb檔。手冊的範例如下:

  • fakeroot debian/rules binary
  • fakeroot debian/rules binary-arch

請注意的dh_make產生的rules範本在面對複雜的套件可能會有非預期的錯誤,此時人肉修改rules script將會是無可避免的事。

關於rules和dh的詳細用法這邊偷懶省略,請讀者自行參考手冊。


參考資料

打包Debian 套件時加入自己的Patch方式

| Comments

在打包Debian套件,有時候會需要從更改原本套件,也就是說需要在打包時自行apply patch。而Debian套件可以透過quilt協助管理patch。Debian New Maintainers’ Guide建議方式如下。

  • 確認有安裝quilt
  • 修改~/.bashrc加入:
~/.bashrc
1
2
alias dquilt="quilt --quiltrc=${HOME}/.quiltrc-dpkg"
complete -F _quilt_completion $_quilt_complete_opt dquilt
  • 新增~/.quiltrc-dpkg
~/.quiltrc-dpkg
1
2
3
4
5
6
7
8
9
10
11
d=. ; while [ ! -d $d/debian -a $(readlink -e $d) != / ]; do d=$d/..; done

if [ -d $d/debian ] && [ -z $QUILT_PATCHES ]; then
  ## if in Debian packaging tree with unset $QUILT_PATCHES
    QUILT_PATCHES="debian/patches"
    QUILT_PATCH_OPTS="--reject-format=unified"
    QUILT_DIFF_ARGS="-p ab --no-timestamps --no-index --color=auto"
    QUILT_REFRESH_ARGS="-p ab --no-timestamps --no-index"
    QUILT_COLORS="diff_hdr=1;32:diff_add=1;34:diff_rem=1;31:diff_hunk=1;33:diff_ctx=35:diff_cctx=33"
    if ! [ -d $d/debian/patches ]; then mkdir $d/debian/patches; fi
fi
  • 打包Debian產生patch時使用dquilt而不是quilt

~/.quiltrc-dpkg說明

不要被這堆符號嚇到(先承認我一開使有被嚇到)。這邊可以看到為什麼要把quilt打包成quilt:

  • QUILT_PATCHES:產生的patch會放在debian/patches目錄而不是預設的patches目錄
  • QUILT_PATCH_OPTS:讓quilt呼叫patch執行檔apply patch時被退貨要使用unified格式
  • QUILT_REFRESH_ARGSQUILT_DIFF_ARGS:讓quilt呼叫diff執行檔指定
    • -p ab:使用a/file b/file的diff格式而不是dir.orig/file dir/file的表示方法
    • 產生patch檔案內不包含timestamp及index的資訊
  • QUILT_COLORS:不用解釋吧

參考資料

Quilt初探 - 使用quilt產生和管理patch

| Comments

傳統的組裝的情況,常常會需要處理patch,然而當patch數量資料多到某種程度,加上時間推移原資料更動,套用patch會讓人生不如死。而quilt是Linux下面處理多個patch檔案的管理軟體。因為組裝軟體和打包Linux 套件會使用到,所以花點時間看一下整理一下。目前感覺有點像是先用git format-patch產生一些patch檔,另外一邊用git am 去merge這些patch檔。然後merge完可以用git reset HEAD^幾次來unmerge。

目錄

quilt的格式是quilt 命令而它處理的對象是stack,也就是說你可以使用push, pop。沒有特別指定的話,command處理的對象是stack top。


透過quilt產生patch

  • quilt new 產生的patch檔案名稱
  • quilt add 要監督那個檔案
  • 更動檔案。
  • quilt refresh產生patch

重複前面動作會再產生新的patch,置於stack的top。可以透過quilt series觀察stack的狀態。另外有兩點值得注意:

  • 假設都是重複更動同一份檔案,quilt refresh就是單純和最原始的檔案diff。舉例來說,下了quilt add後修改了三行再執行quilt refresh產生patch,之後再修改另外五行後執行quilt refresh原本的patch檔案會有第一次改的那三行再加上後來改的那五行。
  • 一個檔案a,經過quilt new ,quilt add, quilt refresh後,再用quilt產生另外一份檔案的patch後。要再修改檔案a會需要經過quilt new ,quilt add, quilt refresh產生另外全新的patch檔案。
  • 可以使用quilt header -e去編輯patch檔的詳細說明。另外也可以用quilt header -e patch名稱指定編輯特定patch名稱的詳細說明。

quilt會將產生的patch放在目前目錄下的patches目錄,這部份可以透過更改QUILT_PATCHES的變數更改名稱。


quilt產生的檔案

quilt new 要產生的patch檔案被執行後會產生

  • pactches目錄
    • 紀錄patch 順序的series檔案
  • .pc目錄
    • applied-patches,和series相同
    • 要產生的patch檔案名稱的目錄

quilt add 要監督那個檔案被執行後會產生 * .pc目錄/要產生的patch檔案名稱的目錄/監督檔案路徑/監督檔案 * ex: ./pc/fix_err_no_27113/libs/liba.c

quilt refresh產生patch

  • .pc目錄/要產生的patch檔案名稱的目錄/.timestamp
  • patches/要產生的patch檔案名稱

我們可以觀察發現quilt add後quilt會將未修改的原始檔複製一份到.pc目錄/要產生的patch檔案名稱的目錄/監督檔案路徑/監督檔案。之後quilt refresh就是單純的diff產生結果


透過quilt管理patch

apply patch並管理的步驟是。

  • 取得未apply patch的原始套件目錄。
  • 將前面產生出來的patches目錄複製到未apply patch的原始的套件目錄當中。
  • 透過quilt 操作並管理patch

假設你有了patches目錄,你可以做以下操作

  • 查詢
    • quilt series查詢有哪些patch
    • quilt applied查詢已經applied那些patch
    • quilt unapplied查詢還沒apply那些patch
    • quilt next查詢下一個要apply的patch
    • quilt next查詢前一個已經applied的patch
    • quilt header顯示目前stack top的註解說明資訊及要patch的檔案
    • quilt header patch名稱顯示目前stack中patch名稱的註解說明資訊及要patch的檔案
  • 套用/還原patch
    • quilt push依照stack反順序apply patch
      • quilt push -a可以一口氣apply patch
      • quilt push patch名稱
    • quilt popundo 一次已經applied的patch。
      • quilt pop -a可以一口氣apply patch

舉例來說,假設patches目錄stack有01_fix, 02_fix03_fix。將該目錄複製到未patch的目錄中,下兩次quilt push會分別patch 01_fix, 02_fix。之後再quilt pop會變成只有apply 01_fix而已。


~/.quiltrc

使用者透過~/.quiltrc自訂quilt的行為,列舉如下

  • QUILT_PATCHES:指定產生的patch存放的目錄,預設為patches目錄
  • QUILT_PATCH_OPTS:讓quilt呼叫patch執行檔apply patch時傳遞的參數
  • QUILT_REFRESH_ARGS和QUILT_DIFF_ARGS:讓quilt呼叫diff執行檔時傳遞的參數
  • QUILT_COLORS:不用解釋吧

參考資料

[Meta-Package] Debian 打包系列

| Comments

meta這個字眼好像找不到精確的中文名詞。基本上就是指和一個「主題」有關的「東西」。舉例來說,在多媒體播放當中,「metadata」指的是附於媒體本身的屬性,如影片解析度、作品名稱等。而「meta-programming」指的是寫程式執行輸出結果是另外一段的程式原始碼。

而在Debian套件中meta package就是該套件本身不會包含任何套件,但是該套件會需要相依於一個或多個套件,因而安裝當該meta package就是安裝完一組套件。

回到主題,最近為了解Debian套件的產生方式做了一些study,因此整理相關連結放在這份文件當中。這樣應該也是一種meta package的概念吧。

Debian套件資源

編譯自動化

打包方式

其他

DRY: 測試檔案Template

| Comments

本頁存在的目的單純是提供其他網頁參考同樣的測試程式使用,不知道算不算是一種metadata?

DRY: Don’t Repeat Your Self,字面上的解釋就是不要一直做重複的事。

由於前面一系列的文章都會用到同樣的測試程式碼,每次都要剪下貼上。加上之後打算整理的資料還是會用到這些測試程式,因此將它們獨立出來。

最原始的出處:Makefile header file dependency問題。這些測試程式的主體是liba.c和libb.c兩個檔案。liba.c提供兩個API

  • from_liba()
    • 印出呼叫的訊息
  • test_liba()
    • 呼叫lib.c中的from_libb()

libb.c的API只是把上面的API中的liba改成libb而已。

liba.h

liba.h
1
2
3
4
5
#ifndef LIBA_H_2013
#define LIBA_H_2013
void test_liba(void);
void from_liba(void);
#endif

liba.c

liba.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "libb.h"
#include <stdio.h>

void test_liba(void)
{
    from_libb();
}

void from_liba()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}

libb.h

libb.h
1
2
3
4
5
#ifndef LIBB_H_2013
#define LIBB_H_2013
void test_libb(void);
void from_libb(void);
#endif

libb.c

libb.c
1
2
3
4
5
6
7
8
9
10
11
12
#include "liba.h"
#include <stdio.h>

void test_libb(void)
{
    from_liba();
}

void from_libb()
{
    printf("%s\n", __PRETTY_FUNCTION__);
}

test.c

test.c
1
2
3
4
5
6
7
8
9
10
#include "libb.h"
#include "liba.h"
#include <stdlib.h>
int main(void)
{
    test_liba();
    test_libb();

    return 0;
}

CMake 初探

| Comments

目錄


概論

CMake是1999年推出的開源自由軟體計劃,目的是提供不同平台之間共同的編譯環境。他的特點有:

  • 支援不同平台。
  • 可以將Build和原本程式碼分開。不分開稱為in-place build,而分開的情況稱為out-place build。out-place build的附加功能就是同樣一包套件可以同時編譯成不同平台的binary並且分別放在不同的目錄中。
  • 支援cache加快編譯速度。

CMake的執行流程簡單來說是

  • 開發者使用CMake語法寫編譯描述,存到CMakeLists.txt。
  • 使用者執行cmake,cmake首先會根據開發描述的規格產生該平台對應的編譯環境檔案如Makefile等。
  • 使用者執行make或是平台上的編譯方法產生最後的結果。
  • 使用者執行cmake install安裝軟體。

另外一點值得注意的是cmake本身沒有提供uninstall功能。


CMakeLists.txt 語法簡介

CMake語法的格式為命令字串(參數),而相關規範可以分為「list和字串」、「變數」、「流程控制」、「Quotation」。分別討論如下:


list和字串

CMake的基本單位是字串,而多個字串可以透過空白或是;組合成字串list。

有興趣的可以直接剪貼下面程式存成CMakeLists.txt後下cmake .看看結果。

CMakeLists.txt
1
2
3
4
set(xxx a b c d)
message(${xxx})
set(xxx e;f;g;h)
message(${xxx})

變數

變數使用set命令設定,格式為set(變數名稱 指定的值)。使用${變數名稱}取值。

有興趣的可以直接剪貼下面程式存成CMakeLists.txt後下cmake .看看結果。

CMakeLists.txt
1
2
3
4
set(xxx a b c d)
message(${xxx})
set(xxx e;f;g;h)
message(${xxx})

另外這邊也列出了CMake內建好用的變數。


流程控制

流程控制又可以分成條件執行、迴圈、和巨集等情況討論:


條件執行

直接看範例,這個範例單純從command line吃變數值,做字串比對。

CMakeLists.txt
1
2
3
4
5
6
7
8
9
if ( NOT DEFINED test)
    message("Use: cmake -Dtest:STRING=val to test")
elseif(${test} STREQUAL yes)
    message("if: ${test}")
elseif(${test} STREQUAL test1)
    message("else if: ${test}")
else()
    message("else: ${test}")
endif()

有幾點需要說明:

  • DEFINED判斷是否該變數有被定義。
  • command透過-D變數名稱:變數型態=變數值來設定CMakeList.txt內部的變數。
  • 條件判斷方法順序可以參考這邊。簡單翻譯一下:
    • 先處理:EXISTS, COMMAND, DEFINED
    • 再來是:EQUAL, LESS, GREATER, STRLESS, STRGREATER, STREQUAL, MATCHES
    • 接下來是:NOT
    • 最後才是:AND, OR

cache的補充

我們第一次 不從command 帶參數 執行結果:

CMakeLists.txt
1
2
3
4
$ cmake  .
-- The C compiler identification is GNU
...
Use: cmake -Dtest:STRING=val to test

接下來我們從command 帶參數 重新執行一次:

CMakeLists.txt
1
2
$ cmake  . -Dtest:STRING=yes
if: yes

然後 不從command 帶參數 再執行會發現test變數變成yes了

CMakeLists.txt
1
2
$ cmake  .
if: yes

看一下目前目錄會發現新的檔案CMakeCache.txt,找一下裏面的字串test會看到

CMakeLists.txt
1
2
3
4
$ grep test CMakeCache.txt
...
test:STRING=yes
...

結論就是CMake的確有cache,而且不小心cache會影響到執行的結果。另外其實set(..)裏面也可以cache行為的相關參數,這邊就先跳過不談。


迴圈

兩種為主,foreachwhile,直接看範例。

  • foreach 為何有cmake_minimum_required(VERSION 2.8)呢?因為不打執行cmake .會產生警告。有興趣的可以打cmake --help-policy CMP0000看說明。
CMakeLists.txt
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 2.8)

set(xxx e;f;g;h)

foreach(i ${xxx})
    message(${i})
endforeach()

執行結果如下:

CMakeLists.txt
1
2
3
4
5
$ cmake .
e
f
g
h
  • while 一個0~9的迴圈,需要透過math command做四則運算。
CMakeLists.txt
1
2
3
4
5
6
cmake_minimum_required(VERSION 2.8)
set(i 0)
while(i LESS 10)
    message(${i})
    math(EXPR i "${i} + 1")
endwhile()

巨集和函數

這兩個差別是在函數內產生的變數scope只存在函數內,而巨集是全域的。直接看範例,範例中的巨集和函數都是在內部產生變數並且印出傳進來的參數。可以仔細看輸出結果的確函數內的變數呼叫完後就消失了。

CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 2.8)
macro(mac_print msg)
    set(mac "macro")
    message("${mac}: ${msg}")
endmacro(mac_print)

function(func_print msg)
    set (func "func")
    message("${func}: ${msg}")
endfunction(func_print)

mac_print("test macro")
message("check var in macro: ${mac}")
func_print("test function")
message("check var in function: ${func}")

執行結果如下

CMakeLists.txt
1
2
3
4
5
$ cmake .
macro: test macro
check var in macro: macro
func: test function
check var in function:

Quotation

  • 字串可以用成對的"表示
  • 支援C語言型態控制字元如\n``\t
  • 可以使用跳脫字元顯示特殊意義符號如\${var}印出來就是${VAR}

專案產生檔案安裝

簡單的語法如下,詳細資料請參考這邊

  • 執行檔安裝(一般安裝目錄路徑:bin)
    • install(TARGETS 執行檔名稱 DESTINATION 安裝目錄路徑)
  • 函式庫安裝(一般安裝目錄路徑:lib)
    • install(TARGETS 函式庫名稱 LIBRARY DESTINATION 安裝目錄路徑)
  • Header 檔安裝(一般安裝目錄路徑:include)
    • install(FILES Header檔名稱 DESTINATION 安裝目錄路徑)

這些安裝描述都是允許多個檔案。另外你可以在執行cmake帶-DCMAKE_INSTALL_PREFIX=安裝目錄指定安裝的top目錄,或是make DESTDIR=安裝目錄也有同樣效果。


範例: 產生執行檔和函式庫

前面有提到in-placeout-place的編譯方式。他們方式的差別是:

  • in-place: 直接在CMakeLists.txt那層下cmake . && make
  • out-place: 直接在CMakeLists.txt那層下mkdir build && cd build && cmake ../ && make
    • build是慣用名稱,不需要強制使用。

範例程式

範例程式細節在這邊,檔案各別分配到src, include, libs這三個目錄。不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。

  • 測試環境:Ubuntu 12.04
原始測試程式樹狀架構
1
2
3
4
5
6
7
8
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── liba.c
│   └── libb.c
└── src
    └── test.c

第一版:單一程式沒有函數庫

先暖身一下,只要在project最上層放一個CMakeLists.txt就好了。

這版本CMakeLists.txt不難理解,就做

  • 填寫project資訊描述。
  • 設定project相關header file路徑。
  • 指定編譯要顯示細節,這是個人偏好習慣。
  • 設定共用編譯參數,CMake可以更進一步地指定release mocde或debug mode的參數,以及指定套用這些參數檔案。
  • 指定要編譯哪些檔案。
  • 指定要編譯成執行檔

top 目錄的CMakeLists.txt如下:

top 目錄的CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)

# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)

# Release mode
set(CMAKE_BUILD_TYPE Debug)

# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")

# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)

# Where to include?
include_directories(${INC_DIR})

# Files to compile
set(test_SRCS ${SRC_DIR}/test.c ${LIB_DIR}/liba.c ${LIB_DIR}/libb.c)
add_executable(${PROJECT_NAME} ${test_SRCS})

這邊可以看到和原本的差別只有多了CMakeLists.txt檔而已。

top 目錄的CMakeLists.txt
1
2
3
4
5
6
7
8
9
├── CMakeLists.txt
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── liba.c
│   └── libb.c
└── src
    └── test.c

第二版:加入編譯函式庫

要編譯函式庫,要在top目錄下的CMakeLists.txt做以下的修改

  • add_library(檔案名稱 函式庫名稱)告訴CMake要搬把哪些檔案編譯函式庫
    • 改成add_library(檔案名稱 SHARED 函式庫名稱)就變成shared library了。
  • target_link_libraries(執行檔名稱 函式庫名稱)
    • 告訴系統最後link要把函式庫一起link進來。

top 目錄的CMakeLists.txt如下:

top 目錄的CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)

# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)

# Release mode
set(CMAKE_BUILD_TYPE Debug)

# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")

# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)

# Where to include?
include_directories(${INC_DIR})

# Build libraries
set(liba_SRCS ${LIB_DIR}/liba.c)
set(libb_SRCS ${LIB_DIR}/libb.c)

add_library(a SHARED ${liba_SRCS})
add_library(b SHARED ${libb_SRCS})

# Build binary
set(test_SRCS ${SRC_DIR}/test.c)
add_executable(${PROJECT_NAME} ${test_SRCS})
target_link_libraries(${PROJECT_NAME} a b)

第三版:每個目錄單獨編譯

要做的事情很簡單,就是

  • srclibs下面加入CMakeLists.txt,描述編譯行為
  • 把根目錄的對應編譯行為搬到子目錄的CMakeLists.txt
  • 使用add_subdirectory(子目錄名稱)把要編譯的子目錄加進去

所以我們現在目錄樹狀結構會變成src和lib目錄都有CMakeLists.txt

top 目錄的CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
├── CMakeLists.txt
├── include
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── CMakeLists.txt
│   ├── liba.c
│   └── libb.c
├── readme.txt
└── src
    ├── CMakeLists.txt
    └── test.c

每個目錄的CMakeLists.txt列出如下

  • CMakeLists.txt
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)

# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)

# Release mode
set(CMAKE_BUILD_TYPE Debug)

# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")

# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)

# Where to include?
include_directories(${INC_DIR})

# Build library in libs directory or not?
# Dive into libs directory
add_subdirectory(${SRC_DIR})
add_subdirectory(${LIB_DIR})
  • libs/CMakeLists.txt
libs/CMakeLists.txt
1
2
3
4
5
6
# Build binary, inherit setting from parent
set(liba_SRCS liba.c)
set(libb_SRCS libb.c)

add_library(a ${liba_SRCS})
add_library(b ${libb_SRCS})
  • src/CMakeLists.txt
src/CMakeLists.txt
1
2
3
4
# Build binary
set(test_SRCS test.c)
add_executable(${PROJECT_NAME} ${test_SRCS})
target_link_libraries(${PROJECT_NAME} a b)

第四版:加入安裝產生的檔案描述

這邊就是單純把前面的install()命令套用到每一個目錄下的CMakeLists.txt。由於我們也要安裝header檔,所以在include目錄下面會新增CMakeLists.txt描述安裝header的細節。

所以我們現在目錄樹狀結構會變成每個目錄都有CMakeLists.txt

src/CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
├── CMakeLists.txt
├── include
│   ├── CMakeLists.txt
│   ├── liba.h
│   └── libb.h
├── libs
│   ├── CMakeLists.txt
│   ├── liba.c
│   └── libb.c
├── readme.txt
└── src
    ├── CMakeLists.txt
    └── test.c

而各CMakeLists.txt新增的描述為

  • CMakeLists.txt
CMakeLists.txt
1
add_subdirectory(${INC_DIR})
  • libs/CMakeLists.txt
libs/CMakeLists.txt
1
install(TARGETS a b LIBRARY DESTINATION lib)
  • src/CMakeLists.txt
src/CMakeLists.txt
1
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
  • include/CMakeLists.txt
include/CMakeLists.txt
1
install(FILES liba.h libb.h DESTINATION include)

第四版執行結果

第四版執行結果
1
2
3
4
5
6
7
8
9
10
11
12
$ cmake  ../  -DCMAKE_INSTALL_PREFIX=`pwd`/test && make && make install
...
$ tree test
test/
├── bin
│   └── testcmake
├── include
│   ├── liba.h
│   └── libb.h
└── lib
    ├── liba.so
    └── libb.so

結論

本篇文章簡單介紹了CMake的語法,以及示範用CMake產生執行檔和函式庫。但是CMake還有太多東西值得去注意,例如把字串代換到程式碼,config.h的建立,搜尋depend 套件等。這部份以後有緣份會用到再跟各位分享。


參考資料

C語言中使用gettext

| Comments

gettext 是1990年代Sun推出的軟體,用於處理Unix下面程式訊息的多國語言問題。後來GNU協會也推出了GNU gettext。本文以GNU的gettext為主。


目錄


概論

這張圖這張圖很清楚地說明整個流程。文字解釋如下:

  • 使用者在程式碼中加料,讓gettext工具可以認出來要處理的訊息
  • 執行xgettext,將程式碼中要處理的訊息抽出存到pot (Portable Object Template)檔
  • 執行msginit,指定需要的語系如正體中文、日文、法文等。程式會將pot檔案轉成對應的po(Portable Object)檔
  • 執行msgfmtpo檔產生和平台相關的文字訊息的mo(machine object)檔
  • 接下來如果程式碼又有新的加料訊息,就不是用msginit產生pot檔,而是透過msgmerge更新pot

範例

  • 測試環境
    • Ubuntu 12.04 LTS

套用gettext到C語言程式碼

講一下程式碼

  • 要include locale.hlibintl.h
  • 加入#define _(String) gettext(String),當有需要翻譯轉換的訊息使用。也可以直接使用char * gettext (const char * msgid);
  • 初始部份
    • setlocale(LC_ALL, "");:根據環境變數設定locale,有興趣的可以man locale
    • bindtextdomain(PACKAGE, LOCALEDIR);: PACKAGE, LOCALEDIR由外部傳入的巨集,請參考下面的Makefile。想像成namespace概念,基本上就是引導系統去特定目錄去取得LOCALEDIR/LC_MESSAGE/PACKAGE.mo檔。有興趣的可以man bindtextdomain
      • 一般來說系統上是存放在/usr/share/locale,然而這不是硬性規定。所以本次的Makefile故意放在local的po目錄之中,建議一定要看一下。
    • textdomain(PACKAGE);:讀取mo檔案,讓後面的gettext可以取得LC_ALL設定對應的訊息。
  • 請注意有些註解是/*-,和一般的不一樣,之後執行xgettext時可以下--add-comments=-抽出註解放到pot
  • 為了易讀性,拔掉錯誤檢查
test_gettext.c 拔掉錯誤檢查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <libintl.h>
#define MAX_CHAR (32)

int main(int argc, char **argv)
{
    char dest[MAX_CHAR];
    char transport[MAX_CHAR];

    /* Init gettext related APIs*/
    setlocale(LC_ALL, "");
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);

    /*- Set up strings */
    strncpy(dest, gettext("Taipei"), MAX_CHAR);
    strncpy(transport, gettext("bus"), MAX_CHAR);

    /*- Let's print out some message */
    printf(gettext("I will go to %s by %s.\n"), dest, transport);

    return 0;
}
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
TARGET=test_gettext
LOCALE=zh_TW
PO_DIR=$(shell pwd)/po
CFLAGS=-Wall -Werror -g -DPACKAGE=\"$(TARGET)\" -DLOCALEDIR=\"$(PO_DIR)\"
OBJS=$(patsubst %, %.o, $(TARGET))

all: $(TARGET).pot $(LOCALE).po $(TARGET).mo $(TARGET)

$(TARGET).pot: $(TARGET).c
  if [ ! -f $(TARGET).pot ] ; then                                \
      xgettext -o $(TARGET).pot --add-comments=- -k_ $(TARGET).c; \
  fi

$(LOCALE).po: $(TARGET).pot
  if [ -f $(LOCALE).po ] ; then                               \
      msgmerge $(LOCALE).po $(TARGET).pot ;                   \
 else                                                        \
     msginit --locale=$(LOCALE) --input=$^ --no-translator ; \
 fi

$(TARGET).mo: $(LOCALE).po
  mkdir -p $(PO_DIR)/$(LOCALE)/LC_MESSAGES
  msgfmt $^ -o $(PO_DIR)/$(LOCALE)/LC_MESSAGES/$@


%.o: %.c
  $(CC) -o $(patsubst %.o, %, $@) $^

clean:
  rm -f *.o *~ $(TARGET) *.po *.pot *.mo
  @echo To remove po directory, use: rm -rf $(PO_DIR)

產生pot檔

  • Makefile中的xgettext -o $(TARGET).pot --add-comments=- -k_ $(TARGET).c會產生pot檔。產生的pot如下,我們可以看到剛才/*-抽出來的註解會被加入pot檔內。另外不要忘記更改template中的metadata。

如果是多個C檔怎麼辦呢?請用-D 指定目錄或是-f指定多個檔案

test_gettext.pot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-30 23:11+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. - Set up strings
#: test_gettext.c:22
msgid "Taipei"
msgstr ""

#: test_gettext.c:23
msgid "bus"
msgstr ""

#. - Let's print out some message
#: test_gettext.c:26
#, c-format
msgid "I will go to %s by %s\n"
msgstr ""

產生正體中文po檔

兩種情況

  • 還沒有po檔產生
    • msginit --locale=$(LOCALE) --input=$^ --no-translator
      • locale會根據指定locale產生對應的po檔,以我們例子就是zh_TW.po
    • --no-translator單純是我懶得回答互動問題,這個可有可無。
  • 產生po檔,就可以分配給專業的翻譯者處理
    • 已經翻譯過了,但是又有新的pot檔產生的話。此時需要合併以經翻譯的po檔案,產生新的po檔。新的po檔會有已經翻譯過的訊息己及新的未翻譯訊息。
      • msgmerge $(LOCALE).po $(TARGET).pot

這邊的翻譯過後po檔如下,幾點要注意的

  • charset要設成UTF-8
  • 由於語言特性,參數有可能會和英文順序不同。所以I will go to Taipei by bus換成正體中文可以轉成我搭乘公車到台北。因此,請注意gettext po檔訊息可以指定參數順序。
test_gettext.po
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Chinese translations for PACKAGE package.
# Copyright (C) 2014 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Automatically generated, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2014-05-30 23:03+0800\n"
"PO-Revision-Date: 2014-05-30 23:03+0800\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#. - Set up strings
#: test_gettext.c:22
msgid "Taipei"
msgstr "台北"

#: test_gettext.c:23
msgid "bus"
msgstr "公車"

#. - Let's print out some message
#: test_gettext.c:26
#, c-format
msgid "I will go to %s by %s.\n"
msgstr "我搭乘%2$s到%1$s。\n"

po檔補充

po檔主要是msgidmsgstr成對,翻譯的文字放在msgstr這邊。而註解以#開頭。

另外gettext的工具在分析時也會使用特殊註解格式輔助產生正確的mo檔案,他們以,開頭,可以串接在同一行註解。列舉一些如下:

  • , fuzzy: 顯示翻譯可能不正確,可由翻譯者或是在msgmerge時產生。翻譯者review確定翻譯沒有問題需要把該註解拿掉。
  • , c-format:表示訊息會用到C的print format,如%s, %d等。

產生mo檔

請參考Makefile部份描述:

test_gettext.po
1
2
3
$(TARGET).mo: $(LOCALE).po
    mkdir -p $(PO_DIR)/$(LOCALE)/LC_MESSAGES
    msgfmt $^ -o $(PO_DIR)/$(LOCALE)/LC_MESSAGES/$@
  • $^表示prerequisite而$@表示target。
  • 一開始我們先建立local的$(PO_DIR)/$(LOCALE)/LC_MESSAGES/,將mo黨產生在該目錄。而$(PO_DIR)/$(LOCALE)目錄也是一開始傳給測試原始碼的定義。

範例結果

注意LC_ALL可以從locale -a指令列出系統支援的語系,所以這邊我們用zh_TW.utf8而不是zh_TW,有興趣的人可以自行改為zh_TW將會發現不會有中文顯示,有加錯誤檢查還會出現檔案找不到的錯誤訊息。

LC_ALL=”en_US.utf8”
1
2
$ LC_ALL="en_US.utf8" ./test_gettext
I will go to Taipei by bus.
LC_ALL=”zh_TW.utf8”
1
2
$ LC_ALL="zh_TW.UTF8" ./test_gettext
我搭乘公車到台北。

而關於, fuzzy我們可以做更多的實驗。我們把zh_TW.po的Taipei更改加入, fuzzy

加入`, fuzzy`
1
2
3
#, fuzzy
msgid "Taipei"
msgstr "台北"

跑起來就結果變成

加入`, fuzzy`
1
2
3
$ LC_ALL="zh_TW.utf8" ./test_gettext
test_gettext
我搭乘公車到Taipei。

參考資料