Makefile 總述

第三章:Makefile 總述

3.1 Makefile內容

在一個完整的Makefile中,包含了5個東西:顯式規則、隱含規則、變量定義、指示符和註釋。關於“規則”、“變量”和“Makefile指示符”將在後續的章節進行詳細的討論。本章討論的是一些基本概念。

²       顯式規則:它描述了在何種情況下如何更新一個或者多個被稱爲目標的文件(Makefile的目標文件)。書寫Makefile時需要明確地給出目標文件、目標的依賴文件列表以及更新目標文件所需要的命令(有些規則沒有命令,這樣的規則只是純粹的描述了文件之間的依賴關係)。

²       隱含規則:它是make根據一類目標文件(典型的是根據文件名的後綴)而自動推導出來的規則。make根據目標文件的名,自動產生目標的依賴文件並使用默認的命令來對目標進行更新(建立一個規則)。

²       變量定義:使用一個字符或字符串代表一段文本串,當定義了一個變量以後,Makefile後續在需要使用此文本串的地方,通過引用這個變量來實現對文本串的使用。第一章的例子中,我們就定義了一個變量“objects”來表示一個.o文件列表。

²       Makefile指示符:指示符指明在make程序讀取makefile文件過程中所要執行的一個動作。其中包括:

²       讀取一個文件,讀取給定文件名的文件,將其內容作爲makefile文件的一部分。

²       決定(通常是根據一個變量的得值)處理或者忽略Makefile中的某一特定部分。

²       定義一個多行變量。

²       註釋:Makefile中“#”字符後的內容被作爲是註釋內容(和shell腳本一樣)處理。如果此行的第一個非空字符爲“#”,那麼此行爲註釋行。註釋行的結尾如果存在反斜線(/),那麼下一行也被作爲註釋行。一般在書寫Makefile時推薦將註釋作爲一個獨立的行,而不要和Makefile的有效行放在一行中書寫。當在Makefile中需要使用字符“#”時,可以使用反斜線加“#”(/#)來實現(對特殊字符“#”的轉義),其表示將“#”作爲一字符而不是註釋的開始標誌。

需要注意的地方:

Makefile中第一個規則之後的所有以[Tab]字符開始的的行,make程序都會將其交給系統shell程序去解釋執行。因此,以[Tab]字符開始的註釋行也會被交給shell來處理,此命令行是否需要被執行(shell執行或者忽略)是由系統shell程序來判決的。

另外,在使用指示符“define”定義一個多行的變量或者命令包時,其定義體(“define”和“endef”之間的內容)會被完整的展開到Makefile中引用此變量的地方(包含定義體中的註釋行);make在引用此變量的地方對所有的定義體進行處理,決定是註釋還是有效內容。Makefile中變量的引用和C語言中的宏類似(但是其實質並不相同,後續將會詳細討論)。對一個變量引用的地方make所做的就是將這個變量根據定義進行基於文本的展開,展開變量的過程不涉及到任何變量的具體含義和功能分析。

3.2 makefile文件的命名

默認的情況下,make會在工作目錄(執行make的目錄)下按照文件名順序尋找makefile文件讀取並執行,查找的文件名順序爲:“GNUmakefile”、“makefile”、“Makefile”。

通常應該使用“makefile”或者“Makefile”作爲一個makefile的文件名(我們推薦使用“Makefile”,首字母大寫而比較顯著,一般在一個目錄中和當前目錄的一些重要文件(README,Chagelist等)靠近,在尋找時會比較容易的發現它)。而“GNUmakefile”是我們不推薦使用的文件名,因爲以此命名的文件只有“GNU make”纔可以識別,而其他版本的make程序只會在工作目錄下“makefile”和“Makefile”這兩個文件。

如果make程序在工作目錄下無法找到以上三個文件中的任何一個,它將不讀取任何其他文件作爲解析對象。但是根據make隱含規則的特性,我們可以通過命令行指定一個目標,如果當前目錄下存在符合此目標的依賴文件,那麼這個命令行所指定的目標將會被創建或者更新,參見注釋。

