底盤組

回報問題 查看原始碼 。 。 。 。 夜間。 。 7.3 。 。 7.2 。 。 7.17.0 。 。 6.5

Depset 是一種專門的資料結構,可以提高效率 從目標的遞移依附元件收集資料。這類技術 規則處理的各個環節

depset 的定義功能為它兼具時間和空間的聯集作業。 depset 建構函式接受元素清單 (「direct」) 和其他 「轉換」鍵,並傳回代表集合,其代表包含所有 直接元素和所有遞移集合的聯集。概念上 建構函式會建立新的圖形節點,並具有直接和遞移節點 和科技的續作定義具有明確定義的排序語意,根據 這張圖表的周遊

解碼器的使用範例包括:

  • 儲存程式程式庫的所有物件檔案路徑, ,然後再透過供應商傳遞至連接器動作。

  • 若是解譯語言,儲存 包含在執行檔的執行檔案中。

說明與作業

概念上,去點是有向非循環圖 (DAG),通常看起來 和目標圖表它從根部延伸至根層級。 依附元件鏈結中的每個目標皆可在 而不需要讀取或複製這些內容

DAG 中的每個節點都含有直接元素清單和子節點清單。 depset 的內容是遞移元素,例如直接元素 所有節點的資料您可以使用 depset 建構函式:接受直接清單 和另一個子節點清單

s = depset(["a", "b", "c"])
t = depset(["d", "e"], transitive = [s])

print(s)    # depset(["a", "b", "c"])
print(t)    # depset(["d", "e", "a", "b", "c"])

如要擷取解碼器的內容,請使用 to_list() 方法。傳回所有遞移性的清單 元素,但不包括重複項目。您無法直接檢查 精確的 DAG 結構,不過這個結構確實會影響 這裡顯示了相應的元素

s = depset(["a", "b", "c"])

print("c" in s.to_list())              # True
print(s.to_list() == ["a", "b", "c"])  # True

解碼器中允許的項目會受到限制,如同 字典會受到限制。特別是,解碼器內容可能無法變動。

Depset 使用參照相等:依附元件等於本身,但不等於任何 其他 Depset,即使兩者的內容和內部結構相同

s = depset(["a", "b", "c"])
t = s
print(s == t)  # True

t = depset(["a", "b", "c"])
print(s == t)  # False

d = {}
d[s] = None
d[t] = None
print(len(d))  # 2

如要依內容內容比較 Depset,請將其轉換為已排序的清單。

s = depset(["a", "b", "c"])
t = depset(["c", "b", "a"])
print(sorted(s.to_list()) == sorted(t.to_list()))  # True

請注意,您無法從模式中移除元素。如有需要, 必須讀出所有解碼器的內容,接著篩選您要 移除,再重新建構一個新的解碼器這樣就沒有效率。

s = depset(["a", "b", "c"])
t = depset(["b", "c"])

# Compute set difference s - t. Precompute t.to_list() so it's not done
# in a loop, and convert it to a dictionary for fast membership tests.
t_items = {e: None for e in t.to_list()}
diff_items = [x for x in s.to_list() if x not in t_items]
# Convert back to depset if it's still going to be used for union operations.
s = depset(diff_items)
print(s)  # depset(["a"])

訂單

to_list 運算會對 DAG 執行週遊。週遊的種類 取決於 depset 先前指定的 order 訓練方式數量這對於 Bazel 能支援多筆訂單,因為有時 這些工具在乎輸入順序舉例來說,連接器動作可能會 必須確保如果 B 依附 A,那麼 A.o 會在 B.o 之前 連結器的指令列其他工具則可能有相反需求。

系統支援以下三筆遍歷訂單:postorderpreordertopological。前兩項工作的運作方式與樹木 週遊 但在 DAG 上作業,並略過已瀏覽的節點。第三筆訂單 運作方式為從根到葉的拓撲排序,基本上與 預購,除非共用兒童位於所有上層。 預購和預購作業的運作方式為從左到右週遊,但請注意 每個節點直接元素都沒有相對於子項的順序。拓撲適用 我們沒有從左到右的保證, 在以下情況下, 在 DAG 的不同節點中重複元素

# This demonstrates different traversal orders.

def create(order):
  cd = depset(["c", "d"], order = order)
  gh = depset(["g", "h"], order = order)
  return depset(["a", "b", "e", "f"], transitive = [cd, gh], order = order)

print(create("postorder").to_list())  # ["c", "d", "g", "h", "a", "b", "e", "f"]
print(create("preorder").to_list())   # ["a", "b", "e", "f", "c", "d", "g", "h"]
# This demonstrates different orders on a diamond graph.

def create(order):
  a = depset(["a"], order=order)
  b = depset(["b"], transitive = [a], order = order)
  c = depset(["c"], transitive = [a], order = order)
  d = depset(["d"], transitive = [b, c], order = order)
  return d

print(create("postorder").to_list())    # ["a", "b", "c", "d"]
print(create("preorder").to_list())     # ["d", "b", "a", "c"]
print(create("topological").to_list())  # ["d", "b", "c", "a"]

基於週遊的執行方式,必須在時指定訂單 Dpset 會使用建構函式的 order 關鍵字引數建立。如果這是 引數遭到省略,則解碼集必須具有特殊的 default 順序,這時 我們無法保證其任何元素的順序 (除非是 具確定性)。

