तारीख सेव करें: BazelCon 2023, 24 से 25 अक्टूबर तक Google म्यूनिख में होगा! ज़्यादा जानें

गहराई

किसी समस्या की शिकायत करें स्रोत देखें

डेप किसी खास डेटा की डिपेंडेंसी के दौरान डेटा को इकट्ठा करने के लिए, एक खास तरह का डेटा स्ट्रक्चर है. वे नियम संसाधन के एक आवश्यक तत्व हैं.

डिपसेट की सुविधा का समय और अंतरिक्ष की कम खपत वाली यूनियन कार्रवाई तय करती है. डिसेट कंस्ट्रक्टर, एलिमेंट ("डायरेक्ट") और दूसरे डेप्सेट ("ट्रांज़िटिव") की सूची को स्वीकार करता है. साथ ही, सभी डायरेक्ट एलिमेंट और सभी ट्रांज़िशनिव सेट को मिलाकर एक सेट दिखाता है. सैद्धांतिक रूप से, कंस्ट्रक्टर एक नया ग्राफ़ नोड बनाता है, जिसमें उसके उत्तराधिकारी के रूप में डायरेक्ट और ट्रांज़िशनल नोड होते हैं. ग्राफ़ के ट्रेवर्सल के आधार पर, गहराई वाले कीवर्ड में ऑर्डरिंग सिमेंटिक अच्छी तरह से तय किए जाते हैं.

पैसे चुकाने के ये तरीके इस्तेमाल किए जा सकते हैं:

  • किसी प्रोग्राम की लाइब्रेरी के लिए सभी ऑब्जेक्ट फ़ाइलों के पाथ को स्टोर करना. इसके बाद, सेवा देने वाली कंपनी के ज़रिए लिंकर को कार्रवाई की जा सकती है.

  • समझने के लिए इस्तेमाल की जाने वाली भाषा के लिए, उन संक्रामक स्रोत फ़ाइलों को स्टोर करें जिन्हें एक्ज़ीक्यूटेबल की रनफ़ाइल में शामिल किया जाता है.

जानकारी और कार्रवाइयां

सैद्धांतिक तौर पर, डिपसेट एक डायरेक्टिक असाइकलिक ग्राफ़ (डीएजी) है, जो आम तौर पर टारगेट ग्राफ़ जैसा दिखता है. इसकी शुरुआत पत्तियों से लेकर रूट तक होती है. डिपेंडेंसी चेन में शामिल हर टारगेट, पिछले कॉन्टेंट के साथ अपना कॉन्टेंट जोड़ सकता है. इसके लिए, उसे पढ़ने या कॉपी करने की ज़रूरत नहीं होती है.

DAG के हर नोड में, डायरेक्ट एलिमेंट और चाइल्ड नोड की सूची होती है. डेपसेट की सामग्री, संक्रामक एलीमेंट होते हैं, जैसे कि सभी नोड के डायरेक्ट एलिमेंट. 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

जिस तरह डिक्शनरी में अनुमति दी गई होती है, उसी तरह डेपसेट में मंज़ूर किए गए आइटम पर पाबंदी लगी होती है. खास तौर पर, डिसेट सामग्री में बदलाव नहीं किया जा सकता.

डिपसेट संदर्भ समानता का इस्तेमाल करता है: डेपसेट अपने आप में बराबर होता है, लेकिन किसी भी दूसरे डेपसेट के बराबर नहीं, चाहे उसकी सामग्री एक जैसी हो या आंतरिक संरचना.

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

