BazelCon 2022, 16 नवंबर से 17 नवंबर तक न्यूयॉर्क में और ऑनलाइन उपलब्ध है.
आज ही रजिस्टर करें!

गहराई

संग्रह की मदद से व्यवस्थित रहें अपनी प्राथमिकताओं के आधार पर, कॉन्टेंट को सेव करें और कैटगरी में बांटें.

डिपेक्ट खास डेटा स्ट्रक्चर होता है, जिसका इस्तेमाल करके टारगेट की गई डिपेंडेंसी से जुड़े डेटा को आसानी से इकट्ठा किया जा सकता है. ये नियम संसाधन का एक ज़रूरी हिस्सा है.

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

Depset की सामग्री को फिर से पाने के लिए, to_list() विधि का इस्तेमाल करें. यह डुप्लीकेट फ़ाइलों के साथ-साथ सभी ट्रांज़िट एलिमेंट की सूची लौटाता है. सीधे डीएजी की सटीक संरचना की जांच करने का कोई तरीका नहीं है, हालांकि यह संरचना उस क्रम को प्रभावित करती है जिसमें एलिमेंट लौटाए जाते हैं.

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 कार्रवाई, डीएजी पर ट्रैवर्स करती है. इस तरह की ट्रैवर्सल, उस ऑर्डर पर निर्भर करती है जो डिप्स के बनाए जाने के समय तय किया गया था. बैजल की मदद से एक से ज़्यादा ऑर्डर किए जा सकते हैं, क्योंकि कभी-कभी टूल उनके इनपुट की परवाह करते हैं. उदाहरण के लिए, किसी लिंकर कार्रवाई को यह पक्का करने की ज़रूरत हो सकती है कि अगर 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 कीवर्ड तर्क के साथ डेप्सेट बनाए जाने के समय तय किया जाना चाहिए. अगर इस तर्क को छोड़ दिया जाता है, तो Depset में खास default ऑर्डर होता है. इस स्थिति में इसके किसी भी एलिमेंट के क्रम की गारंटी नहीं दी जाती है (बशर्ते यह तय न हो).

पूरा उदाहरण

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

मान लीजिए कि काल्पनिक भाषा फ़ू है. हर 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 इसमें मौजूद फ़ाइलेंsrcs के फ़ील्डa, b, c, औरd चुनें. 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

इससे डुप्लीकेट फ़ाइलों से छुटकारा मिल जाता है, लेकिन इससे कमांड लाइन के तर्कों (और इसलिए फ़ाइलों की सामग्री) का क्रम तय नहीं होता है, फिर भी इसे तय करना मुश्किल होता है.

साथ ही, यह भी बहुत ज़्यादा मुश्किल है. ऐसी स्थिति में जाएं जहां फ़ू लाइब्रेरी पर निर्भर लोगों की एक लंबी चेन हो. हर नियम को प्रोसेस करने के लिए, नए डेटा स्ट्रक्चर में आने वाले सभी ट्रांज़िट सोर्स को कॉपी करना ज़रूरी होता है. इसका मतलब है कि किसी एक लाइब्रेरी या बाइनरी टारगेट के विश्लेषण के लिए लगने वाला समय और जगह की लागत, चेन में अपनी ऊंचाई के अनुपात में होती है. लंबाई की एक शृंखला के लिए, foolib_1 ← foolib_2 ← ... ← foolib_n, कुल लागत प्रभावी रूप से O(n^2) है.

आम तौर पर, जब आप अपनी ट्रांज़िट डिपेंडेंसी के ज़रिए जानकारी इकट्ठा कर रहे होते हैं, तो डिप्स का इस्तेमाल किया जाना चाहिए. इससे यह पक्का करने में मदद मिलती है कि आपका बिल्ड स्केल और आपका टारगेट ग्राफ़ ज़्यादा गहराई से बढ़ता है.

आखिर में, यह ज़रूरी है कि आप नियम लागू करने में गैर-ज़रूरी तरीके से डिसेट की सामग्री फिर से न पाएं. बाइनरी नियम के आखिर में to_list() पर एक कॉल करना सही है, क्योंकि कुल लागत सिर्फ़ O(n) है. ऐसा तब होता है, जब कई गैर-टर्मिनल लक्ष्य to_list() को कॉल करने की कोशिश करते हैं, जो क्वाड्रेटिक व्यवहार होता है.

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

एपीआई संदर्भ

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