Hazır Ayarlar

Sorun bildir Kaynağı göster

Derinlikler, bir hedefin geçişli bağımlılıkları genelinde verimli bir şekilde veri toplamak için kullanılan özel bir veri yapısıdır. Bunlar, kural işlemenin temel unsurlarıdır.

Depset'in tanımlayıcı özelliği, zaman ve alan açısından verimli bir birleştirme işlemidir. Depset oluşturucu, bir öğe listesini ("doğrudan") ve diğer derin noktaların listesini ("geçişli") kabul eder ve tüm doğrudan öğeleri ve tüm geçişli kümelerin birleşimini içeren bir kümeyi temsil eden bir depset döndürür. Kavramsal olarak, oluşturucu, halefleri olarak doğrudan ve geçişli düğümlerin bulunduğu yeni bir grafik düğümü oluşturur. Derin noktalar, bu grafiğin geçişine göre iyi tanımlanmış bir sıralama semantiğine sahiptir.

Derinlerin kullanımlarına örnek olarak şunlar verilebilir:

  • Bir programın kitaplıkları için tüm nesne dosyalarının yollarını depolama. Bunlar daha sonra bir sağlayıcı aracılığıyla bir bağlayıcı işlemine iletilebilir.

  • Yorumlanan dil için yürütülebilir dosyanın çalıştırma dosyalarına dahil edilen geçişli kaynak dosyalarını depolama.

Açıklama ve işlemler

Kavramsal olarak, tüketim genellikle hedef grafiğe benzer şekilde görünen yönlendirilmiş bir çember grafiğidir (DAG). Yapraklardan köke kadar oluşturulur. Bağımlılık zincirindeki her hedef, okumak veya kopyalamak zorunda kalmadan kendi içeriklerini bir öncekinin üzerine ekleyebilir.

DAG'deki her düğümde bir doğrudan öğe ve bir alt düğüm listesi bulunur. Depset'in içeriği, tüm düğümlerin doğrudan elemanları gibi geçişli öğelerdir. depset oluşturucu kullanılarak yeni bir depset oluşturulabilir. Bu öğe, doğrudan öğelerin listesini ve başka bir alt düğüm listesini kabul eder.

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"])

Bir alt kümenin içeriğini almak için to_list() yöntemini kullanın. Yinelenenler hariç tüm geçişli öğelerin listesini döndürür. DAG'in kesin yapısını doğrudan incelemenin bir yolu yoktur ancak bu yapı, öğelerin döndürülme sırasını etkiler.

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

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

Bir alt kümede izin verilen öğeler, sözlüklerde izin verilen tuşlar kısıtlandığı gibi kısıtlanır. Özellikle, kullanımdan kaldırılan içerikler değiştirilemez.

Derinlikler referans eşitliği kullanır: Bir depset kendisine eşittir, ancak aynı içeriğe ve aynı dahili yapıya sahip olsa bile diğer herhangi bir düşüş için eşit değildir.

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

Derinleri içeriklerine göre karşılaştırmak için sıralı listelere dönüştürün.

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

Depset'teki öğeler kaldırılamaz. Bu gerekliyse kaldırma işleminin tüm içeriğini okumanız, kaldırmak istediğiniz öğeleri filtrelemeniz ve yeni bir kaldırma işlemini yeniden oluşturmanız gerekir. Bu yöntem çok etkili değildir.

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"])

Sipariş verin

to_list işlemi, DAG üzerinde bir geçiş gerçekleştirir. Geçişin türü, alt kümenin oluşturulduğu sırada belirtilen sıraya bağlıdır. Araçlar bazen girdilerinin sırasına dikkat ettiği için Bazel’in birden çok siparişi desteklemesi işe yarar. Örneğin, bir bağlayıcı işleminin, B değeri A değerine bağlı olduğunda, bağlayıcının komut satırında A.o öğesinin B.o öğesinden önce gelmesi gerekir. Diğer araçlarda ise bu zorunluluk tam tersi olabilir.

Üç geçiş siparişi desteklenir: postorder, preorder ve topological. İlk ikisi, ağaç geçişleri gibi çalışır. Tek fark, DAG'ler üzerinde çalışmaları ve ziyaret edilmiş olan düğümleri atlamalarıdır. Üçüncü sıra, kökten yapraklara topolojik bir sıralama işlevi görür. Ön siparişle aynı şekilde yapılır. Tek fark, paylaşılan alt öğelerin yalnızca tüm üst öğelerinden sonra listelenmesi. Ön sipariş ve son sipariş soldan sağa geçiş olarak çalışır, ancak her düğümde doğrudan öğelerin alt öğelere göre sırası olmadığını unutmayın. Topolojik sıralama için soldan sağa garantisi yoktur ve DAG'in farklı düğümlerinde yinelenen öğeler olması halinde "all-parents-before-child" garantisi bile geçerli olmaz.

