xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/tinfoil.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun# tinfoil: a simple wrapper around cooker for bitbake-based command-line utilities
2*4882a593Smuzhiyun#
3*4882a593Smuzhiyun# Copyright (C) 2012-2017 Intel Corporation
4*4882a593Smuzhiyun# Copyright (C) 2011 Mentor Graphics Corporation
5*4882a593Smuzhiyun# Copyright (C) 2006-2012 Richard Purdie
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
8*4882a593Smuzhiyun#
9*4882a593Smuzhiyun
10*4882a593Smuzhiyunimport logging
11*4882a593Smuzhiyunimport os
12*4882a593Smuzhiyunimport sys
13*4882a593Smuzhiyunimport atexit
14*4882a593Smuzhiyunimport re
15*4882a593Smuzhiyunfrom collections import OrderedDict, defaultdict
16*4882a593Smuzhiyunfrom functools import partial
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunimport bb.cache
19*4882a593Smuzhiyunimport bb.cooker
20*4882a593Smuzhiyunimport bb.providers
21*4882a593Smuzhiyunimport bb.taskdata
22*4882a593Smuzhiyunimport bb.utils
23*4882a593Smuzhiyunimport bb.command
24*4882a593Smuzhiyunimport bb.remotedata
25*4882a593Smuzhiyunfrom bb.main import setup_bitbake, BitBakeConfigParameters
26*4882a593Smuzhiyunimport bb.fetch2
27*4882a593Smuzhiyun
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun# We need this in order to shut down the connection to the bitbake server,
30*4882a593Smuzhiyun# otherwise the process will never properly exit
31*4882a593Smuzhiyun_server_connections = []
32*4882a593Smuzhiyundef _terminate_connections():
33*4882a593Smuzhiyun    for connection in _server_connections:
34*4882a593Smuzhiyun        connection.terminate()
35*4882a593Smuzhiyunatexit.register(_terminate_connections)
36*4882a593Smuzhiyun
37*4882a593Smuzhiyunclass TinfoilUIException(Exception):
38*4882a593Smuzhiyun    """Exception raised when the UI returns non-zero from its main function"""
39*4882a593Smuzhiyun    def __init__(self, returncode):
40*4882a593Smuzhiyun        self.returncode = returncode
41*4882a593Smuzhiyun    def __repr__(self):
42*4882a593Smuzhiyun        return 'UI module main returned %d' % self.returncode
43*4882a593Smuzhiyun
44*4882a593Smuzhiyunclass TinfoilCommandFailed(Exception):
45*4882a593Smuzhiyun    """Exception raised when run_command fails"""
46*4882a593Smuzhiyun
47*4882a593Smuzhiyunclass TinfoilDataStoreConnectorVarHistory:
48*4882a593Smuzhiyun    def __init__(self, tinfoil, dsindex):
49*4882a593Smuzhiyun        self.tinfoil = tinfoil
50*4882a593Smuzhiyun        self.dsindex = dsindex
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun    def remoteCommand(self, cmd, *args, **kwargs):
53*4882a593Smuzhiyun        return self.tinfoil.run_command('dataStoreConnectorVarHistCmd', self.dsindex, cmd, args, kwargs)
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun    def emit(self, var, oval, val, o, d):
56*4882a593Smuzhiyun        ret = self.tinfoil.run_command('dataStoreConnectorVarHistCmdEmit', self.dsindex, var, oval, val, d.dsindex)
57*4882a593Smuzhiyun        o.write(ret)
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun    def __getattr__(self, name):
60*4882a593Smuzhiyun        if not hasattr(bb.data_smart.VariableHistory, name):
61*4882a593Smuzhiyun            raise AttributeError("VariableHistory has no such method %s" % name)
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun        newfunc = partial(self.remoteCommand, name)
64*4882a593Smuzhiyun        setattr(self, name, newfunc)
65*4882a593Smuzhiyun        return newfunc
66*4882a593Smuzhiyun
67*4882a593Smuzhiyunclass TinfoilDataStoreConnectorIncHistory:
68*4882a593Smuzhiyun    def __init__(self, tinfoil, dsindex):
69*4882a593Smuzhiyun        self.tinfoil = tinfoil
70*4882a593Smuzhiyun        self.dsindex = dsindex
71*4882a593Smuzhiyun
72*4882a593Smuzhiyun    def remoteCommand(self, cmd, *args, **kwargs):
73*4882a593Smuzhiyun        return self.tinfoil.run_command('dataStoreConnectorIncHistCmd', self.dsindex, cmd, args, kwargs)
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun    def __getattr__(self, name):
76*4882a593Smuzhiyun        if not hasattr(bb.data_smart.IncludeHistory, name):
77*4882a593Smuzhiyun            raise AttributeError("IncludeHistory has no such method %s" % name)
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun        newfunc = partial(self.remoteCommand, name)
80*4882a593Smuzhiyun        setattr(self, name, newfunc)
81*4882a593Smuzhiyun        return newfunc
82*4882a593Smuzhiyun
83*4882a593Smuzhiyunclass TinfoilDataStoreConnector:
84*4882a593Smuzhiyun    """
85*4882a593Smuzhiyun    Connector object used to enable access to datastore objects via tinfoil
86*4882a593Smuzhiyun    Method calls are transmitted to the remote datastore for processing, if a datastore is
87*4882a593Smuzhiyun    returned we return a connector object for the new store
88*4882a593Smuzhiyun    """
89*4882a593Smuzhiyun
90*4882a593Smuzhiyun    def __init__(self, tinfoil, dsindex):
91*4882a593Smuzhiyun        self.tinfoil = tinfoil
92*4882a593Smuzhiyun        self.dsindex = dsindex
93*4882a593Smuzhiyun        self.varhistory = TinfoilDataStoreConnectorVarHistory(tinfoil, dsindex)
94*4882a593Smuzhiyun        self.inchistory = TinfoilDataStoreConnectorIncHistory(tinfoil, dsindex)
95*4882a593Smuzhiyun
96*4882a593Smuzhiyun    def remoteCommand(self, cmd, *args, **kwargs):
97*4882a593Smuzhiyun        ret = self.tinfoil.run_command('dataStoreConnectorCmd', self.dsindex, cmd, args, kwargs)
98*4882a593Smuzhiyun        if isinstance(ret, bb.command.DataStoreConnectionHandle):
99*4882a593Smuzhiyun            return TinfoilDataStoreConnector(self.tinfoil, ret.dsindex)
100*4882a593Smuzhiyun        return ret
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun    def __getattr__(self, name):
103*4882a593Smuzhiyun        if not hasattr(bb.data._dict_type, name):
104*4882a593Smuzhiyun            raise AttributeError("Data store has no such method %s" % name)
105*4882a593Smuzhiyun
106*4882a593Smuzhiyun        newfunc = partial(self.remoteCommand, name)
107*4882a593Smuzhiyun        setattr(self, name, newfunc)
108*4882a593Smuzhiyun        return newfunc
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun    def __iter__(self):
111*4882a593Smuzhiyun        keys = self.tinfoil.run_command('dataStoreConnectorCmd', self.dsindex, "keys", [], {})
112*4882a593Smuzhiyun        for k in keys:
113*4882a593Smuzhiyun            yield k
114*4882a593Smuzhiyun
115*4882a593Smuzhiyunclass TinfoilCookerAdapter:
116*4882a593Smuzhiyun    """
117*4882a593Smuzhiyun    Provide an adapter for existing code that expects to access a cooker object via Tinfoil,
118*4882a593Smuzhiyun    since now Tinfoil is on the client side it no longer has direct access.
119*4882a593Smuzhiyun    """
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun    class TinfoilCookerCollectionAdapter:
122*4882a593Smuzhiyun        """ cooker.collection adapter """
123*4882a593Smuzhiyun        def __init__(self, tinfoil, mc=''):
124*4882a593Smuzhiyun            self.tinfoil = tinfoil
125*4882a593Smuzhiyun            self.mc = mc
126*4882a593Smuzhiyun        def get_file_appends(self, fn):
127*4882a593Smuzhiyun            return self.tinfoil.get_file_appends(fn, self.mc)
128*4882a593Smuzhiyun        def __getattr__(self, name):
129*4882a593Smuzhiyun            if name == 'overlayed':
130*4882a593Smuzhiyun                return self.tinfoil.get_overlayed_recipes(self.mc)
131*4882a593Smuzhiyun            elif name == 'bbappends':
132*4882a593Smuzhiyun                return self.tinfoil.run_command('getAllAppends', self.mc)
133*4882a593Smuzhiyun            else:
134*4882a593Smuzhiyun                raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name))
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun    class TinfoilRecipeCacheAdapter:
137*4882a593Smuzhiyun        """ cooker.recipecache adapter """
138*4882a593Smuzhiyun        def __init__(self, tinfoil, mc=''):
139*4882a593Smuzhiyun            self.tinfoil = tinfoil
140*4882a593Smuzhiyun            self.mc = mc
141*4882a593Smuzhiyun            self._cache = {}
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun        def get_pkg_pn_fn(self):
144*4882a593Smuzhiyun            pkg_pn = defaultdict(list, self.tinfoil.run_command('getRecipes', self.mc) or [])
145*4882a593Smuzhiyun            pkg_fn = {}
146*4882a593Smuzhiyun            for pn, fnlist in pkg_pn.items():
147*4882a593Smuzhiyun                for fn in fnlist:
148*4882a593Smuzhiyun                    pkg_fn[fn] = pn
149*4882a593Smuzhiyun            self._cache['pkg_pn'] = pkg_pn
150*4882a593Smuzhiyun            self._cache['pkg_fn'] = pkg_fn
151*4882a593Smuzhiyun
152*4882a593Smuzhiyun        def __getattr__(self, name):
153*4882a593Smuzhiyun            # Grab these only when they are requested since they aren't always used
154*4882a593Smuzhiyun            if name in self._cache:
155*4882a593Smuzhiyun                return self._cache[name]
156*4882a593Smuzhiyun            elif name == 'pkg_pn':
157*4882a593Smuzhiyun                self.get_pkg_pn_fn()
158*4882a593Smuzhiyun                return self._cache[name]
159*4882a593Smuzhiyun            elif name == 'pkg_fn':
160*4882a593Smuzhiyun                self.get_pkg_pn_fn()
161*4882a593Smuzhiyun                return self._cache[name]
162*4882a593Smuzhiyun            elif name == 'deps':
163*4882a593Smuzhiyun                attrvalue = defaultdict(list, self.tinfoil.run_command('getRecipeDepends', self.mc) or [])
164*4882a593Smuzhiyun            elif name == 'rundeps':
165*4882a593Smuzhiyun                attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeDepends', self.mc) or [])
166*4882a593Smuzhiyun            elif name == 'runrecs':
167*4882a593Smuzhiyun                attrvalue = defaultdict(lambda: defaultdict(list), self.tinfoil.run_command('getRuntimeRecommends', self.mc) or [])
168*4882a593Smuzhiyun            elif name == 'pkg_pepvpr':
169*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRecipeVersions', self.mc) or {}
170*4882a593Smuzhiyun            elif name == 'inherits':
171*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRecipeInherits', self.mc) or {}
172*4882a593Smuzhiyun            elif name == 'bbfile_priority':
173*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getBbFilePriority', self.mc) or {}
174*4882a593Smuzhiyun            elif name == 'pkg_dp':
175*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getDefaultPreference', self.mc) or {}
176*4882a593Smuzhiyun            elif name == 'fn_provides':
177*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRecipeProvides', self.mc) or {}
178*4882a593Smuzhiyun            elif name == 'packages':
179*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRecipePackages', self.mc) or {}
180*4882a593Smuzhiyun            elif name == 'packages_dynamic':
181*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRecipePackagesDynamic', self.mc) or {}
182*4882a593Smuzhiyun            elif name == 'rproviders':
183*4882a593Smuzhiyun                attrvalue = self.tinfoil.run_command('getRProviders', self.mc) or {}
184*4882a593Smuzhiyun            else:
185*4882a593Smuzhiyun                raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name))
186*4882a593Smuzhiyun
187*4882a593Smuzhiyun            self._cache[name] = attrvalue
188*4882a593Smuzhiyun            return attrvalue
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun    def __init__(self, tinfoil):
191*4882a593Smuzhiyun        self.tinfoil = tinfoil
192*4882a593Smuzhiyun        self.multiconfigs = [''] + (tinfoil.config_data.getVar('BBMULTICONFIG') or '').split()
193*4882a593Smuzhiyun        self.collections = {}
194*4882a593Smuzhiyun        self.recipecaches = {}
195*4882a593Smuzhiyun        for mc in self.multiconfigs:
196*4882a593Smuzhiyun            self.collections[mc] = self.TinfoilCookerCollectionAdapter(tinfoil, mc)
197*4882a593Smuzhiyun            self.recipecaches[mc] = self.TinfoilRecipeCacheAdapter(tinfoil, mc)
198*4882a593Smuzhiyun        self._cache = {}
199*4882a593Smuzhiyun    def __getattr__(self, name):
200*4882a593Smuzhiyun        # Grab these only when they are requested since they aren't always used
201*4882a593Smuzhiyun        if name in self._cache:
202*4882a593Smuzhiyun            return self._cache[name]
203*4882a593Smuzhiyun        elif name == 'skiplist':
204*4882a593Smuzhiyun            attrvalue = self.tinfoil.get_skipped_recipes()
205*4882a593Smuzhiyun        elif name == 'bbfile_config_priorities':
206*4882a593Smuzhiyun            ret = self.tinfoil.run_command('getLayerPriorities')
207*4882a593Smuzhiyun            bbfile_config_priorities = []
208*4882a593Smuzhiyun            for collection, pattern, regex, pri in ret:
209*4882a593Smuzhiyun                bbfile_config_priorities.append((collection, pattern, re.compile(regex), pri))
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun            attrvalue = bbfile_config_priorities
212*4882a593Smuzhiyun        else:
213*4882a593Smuzhiyun            raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name))
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun        self._cache[name] = attrvalue
216*4882a593Smuzhiyun        return attrvalue
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun    def findBestProvider(self, pn):
219*4882a593Smuzhiyun        return self.tinfoil.find_best_provider(pn)
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun
222*4882a593Smuzhiyunclass TinfoilRecipeInfo:
223*4882a593Smuzhiyun    """
224*4882a593Smuzhiyun    Provides a convenient representation of the cached information for a single recipe.
225*4882a593Smuzhiyun    Some attributes are set on construction, others are read on-demand (which internally
226*4882a593Smuzhiyun    may result in a remote procedure call to the bitbake server the first time).
227*4882a593Smuzhiyun    Note that only information which is cached is available through this object - if
228*4882a593Smuzhiyun    you need other variable values you will need to parse the recipe using
229*4882a593Smuzhiyun    Tinfoil.parse_recipe().
230*4882a593Smuzhiyun    """
231*4882a593Smuzhiyun    def __init__(self, recipecache, d, pn, fn, fns):
232*4882a593Smuzhiyun        self._recipecache = recipecache
233*4882a593Smuzhiyun        self._d = d
234*4882a593Smuzhiyun        self.pn = pn
235*4882a593Smuzhiyun        self.fn = fn
236*4882a593Smuzhiyun        self.fns = fns
237*4882a593Smuzhiyun        self.inherit_files = recipecache.inherits[fn]
238*4882a593Smuzhiyun        self.depends = recipecache.deps[fn]
239*4882a593Smuzhiyun        (self.pe, self.pv, self.pr) = recipecache.pkg_pepvpr[fn]
240*4882a593Smuzhiyun        self._cached_packages = None
241*4882a593Smuzhiyun        self._cached_rprovides = None
242*4882a593Smuzhiyun        self._cached_packages_dynamic = None
243*4882a593Smuzhiyun
244*4882a593Smuzhiyun    def __getattr__(self, name):
245*4882a593Smuzhiyun        if name == 'alternates':
246*4882a593Smuzhiyun            return [x for x in self.fns if x != self.fn]
247*4882a593Smuzhiyun        elif name == 'rdepends':
248*4882a593Smuzhiyun            return self._recipecache.rundeps[self.fn]
249*4882a593Smuzhiyun        elif name == 'rrecommends':
250*4882a593Smuzhiyun            return self._recipecache.runrecs[self.fn]
251*4882a593Smuzhiyun        elif name == 'provides':
252*4882a593Smuzhiyun            return self._recipecache.fn_provides[self.fn]
253*4882a593Smuzhiyun        elif name == 'packages':
254*4882a593Smuzhiyun            if self._cached_packages is None:
255*4882a593Smuzhiyun                self._cached_packages = []
256*4882a593Smuzhiyun                for pkg, fns in self._recipecache.packages.items():
257*4882a593Smuzhiyun                    if self.fn in fns:
258*4882a593Smuzhiyun                        self._cached_packages.append(pkg)
259*4882a593Smuzhiyun            return self._cached_packages
260*4882a593Smuzhiyun        elif name == 'packages_dynamic':
261*4882a593Smuzhiyun            if self._cached_packages_dynamic is None:
262*4882a593Smuzhiyun                self._cached_packages_dynamic = []
263*4882a593Smuzhiyun                for pkg, fns in self._recipecache.packages_dynamic.items():
264*4882a593Smuzhiyun                    if self.fn in fns:
265*4882a593Smuzhiyun                        self._cached_packages_dynamic.append(pkg)
266*4882a593Smuzhiyun            return self._cached_packages_dynamic
267*4882a593Smuzhiyun        elif name == 'rprovides':
268*4882a593Smuzhiyun            if self._cached_rprovides is None:
269*4882a593Smuzhiyun                self._cached_rprovides = []
270*4882a593Smuzhiyun                for pkg, fns in self._recipecache.rproviders.items():
271*4882a593Smuzhiyun                    if self.fn in fns:
272*4882a593Smuzhiyun                        self._cached_rprovides.append(pkg)
273*4882a593Smuzhiyun            return self._cached_rprovides
274*4882a593Smuzhiyun        else:
275*4882a593Smuzhiyun            raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name))
276*4882a593Smuzhiyun    def inherits(self, only_recipe=False):
277*4882a593Smuzhiyun        """
278*4882a593Smuzhiyun        Get the inherited classes for a recipe. Returns the class names only.
279*4882a593Smuzhiyun        Parameters:
280*4882a593Smuzhiyun            only_recipe: True to return only the classes inherited by the recipe
281*4882a593Smuzhiyun                         itself, False to return all classes inherited within
282*4882a593Smuzhiyun                         the context for the recipe (which includes globally
283*4882a593Smuzhiyun                         inherited classes).
284*4882a593Smuzhiyun        """
285*4882a593Smuzhiyun        if only_recipe:
286*4882a593Smuzhiyun            global_inherit = [x for x in (self._d.getVar('BBINCLUDED') or '').split() if x.endswith('.bbclass')]
287*4882a593Smuzhiyun        else:
288*4882a593Smuzhiyun            global_inherit = []
289*4882a593Smuzhiyun        for clsfile in self.inherit_files:
290*4882a593Smuzhiyun            if only_recipe and clsfile in global_inherit:
291*4882a593Smuzhiyun                continue
292*4882a593Smuzhiyun            clsname = os.path.splitext(os.path.basename(clsfile))[0]
293*4882a593Smuzhiyun            yield clsname
294*4882a593Smuzhiyun    def __str__(self):
295*4882a593Smuzhiyun        return '%s' % self.pn
296*4882a593Smuzhiyun
297*4882a593Smuzhiyun
298*4882a593Smuzhiyunclass Tinfoil:
299*4882a593Smuzhiyun    """
300*4882a593Smuzhiyun    Tinfoil - an API for scripts and utilities to query
301*4882a593Smuzhiyun    BitBake internals and perform build operations.
302*4882a593Smuzhiyun    """
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun    def __init__(self, output=sys.stdout, tracking=False, setup_logging=True):
305*4882a593Smuzhiyun        """
306*4882a593Smuzhiyun        Create a new tinfoil object.
307*4882a593Smuzhiyun        Parameters:
308*4882a593Smuzhiyun            output: specifies where console output should be sent. Defaults
309*4882a593Smuzhiyun                    to sys.stdout.
310*4882a593Smuzhiyun            tracking: True to enable variable history tracking, False to
311*4882a593Smuzhiyun                    disable it (default). Enabling this has a minor
312*4882a593Smuzhiyun                    performance impact so typically it isn't enabled
313*4882a593Smuzhiyun                    unless you need to query variable history.
314*4882a593Smuzhiyun            setup_logging: True to setup a logger so that things like
315*4882a593Smuzhiyun                    bb.warn() will work immediately and timeout warnings
316*4882a593Smuzhiyun                    are visible; False to let BitBake do this itself.
317*4882a593Smuzhiyun        """
318*4882a593Smuzhiyun        self.logger = logging.getLogger('BitBake')
319*4882a593Smuzhiyun        self.config_data = None
320*4882a593Smuzhiyun        self.cooker = None
321*4882a593Smuzhiyun        self.tracking = tracking
322*4882a593Smuzhiyun        self.ui_module = None
323*4882a593Smuzhiyun        self.server_connection = None
324*4882a593Smuzhiyun        self.recipes_parsed = False
325*4882a593Smuzhiyun        self.quiet = 0
326*4882a593Smuzhiyun        self.oldhandlers = self.logger.handlers[:]
327*4882a593Smuzhiyun        if setup_logging:
328*4882a593Smuzhiyun            # This is the *client-side* logger, nothing to do with
329*4882a593Smuzhiyun            # logging messages from the server
330*4882a593Smuzhiyun            bb.msg.logger_create('BitBake', output)
331*4882a593Smuzhiyun            self.localhandlers = []
332*4882a593Smuzhiyun            for handler in self.logger.handlers:
333*4882a593Smuzhiyun                if handler not in self.oldhandlers:
334*4882a593Smuzhiyun                    self.localhandlers.append(handler)
335*4882a593Smuzhiyun
336*4882a593Smuzhiyun    def __enter__(self):
337*4882a593Smuzhiyun        return self
338*4882a593Smuzhiyun
339*4882a593Smuzhiyun    def __exit__(self, type, value, traceback):
340*4882a593Smuzhiyun        self.shutdown()
341*4882a593Smuzhiyun
342*4882a593Smuzhiyun    def prepare(self, config_only=False, config_params=None, quiet=0, extra_features=None):
343*4882a593Smuzhiyun        """
344*4882a593Smuzhiyun        Prepares the underlying BitBake system to be used via tinfoil.
345*4882a593Smuzhiyun        This function must be called prior to calling any of the other
346*4882a593Smuzhiyun        functions in the API.
347*4882a593Smuzhiyun        NOTE: if you call prepare() you must absolutely call shutdown()
348*4882a593Smuzhiyun        before your code terminates. You can use a "with" block to ensure
349*4882a593Smuzhiyun        this happens e.g.
350*4882a593Smuzhiyun
351*4882a593Smuzhiyun            with bb.tinfoil.Tinfoil() as tinfoil:
352*4882a593Smuzhiyun                tinfoil.prepare()
353*4882a593Smuzhiyun                ...
354*4882a593Smuzhiyun
355*4882a593Smuzhiyun        Parameters:
356*4882a593Smuzhiyun            config_only: True to read only the configuration and not load
357*4882a593Smuzhiyun                        the cache / parse recipes. This is useful if you just
358*4882a593Smuzhiyun                        want to query the value of a variable at the global
359*4882a593Smuzhiyun                        level or you want to do anything else that doesn't
360*4882a593Smuzhiyun                        involve knowing anything about the recipes in the
361*4882a593Smuzhiyun                        current configuration. False loads the cache / parses
362*4882a593Smuzhiyun                        recipes.
363*4882a593Smuzhiyun            config_params: optionally specify your own configuration
364*4882a593Smuzhiyun                        parameters. If not specified an instance of
365*4882a593Smuzhiyun                        TinfoilConfigParameters will be created internally.
366*4882a593Smuzhiyun            quiet:      quiet level controlling console output - equivalent
367*4882a593Smuzhiyun                        to bitbake's -q/--quiet option. Default of 0 gives
368*4882a593Smuzhiyun                        the same output level as normal bitbake execution.
369*4882a593Smuzhiyun            extra_features: extra features to be added to the feature
370*4882a593Smuzhiyun                        set requested from the server. See
371*4882a593Smuzhiyun                        CookerFeatures._feature_list for possible
372*4882a593Smuzhiyun                        features.
373*4882a593Smuzhiyun        """
374*4882a593Smuzhiyun        self.quiet = quiet
375*4882a593Smuzhiyun
376*4882a593Smuzhiyun        if self.tracking:
377*4882a593Smuzhiyun            extrafeatures = [bb.cooker.CookerFeatures.BASEDATASTORE_TRACKING]
378*4882a593Smuzhiyun        else:
379*4882a593Smuzhiyun            extrafeatures = []
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun        if extra_features:
382*4882a593Smuzhiyun            extrafeatures += extra_features
383*4882a593Smuzhiyun
384*4882a593Smuzhiyun        if not config_params:
385*4882a593Smuzhiyun            config_params = TinfoilConfigParameters(config_only=config_only, quiet=quiet)
386*4882a593Smuzhiyun
387*4882a593Smuzhiyun        if not config_only:
388*4882a593Smuzhiyun            # Disable local loggers because the UI module is going to set up its own
389*4882a593Smuzhiyun            for handler in self.localhandlers:
390*4882a593Smuzhiyun                self.logger.handlers.remove(handler)
391*4882a593Smuzhiyun            self.localhandlers = []
392*4882a593Smuzhiyun
393*4882a593Smuzhiyun        self.server_connection, ui_module = setup_bitbake(config_params, extrafeatures)
394*4882a593Smuzhiyun
395*4882a593Smuzhiyun        self.ui_module = ui_module
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun        # Ensure the path to bitbake's bin directory is in PATH so that things like
398*4882a593Smuzhiyun        # bitbake-worker can be run (usually this is the case, but it doesn't have to be)
399*4882a593Smuzhiyun        path = os.getenv('PATH').split(':')
400*4882a593Smuzhiyun        bitbakebinpath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'bin'))
401*4882a593Smuzhiyun        for entry in path:
402*4882a593Smuzhiyun            if entry.endswith(os.sep):
403*4882a593Smuzhiyun                entry = entry[:-1]
404*4882a593Smuzhiyun            if os.path.abspath(entry) == bitbakebinpath:
405*4882a593Smuzhiyun                break
406*4882a593Smuzhiyun        else:
407*4882a593Smuzhiyun            path.insert(0, bitbakebinpath)
408*4882a593Smuzhiyun            os.environ['PATH'] = ':'.join(path)
409*4882a593Smuzhiyun
410*4882a593Smuzhiyun        if self.server_connection:
411*4882a593Smuzhiyun            _server_connections.append(self.server_connection)
412*4882a593Smuzhiyun            if config_only:
413*4882a593Smuzhiyun                config_params.updateToServer(self.server_connection.connection, os.environ.copy())
414*4882a593Smuzhiyun                self.run_command('parseConfiguration')
415*4882a593Smuzhiyun            else:
416*4882a593Smuzhiyun                self.run_actions(config_params)
417*4882a593Smuzhiyun                self.recipes_parsed = True
418*4882a593Smuzhiyun
419*4882a593Smuzhiyun            self.config_data = TinfoilDataStoreConnector(self, 0)
420*4882a593Smuzhiyun            self.cooker = TinfoilCookerAdapter(self)
421*4882a593Smuzhiyun            self.cooker_data = self.cooker.recipecaches['']
422*4882a593Smuzhiyun        else:
423*4882a593Smuzhiyun            raise Exception('Failed to start bitbake server')
424*4882a593Smuzhiyun
425*4882a593Smuzhiyun    def run_actions(self, config_params):
426*4882a593Smuzhiyun        """
427*4882a593Smuzhiyun        Run the actions specified in config_params through the UI.
428*4882a593Smuzhiyun        """
429*4882a593Smuzhiyun        ret = self.ui_module.main(self.server_connection.connection, self.server_connection.events, config_params)
430*4882a593Smuzhiyun        if ret:
431*4882a593Smuzhiyun            raise TinfoilUIException(ret)
432*4882a593Smuzhiyun
433*4882a593Smuzhiyun    def parseRecipes(self):
434*4882a593Smuzhiyun        """
435*4882a593Smuzhiyun        Legacy function - use parse_recipes() instead.
436*4882a593Smuzhiyun        """
437*4882a593Smuzhiyun        self.parse_recipes()
438*4882a593Smuzhiyun
439*4882a593Smuzhiyun    def parse_recipes(self):
440*4882a593Smuzhiyun        """
441*4882a593Smuzhiyun        Load information on all recipes. Normally you should specify
442*4882a593Smuzhiyun        config_only=False when calling prepare() instead of using this
443*4882a593Smuzhiyun        function; this function is designed for situations where you need
444*4882a593Smuzhiyun        to initialise Tinfoil and use it with config_only=True first and
445*4882a593Smuzhiyun        then conditionally call this function to parse recipes later.
446*4882a593Smuzhiyun        """
447*4882a593Smuzhiyun        config_params = TinfoilConfigParameters(config_only=False, quiet=self.quiet)
448*4882a593Smuzhiyun        self.run_actions(config_params)
449*4882a593Smuzhiyun        self.recipes_parsed = True
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun    def run_command(self, command, *params, handle_events=True):
452*4882a593Smuzhiyun        """
453*4882a593Smuzhiyun        Run a command on the server (as implemented in bb.command).
454*4882a593Smuzhiyun        Note that there are two types of command - synchronous and
455*4882a593Smuzhiyun        asynchronous; in order to receive the results of asynchronous
456*4882a593Smuzhiyun        commands you will need to set an appropriate event mask
457*4882a593Smuzhiyun        using set_event_mask() and listen for the result using
458*4882a593Smuzhiyun        wait_event() - with the correct event mask you'll at least get
459*4882a593Smuzhiyun        bb.command.CommandCompleted and possibly other events before
460*4882a593Smuzhiyun        that depending on the command.
461*4882a593Smuzhiyun        """
462*4882a593Smuzhiyun        if not self.server_connection:
463*4882a593Smuzhiyun            raise Exception('Not connected to server (did you call .prepare()?)')
464*4882a593Smuzhiyun
465*4882a593Smuzhiyun        commandline = [command]
466*4882a593Smuzhiyun        if params:
467*4882a593Smuzhiyun            commandline.extend(params)
468*4882a593Smuzhiyun        try:
469*4882a593Smuzhiyun            result = self.server_connection.connection.runCommand(commandline)
470*4882a593Smuzhiyun        finally:
471*4882a593Smuzhiyun            while handle_events:
472*4882a593Smuzhiyun                event = self.wait_event()
473*4882a593Smuzhiyun                if not event:
474*4882a593Smuzhiyun                    break
475*4882a593Smuzhiyun                if isinstance(event, logging.LogRecord):
476*4882a593Smuzhiyun                    if event.taskpid == 0 or event.levelno > logging.INFO:
477*4882a593Smuzhiyun                        self.logger.handle(event)
478*4882a593Smuzhiyun        if result[1]:
479*4882a593Smuzhiyun            raise TinfoilCommandFailed(result[1])
480*4882a593Smuzhiyun        return result[0]
481*4882a593Smuzhiyun
482*4882a593Smuzhiyun    def set_event_mask(self, eventlist):
483*4882a593Smuzhiyun        """Set the event mask which will be applied within wait_event()"""
484*4882a593Smuzhiyun        if not self.server_connection:
485*4882a593Smuzhiyun            raise Exception('Not connected to server (did you call .prepare()?)')
486*4882a593Smuzhiyun        llevel, debug_domains = bb.msg.constructLogOptions()
487*4882a593Smuzhiyun        ret = self.run_command('setEventMask', self.server_connection.connection.getEventHandle(), llevel, debug_domains, eventlist)
488*4882a593Smuzhiyun        if not ret:
489*4882a593Smuzhiyun            raise Exception('setEventMask failed')
490*4882a593Smuzhiyun
491*4882a593Smuzhiyun    def wait_event(self, timeout=0):
492*4882a593Smuzhiyun        """
493*4882a593Smuzhiyun        Wait for an event from the server for the specified time.
494*4882a593Smuzhiyun        A timeout of 0 means don't wait if there are no events in the queue.
495*4882a593Smuzhiyun        Returns the next event in the queue or None if the timeout was
496*4882a593Smuzhiyun        reached. Note that in order to receive any events you will
497*4882a593Smuzhiyun        first need to set the internal event mask using set_event_mask()
498*4882a593Smuzhiyun        (otherwise whatever event mask the UI set up will be in effect).
499*4882a593Smuzhiyun        """
500*4882a593Smuzhiyun        if not self.server_connection:
501*4882a593Smuzhiyun            raise Exception('Not connected to server (did you call .prepare()?)')
502*4882a593Smuzhiyun        return self.server_connection.events.waitEvent(timeout)
503*4882a593Smuzhiyun
504*4882a593Smuzhiyun    def get_overlayed_recipes(self, mc=''):
505*4882a593Smuzhiyun        """
506*4882a593Smuzhiyun        Find recipes which are overlayed (i.e. where recipes exist in multiple layers)
507*4882a593Smuzhiyun        """
508*4882a593Smuzhiyun        return defaultdict(list, self.run_command('getOverlayedRecipes', mc))
509*4882a593Smuzhiyun
510*4882a593Smuzhiyun    def get_skipped_recipes(self):
511*4882a593Smuzhiyun        """
512*4882a593Smuzhiyun        Find recipes which were skipped (i.e. SkipRecipe was raised
513*4882a593Smuzhiyun        during parsing).
514*4882a593Smuzhiyun        """
515*4882a593Smuzhiyun        return OrderedDict(self.run_command('getSkippedRecipes'))
516*4882a593Smuzhiyun
517*4882a593Smuzhiyun    def get_all_providers(self, mc=''):
518*4882a593Smuzhiyun        return defaultdict(list, self.run_command('allProviders', mc))
519*4882a593Smuzhiyun
520*4882a593Smuzhiyun    def find_providers(self, mc=''):
521*4882a593Smuzhiyun        return self.run_command('findProviders', mc)
522*4882a593Smuzhiyun
523*4882a593Smuzhiyun    def find_best_provider(self, pn):
524*4882a593Smuzhiyun        return self.run_command('findBestProvider', pn)
525*4882a593Smuzhiyun
526*4882a593Smuzhiyun    def get_runtime_providers(self, rdep):
527*4882a593Smuzhiyun        return self.run_command('getRuntimeProviders', rdep)
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun    def get_recipe_file(self, pn):
530*4882a593Smuzhiyun        """
531*4882a593Smuzhiyun        Get the file name for the specified recipe/target. Raises
532*4882a593Smuzhiyun        bb.providers.NoProvider if there is no match or the recipe was
533*4882a593Smuzhiyun        skipped.
534*4882a593Smuzhiyun        """
535*4882a593Smuzhiyun        best = self.find_best_provider(pn)
536*4882a593Smuzhiyun        if not best or (len(best) > 3 and not best[3]):
537*4882a593Smuzhiyun            skiplist = self.get_skipped_recipes()
538*4882a593Smuzhiyun            taskdata = bb.taskdata.TaskData(None, skiplist=skiplist)
539*4882a593Smuzhiyun            skipreasons = taskdata.get_reasons(pn)
540*4882a593Smuzhiyun            if skipreasons:
541*4882a593Smuzhiyun                raise bb.providers.NoProvider('%s is unavailable:\n  %s' % (pn, '  \n'.join(skipreasons)))
542*4882a593Smuzhiyun            else:
543*4882a593Smuzhiyun                raise bb.providers.NoProvider('Unable to find any recipe file matching "%s"' % pn)
544*4882a593Smuzhiyun        return best[3]
545*4882a593Smuzhiyun
546*4882a593Smuzhiyun    def get_file_appends(self, fn, mc=''):
547*4882a593Smuzhiyun        """
548*4882a593Smuzhiyun        Find the bbappends for a recipe file
549*4882a593Smuzhiyun        """
550*4882a593Smuzhiyun        return self.run_command('getFileAppends', fn, mc)
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun    def all_recipes(self, mc='', sort=True):
553*4882a593Smuzhiyun        """
554*4882a593Smuzhiyun        Enable iterating over all recipes in the current configuration.
555*4882a593Smuzhiyun        Returns an iterator over TinfoilRecipeInfo objects created on demand.
556*4882a593Smuzhiyun        Parameters:
557*4882a593Smuzhiyun            mc: The multiconfig, default of '' uses the main configuration.
558*4882a593Smuzhiyun            sort: True to sort recipes alphabetically (default), False otherwise
559*4882a593Smuzhiyun        """
560*4882a593Smuzhiyun        recipecache = self.cooker.recipecaches[mc]
561*4882a593Smuzhiyun        if sort:
562*4882a593Smuzhiyun            recipes = sorted(recipecache.pkg_pn.items())
563*4882a593Smuzhiyun        else:
564*4882a593Smuzhiyun            recipes = recipecache.pkg_pn.items()
565*4882a593Smuzhiyun        for pn, fns in recipes:
566*4882a593Smuzhiyun            prov = self.find_best_provider(pn)
567*4882a593Smuzhiyun            recipe = TinfoilRecipeInfo(recipecache,
568*4882a593Smuzhiyun                                       self.config_data,
569*4882a593Smuzhiyun                                       pn=pn,
570*4882a593Smuzhiyun                                       fn=prov[3],
571*4882a593Smuzhiyun                                       fns=fns)
572*4882a593Smuzhiyun            yield recipe
573*4882a593Smuzhiyun
574*4882a593Smuzhiyun    def all_recipe_files(self, mc='', variants=True, preferred_only=False):
575*4882a593Smuzhiyun        """
576*4882a593Smuzhiyun        Enable iterating over all recipe files in the current configuration.
577*4882a593Smuzhiyun        Returns an iterator over file paths.
578*4882a593Smuzhiyun        Parameters:
579*4882a593Smuzhiyun            mc: The multiconfig, default of '' uses the main configuration.
580*4882a593Smuzhiyun            variants: True to include variants of recipes created through
581*4882a593Smuzhiyun                      BBCLASSEXTEND (default) or False to exclude them
582*4882a593Smuzhiyun            preferred_only: True to include only the preferred recipe where
583*4882a593Smuzhiyun                      multiple exist providing the same PN, False to list
584*4882a593Smuzhiyun                      all recipes
585*4882a593Smuzhiyun        """
586*4882a593Smuzhiyun        recipecache = self.cooker.recipecaches[mc]
587*4882a593Smuzhiyun        if preferred_only:
588*4882a593Smuzhiyun            files = []
589*4882a593Smuzhiyun            for pn in recipecache.pkg_pn.keys():
590*4882a593Smuzhiyun                prov = self.find_best_provider(pn)
591*4882a593Smuzhiyun                files.append(prov[3])
592*4882a593Smuzhiyun        else:
593*4882a593Smuzhiyun            files = recipecache.pkg_fn.keys()
594*4882a593Smuzhiyun        for fn in sorted(files):
595*4882a593Smuzhiyun            if not variants and fn.startswith('virtual:'):
596*4882a593Smuzhiyun                continue
597*4882a593Smuzhiyun            yield fn
598*4882a593Smuzhiyun
599*4882a593Smuzhiyun
600*4882a593Smuzhiyun    def get_recipe_info(self, pn, mc=''):
601*4882a593Smuzhiyun        """
602*4882a593Smuzhiyun        Get information on a specific recipe in the current configuration by name (PN).
603*4882a593Smuzhiyun        Returns a TinfoilRecipeInfo object created on demand.
604*4882a593Smuzhiyun        Parameters:
605*4882a593Smuzhiyun            mc: The multiconfig, default of '' uses the main configuration.
606*4882a593Smuzhiyun        """
607*4882a593Smuzhiyun        recipecache = self.cooker.recipecaches[mc]
608*4882a593Smuzhiyun        prov = self.find_best_provider(pn)
609*4882a593Smuzhiyun        fn = prov[3]
610*4882a593Smuzhiyun        if fn:
611*4882a593Smuzhiyun            actual_pn = recipecache.pkg_fn[fn]
612*4882a593Smuzhiyun            recipe = TinfoilRecipeInfo(recipecache,
613*4882a593Smuzhiyun                                        self.config_data,
614*4882a593Smuzhiyun                                        pn=actual_pn,
615*4882a593Smuzhiyun                                        fn=fn,
616*4882a593Smuzhiyun                                        fns=recipecache.pkg_pn[actual_pn])
617*4882a593Smuzhiyun            return recipe
618*4882a593Smuzhiyun        else:
619*4882a593Smuzhiyun            return None
620*4882a593Smuzhiyun
621*4882a593Smuzhiyun    def parse_recipe(self, pn):
622*4882a593Smuzhiyun        """
623*4882a593Smuzhiyun        Parse the specified recipe and return a datastore object
624*4882a593Smuzhiyun        representing the environment for the recipe.
625*4882a593Smuzhiyun        """
626*4882a593Smuzhiyun        fn = self.get_recipe_file(pn)
627*4882a593Smuzhiyun        return self.parse_recipe_file(fn)
628*4882a593Smuzhiyun
629*4882a593Smuzhiyun    def parse_recipe_file(self, fn, appends=True, appendlist=None, config_data=None):
630*4882a593Smuzhiyun        """
631*4882a593Smuzhiyun        Parse the specified recipe file (with or without bbappends)
632*4882a593Smuzhiyun        and return a datastore object representing the environment
633*4882a593Smuzhiyun        for the recipe.
634*4882a593Smuzhiyun        Parameters:
635*4882a593Smuzhiyun            fn: recipe file to parse - can be a file path or virtual
636*4882a593Smuzhiyun                specification
637*4882a593Smuzhiyun            appends: True to apply bbappends, False otherwise
638*4882a593Smuzhiyun            appendlist: optional list of bbappend files to apply, if you
639*4882a593Smuzhiyun                        want to filter them
640*4882a593Smuzhiyun        """
641*4882a593Smuzhiyun        if self.tracking:
642*4882a593Smuzhiyun            # Enable history tracking just for the parse operation
643*4882a593Smuzhiyun            self.run_command('enableDataTracking')
644*4882a593Smuzhiyun        try:
645*4882a593Smuzhiyun            if appends and appendlist == []:
646*4882a593Smuzhiyun                appends = False
647*4882a593Smuzhiyun            if config_data:
648*4882a593Smuzhiyun                 config_data = bb.data.createCopy(config_data)
649*4882a593Smuzhiyun                 dscon = self.run_command('parseRecipeFile', fn, appends, appendlist, config_data.dsindex)
650*4882a593Smuzhiyun            else:
651*4882a593Smuzhiyun                dscon = self.run_command('parseRecipeFile', fn, appends, appendlist)
652*4882a593Smuzhiyun            if dscon:
653*4882a593Smuzhiyun                return self._reconvert_type(dscon, 'DataStoreConnectionHandle')
654*4882a593Smuzhiyun            else:
655*4882a593Smuzhiyun                return None
656*4882a593Smuzhiyun        finally:
657*4882a593Smuzhiyun            if self.tracking:
658*4882a593Smuzhiyun                self.run_command('disableDataTracking')
659*4882a593Smuzhiyun
660*4882a593Smuzhiyun    def build_file(self, buildfile, task, internal=True):
661*4882a593Smuzhiyun        """
662*4882a593Smuzhiyun        Runs the specified task for just a single recipe (i.e. no dependencies).
663*4882a593Smuzhiyun        This is equivalent to bitbake -b, except with the default internal=True
664*4882a593Smuzhiyun        no warning about dependencies will be produced, normal info messages
665*4882a593Smuzhiyun        from the runqueue will be silenced and BuildInit, BuildStarted and
666*4882a593Smuzhiyun        BuildCompleted events will not be fired.
667*4882a593Smuzhiyun        """
668*4882a593Smuzhiyun        return self.run_command('buildFile', buildfile, task, internal)
669*4882a593Smuzhiyun
670*4882a593Smuzhiyun    def build_targets(self, targets, task=None, handle_events=True, extra_events=None, event_callback=None):
671*4882a593Smuzhiyun        """
672*4882a593Smuzhiyun        Builds the specified targets. This is equivalent to a normal invocation
673*4882a593Smuzhiyun        of bitbake. Has built-in event handling which is enabled by default and
674*4882a593Smuzhiyun        can be extended if needed.
675*4882a593Smuzhiyun        Parameters:
676*4882a593Smuzhiyun            targets:
677*4882a593Smuzhiyun                One or more targets to build. Can be a list or a
678*4882a593Smuzhiyun                space-separated string.
679*4882a593Smuzhiyun            task:
680*4882a593Smuzhiyun                The task to run; if None then the value of BB_DEFAULT_TASK
681*4882a593Smuzhiyun                will be used. Default None.
682*4882a593Smuzhiyun            handle_events:
683*4882a593Smuzhiyun                True to handle events in a similar way to normal bitbake
684*4882a593Smuzhiyun                invocation with knotty; False to return immediately (on the
685*4882a593Smuzhiyun                assumption that the caller will handle the events instead).
686*4882a593Smuzhiyun                Default True.
687*4882a593Smuzhiyun            extra_events:
688*4882a593Smuzhiyun                An optional list of events to add to the event mask (if
689*4882a593Smuzhiyun                handle_events=True). If you add events here you also need
690*4882a593Smuzhiyun                to specify a callback function in event_callback that will
691*4882a593Smuzhiyun                handle the additional events. Default None.
692*4882a593Smuzhiyun            event_callback:
693*4882a593Smuzhiyun                An optional function taking a single parameter which
694*4882a593Smuzhiyun                will be called first upon receiving any event (if
695*4882a593Smuzhiyun                handle_events=True) so that the caller can override or
696*4882a593Smuzhiyun                extend the event handling. Default None.
697*4882a593Smuzhiyun        """
698*4882a593Smuzhiyun        if isinstance(targets, str):
699*4882a593Smuzhiyun            targets = targets.split()
700*4882a593Smuzhiyun        if not task:
701*4882a593Smuzhiyun            task = self.config_data.getVar('BB_DEFAULT_TASK')
702*4882a593Smuzhiyun
703*4882a593Smuzhiyun        if handle_events:
704*4882a593Smuzhiyun            # A reasonable set of default events matching up with those we handle below
705*4882a593Smuzhiyun            eventmask = [
706*4882a593Smuzhiyun                        'bb.event.BuildStarted',
707*4882a593Smuzhiyun                        'bb.event.BuildCompleted',
708*4882a593Smuzhiyun                        'logging.LogRecord',
709*4882a593Smuzhiyun                        'bb.event.NoProvider',
710*4882a593Smuzhiyun                        'bb.command.CommandCompleted',
711*4882a593Smuzhiyun                        'bb.command.CommandFailed',
712*4882a593Smuzhiyun                        'bb.build.TaskStarted',
713*4882a593Smuzhiyun                        'bb.build.TaskFailed',
714*4882a593Smuzhiyun                        'bb.build.TaskSucceeded',
715*4882a593Smuzhiyun                        'bb.build.TaskFailedSilent',
716*4882a593Smuzhiyun                        'bb.build.TaskProgress',
717*4882a593Smuzhiyun                        'bb.runqueue.runQueueTaskStarted',
718*4882a593Smuzhiyun                        'bb.runqueue.sceneQueueTaskStarted',
719*4882a593Smuzhiyun                        'bb.event.ProcessStarted',
720*4882a593Smuzhiyun                        'bb.event.ProcessProgress',
721*4882a593Smuzhiyun                        'bb.event.ProcessFinished',
722*4882a593Smuzhiyun                        ]
723*4882a593Smuzhiyun            if extra_events:
724*4882a593Smuzhiyun                eventmask.extend(extra_events)
725*4882a593Smuzhiyun            ret = self.set_event_mask(eventmask)
726*4882a593Smuzhiyun
727*4882a593Smuzhiyun        includelogs = self.config_data.getVar('BBINCLUDELOGS')
728*4882a593Smuzhiyun        loglines = self.config_data.getVar('BBINCLUDELOGS_LINES')
729*4882a593Smuzhiyun
730*4882a593Smuzhiyun        ret = self.run_command('buildTargets', targets, task)
731*4882a593Smuzhiyun        if handle_events:
732*4882a593Smuzhiyun            result = False
733*4882a593Smuzhiyun            # Borrowed from knotty, instead somewhat hackily we use the helper
734*4882a593Smuzhiyun            # as the object to store "shutdown" on
735*4882a593Smuzhiyun            helper = bb.ui.uihelper.BBUIHelper()
736*4882a593Smuzhiyun            helper.shutdown = 0
737*4882a593Smuzhiyun            parseprogress = None
738*4882a593Smuzhiyun            termfilter = bb.ui.knotty.TerminalFilter(helper, helper, self.logger.handlers, quiet=self.quiet)
739*4882a593Smuzhiyun            try:
740*4882a593Smuzhiyun                while True:
741*4882a593Smuzhiyun                    try:
742*4882a593Smuzhiyun                        event = self.wait_event(0.25)
743*4882a593Smuzhiyun                        if event:
744*4882a593Smuzhiyun                            if event_callback and event_callback(event):
745*4882a593Smuzhiyun                                continue
746*4882a593Smuzhiyun                            if helper.eventHandler(event):
747*4882a593Smuzhiyun                                if isinstance(event, bb.build.TaskFailedSilent):
748*4882a593Smuzhiyun                                    self.logger.warning("Logfile for failed setscene task is %s" % event.logfile)
749*4882a593Smuzhiyun                                elif isinstance(event, bb.build.TaskFailed):
750*4882a593Smuzhiyun                                    bb.ui.knotty.print_event_log(event, includelogs, loglines, termfilter)
751*4882a593Smuzhiyun                                continue
752*4882a593Smuzhiyun                            if isinstance(event, bb.event.ProcessStarted):
753*4882a593Smuzhiyun                                if self.quiet > 1:
754*4882a593Smuzhiyun                                    continue
755*4882a593Smuzhiyun                                parseprogress = bb.ui.knotty.new_progress(event.processname, event.total)
756*4882a593Smuzhiyun                                parseprogress.start(False)
757*4882a593Smuzhiyun                                continue
758*4882a593Smuzhiyun                            if isinstance(event, bb.event.ProcessProgress):
759*4882a593Smuzhiyun                                if self.quiet > 1:
760*4882a593Smuzhiyun                                    continue
761*4882a593Smuzhiyun                                if parseprogress:
762*4882a593Smuzhiyun                                    parseprogress.update(event.progress)
763*4882a593Smuzhiyun                                else:
764*4882a593Smuzhiyun                                    bb.warn("Got ProcessProgress event for something that never started?")
765*4882a593Smuzhiyun                                continue
766*4882a593Smuzhiyun                            if isinstance(event, bb.event.ProcessFinished):
767*4882a593Smuzhiyun                                if self.quiet > 1:
768*4882a593Smuzhiyun                                    continue
769*4882a593Smuzhiyun                                if parseprogress:
770*4882a593Smuzhiyun                                    parseprogress.finish()
771*4882a593Smuzhiyun                                parseprogress = None
772*4882a593Smuzhiyun                                continue
773*4882a593Smuzhiyun                            if isinstance(event, bb.command.CommandCompleted):
774*4882a593Smuzhiyun                                result = True
775*4882a593Smuzhiyun                                break
776*4882a593Smuzhiyun                            if isinstance(event, bb.command.CommandFailed):
777*4882a593Smuzhiyun                                self.logger.error(str(event))
778*4882a593Smuzhiyun                                result = False
779*4882a593Smuzhiyun                                break
780*4882a593Smuzhiyun                            if isinstance(event, logging.LogRecord):
781*4882a593Smuzhiyun                                if event.taskpid == 0 or event.levelno > logging.INFO:
782*4882a593Smuzhiyun                                    self.logger.handle(event)
783*4882a593Smuzhiyun                                continue
784*4882a593Smuzhiyun                            if isinstance(event, bb.event.NoProvider):
785*4882a593Smuzhiyun                                self.logger.error(str(event))
786*4882a593Smuzhiyun                                result = False
787*4882a593Smuzhiyun                                break
788*4882a593Smuzhiyun
789*4882a593Smuzhiyun                        elif helper.shutdown > 1:
790*4882a593Smuzhiyun                            break
791*4882a593Smuzhiyun                        termfilter.updateFooter()
792*4882a593Smuzhiyun                    except KeyboardInterrupt:
793*4882a593Smuzhiyun                        termfilter.clearFooter()
794*4882a593Smuzhiyun                        if helper.shutdown == 1:
795*4882a593Smuzhiyun                            print("\nSecond Keyboard Interrupt, stopping...\n")
796*4882a593Smuzhiyun                            ret = self.run_command("stateForceShutdown")
797*4882a593Smuzhiyun                            if ret and ret[2]:
798*4882a593Smuzhiyun                                self.logger.error("Unable to cleanly stop: %s" % ret[2])
799*4882a593Smuzhiyun                        elif helper.shutdown == 0:
800*4882a593Smuzhiyun                            print("\nKeyboard Interrupt, closing down...\n")
801*4882a593Smuzhiyun                            interrupted = True
802*4882a593Smuzhiyun                            ret = self.run_command("stateShutdown")
803*4882a593Smuzhiyun                            if ret and ret[2]:
804*4882a593Smuzhiyun                                self.logger.error("Unable to cleanly shutdown: %s" % ret[2])
805*4882a593Smuzhiyun                        helper.shutdown = helper.shutdown + 1
806*4882a593Smuzhiyun                termfilter.clearFooter()
807*4882a593Smuzhiyun            finally:
808*4882a593Smuzhiyun                termfilter.finish()
809*4882a593Smuzhiyun            if helper.failed_tasks:
810*4882a593Smuzhiyun                result = False
811*4882a593Smuzhiyun            return result
812*4882a593Smuzhiyun        else:
813*4882a593Smuzhiyun            return ret
814*4882a593Smuzhiyun
815*4882a593Smuzhiyun    def shutdown(self):
816*4882a593Smuzhiyun        """
817*4882a593Smuzhiyun        Shut down tinfoil. Disconnects from the server and gracefully
818*4882a593Smuzhiyun        releases any associated resources. You must call this function if
819*4882a593Smuzhiyun        prepare() has been called, or use a with... block when you create
820*4882a593Smuzhiyun        the tinfoil object which will ensure that it gets called.
821*4882a593Smuzhiyun        """
822*4882a593Smuzhiyun        try:
823*4882a593Smuzhiyun            if self.server_connection:
824*4882a593Smuzhiyun                try:
825*4882a593Smuzhiyun                    self.run_command('clientComplete')
826*4882a593Smuzhiyun                finally:
827*4882a593Smuzhiyun                    _server_connections.remove(self.server_connection)
828*4882a593Smuzhiyun                    bb.event.ui_queue = []
829*4882a593Smuzhiyun                    self.server_connection.terminate()
830*4882a593Smuzhiyun                    self.server_connection = None
831*4882a593Smuzhiyun
832*4882a593Smuzhiyun        finally:
833*4882a593Smuzhiyun            # Restore logging handlers to how it looked when we started
834*4882a593Smuzhiyun            if self.oldhandlers:
835*4882a593Smuzhiyun                for handler in self.logger.handlers:
836*4882a593Smuzhiyun                    if handler not in self.oldhandlers:
837*4882a593Smuzhiyun                        self.logger.handlers.remove(handler)
838*4882a593Smuzhiyun
839*4882a593Smuzhiyun    def _reconvert_type(self, obj, origtypename):
840*4882a593Smuzhiyun        """
841*4882a593Smuzhiyun        Convert an object back to the right type, in the case
842*4882a593Smuzhiyun        that marshalling has changed it (especially with xmlrpc)
843*4882a593Smuzhiyun        """
844*4882a593Smuzhiyun        supported_types = {
845*4882a593Smuzhiyun            'set': set,
846*4882a593Smuzhiyun            'DataStoreConnectionHandle': bb.command.DataStoreConnectionHandle,
847*4882a593Smuzhiyun        }
848*4882a593Smuzhiyun
849*4882a593Smuzhiyun        origtype = supported_types.get(origtypename, None)
850*4882a593Smuzhiyun        if origtype is None:
851*4882a593Smuzhiyun            raise Exception('Unsupported type "%s"' % origtypename)
852*4882a593Smuzhiyun        if type(obj) == origtype:
853*4882a593Smuzhiyun            newobj = obj
854*4882a593Smuzhiyun        elif isinstance(obj, dict):
855*4882a593Smuzhiyun            # New style class
856*4882a593Smuzhiyun            newobj = origtype()
857*4882a593Smuzhiyun            for k,v in obj.items():
858*4882a593Smuzhiyun                setattr(newobj, k, v)
859*4882a593Smuzhiyun        else:
860*4882a593Smuzhiyun            # Assume we can coerce the type
861*4882a593Smuzhiyun            newobj = origtype(obj)
862*4882a593Smuzhiyun
863*4882a593Smuzhiyun        if isinstance(newobj, bb.command.DataStoreConnectionHandle):
864*4882a593Smuzhiyun            newobj = TinfoilDataStoreConnector(self, newobj.dsindex)
865*4882a593Smuzhiyun
866*4882a593Smuzhiyun        return newobj
867*4882a593Smuzhiyun
868*4882a593Smuzhiyun
869*4882a593Smuzhiyunclass TinfoilConfigParameters(BitBakeConfigParameters):
870*4882a593Smuzhiyun
871*4882a593Smuzhiyun    def __init__(self, config_only, **options):
872*4882a593Smuzhiyun        self.initial_options = options
873*4882a593Smuzhiyun        # Apply some sane defaults
874*4882a593Smuzhiyun        if not 'parse_only' in options:
875*4882a593Smuzhiyun            self.initial_options['parse_only'] = not config_only
876*4882a593Smuzhiyun        #if not 'status_only' in options:
877*4882a593Smuzhiyun        #    self.initial_options['status_only'] = config_only
878*4882a593Smuzhiyun        if not 'ui' in options:
879*4882a593Smuzhiyun            self.initial_options['ui'] = 'knotty'
880*4882a593Smuzhiyun        if not 'argv' in options:
881*4882a593Smuzhiyun            self.initial_options['argv'] = []
882*4882a593Smuzhiyun
883*4882a593Smuzhiyun        super(TinfoilConfigParameters, self).__init__()
884*4882a593Smuzhiyun
885*4882a593Smuzhiyun    def parseCommandLine(self, argv=None):
886*4882a593Smuzhiyun        # We don't want any parameters parsed from the command line
887*4882a593Smuzhiyun        opts = super(TinfoilConfigParameters, self).parseCommandLine([])
888*4882a593Smuzhiyun        for key, val in self.initial_options.items():
889*4882a593Smuzhiyun            setattr(opts[0], key, val)
890*4882a593Smuzhiyun        return opts
891