GNU編碼標準

GNU編碼標準


引用私有程序

不要在任何情況下,爲你在的GNU中的工作或者在工作中引用Unix的源代碼(或者任何其它私有程序)。如果你對一個Unix程序內容有一些模糊的記憶,這並不因爲着你絕對寫程序來模仿它,但請試圖在內部使用不同的代碼行來組織它,因爲這將使你工作的結果在細節上與Unix版本有所不同。例如,Unix工具通常進行了優化以使用最少的內存;如果你更希望提高速度,你的程序將會有很大的不同。你可以在內核中保存整個輸入文件並且在內存中掃描而不是使用stdio

使用比Unix程序更新的、更明智的算法。不使用暫時文件。在一遍掃描而不是兩遍掃描中完成任務(我在assembler(彙編器)中這樣做了)。或者相反,強調簡單性而不是速度。對於一些應用程序來說,今天的計算機只要使用簡單的算法就夠了。或者注重一般性。例如,Unix程序通常使用靜態的表格和固定大小的字符串,這導致了不可改變的限制;用動態分配來代替。確認你的程序處理了輸入文件爲空和其它滑稽的情況。爲增加擴展性而增加一種程序語言並且用那種語言完成程序的一個部分。或者把程序的一部分修改成獨立的庫。或者用一個簡單的廢物收集器而不是在釋放內存的時候精確地進行跟蹤,

或者使用諸如obstacks這樣的新的GNU工具。


接受他人的奉獻

如果其他人發給你一段添加到你正在編寫程序中的代碼,我們需要准許使用它的法律文書——我們將需要從你那裏取得同樣的法律文書。程序的每個重要的貢獻者都必須簽署

某種法律文書以使得我們可以給程序一個清晰的標題。僅有主要作者是不夠的。所以,在把來自於他人的任何共享添加到程序中之前,告訴我們以便我們可以做出安排以獲取文書。在你實際地使用貢獻之前,請等待直到我們告訴你我們已經收到了簽署的文書。這即適用於你發行程序之前也適用於發行之後。如果你收到了一個修正bug的補丁,並且它們做了主要的修改,我們就需要爲他提供法律文書。

你不需要爲這裏或者那裏的少數幾行修改提供文書,因爲對於達到版權目的沒有意義。還有,如果你從建議中獲得的僅僅是一些想法,而不是你實際上使用的代碼,你也不需要文書。例如,如果你寫了一個程序的不同解決方案,你並不需要獲得許可文書。

我知道這是十分麻煩的;它對我們來說也十分麻煩。但如果你不等待,你就可能誤入歧途,如果這個貢獻者的僱主不肯簽署棄權聲明怎麼辦?你可能不得不再次把代碼剔除出來!最糟糕的情況是如果你忘記告訴我們其它的貢獻者,我們可能會因此而窘迫地出現在法庭上。


修改日誌

爲每個目錄維護一個修改日誌,以記述對這個目錄下源文件的修改。這樣做的目的是使得在將來尋找bug的人可以指導大致是那些修改導致了錯誤。通常,一個新的bug可以在最近進行的修改中被找到。更重要的事,修改日誌有助於消除程序的不同部分之間在概念上的不一致性;它們可以告訴我們概念衝突產生的歷史。使用Emacs命令M-x add-change在修改日之中創建一個新的條目。一個條目應該包含一個星號、被修改的文件的名稱以及被擴在括號內的、被修改了的函數、變量或者任何東西。括號之後是冒號和對你對函數或變量的修改的說明。

用空行把無關的條目分隔開。如果兩個條目反映了同一個修改,因而它們一同工作,那就不要在它們之間使用空行。如果後續的條目針對的是相同的文件,那麼你可以忽略文件名的星號。下面是一些例子:

* register.el (insert-register): Return nil.(jump-to-register): Likewise.* sort.el (sort-subr): Return nil.* tex-mode.el (tex-bibtex-file, tex-file, tex-region):Restart the tex shell if process is gone or stopped.(tex-shell-running): New function.* expr.c (store_one_arg): Round size up for move_block_to_reg.(expand_call): Round up when emitting USE insns.* stmt.c (assign_parms): Round size up for move_block_from_reg.

在這裏沒有必要敘述修改的完整目錄和它們是如何協同工作的。把這些說明作爲註釋放到代碼中更好一些。這就是說爲什麼只要給出“New function”就夠了;在源代碼中,與函數放在一起的註釋說明了它是做什麼的。然而,有時爲一大堆修改寫上一行文字以描述它們的整體目的是有用的。

在概念上,你可以把修改日誌看作解釋原始版本與當前版本的不同的“undo列表”。

人們可以閱讀當前的版本;他們不需要修改日誌告訴他們其中有什麼。他們從修改日之中

得到的是關於早期版本的不同的清晰解釋。在你以簡單的方式修改函數的調用順序,並且你修改了所有對函數的調用時,不必爲所有的調用創建單獨的條目。只要在被調用的函數的條目中寫“All callers changed.”即可。在你僅僅修改了註釋或者文檔字符串的時候,爲該文件寫一個條目,而不必提到函數,就足夠了。只要寫"Doc fix."。不必爲文檔文件維護修改日誌。這是因爲文檔不那麼容易受到難以修正的錯誤的影響。文檔不是由那些必須以精確地工程方式相互作用的部分組成的;要修改一個錯誤,你不需要知道這個錯誤傳播的歷史。

與其它實現的兼容性

作爲一個特例,對於GNU中的工具程序和庫,它們應該和Berkeley Unix相應的部分向上兼容,如果標準C定義了它們的行爲,那它們應該和標準C向上兼容,如果POSIX規範定義了它們的行爲,那它們也應該與POSIX規範向上兼容。當這些標準發生衝突的時候,爲每個標準提供兼容模式是有用的。標準CPOSIX禁止進行任何形式的擴展。自由地進行你的擴展,並且把選‘—ansi’或‘—compatible’包括進來以關閉你的擴展。但是如果擴展很可能導致任何實際程序或者腳本的崩潰,那麼它可能實際上不是向上兼容的。嘗試一下重新定義它的界面。

當一個特徵僅僅被用戶(而不會被程序或者命令文件)所使用的時候,並且在Unix中它完成得並不好,請自由地用完全不同並且更好的方式代替它。(例如,用Emacs代替vi。)但同時提供兼容模式仍然是很好的。(現在有自由的vi實現,所以我們提供了它。)歡迎提供Berkeley Unix沒有提供的有用功能。Unix中沒有的附加功能可能是有用的,但我們優先複製那些Unix已經有的功能。


Makefile慣例

本章敘述爲GNU程序書寫Makefile的慣例。

Makefile的通用慣例