makefile文件的命名不是這三個任何一個時,需要通過make的“-f”或者“--file”選項來指定make讀取的makefile文件。給make指定makefile文件的格式爲:“-f NAME”或者“—file=NAME”,它指定文件“NAME”作爲執行make時讀取的makefile文件。也可以通過多個“-f”或者“--file”選項來指定多個需要讀取的makefile文件,多個makefile文件將會被按照指定的順序進行鏈接並被make解析執行。當通過“-f”或者“--file”指定make讀取makefile的文件時,make就不再自動查找這三個標準命名的makefile文件。

註釋:通過命令指定目標使用make的隱含規則:

當前目錄下不存在以GNUmakefile”、“makefile”、“Makefile”命名的任何文件,

1.        當前目錄下存在一個源文件foo.c的,我們可以使用“make foo.o”來使用make的隱含規則自動生成foo.o。當執行“make foo.o”時。我們可以看到其執行的命令爲:

cc –c –o foo.o foo.c

之後,foo.o將會被創建或者更新。

2.        如果當前目錄下沒有foo.c文件時,就是make.o文件目標的隱含規則中依賴文件不存在。如果使用命令“make foo.o”時,將回到到如下提示:

make: *** No rule to make target ‘foo.o’. Stop.

3.        如果直接使用命令“make”時,得到的提示信息如下:

make: *** No targets specified and no makefile found. Stop.

 

3.3 包含其它makefile文件

本節我們討論如何在一個Makefile中包含其它的makefile文件。Makefile中包含其它文件所需要使用的關鍵字是“include”,和c語言對頭文件的包含方式一致。

include”指示符告訴make暫停讀取當前的Makefile,而轉去讀取“include”指定的一個或者多個文件,完成以後再繼續當前Makefile的讀取。Makefile中指示符“include”書寫在獨立的一行,其形式如下:

 

include FILENAMES...

 

FILENAMESshell所支持的文件名(可以使用通配符)。

指示符“include”所在的行可以一個或者多個空格(make程序在處理時將忽略這些空格)開始,切忌不能以[Tab]字符開始(如果一行以[Tab]字符開始make程序將此行作爲一個命令行來處理)。指示符“include”和文件名之間、多個文件之間使用空格或者[Tab]鍵隔開。行尾的空白字符在處理時被忽略。使用指示符包含進來的Makefile中,如果存在變量或者函數的引用。它們將會在包含它們的Makefile中被展開(詳細可參考 第六章 Makefile中的變量)。

來看一個例子,存在三個.mk文件a.mkb.mkc.mk,“$(bar)”被擴展爲“bish bash”。則

 

include foo *.mk $(bar)

 

等價於

 

include foo a.mk b.mk c.mk bish bash

 

之前已經提到過make程序在處理指示符include時,將暫停對當前使用指示符“include”的makefile文件的讀取,而轉去依此讀取由“include”指示符指定的文件列表。直到完成所有這些文件以後再回過頭繼續讀取指示符“include”所在的makefile文件。

通常指示符“include”用在以下場合:

1.        有多個不同的程序,由不同目錄下的幾個獨立的Makefile來描述其重建規則。它們需要使用一組通用的變量定義或者模式規則。通用的做法是將這些共同使用的變量或者模式規則定義在一個文件中(沒有具體的文件命名限制),在需要使用的Makefile中使用指示符“include”來包含此文件。

2.        當根據源文件自動產生依賴文件時;我們可以將自動產生的依賴關係保存在另外一個文件中,主Makefile使用指示符“include”包含這些文件。這樣的做法比直接在主Makefile中追加依賴文件的方法要明智的多。其它版本的make已經使用這種方式來處理。

如果指示符“include”指定的文件不是以斜線開始(絕對路徑,如/usr/src/Makefile...),而且當前目錄下也不存在此文件;make將根據文件名試圖在以下幾個目錄下查找:首先,查找使用命令行選項“-I”或者“--include-dir”指定的目錄,如果找到指定的文件,則使用這個文件;否則繼續依此搜索以下幾個目錄(如果其存在):“/usr/gnu/include”、“/usr/local/include”和“/usr/include”。

當在這些目錄下都沒有找到“include”指定的文件時,make將會提示一個包含文件未找到的告警提示,但是不會立刻退出。而是繼續處理Makefile的後續內容。當完成讀取整個Makefile後,make將試圖使用規則來創建通過指示符“include”指定的但未找到的文件,當不能創建它時(沒有創建這個文件的規則),make將提示致命錯誤並退出。會輸出類似如下錯誤提示:

 

