Hazır Ayarlar

. Sorun bildirin Kaynağı göster Gece · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Depset'ler, yüksek performans gösteren ve Hedefin geçişli bağımlılıkları genelinde veri toplama. Proje yöneticilerinin öğesi oluşturun.

Depset’in belirleyici özelliği, zaman ve alandan tasarruf sağlayan birleştirme işlemidir. Depset oluşturucu, bir öğe listesini ("doğrudan") ve diğer depsets ("geçişli") çalışır ve tüm doğrudan öğeler ve tüm geçişli kümelerin birleşimi. Kavramsal olarak kurucu, doğrudan ve geçişli düğümlere sahip yeni bir grafik düğümü oluşturur hale geldi. Depset'lerin anlamlarına göre, çapraz geçişidir.

Depset'lerin örnek kullanımları şunlardır:

  • Tüm nesne dosyalarının yollarını bir programın kitaplıklarında depolayarak daha sonra bir sağlayıcı aracılığıyla bağlayıcı işlemine iletilebilir.

  • Yorumlanan bir dil için dosya taşıma işlemleri yürütülebilir.

Açıklama ve işlemler

Kavramsal olarak depset, genelde hedef grafiğe benzer. Yapraklardan köke kadar oluşturulur. Bağımlılık zincirindeki her hedef, önceki sayfaları kullanabilirsiniz.

DAG'deki her düğüm, doğrudan öğelerin ve alt düğümlerin bir listesini içerir. Depset'in içeriği, doğrudan öğeler gibi geçişli öğelerdir tümünü görebiliyoruz. Yeni bir depolama alanı oluşturmak için depset oluşturucu: Doğrudan bir öğelerinin ve başka bir alt düğümlerin listesi.

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 kümenin içeriğini almak için şunu kullanın: to_list() yöntemini kullanın. Tüm geçişlilerin listesini döndürür öğelerinin (yinelenenler dahil) içermediğini unutmayın. Verileri doğrudan incelemenin bir yolu yoktur. kesin bir yapıda olmasa da bu yapı, DAG'nin hangi öğelerin döndürüleceğini belirler.

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

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

Bir depoda izin verilen öğeler, şurada izin verilen anahtarlar gibi kısıtlanır: Sözlükler kısıtlandı. Özellikle, indirilmiş içerikler değiştirilemeyebilir.

Depset'ler referans eşitliği kullanır: "Depset" kendisine eşittir, ancak diğerlerine eşit değildir. aynı içeriğe ve aynı dahili yapıya sahip olsalar bile farklı birimleri kullanabilirler.

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

Denemeleri içeriklerine göre karşılaştırmak için bunları sıralanmış 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

Bir kullanımdan öğe kaldırmak mümkün değildir. Bu gerekiyorsa depset'in tüm içeriğini okumalı, istediğiniz öğeleri filtrelemelisiniz yeniden oluşturmayı öğreneceksiniz. Bu pek de faydalı değil.

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ş

to_list işlemi, DAG üzerinde bir geçiş gerçekleştirir. Geçişin türü dağıtım sırasında belirtilen sıralamaya bağlıdır somut olarak ortaya koyar. Bazen Bazel'in birden fazla siparişi desteklemesi faydalıdır çünkü bazen araçların giriş sırasını önemser. Örneğin, bir bağlayıcı işlemi B, A değerine bağlıysa A.o, sayfada B.o karakterinden önce gelir. bağlayıcının komut satırında yer alır. Diğer araçlar için ise tam tersi bir gereklilik olabilir.

Üç geçiş siparişi desteklenir: postorder, preorder ve topological. İlk ikisi ağaç gibidir geçişler ancak DAG'lerde çalışır ve ziyaret edilmiş düğümleri atlar. Üçüncü sipariş kökten yapraklara kadar topolojik bir sıralama işlevi görür. yalnızca ebeveynlerinden sonra listelenen çocuklar için ön sipariş verilebilir. Ön sipariş ve sonradan sipariş, soldan sağa geçişler olarak çalışır, ancak her bir düğümün doğrudan öğesi, alt öğelere göre bir sıraya sahip değildir. Topolojik için soldan sağa doğru garanti yoktur ve hatta alt öğeden önce tümü garantisi DAG'nin farklı düğümlerinde yinelenen öğeler.