每個Makefile都應該包含這一行:SHELL = /bin/sh

以避免那些由從環境中繼承SHELL變量的系統帶來的麻煩。(GNU make永遠不會出現這個問題。)不要假定‘.’出現在用於尋找可執行的命令的路徑中。當你需要在make期間運行作爲你的包的一部分的程序時,如果程序是作爲make的一部分而創建的,請確保它使用了‘./’,或者如果文件是不會被改變的源代碼的一部分,請確保它使用了‘’$(srcdir)/’

如果運行‘configure’時使用了選項‘—srcdir’,那麼‘./’與‘$(srcdir)/’之間的區別就十分重要。一下形式的規則:

foo.1 : foo.man sedscript sed -e sedscript foo.man > foo.1

將在當前目錄不是源代碼目錄的情況下導致錯誤,這是因爲‘foo.man’和‘sedscript’不在當前目錄中。在使用GNU make的時候,由於不論源文件在那裏,‘make’的自動變量‘$<‘

都將表示它,所以在只存在一個依賴文件的情況下,依靠‘VPATH’來尋找源文件仍然是可行的。(許多版本的make只在隱含規則中設置‘$<‘。)如下的makefile目標:

foo.o : bar.c $(CC) -I. -I$(srcdir) $(CFLAGS) -c bar.c -o foo.o

將被如下目標所替代:

foo.o : bar.c $(CC) $(CFLAGS) $< -o $@

以便使’VPATH’能夠正確地工作。當目標含有多的依賴文件時,顯式地使用‘$(srcdir)’讓規則正常工作的最簡單辦法。例如,上述爲‘foo.1’而提供的目標最好被寫作:

foo.1 : foo.man sedscript sed -s $(srcdir)/sedscript $(srcdir)/foo.man > foo.1

Makefile中的工具

書寫能夠在sh,而不是在csh,中運行的Makefile命令(以及任何shell腳本,例如

configure)。不要使用任何ksh或者bash特殊的功能。爲創建和安裝而提供的configure腳本和Makefile規則不要直接使用任何工具,除了以下的幾個之外:

cat cmp cp echo egrep expr grepln mkdir mv pwd rm rmdir sed test touch

堅持使用這些程序通常支持的選項。例如,因爲許多系統不支持‘mkdir –p’,儘管它可能有些方便,但不要使用它。

爲創建和安裝而提供的Makefile規則還可以使用編譯器和相關的程序,但應該通過make變量以便用戶對它們進行替換。下面是一些我們所說的相關的程序:

ar bison cc flex install ld lexmake makeinfo ranlib texi2dvi yacc

在你使用ranlib的時候,你應該測試它是否存在,並且僅僅在它存在的情況下運行它,以使得發佈版本在那些沒有ranlib的系統中也能夠工作。如果你使用了符號連接,你應該爲沒有符號連接的系統實現一個替代手段。你可以在只打算用於特定系統的Makefile的部分(或者腳本)中使用你能夠確認在那些系統上存在的工具。


爲用戶提供的標準目標

所有的GNU程序應該在它們的Makefile中含有下列目標:

‘all’

編譯整個程序。它應該是缺省目標。這個目標不需要重新創建任何文檔文件;Info文件被包含在發佈版本中,同時,只有在用戶明確地要求創建DVI文件的時候才創建DVI文件。

‘install’

編譯程序並且把可執行文件、庫文件等文件複製到它們在實際應用中應該存在的位置。如果存在一個可以檢測程序是否被正確地安裝了的簡單測試,本目標將首先運行這個測試。如果文件的安裝目錄不存在,該命令將創建這樣的目錄。它們包括由變量prefixexec_prefix的值指明的目錄,以及需要的所有目錄。完成該任務的一種方式是按照後面所說明的方式通過目標installdirs來完成。在任何用戶安裝man手冊的命令之前使用‘-’,以使得make忽略所有的錯誤。錯誤將在那些沒有安裝Unix man手冊文檔系統的系統中出現。

安裝Info文件的方式是用$(INSTALL_DATA)把它們複製到‘$(infodir)’中。(參見爲指明命令而提供的變量),並且如果有程序install-info存在,那麼就運行它。install-info是一個腳本,它編輯Info ‘dir’文件以把給定的Info文件添加或者更新目錄項的腳本;它將是Texinfo包的一個部分。下面是用於安裝一個Info文件一個簡單規則:

$(infodir)/foo.info: foo.info# There may be a newer info file in . than in srcdir. -if test -f foo.info; then d=.; / else d=$(srcdir); fi; / $(INSTALL_DATA) $$d/foo.info $@; /# Run install-info only if it exists.# Use ‘if’ instead of just prepending ‘-’ to the# line so we notice real errors from install-info.# We use ‘$(SHELL) -c’ because some shells do not# fail gracefully when there is an unknown command. if $(SHELL) -c ‘install-info --version’ / >/dev/null 2>&1; then / install-info --infodir=$(infodir) $$d/foo.info; / else true; fi

‘uninstall’

刪除所有由’install’目標創建的所有安裝的文件(但不包括那些由諸如’make all’之類的目標創建的,沒有被安裝的文件)。

‘clean’

從當前目錄中刪除所有在創建程序過程中創建的文件。不要刪除那些紀錄配置情況的文件。有些文件可能是在創建過程中創建的,但因爲它們是和發佈版本一起發佈的,通常不是在創建過程中創建的,這樣的文件也需要保留下來。如果‘.dvi’文件不是發佈版本的一部分,就刪除它們。

‘distclean’

從當前目錄中刪除所有在程序的配置和創建過程中創建的文件。如果你解包源代碼並且在沒有添加任何其它文件的情況下創建程序,‘make distclean’將僅僅保留那些出現在發佈版本中的文件。

‘mostlyclean’

類似於‘clean’,可能不會刪除少數人們通常不希望重新編譯的文件。例如,GCC

‘mostlyclean’目標不會刪除‘libgcc.a’,這是因爲很少需要重新編譯並且重新編譯將花費大量的時間。

‘realclean’

從當前目錄中刪除所有可以由Makefile重新創建的文件。這通常包括所有由distclean刪除的文件,以及:由Bison生成的C源文件、標記表(tags tables)、Info文件等等。然而有一個例外:即使‘onfigure’以通過使用Makefile中的規則重新創建,‘ake realclean’也不會刪除‘onfigure’更一般地說,‘ake realclean’不會刪除爲了運行‘onfigure’而存在的任何東西,並且隨後開始創建程序。

‘AGS’

爲本程序更新標記表(tags table)。

‘nfo’

生成所有需要的Info文件。書寫該規則的最佳方式是:

info: foo.infofoo.info: foo.texi chap1.texi chap2.texi $(MAKEINFO) $(srcdir)/foo.texi