Makefile:錯誤的行數:未找到文件名:提示信息(No such file or directory

Make *** No rule to make target ‘<filename>’. Stop

 

通常我們在Makefile中可使用“-include”來代替“include”,來忽略由於包含文件不存在或者無法創建時的錯誤提示(“-”的意思是告訴make,忽略此操作的錯誤。make繼續執行)。像下邊那樣:

 

-include FILENAMES...

 

使用這種方式時,當所要包含的文件不存在時不會有錯誤提示、make也不會退出;除此之外,和第一種方式效果相同。以下是這兩種方式的比較:

使用“include FILENAMES...”,make程序處理時,如果“FILENAMES”列表中的任何一個文件不能正常讀取而且不存在一個創建此文件的規則時make程序將會提示錯誤並退出。

使用“-include FILENAMES...”的情況是,當所包含的文件不存在或者不存在一個規則去創建它,make程序會繼續執行,只有真正由於不能正確完成終極目標的重建時(某些必需的目標無法在當前已讀取的makefile文件內容中找到正確的重建規則),纔會提示致命錯誤並退出。

爲了和其它的make程序進行兼容。也可以使用“sinclude”來代替“-include”(GNU所支持的方式)。 

3.4  變量 MAKEFILES

如果在當前環境定義了一個“MAKEFILES”環境變量,make執行時首先將此變量的值作爲需要讀入的Makefile文件,多個文件之間使用空格分開。類似使用指示符“include”包含其它Makefile文件一樣,如果文件名非絕對路徑而且當前目錄也不存在此文件,make會在一些默認的目錄去尋找。它和使用“include”的區別:

1.        環境變量指定的makefile文件中的“目標”不會被作爲make執行的“終極目標”。就是說,這些文件中所定義規則的目標,make不會將其作爲“終極目標”來看待。如果在make的工作目錄下沒有一個名爲“Makefile”、“makefile”或者“GNUmakefile”的文件,make同樣會提示“make: *** No targets specified and no makefile found. Stop.”;而在make的工作目錄下存在這樣一個文件(“Makefile”、“makefile”或者“GNUmakefile”),那麼make執行時的“終極目標”就是當前目錄下這個文件中所定義的“終極目標”。

2.        環境變量所定義的文件列表,在執行make時,如果不能找到其中某一個文件(不存在或者無法創建)。make不會提示錯誤,也不退出。就是說環境變量“MAKEFILES”定義的包含文件是否存在不會導致make錯誤(這是比較隱蔽的地方)。

3.        make在執行時,首先讀取的是環境變量“MAKEFILES”所指定的文件列表,之後纔是工作目錄下的makefile文件,“include”所指定的文件是在make發現此關鍵字的時、暫停正在讀取的文件而轉去讀取“include”所指定的文件。

變量“MAKEFILES”主要用在“make”的遞歸調用過程中的的通信。實際應用中很少設置此變量。因爲一旦設置了此變量,在多級make調用時;由於每一級make都會讀取“MAKEFILES”變量所指定的文件,將導致執行出現混亂(這可能不是你想看到的執行結果)。不過,我們可以使用此環境變量來指定一個定義了通用“隱含規則”和變量的文件,比如設置默認搜索路徑;通過這種方式設置的“隱含規則”和定義的變量可以被任何make進程使用(有點象C語言中的全局變量)。

也有人想讓login程序自動的在自己的工作環境中設置此環境變量,編寫的Makefile建立在此環境變量的基礎上。此想法可以肯定地說不是一個好主意。規勸大家千萬不要這麼幹,否則你所編寫的Makefile在其人的工作環境中肯定不能正常工作。因爲別人的工作環境中可能沒有設置相同的環境變量“MAKEFILES”。

推薦的做法實:在需要包含其它makefile文件時使用指示符“include”來實現。 

3.5 變量 MAKEFILE_LIST

make程序在讀取多個makefile文件時,包括由環境變量“MAKEFILES”指定、命令行指、當前工作下的默認的以及使用指示符“include”指定包含的,在對這些文件進行解析執行之前make讀取的文件名將會被自動依次追加到變量“MAKEFILE_LIST”的定義域中。

這樣我們就可以通過測試此變量的最後一個字來獲取當前make程序正在處理的makefile文件名。具體地說就是在一個makefile文件中如果使用指示符“include”包含另外一個文件之後,變量“MAKEFILE_LIST”的最後一個字只可能是指示符“include”指定所要包含的那個文件的名字。一個makefile的內容如下:

 

name1 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))

 

