1#
2# Copyright OpenEmbedded Contributors
3#
4# SPDX-License-Identifier: MIT
5#
6
7def __note(msg, d):
8    bb.note("%s: recipe_sanity: %s" % (d.getVar("P"), msg))
9
10__recipe_sanity_badruntimevars = "RDEPENDS RPROVIDES RRECOMMENDS RCONFLICTS"
11def bad_runtime_vars(cfgdata, d):
12    if bb.data.inherits_class("native", d) or \
13            bb.data.inherits_class("cross", d):
14        return
15
16    for var in d.getVar("__recipe_sanity_badruntimevars").split():
17        val = d.getVar(var, False)
18        if val and val != cfgdata.get(var):
19            __note("%s should be %s:${PN}" % (var, var), d)
20
21__recipe_sanity_reqvars = "DESCRIPTION"
22__recipe_sanity_reqdiffvars = ""
23def req_vars(cfgdata, d):
24    for var in d.getVar("__recipe_sanity_reqvars").split():
25        if not d.getVar(var, False):
26            __note("%s should be set" % var, d)
27
28    for var in d.getVar("__recipe_sanity_reqdiffvars").split():
29        val = d.getVar(var, False)
30        cfgval = cfgdata.get(var)
31
32        if not val:
33            __note("%s should be set" % var, d)
34        elif val == cfgval:
35            __note("%s should be defined to something other than default (%s)" % (var, cfgval), d)
36
37def var_renames_overwrite(cfgdata, d):
38    renames = d.getVar("__recipe_sanity_renames", False)
39    if renames:
40        for (key, newkey, oldvalue, newvalue) in renames:
41            if oldvalue != newvalue and oldvalue != cfgdata.get(newkey):
42                __note("rename of variable '%s' to '%s' overwrote existing value '%s' with '%s'." % (key, newkey, oldvalue, newvalue), d)
43
44def incorrect_nonempty_PACKAGES(cfgdata, d):
45    if bb.data.inherits_class("native", d) or \
46            bb.data.inherits_class("cross", d):
47        if d.getVar("PACKAGES"):
48            return True
49
50def can_use_autotools_base(cfgdata, d):
51    cfg = d.getVar("do_configure")
52    if not bb.data.inherits_class("autotools", d):
53        return False
54
55    for i in ["autoreconf"] + ["%s_do_configure" % cls for cls in ["gnomebase", "gnome", "e", "autotools", "efl", "gpephone", "openmoko", "openmoko2", "xfce", "xlibs"]]:
56        if cfg.find(i) != -1:
57            return False
58
59    for clsfile in d.getVar("__inherit_cache", False):
60        (base, _) = os.path.splitext(os.path.basename(clsfile))
61        if cfg.find("%s_do_configure" % base) != -1:
62            __note("autotools_base usage needs verification, spotted %s_do_configure" % base, d)
63
64    return True
65
66def can_delete_FILESPATH(cfgdata, d):
67    expected = cfgdata.get("FILESPATH")
68    expectedpaths = d.expand(expected)
69    unexpanded = d.getVar("FILESPATH", False)
70    filespath = d.getVar("FILESPATH").split(":")
71    filespath = [os.path.normpath(f) for f in filespath if os.path.exists(f)]
72    for fp in filespath:
73        if not fp in expectedpaths:
74            # __note("Path %s in FILESPATH not in the expected paths %s" %
75            # (fp, expectedpaths), d)
76            return False
77    return expected != unexpanded
78
79def can_delete_others(p, cfgdata, d):
80    for k in ["S", "PV", "PN", "DESCRIPTION", "DEPENDS",
81              "SECTION", "PACKAGES", "EXTRA_OECONF", "EXTRA_OEMAKE"]:
82    #for k in cfgdata:
83        unexpanded = d.getVar(k, False)
84        cfgunexpanded = cfgdata.get(k)
85        if not cfgunexpanded:
86            continue
87
88        try:
89            expanded = d.getVar(k)
90            cfgexpanded = d.expand(cfgunexpanded)
91        except bb.fetch.ParameterError:
92            continue
93
94        if unexpanded != cfgunexpanded and \
95           cfgexpanded == expanded:
96           __note("candidate for removal of %s" % k, d)
97           bb.debug(1, "%s: recipe_sanity:   cfg's '%s' and d's '%s' both expand to %s" %
98                       (p, cfgunexpanded, unexpanded, expanded))
99
100python do_recipe_sanity () {
101    p = d.getVar("P")
102    p = "%s %s %s" % (d.getVar("PN"), d.getVar("PV"), d.getVar("PR"))
103
104    sanitychecks = [
105        (can_delete_FILESPATH, "candidate for removal of FILESPATH"),
106        #(can_use_autotools_base, "candidate for use of autotools_base"),
107        (incorrect_nonempty_PACKAGES, "native or cross recipe with non-empty PACKAGES"),
108    ]
109    cfgdata = d.getVar("__recipe_sanity_cfgdata", False)
110
111    for (func, msg) in sanitychecks:
112        if func(cfgdata, d):
113            __note(msg, d)
114
115    can_delete_others(p, cfgdata, d)
116    var_renames_overwrite(cfgdata, d)
117    req_vars(cfgdata, d)
118    bad_runtime_vars(cfgdata, d)
119}
120do_recipe_sanity[nostamp] = "1"
121addtask recipe_sanity
122
123do_recipe_sanity_all[nostamp] = "1"
124do_recipe_sanity_all[recrdeptask] = "do_recipe_sanity_all do_recipe_sanity"
125do_recipe_sanity_all () {
126    :
127}
128addtask recipe_sanity_all after do_recipe_sanity
129
130python recipe_sanity_eh () {
131    d = e.data
132
133    cfgdata = {}
134    for k in d.keys():
135        if not isinstance(d.getVar(k, False), bb.data_smart.DataSmart):
136            cfgdata[k] = d.getVar(k, False)
137
138    d.setVar("__recipe_sanity_cfgdata", cfgdata)
139    #d.setVar("__recipe_sanity_cfgdata", d)
140
141    # Sick, very sick..
142    from bb.data_smart import DataSmart
143    old = DataSmart.renameVar
144    def myrename(self, key, newkey):
145        oldvalue = self.getVar(newkey, 0)
146        old(self, key, newkey)
147        newvalue = self.getVar(newkey, 0)
148        if oldvalue:
149            renames = self.getVar("__recipe_sanity_renames", 0) or set()
150            renames.add((key, newkey, oldvalue, newvalue))
151            self.setVar("__recipe_sanity_renames", renames)
152    DataSmart.renameVar = myrename
153}
154addhandler recipe_sanity_eh
155recipe_sanity_eh[eventmask] = "bb.event.ConfigParsed"
156