完整範例

這個範例位於 https://github.com/bazelbuild/examples/tree/main/rules/depsets.

假設所謂「Foo」的假設是假設語言,如要 每個 foo_binary 都必須知道其直接或間接的 *.foo 檔案,或 間接依賴

# //depsets:BUILD

load(":foo.bzl", "foo_library", "foo_binary")

# Our hypothetical Foo compiler.
py_binary(
    name = "foocc",
    srcs = ["foocc.py"],
)

foo_library(
    name = "a",
    srcs = ["a.foo", "a_impl.foo"],
)

foo_library(
    name = "b",
    srcs = ["b.foo", "b_impl.foo"],
    deps = [":a"],
)

foo_library(
    name = "c",
    srcs = ["c.foo", "c_impl.foo"],
    deps = [":a"],
)

foo_binary(
    name = "d",
    srcs = ["d.foo"],
    deps = [":b", ":c"],
)
# //depsets:foocc.py

# "Foo compiler" that just concatenates its inputs to form its output.
import sys

if __name__ == "__main__":
  assert len(sys.argv) >= 1
  output = open(sys.argv[1], "wt")
  for path in sys.argv[2:]:
    input = open(path, "rt")
    output.write(input.read())

在這裡,二進位檔 d 的遞移來源是*.foo abcdsrcs 欄位。支付 foo_binary 給您 目標知道 d.foo 以外的任何檔案,foo_library 目標需 透過供應商傳遞每個程式庫都會收到本身的供應器 新增自己的即時來源,並傳入新的供應器 來解決擴增內容foo_binary 規則也有相同之處,差別在於前者會 只會使用完整的來源清單來建構 產生一個動作

以下是 foo_libraryfoo_binary 規則的完整實作方式。

# //depsets/foo.bzl

# A provider with one field, transitive_sources.
FooFiles = provider(fields = ["transitive_sources"])

def get_transitive_srcs(srcs, deps):
  """Obtain the source files for a target and its transitive dependencies.

  Args:
    srcs: a list of source files
    deps: a list of targets that are direct dependencies
  Returns:
    a collection of the transitive sources
  """
  return depset(
        srcs,
        transitive = [dep[FooFiles].transitive_sources for dep in deps])

def _foo_library_impl(ctx):
  trans_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps)
  return [FooFiles(transitive_sources=trans_srcs)]

foo_library = rule(
    implementation = _foo_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files=True),
        "deps": attr.label_list(),
    },
)

def _foo_binary_impl(ctx):
  foocc = ctx.executable._foocc
  out = ctx.outputs.out
  trans_srcs = get_transitive_srcs(ctx.files.srcs, ctx.attr.deps)
  srcs_list = trans_srcs.to_list()
  ctx.actions.run(executable = foocc,
                  arguments = [out.path] + [src.path for src in srcs_list],
                  inputs = srcs_list + [foocc],
                  outputs = [out])

foo_binary = rule(
    implementation = _foo_binary_impl,
    attrs = {
        "srcs": attr.label_list(allow_files=True),
        "deps": attr.label_list(),
        "_foocc": attr.label(default=Label("//depsets:foocc"),
                             allow_files=True, executable=True, cfg="host")
    },
    outputs = {"out": "%{name}.out"},
)

如要進行測試,請將這些檔案複製到新的套件,並重新命名 正確標籤、建立含有虛擬內容的來源 *.foo 檔案,以及 建構 d 目標

成效

如要瞭解使用債務的動機,請考慮使用轉換器時 get_transitive_srcs() 在清單中收集了來源。

def get_transitive_srcs(srcs, deps):
  trans_srcs = []
  for dep in deps:
    trans_srcs += dep[FooFiles].transitive_sources
  trans_srcs += srcs
  return trans_srcs

我們不會考量重複的項目,因此「a」的來源檔案 會顯示在指令列中兩次,輸出的內容則兩次 檔案。

另一種替代方法是使用一般集合,可藉由 這個字典中的鍵是元素,且所有鍵都會對應至 True

def get_transitive_srcs(srcs, deps):
  trans_srcs = {}
  for dep in deps:
    for file in dep[FooFiles].transitive_sources:
      trans_srcs[file] = True
  for file in srcs:
    trans_srcs[file] = True
  return trans_srcs

這麼做會移除重複內容,但會以指令列的順序 未指定引數 (進而控制檔案內容),不過 確定性

此外,這兩種方法都比以函式庫為基礎 。舉例來說,如果依附元件 美食庫。處理每項規則時,都必須複製所有遞移性 擷取至新的資料結構也就是說, 分析個別程式庫或二進位檔目標所需的時間和空間成本 與鏈結中本身的高度成正比如果是長度為 n 的鏈結, foolib_1 ← foolib_2 ← ... ← foolib_n,提升整體成本 O(n^2)。

一般而言,只要在預測期間 擷取及提交資訊這可以確保 您的建構規模,也會更深層的目標圖。

最後,請務必避免擷取解碼器的內容 不需要執行規則。一次呼叫 to_list() 可以放心使用,因為整體成本只有 O(n)。是 當許多非終端目標嘗試呼叫 to_list() 是二次行為時 會發生什麼事

如要進一步瞭解如何有效使用解碼器,請參閱效能頁面。

API 參考資料

詳情請參閱這裡