include inc.mk

 

name2 := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))

 

all:

@echo name1 = $(name1)

@echo name2 = $(name2)

 

執行make,則看到的將是如下的結果:

name1 = Makefile

name2 = inc.mk

此例子中涉及到了make的函數的和變量定義的方式,這些將在後續的章節中有詳細的講述。 

3.6 其他特殊變量

GNU make支持一個特殊的變量,此變量不能通過任何途經給它賦值。它被展開爲一個特定的值。一個重要的特殊的變量是“.VARIABLES”。它被展開以後是此引用點之前、makefile文件中所定義的所有全局變量列表。包括:空變量(未賦值的變量)和make的內嵌變量,但不包含目標指定的變量,目標指定變量值在特定目標的上下文有效。

3.7 makefile文件重建

Makefile可由其它文件生成,例如RCSSCCS文件。如果Makefile由其它文件重建,那麼在make在開始解析這個Makefile時需要重新讀取更新後的Makefile、而不是之前的Makefilemake的處理過程是這樣的:

make在讀入所有makefile文件之後,首先將所讀取的每個makefile作爲一個目標,尋找更新它們的規則。如果存在一個更新某一個makefile文件明確規則或者隱含規則,就去更新對應的makefile文件。完成對所有的makefile文件的更新之後,如果之前所讀取任何一個makefile文件被更新,那麼make就清除本次執行的狀態重新讀取一遍所有的makefile文件(此過程中,同樣在讀取完成以後也會去試圖更新所有的已經讀取的makefile文件,但是一般這些文件不會再次被重建,因爲它們在時間戳上已經是最新的)。讀取完成以後再開始解析已經讀取的makefile文件並開始執行必要的動作。

實際應用中,我們會明確給出makefile文件,而並不需要來由make自動重建它們。但是make在每一次執行時總會自動地試圖重建那些已經存在的makefile文件,如果需要處於效率考慮,可以採用一些辦法來避免make在執行過程時查找重建makefile的隱含規則。例如我們可以書寫一個明確的規則,以makefile文件作爲目標,規則的命令定義爲空。

Makefile規則中,如果使用一個沒有依賴只有命令行的雙冒號規則去更新一個文件,那麼每次執行make時,此規則的目標文件將會被無條件的更新(此規則定義的命令會被無條件執行)。如果這樣一個規則的目標是makefile文件,那麼執行make時,這個makefile文件(雙冒號規則的目標)就會被無條件更新,而使得make的執行陷入到一個死循環(此makefile文件被不斷的更新、重新讀取、更新再重新讀取的過程)。爲了防止這種情況的發生,make在遇到一個目標是makefile文件的雙冒號規則時,將忽略對這個規則的執行(其中包括了使用“MAKEFILES”指定、命令行選項指定、指示符“include”指定的需要make讀取的所有makefile文件中定義的這一類雙冒號規則)。

執行make時,如果沒有使用“-f--file)”選項指定一個文件,make程序將讀取缺省的文件。和使用“-f--file)”選項不同,make無法確定工作目錄下是否存在缺省名稱的makefile文件。如果缺省makefile文件不存在,但可以通過一個規則來創建它(此規則是隱含規則),則會自動創建缺省makefile文件,之後重新讀取它並開始執行。

因此,如果在當前目錄下不存在一個缺省的makefile文件,make將會按照搜索makefile文件的名稱順序去試圖創建它,直到創建成功或者超越其缺省的命名順序。需要明確的一點是:執行make時,如果不能成功地創建缺省的makefile文件,並不一定會導致錯誤。一個存在(缺省命名的或者可被創建的)的makefile文件並不是make正確運行的前提(關於這一點大家會在後續的閱讀過程中體會到)。