你必須在Makefile中定義變量MAKEINFO。它應該運行程序makeinfo,該程序是Texinfo發佈版本的一部分。

‘vi’

爲所有Xinfo文檔VI文件,比如:

dvi: foo.dvifoo.dvi: foo.texi chap1.texi chap2.texi $(TEXI2DVI) $(srcdir)/foo.texi

你必須在Makefile中定義變量TEXI2DVI。它應該運行程序texi2dvi,該程序也是Texinfo

發佈版本的一部分。作爲另一個選擇,只要寫依賴文件並且允許GNU Make提供這個命令就行了。

‘ist’

爲本程序創建一個發佈版本tar文件。該tar文件將被設置以使得在tar文件中的文件名以子目錄名開頭,這個子目錄名是包用於發佈的名字。這個名字可以包含版本號。例如,GCC版本1.40的發佈tar文件將被解包到名爲’gcc-1.40’的子目錄中。完成該任務的最簡單方式是以適當的名稱創建一個子目錄,使用ln或者cp把正確的文件安裝到該目錄中,而後tar這個子目錄。目標dist應該顯式地依賴於發佈版本中所有的非源文件,以確保它們在發佈版本中都不是過時的。參見製作發佈包

‘heck’

(如果有的話)執行自檢測。用戶必須在運行測試之前,但不必在安裝程序之前創建程序;你應該寫下自檢測以便它們在程序創建之後而沒有被安裝之前進行工作。對於那些適用於以下的目標的程序,建議你按照常用的名字提供它們。

‘nstallcheck’

(如果有的話)執行安裝監測。用戶必須在運行該檢測之前創建並且安裝程序。你不應該假定’(bindir)’出現在搜索路徑中。

‘nstalldirs’

添加一個名爲‘installdirs’的目標,以便創建安裝文件的目錄和它們的父目錄。有一個稱爲‘mkinstalldirs’的腳本可以爲此提供便利;在Texinfo包中可以找到它。你可以使用象下面那樣的規則:

# Make sure all installation directories (e.g. $(bindir))# actually exist by making them if necessary.installdirs: mkinstalldirs $(srcdir)/mkinstalldirs $(bindir) $(datadir) / $(libdir) $(infodir) / $(mandir)




爲指明命令而提供的變量

Makefile應該提供變量以覆蓋某些命令、選項等等。特別地,你應該通過變量來運行大部分工具程序。因此,如果你使用了Bison,就定義一個缺省值是通過’BISON = bison’來設定的變量BISON,並且在你需要使用Bison的所有地方通過$(BISON)引用它。在這種方式下,文件管理工具:lnrmmv等等並不需要通過變量引用,這是因爲用戶不需要用其它程序來替代它們。每個程序名變量都應該有一個對應的變量以便爲程序提供選項。把’FLAGS’附加到程序名變量名的後面就是選項變量名--例如,BISONFLAGS。(名字CFLAGS是這項規則的一個例外,但因爲它是標準的而保留了它。)在任何運行預處理器的編譯命令中使用CPPFLAGS,在任何進行連接的編譯命令和任何對ld的直接使用中使用LDFLAGS

如果存在一些爲了正確地編譯某些文件而必須使用的C編譯器選項,不要把它們包括在CFLAGS中。用戶希望能夠自由地指明CFLAGS的值。替代的方式是:通過在編譯命令行中顯式地給出這些必要的選項或者通過定義一條隱含規則,從而以獨立於CFLAGS的方式把選項傳遞給C編譯器。

CFLAGS = -gALL_CFLAGS = -I. $(CFLAGS).c.o: $(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) $<

把選項‘-g’包括在CFLAGS中,因爲它對於正確的編譯來說並不是必要的。你可以認爲它僅僅是關於缺省值的一個建議。如果包被設置成在缺省的狀態下由GCC編譯,那麼你可能還需要把‘-O’包括在CFLAGS的缺省值之中。

CFLAGS放在編譯命令行的最後,就是在其它包含了編譯選項的變量之後,以便於用戶使用CFLAGS來覆蓋其它的選項。每個Makefile都應該定義變量INSTALL,它是把一個文件安裝到系統中的基本命令。每個Makefile還應該定義變量INSTALL_PROGRAMINSTALL_DATA。(兩者的缺省值都應該是$(INSTALL)。)而後,Makefile應該使用這些變量作爲實際安裝的命令,分別用於安裝可執行文件和不可執行的文件。按照下面的方式使用這些變量:

$(INSTALL_PROGRAM) foo $(bindir)/foo$(INSTALL_DATA) libfoo.a $(libdir)/libfoo.a

總是把文件名,而不是目錄名,作爲安裝命令的第二個參數。爲每個需要安裝的文件使用獨立的命令。


爲安裝目錄而提供的變量

安裝目錄總是應該通過變量來命名,以易於把包安裝在其它非標準的位置。這些變量的標準名字是:

‘prefix’

用於構造下列變量的缺省值的前綴。prefix的缺省值應該是‘/usr/local’(至少現在是它)。‘exec_prefix’’

用於構造下列某些變量的缺省值的前綴。exec_prefix的缺省值應該是$(prefix)。一般來說,$(exec_prefix)指的是用於儲存與機器有關的文件(比如說可執行文件和子程序庫)的目錄,而$(prefix)則被直接用於其它目錄。

‘bindir’

用於儲存用戶可以運行的可執行程序的目錄。一般來說應該是‘/usr/local/bin’,但應該被寫作‘$(exec_prefix)/bin’

‘libdir’

用於安裝由程序運行,而不是由用戶運行的可執行文件的目錄。Object文件和object代碼庫也應該被儲存在這個目錄。提供該目錄的意圖是爲了儲存適用於特殊機器結構,但又不必出現在命令路徑中的文件。libdir的值通常是‘/usr/local/lib’,但應該被寫作‘$(exec_prefix)/lib’

‘datadir’

用於安裝程序在運行時需要訪問的只讀數據文件的目錄。該目錄用於儲存與使用的機器獨立的文件。它通常是‘/usr/local/lib’,但應該被寫作‘$(prefix)/lib’

‘statedir’

用於安裝程序在運行時需要修改的數據文件的目錄。這些文件應該與使用的機器類型獨立,並且應該可以在網絡安裝的情況下載不同的機器之間共享。它通常應該是‘/usr/local/lib’,但應該被寫作‘$(prefix)/lib’

‘includedir’

用於儲存將被用戶程序以C預處理指令‘#include’引入的頭文件的目錄。它通常應該是‘/usr/local/include’,但應該被寫作‘$(prefix)/include’。除了GCC以外,大部分編譯器並不在‘/usr/local/include’中尋找頭文件。所以以這種方式安裝頭文件僅僅適用於GCC。但有些庫被設計成與其它編譯器共同工作。它們應該在兩個地方安裝它們的頭文件,一個由includedir給出,另一個由oldincludedir給出。