# 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"]

Geçişlerin uygulanma şekline bağlı olarak sıra, kurucunun order anahtar kelime bağımsız değişkeniyle depset oluşturulduğu anda belirtilmelidir. Bu bağımsız değişken atlanırsa depset, özel default sıralamasına sahip olur. Bu durumda, öğelerinden herhangi birinin sırası hakkında garanti verilmez (deterministik olması dışında).

Tam örnek

Bu örneği https://github.com/bazelbuild/examples/tree/main/rules/depsets adresinde bulabilirsiniz.

Foo dilinde varsayıma dayalı bir yorum bulunduğunu varsayalım. Her bir foo_binary öğesini derlemek için doğrudan veya dolaylı olarak bağlı olduğu tüm *.foo dosyalarını bilmeniz gerekir.

# //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())

Burada, d ikili programının geçişli kaynakları a, b, c ve d öğelerinin srcs alanlarındaki tüm *.foo dosyalarıdır. foo_binary hedefinin d.foo dışındaki dosyaları bilmesi için foo_library hedeflerinin bunları bir sağlayıcıya iletmesi gerekir. Her kitaplık, sağlayıcıları kendi bağımlılıklarından alır, kendi anlık kaynaklarını ekler ve artırılmış içeriklerle yeni bir sağlayıcıya geçirir. foo_binary kuralı da aynı şeyi yapar. Tek fark, sağlayıcı döndürmek yerine bir işlem için komut satırı oluşturmak amacıyla kaynakların tam listesini kullanır.

foo_library ve foo_binary kurallarının eksiksiz bir kullanımını aşağıda görebilirsiniz.

# //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"},
)

Bu dosyaları yeni bir pakete kopyalayarak, etiketleri uygun şekilde yeniden adlandırarak, model içerikle kaynak *.foo dosyaları oluşturarak ve d hedefini oluşturarak bunu test edebilirsiniz.

Performans

Derinlikleri kullanma nedenini öğrenmek için, get_transitive_srcs() kaynaklarını bir listede toplasa ne olacağını düşünün.

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

Bu işlem yinelemeleri dikkate almaz. Bu nedenle a kaynak dosyaları, komut satırında iki kez, çıkış dosyasının içeriğinde de iki kez görünür.

Alternatif bir yöntem de genel küme kullanmaktır. Bu küme, anahtarların öğe olduğu ve tüm tuşların True ile eşlendiği bir sözlükle simüle edilebilir.

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

Bu işlem yinelemeleri ortadan kaldırır ancak komut satırı bağımsız değişkenlerinin sırasını (ve dolayısıyla dosyaların içeriğini) yine de belirleyici olsa da belirtmez.

Dahası, her iki yaklaşım da semptotik olarak, yetersizlik temelli yaklaşımdan daha kötüdür. Foo kitaplıklarında uzun bir bağımlılık zincirinin olduğu durumu düşünün. Her kuralın işlenmesi için kendisinden önceki tüm geçişli kaynakların yeni bir veri yapısına kopyalanması gerekir. Diğer bir deyişle, tek bir kitaplığın veya ikili program hedefinin analiz edilmesi için gereken zaman ve alan maliyeti, zincirdeki kendi yüksekliğiyle orantılıdır. n uzunluktaki bir zincir için, foolib_1 † foolib_2 ÷ ... † foolib_n, toplam maliyet etkin bir şekilde O(n^2) olur.

Genel olarak, geçişli bağımlılıklarınız üzerinden bilgi toplarken destanları kullanmanız gerekir. Böylece yapınızın ölçeklendirilmesine ve hedef grafiğinizin de büyümesine yardımcı olursunuz.

Son olarak, kural uygulamalarında gereksiz yere veri kümesinin içeriğini almamak önemlidir. Toplam maliyet sadece O(n) olduğu için ikili program kuralının sonundaki bir to_list() çağrısı uygundur. Terminal olmayan birçok hedef to_list() çağırmaya çalıştığında bu ikinci dereceden davranış gerçekleşir.

Noktaları verimli bir şekilde kullanma hakkında daha fazla bilgi için performans sayfasına bakın.

API Referansı

Daha fazla bilgi için lütfen buraya göz atın.