當使用“-t--touch)”選項來更新Makefile的目標文件(更新規則目標文件的時間戳)時,對於哪些是makefile文件的目標是無效的,這些目標文件(makefile文件)的時間戳並不會被更新。就是說即使在執行make時使用了選項“-t”,那些目標是makefile文件的規則同樣也會被執行(重建這些makefile文件,而其它的規則不會被執行,make只是簡單的更新規則目標文件的時間戳);類似還有選項“-q—question)”和“-n—just-print ”,這主要是因爲一個過時的makefile文件對其它目標的重建規則在當前看來可能是錯誤的。

正因爲如此,執行命令“make –f mfile –n foo”首先會試圖重建“mfile文件”、並重新讀取它,之後會打印出更新目標“foo”所要執行的命令(但不會真正的執行這些命令)。在這種情況時,如果不希望重建makefile文件。我們需要在執行make時,在命令行中將這個makefile文件作爲一個最終目標,這樣選項“–t”和其它的選項就對這個makefile文件目標有效,防止執行這個makefile作爲目標的規則(如果是“-t”參數,則是簡單的更新這個makefile文件的時間戳)。同樣,命令“make –f mfile –n mfile foo”會讀取文件“mfile”,打印出重建文件“mfile”的命令、重建“foo”的命令而實際不執行任何命令。並且所打印的用於更新“foo”目標的命令是選項“-f”指定的、沒有被重建的“mfile”文件中所定義的命令。

3.8 重載另外一個makefile

有些情況下,存在兩個比較類似的makefile文件。其中一個(makefile-A)需要使用另外一個(makefile-B)中所定義的變量和規則。通常我們會想到在“makefile-A”中使用指示符“include”包含“mkaefile-B”來達到目的。但使用這種方式,如果在兩個makefile文件中存在相同目標,而在不同的文件中其描述規則使用不同的命令。這樣,相同的目標文件就同時存在兩個不同的規則命令,這是makefile所不允許的。遇到這種情況,使用指示符“include”顯然是行不通的。GNU make提供另外一種途徑來實現此目的。具體的做法如下:

在需要包含的makefile文件(makefile-A)中,定義一個稱之爲“所有匹配模式”的規則,它用來述那些在“makefile-A”中沒有給出明確創建規則的目標的重建規則。就是說,如果在當前makefile文件中不能找到重建一個目標的規則時,就使用“所有匹配模式”所在的規則來重建這個目標。

看一個例子,如果存在一個命名爲“Makefile”的makefile文件,其中描述目標“foo”的規則和其他的一些規,我們也可以書寫一個內容如下命名爲“GNUmakefile”的文件。

 

#sample GNUmakefile

foo:

frobnicate > foo

 

%: force

@$(MAKE) -f Makefile $@

force: ;

 

執行命令“make foo”,make將使用工作目錄下命名爲“GNUmakefile”的文件並執行目標“foo”所在的規則,創建目標“foo”的命令是:“frobnicate > foo”。如果執行另外一個命令“make bar”,因爲在“GUNmakefile”中沒有此目標的更新規則。make將使用“所有匹配模式”規則,執行命令“$(MAKE) -f Makefile bar”。如果文件“Makefile”中存在此目標更新規則的定義,那麼這個規則會被執行。此過程同樣適用於其它 GNUmakefile”中沒有給出的目標更新規則。此方式的靈活之處在於:如果在“Makefile”文件中存在同樣一一個目標“foo”的重建規則,由於make執行時首先讀取文件“GUNmakefile”並在其中能夠找到目標“foo”的重建規則,所以make就不會去執行這個“所有模式匹配規則”(上例中目標“%”所在的規則)。這樣就避免了使用指示符“include”包含一個makefile文件時所帶來的目標規則的重複定義問題。

此種方式,模式規則的模式只使用了單獨的“%”(我們稱他爲“所有模式匹配規則”),它可以匹配任何一個目標;它的依賴是“force”,保證了即使目標文件已經存在也會執行這個規則(文件已存在時,需要根據它的依賴文件的修改情況決定是否需要重建這個目標文件);“force”規則中使用空命令是爲了防止make程序試圖尋找一個規則去創建目標“force”時,又使用了模式規則“%: force”而陷入無限循環。

3.9 make如何解析makefile文件

GUN make的執行過程分爲兩個階段。

