Bazel通用定義

通用定義(common-definitions)

本節定義了以下許多功能或構建規則所共有的術語和概念。

內容(contents)

Bourne Shell標記化

根據Bourne shell的標記化規則,某些規則的某些字符串屬性會分成多個詞:未用引號引起來的空格分隔單獨的詞,并且使用單引號和雙引號字符以及反斜杠來防止標記化。

在本文檔的定義中明確指出了受此標記化影響的那些屬性。

受“ Make”變量擴展和Bourne shell標記化影響的屬性通常用于將任意選項傳遞給編譯器和其他工具。此類屬性的示例為 cc_library.coptsjava_library.javacopts。這些替換一起允許單個字符串變量擴展為特定于配置的選項字列表。

標簽擴展

極少數規則的某些字符串屬性會進行標簽擴展:如果這些字符串包含有效的標簽作為子字符串,例如//mypkg:target,并且該標簽是當前規則的已聲明先決條件,則會將其擴展為表示的文件的路徑名按目標//mypkg:target

示例屬性包括genrule.cmdcc_binary.linkopts。在每種情況下,細節可能會因以下問題而有很大不同:如何處理擴展到多個文件的標簽,等等。有關詳細信息,請參閱規則屬性文檔。

所有構建規則共有的屬性

本節描述所有構建規則共有的屬性。
請注意,在標簽列表屬性中兩次列出同一標簽是錯誤的。

data

List of labels; optional此規則在運行時所需的文件列表。
data屬性中 命名的目標*.runfiles(如果有的話)將出現在此規則的區域中。這可能包括二進制文件或庫所需的數據文件,或它所需的其他程序。有關如何依賴和使用數據文件的更多信息,請參見 數據依賴項部分。幾乎所有規則都允許一個data屬性,但是在不允許該屬性的情況下,此事實將在特定規則下記錄。

visibility

List of labels; optional; default default_visibility from package if specified, or //visibility:private otherwise.``visibility`規則上 的屬性控制該規則是否可以被其他包使用。規則對于同一包中聲明的其他規則始終可見。可見性標簽可以采用五種形式(和一種臨時形式):

  • ["http://visibility:public"]:任何人都可以使用此規則。
  • ["http://visibility:private"]:僅此軟件包中的規則可以使用此規則。中的規則javatests/foo/bar 可以始終使用中的規則java/foo/bar
  • ["http://some/package:__pkg__", "http://other/package:__pkg__"]:只有規則some/packageother/package (定義some/package/BUILDother/package/BUILD)可以訪問此規則。請注意,子包無權訪問該規則。例如, //some/package/foo:bar否則 //other/package/testing:bla將無法訪問。 __pkg__是一個特殊的目標,必須逐字使用。它代表包中的所有規則。
  • ["http://project:__subpackages__", "http://other:__subpackages__"]:只有軟件包project或其other子軟件包之一中的規則可以訪問此規則。例如 //project:rule//project/library:lib或者 //other/testing/internal:munge被允許依賴此規則(但不是//independent:evil
  • ["http://some/package:my_package_group"]軟件包組是一命名的軟件包名稱。程序包組還可以授予對整個子樹的訪問權限,例如//myproj/...

的可見性規范 //visibility:public,并//visibility:private 不能與其他任何知名度規范相結合。可見性規范可能包含包裝標簽(即//foo:__pkg__)和package_group的組合。