‘oldincludedir’

爲除了GCC之外的其它編譯器安裝頭文件的目錄。這通常應該是‘/usr/include’Makefile命令應該檢測oldincludedir的值是否爲空。如果爲空,Makefile命令就不應該試圖使用oldincludedirMakefile命令應該放棄對頭文件的第二個安裝。除非頭文件來自於同一個包,包不應該替換已經存在的頭文件。因此,如果你的Foo包提供了一個頭文件‘foo.h’

並且如果它沒有出現在oldincludedir目錄中或者oldincludedir目錄中的‘foo.h’也是來自

Foo包,那麼Foo包就應該把頭文件安裝到oldincludedir中。

爲了判定‘foo.h’是否來自於Foo包,可把一個特殊的字符串作爲註釋的一部分放在文件中,而後用grep搜索這個字符串。

‘mandir’

(如果存在)本包安裝man手冊的目錄。它應該包含對應於正確的手冊部分的後綴--

對於一個工具來說通常是‘1’。它一般是‘/usr/local/man/man1’,但你應該把它寫成:’$(prefix)/man/man1’

‘man1dir’

安裝man手冊第一部分的目錄。

‘man2dir’

安裝man手冊第二部分的目錄。

‘...’

如果包需要把man手冊安裝到手冊系統的多個部分,就用這些名字來代替‘mandir’。不要把man手冊作爲GNU軟件的主要文檔。用Texinfo書寫文檔來代替它。Man手冊只是因爲人們在Unix,它只是一個次要的應用程序,上運行GNU軟件才存在的。

‘manext’

作爲需要安裝的man手冊的文件的擴展名。它應該是一個點加上一個適當的數字;通常它應該是:’.1’

‘man1ext’

將被安裝到man手冊第一部分的文件的擴展名。

‘man2ext’

將被安裝到man手冊第二部分的文件的擴展名。

‘...’

如果包需要把man手冊安裝到手冊系統的多個部分,就用這些名字代替‘manext’。‘infodir’

爲本包安裝Info文件的目錄。在缺省狀態下,它應該是‘/usr/local/info’,當它應該被寫成‘$(prefix)/info’

‘srcdir’

用於編譯源代碼的目錄。該變量的值通常是由configureshell腳本插入的。例如:

# Common prefix for installation directories.# NOTE: This directory must exist when you start the install.prefix = /usr/localexec_prefix = $(prefix)# Where to put the executable for the command ‘gcc’.bindir = $(exec_prefix)/bin# Where to put the directories used by the compiler.libdir = $(exec_prefix)/lib# Where to put the Info files.infodir = $(prefix)/info

如果你的程序在一個標準的用戶給定的目錄中安裝了大量的文件,可能把爲程序特別提供的文件存放到子目錄中會有用一些。如果你這樣做了,你應該改寫install規則以創建這些子目錄。不要指望用戶會把子目錄名包括在上面列出的變量的值中。爲安裝目錄提供統一的變量名集合的意圖是使得用戶可以爲一些不同的GNU包指明完全相同的值。爲了使這些規定變得有用,所有的包都必須這樣設計以便在用戶這樣做的時候它們將能夠有效地工作。


配置是如何進行的

每個GNU發佈版本都應該還有一個名爲configureshell腳本。你需要把你希望在那種機器和系統上編譯程序作爲參數告訴這個腳本。腳本configure必須記錄配置信息以便它們可以影響編譯工作。這樣做的一種方式是把一個諸如‘config.h’的標準名字和爲選定的系統匹配的正確配置文件連接起來。如果你使用了這種技術,發佈版本中就不應該包含名爲

‘config.h’的文件。這樣做是爲了保證用戶在配置程序之前不能夠創建它。configure可以做的另一件事情是編輯Makefile。如果你這樣做了,發佈版本中就不能包含名爲‘Makefile’的文件。用‘Makefile.in’來代替它,並且‘Makefile.in’configure的編輯提供了輸入。同樣,這樣做是爲了保證用戶在配置程序之前不能創建它。如果configure生成了‘Makefile’,那麼‘Makefile’就應該包含一個名爲‘Makefile’的目標,這個目標將重新運行configure以與獲取上一次配置相同的配置信息。由configure讀取的文件,應該作爲依賴性文件而在‘Makefile’中被列出。所有由configure腳本生成的文件在它們的第一行都應該包含一條註釋以說明它們是由configure自動生成的。這樣做是爲了確保用戶不會試圖手工修改它們。

腳本configure應該寫入一個名爲’config.status’的文件,該文件說明了在程序的最後一次配置中給出了那些配置選項。該文件應該是一個shell腳本,如果運行它,將重新生成相同的配置。腳本configure應該接受形式爲‘--srcdir=dirname’的選項以指明在那個目錄中可以找到源代碼(如果源代碼不在當前目錄中)。這使得可以在實際代碼目錄沒有被修改的情況下,在分離的中創建程序成爲可能。如果用戶沒有給出‘--srcdir’,那麼configure將在‘.’和‘..’中尋找源文件。如果它在上述地方之一發現源文件,它就應該在那裏使用它們。否則,它應該報告它沒有找到源文件,並且以非零狀態退出。通常,支持‘--srcdir’的簡單方式是通過編輯被放到Makefile中的一個VPATH的定義。可能有一些規則需要被顯式地引用以指明源代碼目錄。爲了達到這個目的,configure可以把一個名爲srcdir的變量添加到Makefile中,該變量的值就是給定的目錄。腳本configure還應該提供一個可以指明程序是究竟爲那種系統而創建的選項。這個選項看起來應該象:

cpu-company-system

例如,一個Sun 3可能是‘m68k-sun-sunos4.1’

腳本configure需要能夠解釋所有對機器的似是而非的描述方式。因此,‘sun3-sunos4.1’應該是有效的別名。‘sun3-bsd4.2’也是如此,因爲SunOS是基於BSD的並且沒有其它的BSD系統被用於Sun。對於許多程序來說,因爲UltrixBSD之間的區別很少被注意到,所以‘vax-dec-ultrix’將是‘vax-dec-bsd’的一個別名。但少數程序可能需要區分它們。

這裏有一個被稱爲‘config.sub’shell腳本,你可以把它作爲一個子程序使用以檢查系統類型並且對別名進行規範化。允許出現其它選項以指明關於機器的軟件或者硬件的更多細節:

‘--with-package’