第一階段:讀取所有的makefile文件(包括“MAKIFILES”變量指定的、指示符“include”指定的、以及命令行選項“-f(--file)”指定的makefile文件),內建所有的變量、明確規則和隱含規則,並建立所有目標和依賴之間的依賴關係結構鏈表。

在第二階段:根據第一階段已經建立的依賴關係結構鏈表決定哪些目標需要更新,並使用對應的規則來重建這些目標。

理解make執行過程的兩個階段是很重要的。它能幫助我們更深入的瞭解執行過程中變量以及函數是如何被展開的。變量和函數的展開問題是書寫Makefile時容易犯錯和引起大家迷惑的地方之一。本節將對這些不同的結構的展開階段進行簡單的總結(明確變量和函數的展開階段,對正確的使用變量非常有幫助)。首先,明確以下基本的概念;在make執行的第一階段中如果變量和函數被展開,那麼稱此展開是“立即”的,此時所有的變量和函數被展開在需要構建的結構鏈表的對應規則中(此規則在建立鏈表是需要使用)。其他的展開稱之爲“延後”的。這些變量和函數不會被“立即”展開,而是直到後續某些規則須要使用時或者在make處理的第二階段它們纔會被展開。

可能現在講述的這些還不能完全理解。不過沒有關係,通過後續章節內容的學習,我們會一步一步的熟悉make的執行過程。學習過程中可以回過頭來參考本節的內容。相信在看完本書之後,會對make的整個過程有全面深入的理解

3.9.1 變量取值

變量定義解析的規則如下:

 

IMMEDIATE = DEFERRED

IMMEDIATE ?= DEFERRED

IMMEDIATE := IMMEDIATE

IMMEDIATE += DEFERRED or IMMEDIATE

define IMMEDIATE

DEFERRED

Endef

 

當變量使用追加符(+=)時,如果此前這個變量是一個簡單變量(使用 :=定義的)則認爲它是立即展開的,其它情況時都被認爲是“延後”展開的變量。

3.9.2 條件語句

所有使用到條件語句在產生分支的地方,make程序會根據預設條件將正確地分支展開。就是說條件分支的展開是“立即”的。其中包括:“ifdef”、“ifeq”、“ifndef”和“ifneq”所確定的所有分支命令。

3.9.3 規則的定義

所有的規則在make執行時,都按照如下的模式展開:

 

IMMEDIATE : IMMEDIATE ; DEFERRED

DEFERRED

 

其中,規則中目標和依賴如果引用其他的變量,則被立即展開。而規則的命令行中的變量引用會被延後展開。此模板適合所有的規則,包括明確規則、模式規則、後綴規則、靜態模式規則。

3.10 總結

make的執行過程如下:

1.        依次讀取變量“MAKEFILES”定義的makefile文件列表

2.        讀取工作目錄下的makefile文件(根據命名的查找順序“GNUmakefile”,“makefile”,“Makefile”,首先找到那個就讀取那個)

3.        依次讀取工作目錄makefile文件中使用指示符“include”包含的文件

4.        查找重建所有已讀取的makefile文件的規則(如果存在一個目標是當前讀取的某一個makefile文件,則執行此規則重建此makefile文件,完成以後從第一步開始重新執行)

5.        初始化變量值並展開那些需要立即展開的變量和函數並根據預設條件確定執行分支

6.        根據“終極目標”以及其他目標的依賴關係建立依賴關係鏈表

7.        執行除“終極目標”以外的所有的目標的規則(規則中如果依賴文件中任一個文件的時間戳比目標文件新,則使用規則所定義的命令重建目標文件)

8.        執行“終極目標”所在的規則

 

說明:

執行一個規則的過程是這樣的:

對於一個存在的規則(明確規則和隱含規則)首先,make程序將比較目標文件和所有的依賴文件的時間戳。如果目標的時間戳比所有依賴文件的時間戳更新(依賴文件在上一次執行make之後沒有被修改),那麼什麼也不做。否則(依賴文件中的某一個或者全部在上一次執行make後已經被修改過),規則所定義的重建目標的命令將會被執行。這就是make工作的基礎,也是其執行規制所定義命令的依據。(後續討論規則時將會對此詳細地說明)

----------------------------------------

本文轉自:http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html/make-03.html

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