डेपट्स

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

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

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

डिपसेट के इस्तेमाल के उदाहरण:

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

  • इंटरप्रेटेड लैंग्वेज के लिए, ऐसी ट्रांज़िटिव सोर्स फ़ाइलों को सेव करना जो एक्ज़ीक्यूटेबल की रनफ़ाइल में शामिल हैं.

ब्यौरा और कार्रवाइयां

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

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

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

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

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

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