# 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 biçimi nedeniyle, sıra, zamanda belirtilmelidir depset, oluşturucunun order anahtar kelime bağımsız değişkeniyle oluşturulur. Bu bağımsız değişken atlanırsa depset, özel default sırasına sahiptir. Bu durumda, unsurlarından herhangi birinin sıralamasıyla ilgili garanti verilmez (yalnızca deterministiktir).

Tam örnek

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

Foo varsayıma dayalı yorumlanmış bir dil bulunduğunu varsayalım. Herkes için her foo_binary içindeki tüm *.foo dosyalarını da bilmeniz gerekir veya dolaylı olarak da değişir.

# //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ı *.foo içindeki tüm dosyalardır a, b, c ve d srcs alanları. foo_binary için diğer dosyalar hakkında bilgilendirilmesi için foo_library hedeflerinind.foo sağlayıcıda iletebilirim. Her kütüphane kendi sağlayıcıları alır bağımlılıklarını, kendi kaynaklarını ekler ve mevcut kaynaklarla arttırılmış içeriklerdir. foo_binary kuralı da aynısını yapar. Ancak bu kural, bir kaynak listesi oluşturmak için tüm kaynakların listesini kullanır. komut satırını kullanın.

foo_library ve foo_binary kurallarının eksiksiz bir şekilde nasıl uygulandığı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 durumu, bu dosyaları yeni bir pakete kopyalayıp etiketlerine uygun şekilde, kaynak *.foo dosyalarını sahte içerikle oluşturma ve d hedefini oluşturacaksınız.

Performans

Depsy'leri kullanmak için motivasyonu öğrenmek istiyorsanız, get_transitive_srcs(), kaynaklarını bir listede topladı.

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 durumda yinelemeler dikkate alınmaz. Bu nedenle a için kaynak dosyalar komut satırında iki kez, çıkışın içeriğinde de iki kez görünür dosyası olarak kaydedebilirsiniz.

Alternatif bir yöntem de, bir simülasyon aracı tarafından simüle edilen genel bir küme kullanmaktır Sözlüğün tamamındaki anahtarlar öğe, tüm anahtarlar ise True ile eşlenir.

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, yinelemeleri ortadan kaldırır ancak komut satırının sırasını belirler bağımsız değişkenler (ve dolayısıyla dosyaların içeriği) belirtilmemiştir ancak deterministik.

Dahası, her iki yaklaşım da bağımlılık temelli yaklaşımlardan asimptotik olarak daha kötüdür. bahsedeceğiz. Scrum Ekibi’nde uzun bir bağımlılık zincirinin olduğu Foo kitaplıkları. Her kuralın işlenmesi için tüm geçişli kuralların kopyalanması yeni veri yapısına entegre etmek için kullanılır. Bu, tek bir kitaplığın veya ikili hedefin analiz edilmesi için zaman ve alan maliyeti zincirdeki kendi yüksekliğiyle doğru orantılıdır. n uzunluğundaki 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ızla bilgi edinmeye çalışır. Böylece ekibin ve hedef grafiğiniz derinleşir.

Son olarak, depset öğesinin içeriğini almamak önemlidir. gereksiz yere kullanmamalıdır. to_list() için bir arama toplam maliyet yalnızca O(n) olduğundan, bir ikili kuralının sonunda herhangi bir sorun yoktur. İnsanların terminal olmayan birçok hedef to_list() işlemini, ikinci dereceden davranışın gerçekleşir.

Uygulamaları verimli bir şekilde kullanma hakkında daha fazla bilgi için performans sayfasını inceleyin.

API Referansı

Daha fazla bilgiyi burada bulabilirsiniz.