package將被安裝,所以把本包配置成與package一同工作。package可能的取值包括‘x’、‘gnu-as’(或者‘gas’)、‘gnu-ld’、‘gnu-libc’和‘gdb’

‘--nfp’

目標機器沒有浮點數處理器。

‘--gas’

目標機器的彙編器是GASGNU的彙編器。該選項已經過時了;用’--with-gnu-as’來代替。

‘--x’

目標機器已經安裝了X Window系統。該選項已經過時了;用’--with-x’ instead來代替。

所有的configure腳本都應該接受所有這些“細節”選項,而不論它們是否會對手頭的特定包產生影響。特別地,它們應該接受任何以’--with-’開頭的選項。這樣做是因爲這使得用戶可以用同一組選項配置整個GNU源代碼樹。

作爲編譯的一部分的包可能支持交叉編譯(cross-compliation)。在這種情況下,程序的主機和目標機器可能是不同的。configure通常把指明的系統類型當作主機和目標機器,因此將創建與運行它的機器類型相同的機器上運行的程序。創建交叉編譯器(cross-compiler)、交叉彙編器(cross-assembler)、或者你自己的程序,通過在運行configure時給出選項‘--host=hosttype’來完成。它在不影響目標機器的情況下指明瞭主機名。hosttype的語法與前面所說的一樣。因爲爲互操作(cross-operation)配置整個操作系統是一件沒有意義的事,對互操作來說沒有意義的程序就不必接受選項‘--host’

有些程序自動地配置它們自己。如果你的程序被設置成這樣,你的configure腳本只需要簡單地忽略它的大部分參數就行了。


使用C以外的語言

使用C以外的語言就好像使用非標準特徵:它將爲用戶帶來麻煩。即使GCC能夠支持其它語言,用戶也可能因爲不得不安裝其它語言的編譯器以創建你的程序而感到不便。所以請使用C語言。這條規則有三個例外:

如果有些程序包括了特殊語言的解釋器,那麼就可以使用這種語言。因此,GNU Emacs包含用Emacs Lisp寫的代碼就沒有問題,因爲GNU Emacs包含了Lisp解釋器。

如果一個工具就是爲了某種語言而編寫的,那麼就可以使用那種語言。這是因爲那些需要創建這個工具的人必然是那些已經安裝了其它語言的人。

如果一個應用程序沒有被極端廣泛地關注,那麼應用程序的安裝不太方面就不是特別重要。


格式化你的源代碼

把作爲C函數的開頭的左花括號放到第零列是十分重要的,並且避免把任何其它的左花括號、左括號或者左方括號放到第零列。有些工具通過尋找在第零列的左花括號來尋找C函數的起點。這些工具將不能處理那些不按照這種方式排版的代碼。對於函數定義來說,把函數名的起始字符放到第零列也同樣重要。這幫助任何尋找函數定義,並且可能有助於幫助某些工具識別它們。因此,正確的格式應該是:

static char *concat (s1, s2) /* Name starts in column zero here */ char *s1, *s2;{ /* Open brace in column zero here */ ...}

或者,如果你希望使用標準C,定義的格式是:

static char *concat (char *s1, char *s2){ ...}

在標準C中,如果參數不能夠被美觀地放在一行中,按照下面的方式把它們分開:

intlots_of_args (int an_integer, long a_long, short a_short, double a_double, float a_float)...

對於函數體,我們希望它按照如下方式排版:

if (x < foo (y, z)) haha = bar[4] 5;else { while (z) { haha = foo (z, z); z--; } return x bar (); }

我們發現如果在左括號之前以及逗號之後添加空格將使程序更加容易閱讀。尤其是在

逗號之後添加空格。當我們把一個表達式分成多行的時候,在操作符之前而不是之後分割。下面是正確的方式:

if (foo_this_is_long && bar > win (x, y, z) && remaining_condition)

盡力避免讓兩個不同優先級的操作符出現在相同的對齊方式中。例如,不要象下面那樣寫:

mode = (inmode[j] == VOIDmode || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j]) ? outmode[j] : inmode[j]);

應該附加額外的括號以使得文本縮進可以表示出這種嵌套:

mode = ((inmode[j] == VOIDmode || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j]))) ? outmode[j] : inmode[j]);

插入額外的括號以使得Emacs可以正確地對齊它們。例如,如果你手工完成縮進工作,

那麼它們看起來不錯,但Emacs將把它們混在一起:

v = rup->ru_utime.tv_sec*1000 rup->ru_utime.tv_usec/1000 rup->ru_stime.tv_sec*1000 rup->ru_stime.tv_usec/1000;

但添加一組括號解決了這個問題:

v = (rup->ru_utime.tv_sec*1000 rup->ru_utime.tv_usec/1000 rup->ru_stime.tv_sec*1000 rup->ru_stime.tv_usec/1000);

按照如下方式排版do-while語句:

do { a = foo (a); }while (a > 0);

請按照邏輯關係(而不是在函數中)使用走紙字符(control-L)以把程序劃分成頁。頁有多長並不重要,因爲它們不必被放在一個打印的頁中。走紙字符應該單獨地出現在一行中。


爲你的工作寫註釋

每個程序都應該以一段簡短地、說明其功能的註釋開頭。例如:‘fmt - filter for simple filling of text’。請爲每個函數書寫註釋以說明函數做了些什麼,需要哪些種類的參數,參數可能值的含義以及用途。如果按照常見的方式使用C語言類型,就沒有必要逐字重寫C參數聲明的含義。如果它使用了任何非標準的東西(例如,一個類型爲char *的參數實際上給出了一個字符串的第二個字符,而不是第一個字符,的地址),或者是可能導致函數不能工作的任何可能的值(例如,不能保證正確處理一個包含了新行的字符串),請確認對它們進行了說明。如果存在重要的返回值,也需要對其進行解釋。

請在你的註釋之後添加兩個空格,以便Emacs句子命名進行處理。還有,請書寫完整的句子並且使頭一個單詞以大寫字母開頭。如果小寫字母組成的標識符出現在句子的開頭,不要把它變成大寫的!修改拼寫就構成了不同的標識符。如果你不希望句子以小寫字母開頭,可以寫下不同的句子(例如,“The identifier lower-case is ...”)。

如果你使用參數名來說明參數值,關於函數的註釋就會更清晰。變量名本身應該是小寫的,但在你說到它的值而不是變量本身的時候就使用大寫字母。因此,“the inode number node_num”比“an inode”要好。通常在函數之前的註釋中沒有必要重新提到函數的名字,因爲讀者可以自己看到它。一種可能的例外是:註釋太長了,以至於函數本身被擠出了屏幕底端之外。對於每個靜態變量,也象下面那樣應該提供註釋:

/* Nonzero means truncate lines in the display; zero means continue them. */int truncate_lines;