如果規則確實指定了可見性屬性,則該規范將覆蓋 包含該規則的BUILD文件中的語句的任何 [default_visibility](https://docs.bazel.build/versions/master/be/functions.html#package.default_visibility) 屬性[package](https://docs.bazel.build/versions/master/be/functions.html#package)

否則,如果規則未指定可見性屬性,則使用程序包的default_visibility(exports_files除外 )。

否則,如果未指定包的default_visibility, //visibility:private則使用。

范例

文件 //frobber/bin/BUILD

# This rule is visible to everyone
cc_binary(
    name = "executable",
    visibility = ["http://visibility:public"],
    deps = [":library"],
)

# This rule is visible only to rules declared in the same package
cc_library(
    name = "library",
    visibility = ["http://visibility:private"],
)

# This rule is visible to rules in package //object and //noun
cc_library(
    name = "subject",
    visibility = [
        "http://noun:__pkg__",
        "http://object:__pkg__",
    ],
)

# See package group "http://frobber:friends" (below) for who can
# access this rule.
cc_library(
    name = "thingy",
    visibility = ["http://frobber:friends"],
)

文件 //frobber/BUILD

# This is the package group declaration to which rule
# //frobber/bin:thingy refers.
#
# Our friends are packages //frobber, //fribber and any
# subpackage of //fribber.
package_group(
    name = "friends",
    packages = [
        "http://fribber/...",
        "http://frobber",
    ],
)

toolchains

List of [labels](https://docs.bazel.build/versions/master/build-ref.html#labels); optional

允許訪問其規則 的Make變量的目標集。這些規則既可以是提供TemplateVariableInfo程序的規則,也可以是Bazel中內置的工具鏈類型的特殊目標。這些包括:

  • @bazel_tools//tools/cpp:current_cc_toolchain
  • @bazel_tools//tools/cpp:current_java_runtime

請注意,這與 規則實現用于平臺相關配置的工具鏈解析概念不同 。您不能使用此屬性來確定目標將使用哪個特定的cc_toolchain或java_toolchain。

deps

List of labels; optional

此規則的依賴項列表。

該規則依賴于另一個使用的含義的確切語義deps特定于此規則的種類,下面的特定于規則的文檔將更詳細地介紹。但是,至少有一個名為via的目標deps會出現在*.runfiles此規則的區域中(如果有的話)。

大多數情況下,deps依賴項用于允許一個模塊使用以相同編程語言編寫并單獨編譯的另一模塊中定義的符號。在許多情況下,還允許跨語言依賴性:例如,一條java_library規則可以cc_library通過在deps屬性中聲明后者來依賴規則中的C ++代碼。有關更多信息,請參見依賴性的定義。

幾乎所有規則都允許一個deps屬性,但是在不允許該屬性的情況下,此事實將在特定規則下記錄。

deprecation

String; optional

與該規則關聯的說明性警告消息。通常,它用于通知用戶某條規則已過時或已被另一條規則所取代,對某個包是私有的或出于某種原因可能被視為有害。最好包含一些參考信息(例如網頁,錯誤號或示例遷移CL),以便人們可以輕松地找到需要哪些更改以避免出現此消息。如果有一個新目標可以用作替換目標,則最好遷移舊目標的所有用戶。

該屬性對構建事物的方式沒有影響,但是可能會影響構建工具的診斷輸出。當具有deprecation屬性的規則被另一個規則所依賴時,構建工具會發出警告。

包內依賴項不受此警告的影響,因此,例如,構建不贊成使用的規則的測試不會遇到警告。

如果不推薦使用的規則依賴于另一個不推薦使用的規則,則不會發出警告消息。

一旦人們停止使用它,就可以將其取出。

tags

List of arbitrary text tags. Tags may be any valid string; default is the empty list.

標簽可以在任何規則上使用。測試和 規則上的標簽test_suite對于將測試分類非常有用。 非測試規則上的標記用于控制genrules和 Starlark 操作的沙盒執行 ,并用于人工和/或外部工具的解析。

如果Bazel在tags任何測試規則或的屬性中或 任何Starlark操作genrule的鍵中找到以下關鍵字,則Bazel會修改其沙盒代碼的行為execution_requirements

  • no-sandbox關鍵字導致動作或測試永遠不會被 沙盒化 ; 它仍然可以緩存或遠程運行-使用no-cacheno-remote 防止兩者之一或全部。
  • no-cache 關鍵字導致操作或測試永遠不會被緩存(遠程或本地)
  • no-remote-cache關鍵字導致操作或測試永遠不會被遠程緩存(但可以在本地緩存;也可以遠程執行)。注意:就此標記而言,磁盤緩存被視為本地緩存,而http和gRPC緩存被視為遠程緩存。如果指定了組合緩存(即具有本地和遠程組件的緩存),則將其視為遠程緩存并完全禁用。
  • no-remote-exec 關鍵字會導致操作或測試永遠不會遠程執行(但可能會遠程緩存)。
  • no-remote關鍵字可防止操作或測試被遠程執行或遠程緩存。這等效于同時使用no-remote-cache和no-remote-exec。
  • local關鍵字可阻止操作或測試在沙盒中進行遠程緩存,遠程執行或運行 。對于類型和測試,使用local = True 屬性標記規則具有相同的效果。
  • requires-network關鍵字允許從沙箱內部訪問外部網絡。只有啟用了沙箱功能,此標記才有效。
  • block-network關鍵字會阻止從沙箱內部訪問外部網絡。在這種情況下,僅允許與本地主機通信。只有啟用了沙箱功能,此標記才有效。
  • requires-fakeroot以uid和gid 0(即root用戶)運行測試或操作。僅在Linux上支持。此標記優先于 --sandbox_fake_username命令行選項。

測試上的標簽通常用于注釋測試在調試和發布過程中的角色。通常,標簽對于缺少任何運行時注釋功能的C ++和Python測試最有用。標簽和大小元素的使用為基于代碼庫簽入策略的測試套件組合提供了靈活性。

如果Bazel tags在測試規則的屬性中找到以下關鍵字,則它會修改測試運行行為 :

  • exclusive關鍵字將強制測試以“獨占”模式運行,確保沒有其他測試同時運行。在完成所有構建活動和非排他性測試之后,將以串行方式執行此類測試。它們還將始終在本地運行,因此無需沙箱。
  • manual關鍵字將迫使測試目標不被包括在目標圖案通配符(...:*:all等); 測試目標既不會構建也不會運行。test_suite沒有明確提及該測試的規則也將忽略它。建立或運行這種測試的唯一方法是通過命令行上的顯式目標模式進行指定。Bazel查詢不接受該manual標記。
  • external關鍵字將強制無條件執行測試(無論--cache_test_results 值如何)。

有關附加到測試規則的標簽的更多約定,請參見測試百科全書中的“標記約定”

testonly

Boolean; optional; default False except as noted

如果為True,則僅testonly目標(例如測試)可以依賴于此目標。

等效地,testonly不允許的規則不允許依賴于的任何規則testonly

默認情況下, 測試(*_test規則)和測試套件(test_suite規則)testonly

此屬性旨在表示目標不應包含在發布到生產中的二進制文件中。

由于testonly是在構建時而非運行時強制執行的,并且會通過依賴關系樹進行病毒傳播,因此應明智地應用它。例如,對于單元測試有用的存根和偽造品也可能對涉及將要發布到生產中的相同二進制文件的集成測試有用,因此可能不應標記為僅測試。相反,即使因為危險而無條件地覆蓋正常行為的規則,也可能會被鏈接為危險。

features

List of *features*. Default is the empty list.

功能上的規則修改功能上當前啟用的級通過功能屬性。
例如,如果在軟件包級別啟用了功能['a','b'],并且規則功能屬性包含['-a','c'],則為該規則啟用的功能將為“ b”和“ c”。

licenses

List of strings; optional

用于此特定構建規則的許可證類型字符串的列表。這是Bazel不再使用的已棄用的許可API的一部分。不要使用這個。

compatible_with

List of [labels](https://docs.bazel.build/versions/master/build-ref.html#labels); optional

除了默認支持的環境之外,可以為該規則構建環境的列表。

這是Bazel的軟啟動約束系統的一部分,該系統使用戶可以聲明哪些規則可以相互依賴,也可以不相互依賴。例如,可外部部署的二進制文件不應依賴于帶有公司機密代碼的庫。有關詳細信息,請參見 ConstraintSemantics

exec_properties

Dictionary of strings. Default is an empty dictionary.

字符串字典,將添加到exec_properties為此目標選擇的平臺的。見exec_properties的的平臺規則。

如果平臺和目標級別屬性中都存在鍵,則將從目標中獲取值。

distribs

List of strings; optional

用于此特定構建規則的分發方法字符串的列表。這是Bazel不再使用的已棄用的許可API的一部分。不要使用這個。

exec_compatible_with

List of [labels](https://docs.bazel.build/versions/master/build-ref.html#labels); optional

該列表 [constraint_values](https://docs.bazel.build/versions/master/be/platform.html#constraint_value) 必須存在于該目標的執行平臺中。這是規則類型已經設置的任何約束的補充。約束用于限制可用執行平臺的列表,有關詳細信息,請參見 工具鏈解析的描述。

此屬性僅在[genrule](https://docs.bazel.build/versions/master/be/general.html#genrule)shell規則和所有測試規則(* _test)上可用。

|
| restricted_to |

List of [labels](https://docs.bazel.build/versions/master/build-ref.html#labels); optional

可以構建此規則的環境列表,而不是默認支持的環境。

這是Bazel的軟啟動約束系統的一部分。有關[compatible_with](https://docs.bazel.build/versions/master/be/common-definitions.html#common.compatible_with) 詳細信息,請參見 。

|

所有測試規則共有的屬性(* _test)

本節介紹所有測試規則共有的屬性。

args

List of strings; optional; subject to [$(location)](https://docs.bazel.build/versions/master/be/make-variables.html#location) and ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution, and [Bourne shell tokenization](https://docs.bazel.build/versions/master/be/common-definitions.html#sh-tokenization)

將這些參數添加到由--test_arg 執行時bazel test

這些參數--test_argbazel test命令行上指定的值之前傳遞。

size

String "enormous", "large" "medium" or "small", default is "medium"; optional

測試多么“繁重”。

測試“繁重”的分類:需要運行多少時間/資源。

單元測試被認為是“小型”,集成測試被認為是“中”,端到端測試被認為是“大”或“巨大”。Bazel使用該大小來確定默認超時(可以使用timeout屬性覆蓋 ),以及運行測試必須獲取的資源量。測試大小對應于以下資源和默認超時:

尺寸 內存(MB) CPU(在CPU內核中) 默認超時
20 1個 短(1分鐘)
介質 100 1個 中度(5分鐘)
300 1個 長(15分鐘)
巨大 800 1個 永恒(60分鐘)

timeout

String "short", "moderate", "long", "eternal" (with the default derived from the test's size attribute)

返回之前,測試應運行多長時間。

雖然測試的大小屬性控制資源估計,但是可以獨立設置測試的超時。如果未明確指定,則超時基于測試的size。可以用該--test_timeout標志來覆蓋測試超時,例如,用于在已知較慢的某些條件下運行。測試超時值對應于以下時間段:

超時值 時間段
1分鐘
中等 5分鐘
15分鐘
永恒 60分鐘

對于上述時間以外的其他時間,測試超時可以用--test_timeoutbazel標志覆蓋 ,例如,在已知緩慢的條件下手動運行。該--test_timeout值以秒。例如,--test_timeout=120將測試超時設置為兩分鐘。

flaky

Boolean; optional

標記測試為片狀。

如果設置,則在聲明為失敗之前最多執行3次測試。默認情況下,此屬性設置為0,并且測試被認為是穩定的。請注意,通常不鼓勵使用此屬性-我們確實希望所有測試都穩定。

local

Boolean; optional

強制測試在本地運行,而無需沙箱測試。

默認情況下,此屬性設置為0,并使用默認測試策略。這等效于提供“本地”作為標簽(tags=["local"])。

shard_count

小于或等于50的非負整數;可選的

指定用于運行測試的并行分片的數量。

該值將覆蓋用于確定運行測試的并行分片數量的所有試探法。請注意,對于某些測試規則,可能首先需要此參數才能啟用分片。另請參閱--test_sharding_strategy

分片要求測試運行器支持測試分片協議。如果不是,那么它很可能會在每個分片中運行每個測試,這不是您想要的。

所有二進制規則(* _binary)共有的屬性

本節介紹所有二進制規則共有的屬性。

| args |

List of strings; optional; subject to [$(location)](https://docs.bazel.build/versions/master/be/make-variables.html#location) and ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution, and [Bourne shell tokenization](https://docs.bazel.build/versions/master/be/common-definitions.html#sh-tokenization)

run命令執行或作為測試執行時,bazel將傳遞給目標的命令行參數。這些參數在bazel runbazel test命令行上指定的參數之前傳遞。

注意:在bazel之外運行目標時(例如,通過在中手動執行二進制文件bazel-bin/),不會傳遞參數 。

大多數二進制規則都允許使用args屬性,但是在不允許使用此屬性的情況下,此事實記錄在特定規則下。

|
| output_licenses |

List of strings; optional

該二進制文件生成的輸出文件的許可證。這是Bazel不再使用的已棄用的許可API的一部分。不要使用這個。

|

可配置的屬性

大多數屬性都是“可配置的”,這意味著當以不同方式構建目標時,它們的值可能會更改。具體來說,可配置屬性可能會根據傳遞給Bazel命令行的標志或請求目標的下游依賴關系而有所不同。例如,這可以用于為多個平臺或編譯模式自定義目標。

以下示例為不同的目標體系結構聲明了不同的源。運行bazel build :multiplatform_lib --cpu x86 將使用構建目標x86_impl.cc,而替代 --cpu arm將導致使用目標arm_impl.cc

<pre class="code" style="box-sizing: border-box; overflow: auto; font-family: Monaco, "Source Code Pro", monospace; font-size: 14px; display: block; padding: 8px 16px; margin: 8px 0px; line-height: 20px; color: rgb(0, 0, 0); word-break: break-all; overflow-wrap: break-word; background-color: rgb(238, 238, 238); border: 0px; border-radius: 6px; box-shadow: rgb(170, 170, 170) 1px 1px 5px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">cc_library( name =“ multiplatform_lib”, srcs = select({ “:x86_mode”:[“ x86_impl.cc”], “:arm_mode”:[“ arm_impl.cc”] }) ) config_setting( 名稱=“ x86_mode”, 值= {“ cpu”:“ x86”} ) config_setting( 名稱=“ arm_mode”, 值= {“” cpu“:”手臂“} )
</pre>

select()函數根據當前配置中滿足的條件為可配置屬性選擇不同的替代值。 config_setting

在處理宏之后和處理規則之前(在技術上,在 加載和分析階段之間)將評估可配置屬性 。Bazel在select()評估之前進行的任何處理都不會知道將選擇哪個分支。特別是,宏不能根據所選分支更改其行為,并且bazel query只能對目標的可配置依賴項做出保守的猜測;相反,在編寫新類型的規則時,您無需擔心可配置屬性的歧義,因為所有 select()表達式都已經已由其解析值替換。有關與規則和宏結合 使用的更多信息,請參 見此常見問題解答select()

nonconfigurable在其文檔中 標記的屬性不能使用此功能。通常,屬性是不可配置的,因為Bazel內部需要知道其值才能確定如何選擇 select()分支。

有關更多信息,請參見可 配置的構建屬性

隱式輸出目標

在BUILD文件中定義構建規則時,將在包中顯式聲明一個新的命名規則目標。許多構建規則功能還隱式包含一個或多個輸出文件目標,其內容和含義是特定于規則的。例如,當您明確聲明一條 java_binary(name='foo', ...)規則時,您還將 隱式聲明一個輸出文件目標foo_deploy.jar為同一包的成員。(此特定目標是適合部署的自包含Java歸檔。)

隱式輸出目標是全局目標圖的一等成員。就像其他目標一樣,它們是按需構建的,既可以在頂級構建命令中指定,也可以在其他構建目標的必要前提下使用。可以將它們稱為BUILD文件中的依賴項,并且可以在分析工具(如)的輸出中觀察到bazel query

對于每種構建規則,該規則的文檔都包含一個特殊的部分,詳細說明了由該類型的聲明引起的任何隱式輸出的名稱和內容。

構建系統使用的兩個名稱空間之間的一個重要但有點微妙的區別: 標簽標識目標(可以是規則或文件),目標文件可以分為源(或輸入)文件目標和派生(或輸出)文件目標。這些都是您可以在BUILD文件中提及的,從命令行構建或使用進行檢查的內容bazel query;這是目標名稱空間。每個文件目標對應一個磁盤上的實際文件(“文件系統名稱空間”);每個規則目標可能對應于磁盤上的零個,一個或多個實際文件。磁盤上可能有沒有對應目標的文件;例如,.o在C ++編譯過程中生成的目標文件不能從BUILD文件內部或命令行中引用。以這種方式,構建工具可以隱藏其工作方式的某些實現細節。這在《BUILD概念參考》中有更全面的說明。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容