गिरावट की तुलना कॉन्टेंट के हिसाब से करने के लिए, उन्हें क्रम से लगाई गई सूचियों में बदलें.

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 के ऊपर ट्रैवर्सल करती है. एक तरह का ट्रैवर्सल, ऑर्डर पर निर्भर करता है. यह डिपार्चर बनाते समय बताया गया था. Bazel के लिए एक से ज़्यादा ऑर्डर के साथ काम करना मददगार होता है, क्योंकि कभी-कभी टूल उनके इनपुट के क्रम पर ध्यान देते हैं. उदाहरण के लिए, लिंक करने वाले को यह पक्का करने की ज़रूरत पड़ सकती है कि अगर B, A पर निर्भर है, तो लिंकर की कमांड लाइन पर A.o, B.o से पहले आ जाए. दूसरे टूल के लिए भी ऐसा ही करना पड़ सकता है.

तीन ट्रैवर्सल ऑर्डर काम करते हैं: postorder, preorder, और topological. पहले दो काम बिल्कुल पेड़ों के ट्रैवर्सल की तरह करते हैं, हालांकि वे डीएजी पर काम करते हैं और पहले से विज़िट किए गए नोड को छोड़ देते हैं. तीसरा क्रम, रूट से पत्तियों तक एक टोपिकल क्रम की तरह काम करता है. हालांकि, यह बिलकुल पहले से ऑर्डर करने की तरह ही होता है, सिवाय इसके कि शेयर किए गए बच्चों की सूची उनके सभी माता-पिता के बाद ही दी जाती है. पहले से ऑर्डर करें और पोस्टऑर्डर दें को बाएं से दाएं ट्रैवर्सल के रूप में ऑपरेट करें, लेकिन ध्यान दें कि हर नोड में डायरेक्ट एलिमेंट का बच्चों से कोई क्रम नहीं है. समय के हिसाब से क्रम की बात है, तो बाईं से दाईं ओर कोई गारंटी नहीं होती है. साथ ही, डीएजी के अलग-अलग नोड में डुप्लीकेट एलिमेंट होने पर भी, सभी-माता-पिता की पहले की चाइल्ड गारंटी लागू नहीं होती है.

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

ट्रैवर्सल के लागू होने की वजह से, कंस्ट्रक्टर के order कीवर्ड आर्ग्युमेंट के साथ डेप्सेट बनाते समय ऑर्डर की जानकारी देनी होगी. अगर इस आर्ग्युमेंट को शामिल नहीं किया जाता है, तो डिस्क्रिप्ट में खास default ऑर्डर होता है. इस मामले में, इसके किसी भी एलिमेंट के क्रम के बारे में कोई गारंटी नहीं होती है (हालांकि, इसके लिए तय की जाने वाली वैल्यू तय नहीं होती).

पूरा उदाहरण

यह उदाहरण https://github.com/bazelbuild/examples/tree/main/rule/depset पर उपलब्ध है.

मान लें कि Foo की एक काल्पनिक भाषा है 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 बाइनरी के अस्थायी स्रोत a, b, c, और d के srcs फ़ील्ड में मौजूद सभी *.foo फ़ाइलें हैं. foo_binary टारगेट को d.foo के अलावा किसी दूसरी फ़ाइल के बारे में बताने के लिए, foo_library टारगेट को सेवा देने वाली कंपनी में पास करना होगा. हर लाइब्रेरी को, उसकी डिपेंडेंसी के आधार पर सेवा देने वाली कंपनियों को मिलता है. साथ ही, वह अपने सोर्स को तुरंत जोड़ती है और ऑगमेंटेड कॉन्टेंट वाली नई कंपनी पास करती है. foo_binary नियम यही काम करता है. हालांकि, सेवा देने वाली कंपनी देने के बजाय, यह सोर्स की पूरी सूची का इस्तेमाल करता है, ताकि किसी कार्रवाई के लिए कमांड लाइन बनाई जा सके.

यहां foo_library और foo_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() को कॉल करने की कोशिश करते हैं, तो यह एक क्वाड्रेटिक व्यवहार होता है.

गिरावट का बेहतर तरीके से इस्तेमाल करने के बारे में ज़्यादा जानकारी के लिए, परफ़ॉर्मेंस पेज देखें.

एपीआई का संदर्भ

ज़्यादा जानकारी के लिए, कृपया यहां जाएं.