除非’#endif’是一個沒有嵌套而且很短(只有幾行)的條件,每個‘#endif’都應該含有一個註釋。註釋應該說明它所結束的條件,包括它的含義。‘#else’應該含有一個說明條件與隨後代碼的含義的註釋。例如:

#ifdef foo ...#else /* not foo */ ...#endif /* not foo */

但相反,按照如下方式爲’#ifndef’寫註釋:

#ifndef foo ...#else /* foo */ ...#endif /* foo */


清晰地使用C語言成分

請顯式地聲明函數的所有參數。不要因爲它們是整數就忽略它們。對外部函數以即將隨後出現在源文件中的函數的聲明應該出現在靠近文件開頭(在第一個函數定義之前的某個地方)的同一個地方。或者其它的聲明應該出現在頭文件中。不要在函數中放置外部聲明。在過去一種常見的做法是在同一個函數中把同一個局部變量(比如說名爲tem的變量反覆地用於不同的值。但現在,更好的方式是爲每個不同的目的分別定義局部變量,並且給它們以更

有意義的名字。這不僅僅是程序更容易理解,它還會被好的編譯程序所優化。你還可以把對局部變量的聲明放到包含對它的使用的最小範圍中。這可以把程序變得更清晰。

不要使用可以遮蔽全局標識符的局部變量和參數。不要在跨越了行的聲明中聲明多個變量。在每一行中都以一個新的聲明開頭。例如,不應該:

int foo, bar;

而應該:

int foo, bar;

或者:

int foo;int bar;

(如果它們是全局變量,在它們之中的每一個之前都應該添加一條註釋。)

當你在一個if語句中嵌套了另一個if-else語句,總是用花括號把if-else括起來。因此,不要寫:

if (foo) if (bar) win (); else lose ();

而總是要寫:

if (foo) { if (bar) win (); else lose (); }

如果你在else語句中嵌套了一個if語句,即可以像下面那樣寫else if

if (foo) ...else if (bar) ...

按照與then那部分代碼相同的縮進方式縮進else ifthen部分代碼,也可以在

花括號中像下面那樣把if嵌套起來:

if (foo) ...else { if (bar) ... }

不要在同一個聲明中同時說明結構標識和變量或者結構標試和類型定義(typedef)。

單獨地說明結構標試,而後用它定義變量或者定義類型。盡力避免在if的條件中進行賦值。例如,不要寫:

if ((foo = (char *) malloc (sizeof *foo)) == 0) fatal ("virtual memory exhausted");

而要寫:

foo = (char *) malloc (sizeof *foo);if (foo == 0) fatal ("virtual memory exhausted");

不要爲了通過lint的檢查而把程序修改得難看。請不要加入任何關於void的強制類型轉換。沒有進行類型轉換的零作爲空指針常量是很好的。


命名變量和函數

請在名字中使用下劃線以分隔單詞,以便Emacs單詞命令對它們來說有用。堅持使用小寫;把大寫字母留給宏和枚舉常量,以及根據統一的慣例使用的前綴。例如,你應該使用類似ignore_space_change_flag的名字;不要使用類似iCantReadThis的名字。用於標明一個命令行選項是否被給出的變量應該在選項含義的說明之後,而不是選項字符之後,被命名。一條註釋即應該說明選項的精確含義,還應該說明選項的字母。例如,

/* Ignore changes in horizontal whitespace (-b). */int ignore_space_change_flag;

當你需要爲常量整數值定義名字的時候,使用enum而不是’#define’GDB知道枚舉常量。使用14個字符或者少於14個字符的文件名,以避免無緣無故地在System V上導致問題。


使用非標準的特徵

許多現有的GNU工具在兼容Unix工具的基礎上提供了許多方便的擴展。在實現你的程序時是否使用這些擴展是一個難以回答的問題。一方面,使用擴展可以使程序變得清晰。但另一方面,除非人們可以得到其它的GNU工具,人們就不能創建程序。這可能使得程序只能在較少類型的機器上工作。對於某些擴展,可能可以很容易地應付上述兩種選擇。例如,你可以用“關鍵字”INLINE定義函數並且把INLINE定義成一個宏,在根據編譯器確定它是被擴展成inline或者擴展成空。

一般地,如果你能夠在沒有它們的情況下直截了當地完成任務,可能最好的辦法是不使用擴展,但如果擴展可以大大地改進你的工作,就使用擴展。對這一規則的一個例外是那些運行在大量不同系統上的大規模、已經創建的程序(例如Emacs)。使用GNU擴展將破壞這些程序。另一個例外是那些作爲編譯本身的一部分的程序:這包括必須用其它編譯器進行編譯以構造GNU編譯工具的任何東西。如果它們需要GNU編譯器,那麼沒有人可以在沒有安裝它們的情況下編譯它們。這將是不好的。由於大部分計算機系統還沒有實現標準C,使用標準C的特徵,所以使用標準C就相當於使用GNU擴展,所以前面的考慮也適用於它。

(除了那些令我們失望的標準特徵,例如三元組序列(trigraphs--永遠不要使用它們。)三元組序列是標準C中爲了彌補某些終端上可用字符的不足而提供的、用三個字符組合代替

一個特殊字符的方法。所有可用的三元組爲:“??=”轉換成“#”、“??/”轉換成“/”、“??’”轉換成“^”、“??(”轉換成“[”、“??)”轉換成“]”、“??!”轉換成“|”、“??<”轉換成“{”

“??>”轉換成“}”、“??-”轉換成“~”


適用於所有程序的程序行爲

通過動態地分配所有的數據結構來避免對任何數據結構,包括變量名、行、文件和符號,的長度和數量施加任何限制。在大多數Unix工具中,“長行被沒有提示地截斷”了。對於GNU工具來說,這是不可接受的。讀入文件的工具不應該放棄NUL字符、或者任何不可打印的字符,包括那些大於0177的字符。唯一明智的例外是那些爲訪問與不能處理這些字符的特定類型的打印機的界面而設計的工具。爲每個系統調用的返回值進行錯誤檢查,除非你知道你希望忽略錯誤。把那些系統錯誤文字(來自於perror或者它的等價物)包括在每個有失敗的系統調用導致的錯誤消息中,如果有的話還要包括文件名和工具名。僅僅給出“cannot open foo.c”或者“stat failed”是不夠的。

檢查每個對malloc或者realloc的調用以察看它是否返回0。即使在realloc使塊變小的時候,也要檢查它的返回值;在有些系統中總是把塊的大小擴大到2的冪次。如果你申請更少的空間,realloc可能得到一個不同的塊。在Unix中,如果realloc返回0,那麼它就可以破壞存儲塊。GNU realloc沒有這個錯誤:如果它失敗了,原來的塊不會被改變。放心地假定這個錯誤已經被修正了。如果你希望在Unix上運行你的程序,並且在這種情況下不希望失去內存塊,你可以使用GNU malloc。你必須假定free將改變被釋放的塊的內容。任何你希望從塊中獲得的東西,你必須在調用free之前拿到它。

使用getopt_long對參數進行解碼,除非參數的語法使得這樣做變得不合情理。當靜態內存在程序執行的時候被寫入的情況下,顯式地使用C代碼來初始化它。保留對那些不會被改變的數據的C初始化聲明。

盡力避免訪問晦澀的Unix數據結構的低級界面(例如文件目錄、utmp或者內核內存的分佈),因爲它們通常會降低兼容性。如果你希望找到目錄中的所有文件,使用readdir或者其它高級的界面。GNU兼容將地支持它們。

在缺省狀態下,GNU系統將提供BSD的信號處理函數和POSIX的信號處理函數。因此GNU軟件應該使用它們。在錯誤中檢測到“不可能”的條件是,只要退出就行。沒有理由打印任何消息。這些檢查表明有bug存在。任何希望修正錯誤的人都必須閱讀源代碼並且運行調試器。所以在源代碼中通過註釋給出問題的解釋。相關的數據將儲存在變量中,這些變量很容易被調試器檢測到,所以沒有理由把它們轉移的其它任何地方。


格式化錯誤信息

來自於編譯器的錯誤信息應該使用格式:

source-file-name:lineno: message

如果有適當的源文件存在,則來自於非交互式程序的錯誤信息應該使用格式:

program:source-file-name:lineno: message

或者,如果沒有相關的源文件,則應該使用格式:

program: message

在一個交互式程序(從終端讀入命令的程序)中,不把程序名包括在錯誤信息中更好一些。指明程序正在運行的地方應該是提示符或者屏幕的佈局。(當相同的程序在運行時從源文件中,而不是從終端中讀取輸入,它就不是交互式的了,並且最好按照非交互方式風格打印錯誤信息。)在字符串message被放置在程序名和/或文件名之後時,它不應該以大寫字母開頭。此外,它也不應該以句點結尾。來自於交互式程序的錯誤信息,以及其它的諸如使用信息的信息,應該以大寫字母開頭。但它們不應該以句點結尾。


庫的行爲

試着使庫函數成爲可再入的。如果它們需要進行動態內存分配,至少要試圖避免任何來自malloc本身的非可再入的方面。這裏給出了一些庫的命名慣例,以避免名字的衝突。爲庫選擇一個多於兩個字符的命名前綴。所有的外部函數和變量名都應該以這個前綴開頭。

還有,在任何給定的庫成員中,僅僅應該含有一個函數或者變量。這通常意味着要把每個函數和變量都放在單獨的源文件中。一個例外是如果兩個符號總是在一起使用,而使得沒有任何合理的程序只需要使用其中的一個而不使用另外一個;那麼它們可以被放在同一個文件中。標示沒有在文檔中給予說明的調用點的外部符號的名字應該以‘_’開頭。它們還要包括爲庫選擇的名字前綴,以防止與其它庫可能產生的衝突。如果你願意,它們可以和

用戶調用點放在同一個文件中。你可以按照你的意願使用靜態函數和靜態變量並且它們不需要服從任何命名慣例。


適用於GNU的移植性

Unix世界中,“移植性”往往指的是移植到不同的Unix版本中。對於GNU軟件來說是次要的,這是因爲它們的主要目的是運行在GNU內核上而且僅僅運行與其上,使用GNU C編譯器編譯並且僅僅由它進行編譯。不同cpu上的各種GNU系統的變種數量將象不同cpu上的Berkeley 4.3系統的變種一樣多。今天所有的用戶都在非GNU系統上運行GNU軟件。所以有必要支持各種非GNU系統;但不是特別重要。在合理範圍的系統上獲得可移植性的最簡單方式是使用Autoconf。由於大部分程序需要知道的關於主機的知識已經由Autoconf寫下來了,所以你的程序不太可能需要知道比Autoconf所能夠提供的知識的更多知識。

因爲目前GNU內核還沒有被完成,所以還難以確定GNU內核將提供那些工具。因此,就假定你可以使用你在4.3中可以使用的任何東西;只要避免使用有更高級的替代結構(readdir)存在的半內部的數據結構(例如,directories)就可以了。你可以自由地假設任何合理C語言標準工具、庫或者內核,因爲我們將發現有必要在完整的GNU系統中支持它們,而不論我們是否已經這樣做了。一些現有的內核或者C編譯器所缺少的功能與GNU內核和

C編譯器對它們的支持沒有關係。

另外一個需要擔心的是cpy類型間的不同,例如字節序(byte order)的不同和對齊限制的不同。16位的機器恐怕不會被GNU所支持,所以沒有必要花費任何時間去考慮整數少於32位的可能性。你可以假定所有的指針都具有相同的格式,而不論它們指向什麼,並且它們實際上都是一個整數。在一些怪異的機器上不是這樣,但它們並不重要;不要爲迎合它們而浪費時間。此外,我們最終將把函數原型放到所有的GNU程序中,而那將可能使你的程序即使在怪異的機器上也能夠工作。因爲一些重要的機器(包括68000)是高位開頭(big-endian),不能假定整數對象的地址就是最低位字節(least-significant)的地址是十分重要的。因此,不要犯如下的錯誤:

int c;...while ((c = getchar()) != EOF) write(file_descriptor, &c, 1);

你可以假定使用一兆內存是合理的。除非可以在那個層次可以做到,不要爲減少內存的使用而費力。如果你的程序創建了複雜的數據結構,就把它們存放在內核中,並且在malloc返回0的時候給出一個致命錯誤就行了。

如果一個程序按照行工作並且可以被用於任何用戶提供的輸入文件,它就應該在內存中

僅僅保存一行,因爲這並不十分困難並且用戶將需要能夠操作比內核一次能夠處理的文件更大的輸入文件。


命令行界面標準

請不要讓工具的行爲依賴於調用它的名字。有時需要把一個工具連接到不同的名字,

並且這將不會改變它的功能。最爲替代,可以在運行時使用選項或者編譯器選項,或者同時使用兩者來選擇不同的程序行爲。服從POSIX關於程序的命令行選項的指導是一個好主意。這樣做的最簡單方式是使用getopt來分析選項。需要指出的是,除非使用了特殊參數‘--’GNU版本的getopt通常允許選項出現在參數中的任何位置。這不是POSIX的規定;它是GNU的擴展。請爲Unix風格的單字符選項定義等價的長名字選項。我們希望以這種方式使GNU對用戶更加友好。通過使用GNU函數getopt_long很容易做到這一點。

通常僅僅把最爲普通參數給出的文件名當作輸入文件是一個好主意;所有輸出文件都應該通過選項給出(使用‘-o’更好)。即使你爲了保持兼容性而允許把普通參數當作輸出文件名,你仍然可以試圖爲輸出文件名提供一個合適的選項。這將爲GNU工具帶來更多的一致性,以減少用戶需要記憶的特徵。程序還應該支持一個用於輸出程序的版本號的選項‘--version’,以及支持一個用於輸出選項用法信息的選項‘--help’


爲程序製作文檔

GNU程序製作文檔請使用Texinfo。參見Texinfo手冊,硬拷貝或者GNU Emacs Info子系統中的版本都行。(C-h i)。作爲例子,可以看看現有的GNU Texinfo文件(例如,在GNU Emacs發佈版本中‘man/’目錄下的Texinfo文件)。手冊的標題頁應該說明本手冊適用於程序的那個版本。手冊的頂節點也應該包含這個信息。如果手冊比程序改變得還要快,後者與程序是無關的,請在上述兩個地方說明手冊的版本號。手冊應該說明所有的命令行參數和所有的命令。手冊應該給出使用它們的例子。但不要把手冊組織成一個特徵的列表。相反,在手冊中按照把用戶需要理解的概念放在特徵之前的方式組織文檔。說明用戶可能需要達到的目的,並且解釋如何完成它們。不要把Unix man手冊作爲書寫GNU文檔的模式;它們不是值得模仿的好例子。

手冊應該有一個名爲‘program Invocation’或者‘program Invoke’或者‘Invoking program’的節點,其中program是該程序的程序名,也就是在shell中輸入以運行程序的那個名字。該節點(如果它有子節點,也包括子節點)應該說明程序的命令行參數以及如何運行它(也就是人們將在man手冊中看到的那一類信息)。以‘@example’開頭包含一個程序可以使用的所有選項和參數的模板。

另一種方式是,在某些菜單中添加一個名字符合上述模式之一的菜單項。它表明由菜單項指出的節點就是爲此而創建的,而不管這個節點的實際名字是什麼。將會有一個自動的功能使得用戶可以給出程序名並且只需要快速地閱讀手冊的這個部分。如果一個手冊說明了多個程序,那麼手冊應該爲每個它所說明的程序定義一個這樣的節點。除了程序包的手冊之外,包還應該包含一個名爲’NEWS’的文件,它包含了一個對用戶來說是可見的、並且值得一提的修改。在每個新的發行版本中,在文件的前面添加新的條目並且指出適用於它們的版本。不要刪除原來的條目;把它們保留在新條目的後面。按照這種方式,從以前的版本升級的

用戶就可以看到有那些新的功能。

如果‘NEWS’文件變得太長了,可以把一些陳舊的條目放到一個名爲‘ONEWS’

的文件中,並且在‘NEWS’文件的結尾加一個說明以告知用戶參考‘ONEWS’。如果你願意,你可以在提供Texinfo手冊的同時提供man手冊。但請記住維護man手冊需要在每次程序改變的時候都付出努力。你花費在man手冊上的任何時間都消耗了你本來能夠用在貢獻更有價值的東西上的時間。

因此,即使用戶自願提供man手冊,你可能會發現這個禮物太麻煩而不值得接受。除非你手頭有時間,並且除非有志願者願意承擔維護它的全部責任--以至於你可以把它完整地交給他,拒絕提供man手冊可能會更好一些。如果志願者停止維護man手冊,那麼也不必感到有責任讓你自己承擔它;在其它的出現志願者維護它之前撤銷man手冊也許更好一些。

另一種方式是,如果你希望man手冊的內容與實際情況區別很小而使得man手冊仍然有用,可以在man手冊的開頭給出顯著的聲明以說明你沒有維護它並且Texinfo手冊是更加權威的,同時指出如何訪問Texinfo文檔。


製作發行包

發行Foo的版本69.96tar文件包的名字是’foo-69.96.tar’。它應該被解包到名爲

‘foo-69.96’的子目錄中。對程序的創建和安裝不應該修改發佈版本中的任何文件。這意味着以任何方式作爲程序的正式部分的所有文件都必須被分成源文件和非源文件兩類。源文件由人手工編寫並且不會被自動改變;非源文件則在Makefile的控制下由程序從源文件生成。自然地,所有源文件必須出現在發佈版本中。只有在非源文件不是過時的並且與機器是無關的情況下,從而在創建發佈版本時將不會需要修改它們,才能把非源文件包含在發佈版本中。我們一般把由BisonLexTeXMakeinfo生成的非源文件包括進去;這有助於避免在我們的發佈版本中引入不必要的依賴性,以使得用戶可以安裝他們需要安裝的包。

永遠不要把實際上可能在程序的創建和安裝中被修改的非源文件包含在發佈版本中。所以如果你發佈非源文件,在你作新的發佈時,總是要確認它們沒有過時。確保從發佈包中解開的目錄(以及所有的子目錄)對於所有人來說都是可寫的(八進制模式777)。這樣做是爲了使那些保留從tar包中取出的文件的所有權(ownership)和許可權(permissions)的老版本的tar,即使在用戶沒有授權的情況下也能夠提取出所有的文件。確保在發佈版本中的沒有多於14個字符的文件名。同樣地,由程序創建的文件都不含有長於14個字符的文件名。這樣做的原因是有些系統堅持POSIX標準的愚蠢解釋,並且拒絕打開長文件名,而不是象在過去那樣把文件名截短。

不要在發佈版本本身包含任何符號連接。如果tar文件包括符號連接,那麼人們甚至不能在那些不能支持符號連接的系統上打開包。還有,不要在不同的目錄中爲一個文件使用多個名字,因爲某些文件系統不能處理它並且這使得包不能在這類文件系統上被打開。試着確保所有的文件名在MS-DOG下都是唯一的。在MS-DOG下文件名由8個字符組成,後面還可以附加一個點和至多三個字符。MS-DOG將截斷點之前和之後的多餘字符。因此,‘foobarhacker.c’和‘foobarhacker.o’不會被混淆;它們被截斷成‘foobarha.c’和‘foobarha.o’

,它們是截然不同的。

在你的發佈版本中包含一個你用來測試打印所有‘*.texinfo’文件的‘texinfo.tex’的副本。同樣地,如果你的程序使用了諸如regexgetoptobstack或者termcap之類的小GNU軟件包,把它們包括在發佈文件中。把它們排除在外將使發佈文件小一些,其代價是給那些不知道需要哪些額外文件的用戶帶來不便。

發佈了20 篇原創文章 · 獲贊 10 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章