xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/build.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# BitBake 'Build' implementation
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# Core code for function execution and task handling in the
5*4882a593Smuzhiyun# BitBake build tools.
6*4882a593Smuzhiyun#
7*4882a593Smuzhiyun# Copyright (C) 2003, 2004  Chris Larson
8*4882a593Smuzhiyun#
9*4882a593Smuzhiyun# Based on Gentoo's portage.py.
10*4882a593Smuzhiyun#
11*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
12*4882a593Smuzhiyun#
13*4882a593Smuzhiyun# Based on functions from the base bb module, Copyright 2003 Holger Schurig
14*4882a593Smuzhiyun
15*4882a593Smuzhiyunimport os
16*4882a593Smuzhiyunimport sys
17*4882a593Smuzhiyunimport logging
18*4882a593Smuzhiyunimport glob
19*4882a593Smuzhiyunimport itertools
20*4882a593Smuzhiyunimport time
21*4882a593Smuzhiyunimport re
22*4882a593Smuzhiyunimport stat
23*4882a593Smuzhiyunimport bb
24*4882a593Smuzhiyunimport bb.msg
25*4882a593Smuzhiyunimport bb.process
26*4882a593Smuzhiyunimport bb.progress
27*4882a593Smuzhiyunfrom bb import data, event, utils
28*4882a593Smuzhiyun
29*4882a593Smuzhiyunbblogger = logging.getLogger('BitBake')
30*4882a593Smuzhiyunlogger = logging.getLogger('BitBake.Build')
31*4882a593Smuzhiyun
32*4882a593SmuzhiyunverboseShellLogging = False
33*4882a593SmuzhiyunverboseStdoutLogging = False
34*4882a593Smuzhiyun
35*4882a593Smuzhiyun__mtime_cache = {}
36*4882a593Smuzhiyun
37*4882a593Smuzhiyundef cached_mtime_noerror(f):
38*4882a593Smuzhiyun    if f not in __mtime_cache:
39*4882a593Smuzhiyun        try:
40*4882a593Smuzhiyun            __mtime_cache[f] = os.stat(f)[stat.ST_MTIME]
41*4882a593Smuzhiyun        except OSError:
42*4882a593Smuzhiyun            return 0
43*4882a593Smuzhiyun    return __mtime_cache[f]
44*4882a593Smuzhiyun
45*4882a593Smuzhiyundef reset_cache():
46*4882a593Smuzhiyun    global __mtime_cache
47*4882a593Smuzhiyun    __mtime_cache = {}
48*4882a593Smuzhiyun
49*4882a593Smuzhiyun# When we execute a Python function, we'd like certain things
50*4882a593Smuzhiyun# in all namespaces, hence we add them to __builtins__.
51*4882a593Smuzhiyun# If we do not do this and use the exec globals, they will
52*4882a593Smuzhiyun# not be available to subfunctions.
53*4882a593Smuzhiyunif hasattr(__builtins__, '__setitem__'):
54*4882a593Smuzhiyun    builtins = __builtins__
55*4882a593Smuzhiyunelse:
56*4882a593Smuzhiyun    builtins = __builtins__.__dict__
57*4882a593Smuzhiyun
58*4882a593Smuzhiyunbuiltins['bb'] = bb
59*4882a593Smuzhiyunbuiltins['os'] = os
60*4882a593Smuzhiyun
61*4882a593Smuzhiyunclass TaskBase(event.Event):
62*4882a593Smuzhiyun    """Base class for task events"""
63*4882a593Smuzhiyun
64*4882a593Smuzhiyun    def __init__(self, t, fn, logfile, d):
65*4882a593Smuzhiyun        self._task = t
66*4882a593Smuzhiyun        self._fn = fn
67*4882a593Smuzhiyun        self._package = d.getVar("PF")
68*4882a593Smuzhiyun        self._mc = d.getVar("BB_CURRENT_MC")
69*4882a593Smuzhiyun        self.taskfile = d.getVar("FILE")
70*4882a593Smuzhiyun        self.taskname = self._task
71*4882a593Smuzhiyun        self.logfile = logfile
72*4882a593Smuzhiyun        self.time = time.time()
73*4882a593Smuzhiyun        self.pn = d.getVar("PN")
74*4882a593Smuzhiyun        self.pv = d.getVar("PV")
75*4882a593Smuzhiyun        event.Event.__init__(self)
76*4882a593Smuzhiyun        self._message = "recipe %s: task %s: %s" % (d.getVar("PF"), t, self.getDisplayName())
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun    def getTask(self):
79*4882a593Smuzhiyun        return self._task
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun    def setTask(self, task):
82*4882a593Smuzhiyun        self._task = task
83*4882a593Smuzhiyun
84*4882a593Smuzhiyun    def getDisplayName(self):
85*4882a593Smuzhiyun        return bb.event.getName(self)[4:]
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun    task = property(getTask, setTask, None, "task property")
88*4882a593Smuzhiyun
89*4882a593Smuzhiyunclass TaskStarted(TaskBase):
90*4882a593Smuzhiyun    """Task execution started"""
91*4882a593Smuzhiyun    def __init__(self, t, fn, logfile, taskflags, d):
92*4882a593Smuzhiyun        super(TaskStarted, self).__init__(t, fn, logfile, d)
93*4882a593Smuzhiyun        self.taskflags = taskflags
94*4882a593Smuzhiyun
95*4882a593Smuzhiyunclass TaskSucceeded(TaskBase):
96*4882a593Smuzhiyun    """Task execution completed"""
97*4882a593Smuzhiyun
98*4882a593Smuzhiyunclass TaskFailed(TaskBase):
99*4882a593Smuzhiyun    """Task execution failed"""
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun    def __init__(self, task, fn, logfile, metadata, errprinted = False):
102*4882a593Smuzhiyun        self.errprinted = errprinted
103*4882a593Smuzhiyun        super(TaskFailed, self).__init__(task, fn, logfile, metadata)
104*4882a593Smuzhiyun
105*4882a593Smuzhiyunclass TaskFailedSilent(TaskBase):
106*4882a593Smuzhiyun    """Task execution failed (silently)"""
107*4882a593Smuzhiyun    def getDisplayName(self):
108*4882a593Smuzhiyun        # Don't need to tell the user it was silent
109*4882a593Smuzhiyun        return "Failed"
110*4882a593Smuzhiyun
111*4882a593Smuzhiyunclass TaskInvalid(TaskBase):
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun    def __init__(self, task, fn, metadata):
114*4882a593Smuzhiyun        super(TaskInvalid, self).__init__(task, fn, None, metadata)
115*4882a593Smuzhiyun        self._message = "No such task '%s'" % task
116*4882a593Smuzhiyun
117*4882a593Smuzhiyunclass TaskProgress(event.Event):
118*4882a593Smuzhiyun    """
119*4882a593Smuzhiyun    Task made some progress that could be reported to the user, usually in
120*4882a593Smuzhiyun    the form of a progress bar or similar.
121*4882a593Smuzhiyun    NOTE: this class does not inherit from TaskBase since it doesn't need
122*4882a593Smuzhiyun    to - it's fired within the task context itself, so we don't have any of
123*4882a593Smuzhiyun    the context information that you do in the case of the other events.
124*4882a593Smuzhiyun    The event PID can be used to determine which task it came from.
125*4882a593Smuzhiyun    The progress value is normally 0-100, but can also be negative
126*4882a593Smuzhiyun    indicating that progress has been made but we aren't able to determine
127*4882a593Smuzhiyun    how much.
128*4882a593Smuzhiyun    The rate is optional, this is simply an extra string to display to the
129*4882a593Smuzhiyun    user if specified.
130*4882a593Smuzhiyun    """
131*4882a593Smuzhiyun    def __init__(self, progress, rate=None):
132*4882a593Smuzhiyun        self.progress = progress
133*4882a593Smuzhiyun        self.rate = rate
134*4882a593Smuzhiyun        event.Event.__init__(self)
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun
137*4882a593Smuzhiyunclass LogTee(object):
138*4882a593Smuzhiyun    def __init__(self, logger, outfile):
139*4882a593Smuzhiyun        self.outfile = outfile
140*4882a593Smuzhiyun        self.logger = logger
141*4882a593Smuzhiyun        self.name = self.outfile.name
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun    def write(self, string):
144*4882a593Smuzhiyun        self.logger.plain(string)
145*4882a593Smuzhiyun        self.outfile.write(string)
146*4882a593Smuzhiyun
147*4882a593Smuzhiyun    def __enter__(self):
148*4882a593Smuzhiyun        self.outfile.__enter__()
149*4882a593Smuzhiyun        return self
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun    def __exit__(self, *excinfo):
152*4882a593Smuzhiyun        self.outfile.__exit__(*excinfo)
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun    def __repr__(self):
155*4882a593Smuzhiyun        return '<LogTee {0}>'.format(self.name)
156*4882a593Smuzhiyun
157*4882a593Smuzhiyun    def flush(self):
158*4882a593Smuzhiyun        self.outfile.flush()
159*4882a593Smuzhiyun
160*4882a593Smuzhiyun
161*4882a593Smuzhiyunclass StdoutNoopContextManager:
162*4882a593Smuzhiyun    """
163*4882a593Smuzhiyun    This class acts like sys.stdout, but adds noop __enter__ and __exit__ methods.
164*4882a593Smuzhiyun    """
165*4882a593Smuzhiyun    def __enter__(self):
166*4882a593Smuzhiyun        return sys.stdout
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun    def __exit__(self, *exc_info):
169*4882a593Smuzhiyun        pass
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun    def write(self, string):
172*4882a593Smuzhiyun        return sys.stdout.write(string)
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun    def flush(self):
175*4882a593Smuzhiyun        sys.stdout.flush()
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun    @property
178*4882a593Smuzhiyun    def name(self):
179*4882a593Smuzhiyun        return sys.stdout.name
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun
182*4882a593Smuzhiyundef exec_func(func, d, dirs = None):
183*4882a593Smuzhiyun    """Execute a BB 'function'"""
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun    try:
186*4882a593Smuzhiyun        oldcwd = os.getcwd()
187*4882a593Smuzhiyun    except:
188*4882a593Smuzhiyun        oldcwd = None
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun    flags = d.getVarFlags(func)
191*4882a593Smuzhiyun    cleandirs = flags.get('cleandirs') if flags else None
192*4882a593Smuzhiyun    if cleandirs:
193*4882a593Smuzhiyun        for cdir in d.expand(cleandirs).split():
194*4882a593Smuzhiyun            bb.utils.remove(cdir, True)
195*4882a593Smuzhiyun            bb.utils.mkdirhier(cdir)
196*4882a593Smuzhiyun
197*4882a593Smuzhiyun    if flags and dirs is None:
198*4882a593Smuzhiyun        dirs = flags.get('dirs')
199*4882a593Smuzhiyun        if dirs:
200*4882a593Smuzhiyun            dirs = d.expand(dirs).split()
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun    if dirs:
203*4882a593Smuzhiyun        for adir in dirs:
204*4882a593Smuzhiyun            bb.utils.mkdirhier(adir)
205*4882a593Smuzhiyun        adir = dirs[-1]
206*4882a593Smuzhiyun    else:
207*4882a593Smuzhiyun        adir = None
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun    body = d.getVar(func, False)
210*4882a593Smuzhiyun    if not body:
211*4882a593Smuzhiyun        if body is None:
212*4882a593Smuzhiyun            logger.warning("Function %s doesn't exist", func)
213*4882a593Smuzhiyun        return
214*4882a593Smuzhiyun
215*4882a593Smuzhiyun    ispython = flags.get('python')
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun    lockflag = flags.get('lockfiles')
218*4882a593Smuzhiyun    if lockflag:
219*4882a593Smuzhiyun        lockfiles = [f for f in d.expand(lockflag).split()]
220*4882a593Smuzhiyun    else:
221*4882a593Smuzhiyun        lockfiles = None
222*4882a593Smuzhiyun
223*4882a593Smuzhiyun    tempdir = d.getVar('T')
224*4882a593Smuzhiyun
225*4882a593Smuzhiyun    # or func allows items to be executed outside of the normal
226*4882a593Smuzhiyun    # task set, such as buildhistory
227*4882a593Smuzhiyun    task = d.getVar('BB_RUNTASK') or func
228*4882a593Smuzhiyun    if task == func:
229*4882a593Smuzhiyun        taskfunc = task
230*4882a593Smuzhiyun    else:
231*4882a593Smuzhiyun        taskfunc = "%s.%s" % (task, func)
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun    runfmt = d.getVar('BB_RUNFMT') or "run.{func}.{pid}"
234*4882a593Smuzhiyun    runfn = runfmt.format(taskfunc=taskfunc, task=task, func=func, pid=os.getpid())
235*4882a593Smuzhiyun    runfile = os.path.join(tempdir, runfn)
236*4882a593Smuzhiyun    bb.utils.mkdirhier(os.path.dirname(runfile))
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun    # Setup the courtesy link to the runfn, only for tasks
239*4882a593Smuzhiyun    # we create the link 'just' before the run script is created
240*4882a593Smuzhiyun    # if we create it after, and if the run script fails, then the
241*4882a593Smuzhiyun    # link won't be created as an exception would be fired.
242*4882a593Smuzhiyun    if task == func:
243*4882a593Smuzhiyun        runlink = os.path.join(tempdir, 'run.{0}'.format(task))
244*4882a593Smuzhiyun        if runlink:
245*4882a593Smuzhiyun            bb.utils.remove(runlink)
246*4882a593Smuzhiyun
247*4882a593Smuzhiyun            try:
248*4882a593Smuzhiyun                os.symlink(runfn, runlink)
249*4882a593Smuzhiyun            except OSError:
250*4882a593Smuzhiyun                pass
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun    with bb.utils.fileslocked(lockfiles):
253*4882a593Smuzhiyun        if ispython:
254*4882a593Smuzhiyun            exec_func_python(func, d, runfile, cwd=adir)
255*4882a593Smuzhiyun        else:
256*4882a593Smuzhiyun            exec_func_shell(func, d, runfile, cwd=adir)
257*4882a593Smuzhiyun
258*4882a593Smuzhiyun    try:
259*4882a593Smuzhiyun        curcwd = os.getcwd()
260*4882a593Smuzhiyun    except:
261*4882a593Smuzhiyun        curcwd = None
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun    if oldcwd and curcwd != oldcwd:
264*4882a593Smuzhiyun        try:
265*4882a593Smuzhiyun            bb.warn("Task %s changed cwd to %s" % (func, curcwd))
266*4882a593Smuzhiyun            os.chdir(oldcwd)
267*4882a593Smuzhiyun        except:
268*4882a593Smuzhiyun            pass
269*4882a593Smuzhiyun
270*4882a593Smuzhiyun_functionfmt = """
271*4882a593Smuzhiyun{function}(d)
272*4882a593Smuzhiyun"""
273*4882a593Smuzhiyunlogformatter = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
274*4882a593Smuzhiyundef exec_func_python(func, d, runfile, cwd=None):
275*4882a593Smuzhiyun    """Execute a python BB 'function'"""
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun    code = _functionfmt.format(function=func)
278*4882a593Smuzhiyun    bb.utils.mkdirhier(os.path.dirname(runfile))
279*4882a593Smuzhiyun    with open(runfile, 'w') as script:
280*4882a593Smuzhiyun        bb.data.emit_func_python(func, script, d)
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun    if cwd:
283*4882a593Smuzhiyun        try:
284*4882a593Smuzhiyun            olddir = os.getcwd()
285*4882a593Smuzhiyun        except OSError as e:
286*4882a593Smuzhiyun            bb.warn("%s: Cannot get cwd: %s" % (func, e))
287*4882a593Smuzhiyun            olddir = None
288*4882a593Smuzhiyun        os.chdir(cwd)
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun    bb.debug(2, "Executing python function %s" % func)
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun    try:
293*4882a593Smuzhiyun        text = "def %s(d):\n%s" % (func, d.getVar(func, False))
294*4882a593Smuzhiyun        fn = d.getVarFlag(func, "filename", False)
295*4882a593Smuzhiyun        lineno = int(d.getVarFlag(func, "lineno", False))
296*4882a593Smuzhiyun        bb.methodpool.insert_method(func, text, fn, lineno - 1)
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun        comp = utils.better_compile(code, func, "exec_func_python() autogenerated")
299*4882a593Smuzhiyun        utils.better_exec(comp, {"d": d}, code, "exec_func_python() autogenerated")
300*4882a593Smuzhiyun    finally:
301*4882a593Smuzhiyun        # We want any stdout/stderr to be printed before any other log messages to make debugging
302*4882a593Smuzhiyun        # more accurate. In some cases we seem to lose stdout/stderr entirely in logging tests without this.
303*4882a593Smuzhiyun        sys.stdout.flush()
304*4882a593Smuzhiyun        sys.stderr.flush()
305*4882a593Smuzhiyun        bb.debug(2, "Python function %s finished" % func)
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun        if cwd and olddir:
308*4882a593Smuzhiyun            try:
309*4882a593Smuzhiyun                os.chdir(olddir)
310*4882a593Smuzhiyun            except OSError as e:
311*4882a593Smuzhiyun                bb.warn("%s: Cannot restore cwd %s: %s" % (func, olddir, e))
312*4882a593Smuzhiyun
313*4882a593Smuzhiyundef shell_trap_code():
314*4882a593Smuzhiyun    return '''#!/bin/sh\n
315*4882a593Smuzhiyun__BITBAKE_LAST_LINE=0
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun# Emit a useful diagnostic if something fails:
318*4882a593Smuzhiyunbb_sh_exit_handler() {
319*4882a593Smuzhiyun    ret=$?
320*4882a593Smuzhiyun    if [ "$ret" != 0 ]; then
321*4882a593Smuzhiyun        echo "WARNING: exit code $ret from a shell command."
322*4882a593Smuzhiyun    fi
323*4882a593Smuzhiyun    exit $ret
324*4882a593Smuzhiyun}
325*4882a593Smuzhiyun
326*4882a593Smuzhiyunbb_bash_exit_handler() {
327*4882a593Smuzhiyun    ret=$?
328*4882a593Smuzhiyun    { set +x; } > /dev/null
329*4882a593Smuzhiyun    trap "" DEBUG
330*4882a593Smuzhiyun    if [ "$ret" != 0 ]; then
331*4882a593Smuzhiyun        echo "WARNING: ${BASH_SOURCE[0]}:${__BITBAKE_LAST_LINE} exit $ret from '$1'"
332*4882a593Smuzhiyun
333*4882a593Smuzhiyun        echo "WARNING: Backtrace (BB generated script): "
334*4882a593Smuzhiyun        for i in $(seq 1 $((${#FUNCNAME[@]} - 1))); do
335*4882a593Smuzhiyun            if [ "$i" -eq 1 ]; then
336*4882a593Smuzhiyun                echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${__BITBAKE_LAST_LINE}"
337*4882a593Smuzhiyun            else
338*4882a593Smuzhiyun                echo -e "\t#$((i)): ${FUNCNAME[$i]}, ${BASH_SOURCE[$((i-1))]}, line ${BASH_LINENO[$((i-1))]}"
339*4882a593Smuzhiyun            fi
340*4882a593Smuzhiyun        done
341*4882a593Smuzhiyun    fi
342*4882a593Smuzhiyun    exit $ret
343*4882a593Smuzhiyun}
344*4882a593Smuzhiyun
345*4882a593Smuzhiyunbb_bash_debug_handler() {
346*4882a593Smuzhiyun    local line=${BASH_LINENO[0]}
347*4882a593Smuzhiyun    # For some reason the DEBUG trap trips with lineno=1 when scripts exit; ignore it
348*4882a593Smuzhiyun    if [ "$line" -eq 1 ]; then
349*4882a593Smuzhiyun        return
350*4882a593Smuzhiyun    fi
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun    # Track the line number of commands as they execute. This is so we can have access to the failing line number
353*4882a593Smuzhiyun    # in the EXIT trap. See http://gnu-bash.2382.n7.nabble.com/trap-echo-quot-trap-exit-on-LINENO-quot-EXIT-gt-wrong-linenumber-td3666.html
354*4882a593Smuzhiyun    if [ "${FUNCNAME[1]}" != "bb_bash_exit_handler" ]; then
355*4882a593Smuzhiyun        __BITBAKE_LAST_LINE=$line
356*4882a593Smuzhiyun    fi
357*4882a593Smuzhiyun}
358*4882a593Smuzhiyun
359*4882a593Smuzhiyuncase $BASH_VERSION in
360*4882a593Smuzhiyun"") trap 'bb_sh_exit_handler' 0
361*4882a593Smuzhiyun    set -e
362*4882a593Smuzhiyun    ;;
363*4882a593Smuzhiyun*)  trap 'bb_bash_exit_handler "$BASH_COMMAND"' 0
364*4882a593Smuzhiyun    trap '{ bb_bash_debug_handler; } 2>/dev/null' DEBUG
365*4882a593Smuzhiyun    set -e
366*4882a593Smuzhiyun    shopt -s extdebug
367*4882a593Smuzhiyun    ;;
368*4882a593Smuzhiyunesac
369*4882a593Smuzhiyun'''
370*4882a593Smuzhiyun
371*4882a593Smuzhiyundef create_progress_handler(func, progress, logfile, d):
372*4882a593Smuzhiyun    if progress == 'percent':
373*4882a593Smuzhiyun        # Use default regex
374*4882a593Smuzhiyun        return bb.progress.BasicProgressHandler(d, outfile=logfile)
375*4882a593Smuzhiyun    elif progress.startswith('percent:'):
376*4882a593Smuzhiyun        # Use specified regex
377*4882a593Smuzhiyun        return bb.progress.BasicProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
378*4882a593Smuzhiyun    elif progress.startswith('outof:'):
379*4882a593Smuzhiyun        # Use specified regex
380*4882a593Smuzhiyun        return bb.progress.OutOfProgressHandler(d, regex=progress.split(':', 1)[1], outfile=logfile)
381*4882a593Smuzhiyun    elif progress.startswith("custom:"):
382*4882a593Smuzhiyun        # Use a custom progress handler that was injected via OE_EXTRA_IMPORTS or __builtins__
383*4882a593Smuzhiyun        import functools
384*4882a593Smuzhiyun        from types import ModuleType
385*4882a593Smuzhiyun
386*4882a593Smuzhiyun        parts = progress.split(":", 2)
387*4882a593Smuzhiyun        _, cls, otherargs = parts[0], parts[1], (parts[2] or None) if parts[2:] else None
388*4882a593Smuzhiyun        if cls:
389*4882a593Smuzhiyun            def resolve(x, y):
390*4882a593Smuzhiyun                if not x:
391*4882a593Smuzhiyun                    return None
392*4882a593Smuzhiyun                if isinstance(x, ModuleType):
393*4882a593Smuzhiyun                    return getattr(x, y, None)
394*4882a593Smuzhiyun                return x.get(y)
395*4882a593Smuzhiyun            cls_obj = functools.reduce(resolve, cls.split("."), bb.utils._context)
396*4882a593Smuzhiyun            if not cls_obj:
397*4882a593Smuzhiyun                # Fall-back on __builtins__
398*4882a593Smuzhiyun                cls_obj = functools.reduce(resolve, cls.split("."), __builtins__)
399*4882a593Smuzhiyun            if cls_obj:
400*4882a593Smuzhiyun                return cls_obj(d, outfile=logfile, otherargs=otherargs)
401*4882a593Smuzhiyun            bb.warn('%s: unknown custom progress handler in task progress varflag value "%s", ignoring' % (func, cls))
402*4882a593Smuzhiyun    else:
403*4882a593Smuzhiyun        bb.warn('%s: invalid task progress varflag value "%s", ignoring' % (func, progress))
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun    return logfile
406*4882a593Smuzhiyun
407*4882a593Smuzhiyundef exec_func_shell(func, d, runfile, cwd=None):
408*4882a593Smuzhiyun    """Execute a shell function from the metadata
409*4882a593Smuzhiyun
410*4882a593Smuzhiyun    Note on directory behavior.  The 'dirs' varflag should contain a list
411*4882a593Smuzhiyun    of the directories you need created prior to execution.  The last
412*4882a593Smuzhiyun    item in the list is where we will chdir/cd to.
413*4882a593Smuzhiyun    """
414*4882a593Smuzhiyun
415*4882a593Smuzhiyun    # Don't let the emitted shell script override PWD
416*4882a593Smuzhiyun    d.delVarFlag('PWD', 'export')
417*4882a593Smuzhiyun
418*4882a593Smuzhiyun    with open(runfile, 'w') as script:
419*4882a593Smuzhiyun        script.write(shell_trap_code())
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun        bb.data.emit_func(func, script, d)
422*4882a593Smuzhiyun
423*4882a593Smuzhiyun        if verboseShellLogging or bb.utils.to_boolean(d.getVar("BB_VERBOSE_LOGS", False)):
424*4882a593Smuzhiyun            script.write("set -x\n")
425*4882a593Smuzhiyun        if cwd:
426*4882a593Smuzhiyun            script.write("cd '%s'\n" % cwd)
427*4882a593Smuzhiyun        script.write("%s\n" % func)
428*4882a593Smuzhiyun        script.write('''
429*4882a593Smuzhiyun# cleanup
430*4882a593Smuzhiyunret=$?
431*4882a593Smuzhiyuntrap '' 0
432*4882a593Smuzhiyunexit $ret
433*4882a593Smuzhiyun''')
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun    os.chmod(runfile, 0o775)
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun    cmd = runfile
438*4882a593Smuzhiyun    if d.getVarFlag(func, 'fakeroot', False):
439*4882a593Smuzhiyun        fakerootcmd = d.getVar('FAKEROOT')
440*4882a593Smuzhiyun        if fakerootcmd:
441*4882a593Smuzhiyun            cmd = [fakerootcmd, runfile]
442*4882a593Smuzhiyun
443*4882a593Smuzhiyun    if verboseStdoutLogging:
444*4882a593Smuzhiyun        logfile = LogTee(logger, StdoutNoopContextManager())
445*4882a593Smuzhiyun    else:
446*4882a593Smuzhiyun        logfile = StdoutNoopContextManager()
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun    progress = d.getVarFlag(func, 'progress')
449*4882a593Smuzhiyun    if progress:
450*4882a593Smuzhiyun        try:
451*4882a593Smuzhiyun            logfile = create_progress_handler(func, progress, logfile, d)
452*4882a593Smuzhiyun        except:
453*4882a593Smuzhiyun            from traceback import format_exc
454*4882a593Smuzhiyun            logger.error("Failed to create progress handler")
455*4882a593Smuzhiyun            logger.error(format_exc())
456*4882a593Smuzhiyun            raise
457*4882a593Smuzhiyun
458*4882a593Smuzhiyun    fifobuffer = bytearray()
459*4882a593Smuzhiyun    def readfifo(data):
460*4882a593Smuzhiyun        nonlocal fifobuffer
461*4882a593Smuzhiyun        fifobuffer.extend(data)
462*4882a593Smuzhiyun        while fifobuffer:
463*4882a593Smuzhiyun            message, token, nextmsg = fifobuffer.partition(b"\00")
464*4882a593Smuzhiyun            if token:
465*4882a593Smuzhiyun                splitval = message.split(b' ', 1)
466*4882a593Smuzhiyun                cmd = splitval[0].decode("utf-8")
467*4882a593Smuzhiyun                if len(splitval) > 1:
468*4882a593Smuzhiyun                    value = splitval[1].decode("utf-8")
469*4882a593Smuzhiyun                else:
470*4882a593Smuzhiyun                    value = ''
471*4882a593Smuzhiyun                if cmd == 'bbplain':
472*4882a593Smuzhiyun                    bb.plain(value)
473*4882a593Smuzhiyun                elif cmd == 'bbnote':
474*4882a593Smuzhiyun                    bb.note(value)
475*4882a593Smuzhiyun                elif cmd == 'bbverbnote':
476*4882a593Smuzhiyun                    bb.verbnote(value)
477*4882a593Smuzhiyun                elif cmd == 'bbwarn':
478*4882a593Smuzhiyun                    bb.warn(value)
479*4882a593Smuzhiyun                elif cmd == 'bberror':
480*4882a593Smuzhiyun                    bb.error(value)
481*4882a593Smuzhiyun                elif cmd == 'bbfatal':
482*4882a593Smuzhiyun                    # The caller will call exit themselves, so bb.error() is
483*4882a593Smuzhiyun                    # what we want here rather than bb.fatal()
484*4882a593Smuzhiyun                    bb.error(value)
485*4882a593Smuzhiyun                elif cmd == 'bbfatal_log':
486*4882a593Smuzhiyun                    bb.error(value, forcelog=True)
487*4882a593Smuzhiyun                elif cmd == 'bbdebug':
488*4882a593Smuzhiyun                    splitval = value.split(' ', 1)
489*4882a593Smuzhiyun                    level = int(splitval[0])
490*4882a593Smuzhiyun                    value = splitval[1]
491*4882a593Smuzhiyun                    bb.debug(level, value)
492*4882a593Smuzhiyun                else:
493*4882a593Smuzhiyun                    bb.warn("Unrecognised command '%s' on FIFO" % cmd)
494*4882a593Smuzhiyun                fifobuffer = nextmsg
495*4882a593Smuzhiyun            else:
496*4882a593Smuzhiyun                break
497*4882a593Smuzhiyun
498*4882a593Smuzhiyun    tempdir = d.getVar('T')
499*4882a593Smuzhiyun    fifopath = os.path.join(tempdir, 'fifo.%s' % os.getpid())
500*4882a593Smuzhiyun    if os.path.exists(fifopath):
501*4882a593Smuzhiyun        os.unlink(fifopath)
502*4882a593Smuzhiyun    os.mkfifo(fifopath)
503*4882a593Smuzhiyun    with open(fifopath, 'r+b', buffering=0) as fifo:
504*4882a593Smuzhiyun        try:
505*4882a593Smuzhiyun            bb.debug(2, "Executing shell function %s" % func)
506*4882a593Smuzhiyun            with open(os.devnull, 'r+') as stdin, logfile:
507*4882a593Smuzhiyun                bb.process.run(cmd, shell=False, stdin=stdin, log=logfile, extrafiles=[(fifo,readfifo)])
508*4882a593Smuzhiyun        except bb.process.ExecutionError as exe:
509*4882a593Smuzhiyun            # Find the backtrace that the shell trap generated
510*4882a593Smuzhiyun            backtrace_marker_regex = re.compile(r"WARNING: Backtrace \(BB generated script\)")
511*4882a593Smuzhiyun            stdout_lines = (exe.stdout or "").split("\n")
512*4882a593Smuzhiyun            backtrace_start_line = None
513*4882a593Smuzhiyun            for i, line in enumerate(reversed(stdout_lines)):
514*4882a593Smuzhiyun                if backtrace_marker_regex.search(line):
515*4882a593Smuzhiyun                    backtrace_start_line = len(stdout_lines) - i
516*4882a593Smuzhiyun                    break
517*4882a593Smuzhiyun
518*4882a593Smuzhiyun            # Read the backtrace frames, starting at the location we just found
519*4882a593Smuzhiyun            backtrace_entry_regex = re.compile(r"#(?P<frameno>\d+): (?P<funcname>[^\s]+), (?P<file>.+?), line ("
520*4882a593Smuzhiyun                                               r"?P<lineno>\d+)")
521*4882a593Smuzhiyun            backtrace_frames = []
522*4882a593Smuzhiyun            if backtrace_start_line:
523*4882a593Smuzhiyun                for line in itertools.islice(stdout_lines, backtrace_start_line, None):
524*4882a593Smuzhiyun                    match = backtrace_entry_regex.search(line)
525*4882a593Smuzhiyun                    if match:
526*4882a593Smuzhiyun                        backtrace_frames.append(match.groupdict())
527*4882a593Smuzhiyun
528*4882a593Smuzhiyun            with open(runfile, "r") as script:
529*4882a593Smuzhiyun                script_lines = [line.rstrip() for line in script.readlines()]
530*4882a593Smuzhiyun
531*4882a593Smuzhiyun            # For each backtrace frame, search backwards in the script (from the line number called out by the frame),
532*4882a593Smuzhiyun            # to find the comment that emit_vars injected when it wrote the script. This will give us the metadata
533*4882a593Smuzhiyun            # filename (e.g. .bb or .bbclass) and line number where the shell function was originally defined.
534*4882a593Smuzhiyun            script_metadata_comment_regex = re.compile(r"# line: (?P<lineno>\d+), file: (?P<file>.+)")
535*4882a593Smuzhiyun            better_frames = []
536*4882a593Smuzhiyun            # Skip the very last frame since it's just the call to the shell task in the body of the script
537*4882a593Smuzhiyun            for frame in backtrace_frames[:-1]:
538*4882a593Smuzhiyun                # Check whether the frame corresponds to a function defined in the script vs external script.
539*4882a593Smuzhiyun                if os.path.samefile(frame["file"], runfile):
540*4882a593Smuzhiyun                    # Search backwards from the frame lineno to locate the comment that BB injected
541*4882a593Smuzhiyun                    i = int(frame["lineno"]) - 1
542*4882a593Smuzhiyun                    while i >= 0:
543*4882a593Smuzhiyun                        match = script_metadata_comment_regex.match(script_lines[i])
544*4882a593Smuzhiyun                        if match:
545*4882a593Smuzhiyun                            # Calculate the relative line in the function itself
546*4882a593Smuzhiyun                            relative_line_in_function = int(frame["lineno"]) - i - 2
547*4882a593Smuzhiyun                            # Calculate line in the function as declared in the metadata
548*4882a593Smuzhiyun                            metadata_function_line = relative_line_in_function + int(match["lineno"])
549*4882a593Smuzhiyun                            better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(
550*4882a593Smuzhiyun                                frameno=frame["frameno"],
551*4882a593Smuzhiyun                                funcname=frame["funcname"],
552*4882a593Smuzhiyun                                file=match["file"],
553*4882a593Smuzhiyun                                lineno=metadata_function_line
554*4882a593Smuzhiyun                            ))
555*4882a593Smuzhiyun                            break
556*4882a593Smuzhiyun                        i -= 1
557*4882a593Smuzhiyun                else:
558*4882a593Smuzhiyun                    better_frames.append("#{frameno}: {funcname}, {file}, line {lineno}".format(**frame))
559*4882a593Smuzhiyun
560*4882a593Smuzhiyun            if better_frames:
561*4882a593Smuzhiyun                better_frames = ("\t{0}".format(frame) for frame in better_frames)
562*4882a593Smuzhiyun                exe.extra_message = "\nBacktrace (metadata-relative locations):\n{0}".format("\n".join(better_frames))
563*4882a593Smuzhiyun            raise
564*4882a593Smuzhiyun        finally:
565*4882a593Smuzhiyun            os.unlink(fifopath)
566*4882a593Smuzhiyun
567*4882a593Smuzhiyun    bb.debug(2, "Shell function %s finished" % func)
568*4882a593Smuzhiyun
569*4882a593Smuzhiyundef _task_data(fn, task, d):
570*4882a593Smuzhiyun    localdata = bb.data.createCopy(d)
571*4882a593Smuzhiyun    localdata.setVar('BB_FILENAME', fn)
572*4882a593Smuzhiyun    localdata.setVar('OVERRIDES', 'task-%s:%s' %
573*4882a593Smuzhiyun                     (task[3:].replace('_', '-'), d.getVar('OVERRIDES', False)))
574*4882a593Smuzhiyun    localdata.finalize()
575*4882a593Smuzhiyun    bb.data.expandKeys(localdata)
576*4882a593Smuzhiyun    return localdata
577*4882a593Smuzhiyun
578*4882a593Smuzhiyundef _exec_task(fn, task, d, quieterr):
579*4882a593Smuzhiyun    """Execute a BB 'task'
580*4882a593Smuzhiyun
581*4882a593Smuzhiyun    Execution of a task involves a bit more setup than executing a function,
582*4882a593Smuzhiyun    running it with its own local metadata, and with some useful variables set.
583*4882a593Smuzhiyun    """
584*4882a593Smuzhiyun    if not d.getVarFlag(task, 'task', False):
585*4882a593Smuzhiyun        event.fire(TaskInvalid(task, fn, d), d)
586*4882a593Smuzhiyun        logger.error("No such task: %s" % task)
587*4882a593Smuzhiyun        return 1
588*4882a593Smuzhiyun
589*4882a593Smuzhiyun    logger.debug("Executing task %s", task)
590*4882a593Smuzhiyun
591*4882a593Smuzhiyun    localdata = _task_data(fn, task, d)
592*4882a593Smuzhiyun    tempdir = localdata.getVar('T')
593*4882a593Smuzhiyun    if not tempdir:
594*4882a593Smuzhiyun        bb.fatal("T variable not set, unable to build")
595*4882a593Smuzhiyun
596*4882a593Smuzhiyun    # Change nice level if we're asked to
597*4882a593Smuzhiyun    nice = localdata.getVar("BB_TASK_NICE_LEVEL")
598*4882a593Smuzhiyun    if nice:
599*4882a593Smuzhiyun        curnice = os.nice(0)
600*4882a593Smuzhiyun        nice = int(nice) - curnice
601*4882a593Smuzhiyun        newnice = os.nice(nice)
602*4882a593Smuzhiyun        logger.debug("Renice to %s " % newnice)
603*4882a593Smuzhiyun    ionice = localdata.getVar("BB_TASK_IONICE_LEVEL")
604*4882a593Smuzhiyun    if ionice:
605*4882a593Smuzhiyun        try:
606*4882a593Smuzhiyun            cls, prio = ionice.split(".", 1)
607*4882a593Smuzhiyun            bb.utils.ioprio_set(os.getpid(), int(cls), int(prio))
608*4882a593Smuzhiyun        except:
609*4882a593Smuzhiyun            bb.warn("Invalid ionice level %s" % ionice)
610*4882a593Smuzhiyun
611*4882a593Smuzhiyun    bb.utils.mkdirhier(tempdir)
612*4882a593Smuzhiyun
613*4882a593Smuzhiyun    # Determine the logfile to generate
614*4882a593Smuzhiyun    logfmt = localdata.getVar('BB_LOGFMT') or 'log.{task}.{pid}'
615*4882a593Smuzhiyun    logbase = logfmt.format(task=task, pid=os.getpid())
616*4882a593Smuzhiyun
617*4882a593Smuzhiyun    # Document the order of the tasks...
618*4882a593Smuzhiyun    logorder = os.path.join(tempdir, 'log.task_order')
619*4882a593Smuzhiyun    try:
620*4882a593Smuzhiyun        with open(logorder, 'a') as logorderfile:
621*4882a593Smuzhiyun            logorderfile.write('{0} ({1}): {2}\n'.format(task, os.getpid(), logbase))
622*4882a593Smuzhiyun    except OSError:
623*4882a593Smuzhiyun        logger.exception("Opening log file '%s'", logorder)
624*4882a593Smuzhiyun        pass
625*4882a593Smuzhiyun
626*4882a593Smuzhiyun    # Setup the courtesy link to the logfn
627*4882a593Smuzhiyun    loglink = os.path.join(tempdir, 'log.{0}'.format(task))
628*4882a593Smuzhiyun    logfn = os.path.join(tempdir, logbase)
629*4882a593Smuzhiyun    if loglink:
630*4882a593Smuzhiyun        bb.utils.remove(loglink)
631*4882a593Smuzhiyun
632*4882a593Smuzhiyun        try:
633*4882a593Smuzhiyun           os.symlink(logbase, loglink)
634*4882a593Smuzhiyun        except OSError:
635*4882a593Smuzhiyun           pass
636*4882a593Smuzhiyun
637*4882a593Smuzhiyun    prefuncs = localdata.getVarFlag(task, 'prefuncs', expand=True)
638*4882a593Smuzhiyun    postfuncs = localdata.getVarFlag(task, 'postfuncs', expand=True)
639*4882a593Smuzhiyun
640*4882a593Smuzhiyun    class ErrorCheckHandler(logging.Handler):
641*4882a593Smuzhiyun        def __init__(self):
642*4882a593Smuzhiyun            self.triggered = False
643*4882a593Smuzhiyun            logging.Handler.__init__(self, logging.ERROR)
644*4882a593Smuzhiyun        def emit(self, record):
645*4882a593Smuzhiyun            if getattr(record, 'forcelog', False):
646*4882a593Smuzhiyun                self.triggered = False
647*4882a593Smuzhiyun            else:
648*4882a593Smuzhiyun                self.triggered = True
649*4882a593Smuzhiyun
650*4882a593Smuzhiyun    # Handle logfiles
651*4882a593Smuzhiyun    try:
652*4882a593Smuzhiyun        bb.utils.mkdirhier(os.path.dirname(logfn))
653*4882a593Smuzhiyun        logfile = open(logfn, 'w')
654*4882a593Smuzhiyun    except OSError:
655*4882a593Smuzhiyun        logger.exception("Opening log file '%s'", logfn)
656*4882a593Smuzhiyun        pass
657*4882a593Smuzhiyun
658*4882a593Smuzhiyun    # Dup the existing fds so we dont lose them
659*4882a593Smuzhiyun    osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
660*4882a593Smuzhiyun    oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
661*4882a593Smuzhiyun    ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
662*4882a593Smuzhiyun
663*4882a593Smuzhiyun    # Replace those fds with our own
664*4882a593Smuzhiyun    with open('/dev/null', 'r') as si:
665*4882a593Smuzhiyun        os.dup2(si.fileno(), osi[1])
666*4882a593Smuzhiyun    os.dup2(logfile.fileno(), oso[1])
667*4882a593Smuzhiyun    os.dup2(logfile.fileno(), ose[1])
668*4882a593Smuzhiyun
669*4882a593Smuzhiyun    # Ensure Python logging goes to the logfile
670*4882a593Smuzhiyun    handler = logging.StreamHandler(logfile)
671*4882a593Smuzhiyun    handler.setFormatter(logformatter)
672*4882a593Smuzhiyun    # Always enable full debug output into task logfiles
673*4882a593Smuzhiyun    handler.setLevel(logging.DEBUG - 2)
674*4882a593Smuzhiyun    bblogger.addHandler(handler)
675*4882a593Smuzhiyun
676*4882a593Smuzhiyun    errchk = ErrorCheckHandler()
677*4882a593Smuzhiyun    bblogger.addHandler(errchk)
678*4882a593Smuzhiyun
679*4882a593Smuzhiyun    localdata.setVar('BB_LOGFILE', logfn)
680*4882a593Smuzhiyun    localdata.setVar('BB_RUNTASK', task)
681*4882a593Smuzhiyun    localdata.setVar('BB_TASK_LOGGER', bblogger)
682*4882a593Smuzhiyun
683*4882a593Smuzhiyun    flags = localdata.getVarFlags(task)
684*4882a593Smuzhiyun
685*4882a593Smuzhiyun    try:
686*4882a593Smuzhiyun        try:
687*4882a593Smuzhiyun            event.fire(TaskStarted(task, fn, logfn, flags, localdata), localdata)
688*4882a593Smuzhiyun
689*4882a593Smuzhiyun            for func in (prefuncs or '').split():
690*4882a593Smuzhiyun                exec_func(func, localdata)
691*4882a593Smuzhiyun            exec_func(task, localdata)
692*4882a593Smuzhiyun            for func in (postfuncs or '').split():
693*4882a593Smuzhiyun                exec_func(func, localdata)
694*4882a593Smuzhiyun        finally:
695*4882a593Smuzhiyun            # Need to flush and close the logs before sending events where the
696*4882a593Smuzhiyun            # UI may try to look at the logs.
697*4882a593Smuzhiyun            sys.stdout.flush()
698*4882a593Smuzhiyun            sys.stderr.flush()
699*4882a593Smuzhiyun
700*4882a593Smuzhiyun            bblogger.removeHandler(handler)
701*4882a593Smuzhiyun
702*4882a593Smuzhiyun            # Restore the backup fds
703*4882a593Smuzhiyun            os.dup2(osi[0], osi[1])
704*4882a593Smuzhiyun            os.dup2(oso[0], oso[1])
705*4882a593Smuzhiyun            os.dup2(ose[0], ose[1])
706*4882a593Smuzhiyun
707*4882a593Smuzhiyun            # Close the backup fds
708*4882a593Smuzhiyun            os.close(osi[0])
709*4882a593Smuzhiyun            os.close(oso[0])
710*4882a593Smuzhiyun            os.close(ose[0])
711*4882a593Smuzhiyun
712*4882a593Smuzhiyun            logfile.close()
713*4882a593Smuzhiyun            if os.path.exists(logfn) and os.path.getsize(logfn) == 0:
714*4882a593Smuzhiyun                logger.debug2("Zero size logfn %s, removing", logfn)
715*4882a593Smuzhiyun                bb.utils.remove(logfn)
716*4882a593Smuzhiyun                bb.utils.remove(loglink)
717*4882a593Smuzhiyun    except (Exception, SystemExit) as exc:
718*4882a593Smuzhiyun        handled = False
719*4882a593Smuzhiyun        if isinstance(exc, bb.BBHandledException):
720*4882a593Smuzhiyun            handled = True
721*4882a593Smuzhiyun
722*4882a593Smuzhiyun        if quieterr:
723*4882a593Smuzhiyun            if not handled:
724*4882a593Smuzhiyun                logger.warning(repr(exc))
725*4882a593Smuzhiyun            event.fire(TaskFailedSilent(task, fn, logfn, localdata), localdata)
726*4882a593Smuzhiyun        else:
727*4882a593Smuzhiyun            errprinted = errchk.triggered
728*4882a593Smuzhiyun            # If the output is already on stdout, we've printed the information in the
729*4882a593Smuzhiyun            # logs once already so don't duplicate
730*4882a593Smuzhiyun            if verboseStdoutLogging or handled:
731*4882a593Smuzhiyun                errprinted = True
732*4882a593Smuzhiyun            if not handled:
733*4882a593Smuzhiyun                logger.error(repr(exc))
734*4882a593Smuzhiyun            event.fire(TaskFailed(task, fn, logfn, localdata, errprinted), localdata)
735*4882a593Smuzhiyun        return 1
736*4882a593Smuzhiyun
737*4882a593Smuzhiyun    event.fire(TaskSucceeded(task, fn, logfn, localdata), localdata)
738*4882a593Smuzhiyun
739*4882a593Smuzhiyun    if not localdata.getVarFlag(task, 'nostamp', False) and not localdata.getVarFlag(task, 'selfstamp', False):
740*4882a593Smuzhiyun        make_stamp(task, localdata)
741*4882a593Smuzhiyun
742*4882a593Smuzhiyun    return 0
743*4882a593Smuzhiyun
744*4882a593Smuzhiyundef exec_task(fn, task, d, profile = False):
745*4882a593Smuzhiyun    try:
746*4882a593Smuzhiyun        quieterr = False
747*4882a593Smuzhiyun        if d.getVarFlag(task, "quieterrors", False) is not None:
748*4882a593Smuzhiyun            quieterr = True
749*4882a593Smuzhiyun
750*4882a593Smuzhiyun        if profile:
751*4882a593Smuzhiyun            profname = "profile-%s.log" % (d.getVar("PN") + "-" + task)
752*4882a593Smuzhiyun            try:
753*4882a593Smuzhiyun                import cProfile as profile
754*4882a593Smuzhiyun            except:
755*4882a593Smuzhiyun                import profile
756*4882a593Smuzhiyun            prof = profile.Profile()
757*4882a593Smuzhiyun            ret = profile.Profile.runcall(prof, _exec_task, fn, task, d, quieterr)
758*4882a593Smuzhiyun            prof.dump_stats(profname)
759*4882a593Smuzhiyun            bb.utils.process_profilelog(profname)
760*4882a593Smuzhiyun
761*4882a593Smuzhiyun            return ret
762*4882a593Smuzhiyun        else:
763*4882a593Smuzhiyun            return _exec_task(fn, task, d, quieterr)
764*4882a593Smuzhiyun
765*4882a593Smuzhiyun    except Exception:
766*4882a593Smuzhiyun        from traceback import format_exc
767*4882a593Smuzhiyun        if not quieterr:
768*4882a593Smuzhiyun            logger.error("Build of %s failed" % (task))
769*4882a593Smuzhiyun            logger.error(format_exc())
770*4882a593Smuzhiyun            failedevent = TaskFailed(task, None, d, True)
771*4882a593Smuzhiyun            event.fire(failedevent, d)
772*4882a593Smuzhiyun        return 1
773*4882a593Smuzhiyun
774*4882a593Smuzhiyundef stamp_internal(taskname, d, file_name, baseonly=False, noextra=False):
775*4882a593Smuzhiyun    """
776*4882a593Smuzhiyun    Internal stamp helper function
777*4882a593Smuzhiyun    Makes sure the stamp directory exists
778*4882a593Smuzhiyun    Returns the stamp path+filename
779*4882a593Smuzhiyun
780*4882a593Smuzhiyun    In the bitbake core, d can be a CacheData and file_name will be set.
781*4882a593Smuzhiyun    When called in task context, d will be a data store, file_name will not be set
782*4882a593Smuzhiyun    """
783*4882a593Smuzhiyun    taskflagname = taskname
784*4882a593Smuzhiyun    if taskname.endswith("_setscene") and taskname != "do_setscene":
785*4882a593Smuzhiyun        taskflagname = taskname.replace("_setscene", "")
786*4882a593Smuzhiyun
787*4882a593Smuzhiyun    if file_name:
788*4882a593Smuzhiyun        stamp = d.stamp[file_name]
789*4882a593Smuzhiyun        extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
790*4882a593Smuzhiyun    else:
791*4882a593Smuzhiyun        stamp = d.getVar('STAMP')
792*4882a593Smuzhiyun        file_name = d.getVar('BB_FILENAME')
793*4882a593Smuzhiyun        extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
794*4882a593Smuzhiyun
795*4882a593Smuzhiyun    if baseonly:
796*4882a593Smuzhiyun        return stamp
797*4882a593Smuzhiyun    if noextra:
798*4882a593Smuzhiyun        extrainfo = ""
799*4882a593Smuzhiyun
800*4882a593Smuzhiyun    if not stamp:
801*4882a593Smuzhiyun        return
802*4882a593Smuzhiyun
803*4882a593Smuzhiyun    stamp = bb.parse.siggen.stampfile(stamp, file_name, taskname, extrainfo)
804*4882a593Smuzhiyun
805*4882a593Smuzhiyun    stampdir = os.path.dirname(stamp)
806*4882a593Smuzhiyun    if cached_mtime_noerror(stampdir) == 0:
807*4882a593Smuzhiyun        bb.utils.mkdirhier(stampdir)
808*4882a593Smuzhiyun
809*4882a593Smuzhiyun    return stamp
810*4882a593Smuzhiyun
811*4882a593Smuzhiyundef stamp_cleanmask_internal(taskname, d, file_name):
812*4882a593Smuzhiyun    """
813*4882a593Smuzhiyun    Internal stamp helper function to generate stamp cleaning mask
814*4882a593Smuzhiyun    Returns the stamp path+filename
815*4882a593Smuzhiyun
816*4882a593Smuzhiyun    In the bitbake core, d can be a CacheData and file_name will be set.
817*4882a593Smuzhiyun    When called in task context, d will be a data store, file_name will not be set
818*4882a593Smuzhiyun    """
819*4882a593Smuzhiyun    taskflagname = taskname
820*4882a593Smuzhiyun    if taskname.endswith("_setscene") and taskname != "do_setscene":
821*4882a593Smuzhiyun        taskflagname = taskname.replace("_setscene", "")
822*4882a593Smuzhiyun
823*4882a593Smuzhiyun    if file_name:
824*4882a593Smuzhiyun        stamp = d.stampclean[file_name]
825*4882a593Smuzhiyun        extrainfo = d.stamp_extrainfo[file_name].get(taskflagname) or ""
826*4882a593Smuzhiyun    else:
827*4882a593Smuzhiyun        stamp = d.getVar('STAMPCLEAN')
828*4882a593Smuzhiyun        file_name = d.getVar('BB_FILENAME')
829*4882a593Smuzhiyun        extrainfo = d.getVarFlag(taskflagname, 'stamp-extra-info') or ""
830*4882a593Smuzhiyun
831*4882a593Smuzhiyun    if not stamp:
832*4882a593Smuzhiyun        return []
833*4882a593Smuzhiyun
834*4882a593Smuzhiyun    cleanmask = bb.parse.siggen.stampcleanmask(stamp, file_name, taskname, extrainfo)
835*4882a593Smuzhiyun
836*4882a593Smuzhiyun    return [cleanmask, cleanmask.replace(taskflagname, taskflagname + "_setscene")]
837*4882a593Smuzhiyun
838*4882a593Smuzhiyundef clean_stamp(task, d, file_name = None):
839*4882a593Smuzhiyun    cleanmask = stamp_cleanmask_internal(task, d, file_name)
840*4882a593Smuzhiyun    for mask in cleanmask:
841*4882a593Smuzhiyun        for name in glob.glob(mask):
842*4882a593Smuzhiyun            # Preserve sigdata files in the stamps directory
843*4882a593Smuzhiyun            if "sigdata" in name or "sigbasedata" in name:
844*4882a593Smuzhiyun                continue
845*4882a593Smuzhiyun            # Preserve taint files in the stamps directory
846*4882a593Smuzhiyun            if name.endswith('.taint'):
847*4882a593Smuzhiyun                continue
848*4882a593Smuzhiyun            os.unlink(name)
849*4882a593Smuzhiyun    return
850*4882a593Smuzhiyun
851*4882a593Smuzhiyundef make_stamp(task, d, file_name = None):
852*4882a593Smuzhiyun    """
853*4882a593Smuzhiyun    Creates/updates a stamp for a given task
854*4882a593Smuzhiyun    (d can be a data dict or dataCache)
855*4882a593Smuzhiyun    """
856*4882a593Smuzhiyun    clean_stamp(task, d, file_name)
857*4882a593Smuzhiyun
858*4882a593Smuzhiyun    stamp = stamp_internal(task, d, file_name)
859*4882a593Smuzhiyun    # Remove the file and recreate to force timestamp
860*4882a593Smuzhiyun    # change on broken NFS filesystems
861*4882a593Smuzhiyun    if stamp:
862*4882a593Smuzhiyun        bb.utils.remove(stamp)
863*4882a593Smuzhiyun        open(stamp, "w").close()
864*4882a593Smuzhiyun
865*4882a593Smuzhiyun    # If we're in task context, write out a signature file for each task
866*4882a593Smuzhiyun    # as it completes
867*4882a593Smuzhiyun    if not task.endswith("_setscene") and task != "do_setscene" and not file_name:
868*4882a593Smuzhiyun        stampbase = stamp_internal(task, d, None, True)
869*4882a593Smuzhiyun        file_name = d.getVar('BB_FILENAME')
870*4882a593Smuzhiyun        bb.parse.siggen.dump_sigtask(file_name, task, stampbase, True)
871*4882a593Smuzhiyun
872*4882a593Smuzhiyundef find_stale_stamps(task, d, file_name=None):
873*4882a593Smuzhiyun    current = stamp_internal(task, d, file_name)
874*4882a593Smuzhiyun    current2 = stamp_internal(task + "_setscene", d, file_name)
875*4882a593Smuzhiyun    cleanmask = stamp_cleanmask_internal(task, d, file_name)
876*4882a593Smuzhiyun    found = []
877*4882a593Smuzhiyun    for mask in cleanmask:
878*4882a593Smuzhiyun        for name in glob.glob(mask):
879*4882a593Smuzhiyun            if "sigdata" in name or "sigbasedata" in name:
880*4882a593Smuzhiyun                continue
881*4882a593Smuzhiyun            if name.endswith('.taint'):
882*4882a593Smuzhiyun                continue
883*4882a593Smuzhiyun            if name == current or name == current2:
884*4882a593Smuzhiyun                continue
885*4882a593Smuzhiyun            logger.debug2("Stampfile %s does not match %s or %s" % (name, current, current2))
886*4882a593Smuzhiyun            found.append(name)
887*4882a593Smuzhiyun    return found
888*4882a593Smuzhiyun
889*4882a593Smuzhiyundef del_stamp(task, d, file_name = None):
890*4882a593Smuzhiyun    """
891*4882a593Smuzhiyun    Removes a stamp for a given task
892*4882a593Smuzhiyun    (d can be a data dict or dataCache)
893*4882a593Smuzhiyun    """
894*4882a593Smuzhiyun    stamp = stamp_internal(task, d, file_name)
895*4882a593Smuzhiyun    bb.utils.remove(stamp)
896*4882a593Smuzhiyun
897*4882a593Smuzhiyundef write_taint(task, d, file_name = None):
898*4882a593Smuzhiyun    """
899*4882a593Smuzhiyun    Creates a "taint" file which will force the specified task and its
900*4882a593Smuzhiyun    dependents to be re-run the next time by influencing the value of its
901*4882a593Smuzhiyun    taskhash.
902*4882a593Smuzhiyun    (d can be a data dict or dataCache)
903*4882a593Smuzhiyun    """
904*4882a593Smuzhiyun    import uuid
905*4882a593Smuzhiyun    if file_name:
906*4882a593Smuzhiyun        taintfn = d.stamp[file_name] + '.' + task + '.taint'
907*4882a593Smuzhiyun    else:
908*4882a593Smuzhiyun        taintfn = d.getVar('STAMP') + '.' + task + '.taint'
909*4882a593Smuzhiyun    bb.utils.mkdirhier(os.path.dirname(taintfn))
910*4882a593Smuzhiyun    # The specific content of the taint file is not really important,
911*4882a593Smuzhiyun    # we just need it to be random, so a random UUID is used
912*4882a593Smuzhiyun    with open(taintfn, 'w') as taintf:
913*4882a593Smuzhiyun        taintf.write(str(uuid.uuid4()))
914*4882a593Smuzhiyun
915*4882a593Smuzhiyundef stampfile(taskname, d, file_name = None, noextra=False):
916*4882a593Smuzhiyun    """
917*4882a593Smuzhiyun    Return the stamp for a given task
918*4882a593Smuzhiyun    (d can be a data dict or dataCache)
919*4882a593Smuzhiyun    """
920*4882a593Smuzhiyun    return stamp_internal(taskname, d, file_name, noextra=noextra)
921*4882a593Smuzhiyun
922*4882a593Smuzhiyundef add_tasks(tasklist, d):
923*4882a593Smuzhiyun    task_deps = d.getVar('_task_deps', False)
924*4882a593Smuzhiyun    if not task_deps:
925*4882a593Smuzhiyun        task_deps = {}
926*4882a593Smuzhiyun    if not 'tasks' in task_deps:
927*4882a593Smuzhiyun        task_deps['tasks'] = []
928*4882a593Smuzhiyun    if not 'parents' in task_deps:
929*4882a593Smuzhiyun        task_deps['parents'] = {}
930*4882a593Smuzhiyun
931*4882a593Smuzhiyun    for task in tasklist:
932*4882a593Smuzhiyun        task = d.expand(task)
933*4882a593Smuzhiyun
934*4882a593Smuzhiyun        d.setVarFlag(task, 'task', 1)
935*4882a593Smuzhiyun
936*4882a593Smuzhiyun        if not task in task_deps['tasks']:
937*4882a593Smuzhiyun            task_deps['tasks'].append(task)
938*4882a593Smuzhiyun
939*4882a593Smuzhiyun        flags = d.getVarFlags(task)
940*4882a593Smuzhiyun        def getTask(name):
941*4882a593Smuzhiyun            if not name in task_deps:
942*4882a593Smuzhiyun                task_deps[name] = {}
943*4882a593Smuzhiyun            if name in flags:
944*4882a593Smuzhiyun                deptask = d.expand(flags[name])
945*4882a593Smuzhiyun                if name in ['noexec', 'fakeroot', 'nostamp']:
946*4882a593Smuzhiyun                    if deptask != '1':
947*4882a593Smuzhiyun                        bb.warn("In a future version of BitBake, setting the '{}' flag to something other than '1' "
948*4882a593Smuzhiyun                                "will result in the flag not being set. See YP bug #13808.".format(name))
949*4882a593Smuzhiyun
950*4882a593Smuzhiyun                task_deps[name][task] = deptask
951*4882a593Smuzhiyun        getTask('mcdepends')
952*4882a593Smuzhiyun        getTask('depends')
953*4882a593Smuzhiyun        getTask('rdepends')
954*4882a593Smuzhiyun        getTask('deptask')
955*4882a593Smuzhiyun        getTask('rdeptask')
956*4882a593Smuzhiyun        getTask('recrdeptask')
957*4882a593Smuzhiyun        getTask('recideptask')
958*4882a593Smuzhiyun        getTask('nostamp')
959*4882a593Smuzhiyun        getTask('fakeroot')
960*4882a593Smuzhiyun        getTask('noexec')
961*4882a593Smuzhiyun        getTask('umask')
962*4882a593Smuzhiyun        task_deps['parents'][task] = []
963*4882a593Smuzhiyun        if 'deps' in flags:
964*4882a593Smuzhiyun            for dep in flags['deps']:
965*4882a593Smuzhiyun                # Check and warn for "addtask task after foo" while foo does not exist
966*4882a593Smuzhiyun                #if not dep in tasklist:
967*4882a593Smuzhiyun                #    bb.warn('%s: dependent task %s for %s does not exist' % (d.getVar('PN'), dep, task))
968*4882a593Smuzhiyun                dep = d.expand(dep)
969*4882a593Smuzhiyun                task_deps['parents'][task].append(dep)
970*4882a593Smuzhiyun
971*4882a593Smuzhiyun    # don't assume holding a reference
972*4882a593Smuzhiyun    d.setVar('_task_deps', task_deps)
973*4882a593Smuzhiyun
974*4882a593Smuzhiyundef addtask(task, before, after, d):
975*4882a593Smuzhiyun    if task[:3] != "do_":
976*4882a593Smuzhiyun        task = "do_" + task
977*4882a593Smuzhiyun
978*4882a593Smuzhiyun    d.setVarFlag(task, "task", 1)
979*4882a593Smuzhiyun    bbtasks = d.getVar('__BBTASKS', False) or []
980*4882a593Smuzhiyun    if task not in bbtasks:
981*4882a593Smuzhiyun        bbtasks.append(task)
982*4882a593Smuzhiyun    d.setVar('__BBTASKS', bbtasks)
983*4882a593Smuzhiyun
984*4882a593Smuzhiyun    existing = d.getVarFlag(task, "deps", False) or []
985*4882a593Smuzhiyun    if after is not None:
986*4882a593Smuzhiyun        # set up deps for function
987*4882a593Smuzhiyun        for entry in after.split():
988*4882a593Smuzhiyun            if entry not in existing:
989*4882a593Smuzhiyun                existing.append(entry)
990*4882a593Smuzhiyun    d.setVarFlag(task, "deps", existing)
991*4882a593Smuzhiyun    if before is not None:
992*4882a593Smuzhiyun        # set up things that depend on this func
993*4882a593Smuzhiyun        for entry in before.split():
994*4882a593Smuzhiyun            existing = d.getVarFlag(entry, "deps", False) or []
995*4882a593Smuzhiyun            if task not in existing:
996*4882a593Smuzhiyun                d.setVarFlag(entry, "deps", [task] + existing)
997*4882a593Smuzhiyun
998*4882a593Smuzhiyundef deltask(task, d):
999*4882a593Smuzhiyun    if task[:3] != "do_":
1000*4882a593Smuzhiyun        task = "do_" + task
1001*4882a593Smuzhiyun
1002*4882a593Smuzhiyun    bbtasks = d.getVar('__BBTASKS', False) or []
1003*4882a593Smuzhiyun    if task in bbtasks:
1004*4882a593Smuzhiyun        bbtasks.remove(task)
1005*4882a593Smuzhiyun        d.delVarFlag(task, 'task')
1006*4882a593Smuzhiyun        d.setVar('__BBTASKS', bbtasks)
1007*4882a593Smuzhiyun
1008*4882a593Smuzhiyun    d.delVarFlag(task, 'deps')
1009*4882a593Smuzhiyun    for bbtask in d.getVar('__BBTASKS', False) or []:
1010*4882a593Smuzhiyun        deps = d.getVarFlag(bbtask, 'deps', False) or []
1011*4882a593Smuzhiyun        if task in deps:
1012*4882a593Smuzhiyun            deps.remove(task)
1013*4882a593Smuzhiyun            d.setVarFlag(bbtask, 'deps', deps)
1014*4882a593Smuzhiyun
1015*4882a593Smuzhiyundef preceedtask(task, with_recrdeptasks, d):
1016*4882a593Smuzhiyun    """
1017*4882a593Smuzhiyun    Returns a set of tasks in the current recipe which were specified as
1018*4882a593Smuzhiyun    precondition by the task itself ("after") or which listed themselves
1019*4882a593Smuzhiyun    as precondition ("before"). Preceeding tasks specified via the
1020*4882a593Smuzhiyun    "recrdeptask" are included in the result only if requested. Beware
1021*4882a593Smuzhiyun    that this may lead to the task itself being listed.
1022*4882a593Smuzhiyun    """
1023*4882a593Smuzhiyun    preceed = set()
1024*4882a593Smuzhiyun
1025*4882a593Smuzhiyun    # Ignore tasks which don't exist
1026*4882a593Smuzhiyun    tasks = d.getVar('__BBTASKS', False)
1027*4882a593Smuzhiyun    if task not in tasks:
1028*4882a593Smuzhiyun        return preceed
1029*4882a593Smuzhiyun
1030*4882a593Smuzhiyun    preceed.update(d.getVarFlag(task, 'deps') or [])
1031*4882a593Smuzhiyun    if with_recrdeptasks:
1032*4882a593Smuzhiyun        recrdeptask = d.getVarFlag(task, 'recrdeptask')
1033*4882a593Smuzhiyun        if recrdeptask:
1034*4882a593Smuzhiyun            preceed.update(recrdeptask.split())
1035*4882a593Smuzhiyun    return preceed
1036*4882a593Smuzhiyun
1037*4882a593Smuzhiyundef tasksbetween(task_start, task_end, d):
1038*4882a593Smuzhiyun    """
1039*4882a593Smuzhiyun    Return the list of tasks between two tasks in the current recipe,
1040*4882a593Smuzhiyun    where task_start is to start at and task_end is the task to end at
1041*4882a593Smuzhiyun    (and task_end has a dependency chain back to task_start).
1042*4882a593Smuzhiyun    """
1043*4882a593Smuzhiyun    outtasks = []
1044*4882a593Smuzhiyun    tasks = list(filter(lambda k: d.getVarFlag(k, "task"), d.keys()))
1045*4882a593Smuzhiyun    def follow_chain(task, endtask, chain=None):
1046*4882a593Smuzhiyun        if not chain:
1047*4882a593Smuzhiyun            chain = []
1048*4882a593Smuzhiyun        if task in chain:
1049*4882a593Smuzhiyun            bb.fatal("Circular task dependencies as %s depends on itself via the chain %s" % (task, " -> ".join(chain)))
1050*4882a593Smuzhiyun        chain.append(task)
1051*4882a593Smuzhiyun        for othertask in tasks:
1052*4882a593Smuzhiyun            if othertask == task:
1053*4882a593Smuzhiyun                continue
1054*4882a593Smuzhiyun            if task == endtask:
1055*4882a593Smuzhiyun                for ctask in chain:
1056*4882a593Smuzhiyun                    if ctask not in outtasks:
1057*4882a593Smuzhiyun                        outtasks.append(ctask)
1058*4882a593Smuzhiyun            else:
1059*4882a593Smuzhiyun                deps = d.getVarFlag(othertask, 'deps', False)
1060*4882a593Smuzhiyun                if task in deps:
1061*4882a593Smuzhiyun                    follow_chain(othertask, endtask, chain)
1062*4882a593Smuzhiyun        chain.pop()
1063*4882a593Smuzhiyun    follow_chain(task_start, task_end)
1064*4882a593Smuzhiyun    return outtasks
1065