summaryrefslogtreecommitdiffstats
path: root/bitbake/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-xbitbake/bin/bitbake400
-rwxr-xr-xbitbake/bin/bitbake-diffsigs122
-rwxr-xr-xbitbake/bin/bitbake-dumpsig65
-rwxr-xr-xbitbake/bin/bitbake-layers758
-rwxr-xr-xbitbake/bin/bitbake-prserv55
-rwxr-xr-xbitbake/bin/bitbake-selftest49
-rwxr-xr-xbitbake/bin/bitbake-worker415
-rwxr-xr-xbitbake/bin/bitdoc531
-rwxr-xr-xbitbake/bin/image-writer122
-rwxr-xr-xbitbake/bin/toaster267
-rwxr-xr-xbitbake/bin/toaster-eventreplay179
11 files changed, 2963 insertions, 0 deletions
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
new file mode 100755
index 0000000000..41cf8c86c0
--- /dev/null
+++ b/bitbake/bin/bitbake
@@ -0,0 +1,400 @@
1#!/usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# Copyright (C) 2003, 2004 Chris Larson
6# Copyright (C) 2003, 2004 Phil Blundell
7# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
8# Copyright (C) 2005 Holger Hans Peter Freyther
9# Copyright (C) 2005 ROAD GmbH
10# Copyright (C) 2006 Richard Purdie
11#
12# This program is free software; you can redistribute it and/or modify
13# it under the terms of the GNU General Public License version 2 as
14# published by the Free Software Foundation.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License along
22# with this program; if not, write to the Free Software Foundation, Inc.,
23# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24
25import os
26import sys, logging
27sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),
28 'lib'))
29
30import optparse
31import warnings
32from traceback import format_exception
33try:
34 import bb
35except RuntimeError as exc:
36 sys.exit(str(exc))
37from bb import event
38import bb.msg
39from bb import cooker
40from bb import ui
41from bb import server
42from bb import cookerdata
43
44__version__ = "1.24.0"
45logger = logging.getLogger("BitBake")
46
47# Python multiprocessing requires /dev/shm
48if not os.access('/dev/shm', os.W_OK | os.X_OK):
49 sys.exit("FATAL: /dev/shm does not exist or is not writable")
50
51# Unbuffer stdout to avoid log truncation in the event
52# of an unorderly exit as well as to provide timely
53# updates to log files for use with tail
54try:
55 if sys.stdout.name == '<stdout>':
56 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
57except:
58 pass
59
60
61def get_ui(config):
62 if not config.ui:
63 # modify 'ui' attribute because it is also read by cooker
64 config.ui = os.environ.get('BITBAKE_UI', 'knotty')
65
66 interface = config.ui
67
68 try:
69 # Dynamically load the UI based on the ui name. Although we
70 # suggest a fixed set this allows you to have flexibility in which
71 # ones are available.
72 module = __import__("bb.ui", fromlist = [interface])
73 return getattr(module, interface)
74 except AttributeError:
75 sys.exit("FATAL: Invalid user interface '%s' specified.\n"
76 "Valid interfaces: depexp, goggle, ncurses, hob, knotty [default]." % interface)
77
78
79# Display bitbake/OE warnings via the BitBake.Warnings logger, ignoring others"""
80warnlog = logging.getLogger("BitBake.Warnings")
81_warnings_showwarning = warnings.showwarning
82def _showwarning(message, category, filename, lineno, file=None, line=None):
83 if file is not None:
84 if _warnings_showwarning is not None:
85 _warnings_showwarning(message, category, filename, lineno, file, line)
86 else:
87 s = warnings.formatwarning(message, category, filename, lineno)
88 warnlog.warn(s)
89
90warnings.showwarning = _showwarning
91warnings.filterwarnings("ignore")
92warnings.filterwarnings("default", module="(<string>$|(oe|bb)\.)")
93warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
94warnings.filterwarnings("ignore", category=ImportWarning)
95warnings.filterwarnings("ignore", category=DeprecationWarning, module="<string>$")
96warnings.filterwarnings("ignore", message="With-statements now directly support multiple context managers")
97
98class BitBakeConfigParameters(cookerdata.ConfigParameters):
99
100 def parseCommandLine(self):
101 parser = optparse.OptionParser(
102 version = "BitBake Build Tool Core version %s, %%prog version %s" % (bb.__version__, __version__),
103 usage = """%prog [options] [recipename/target ...]
104
105 Executes the specified task (default is 'build') for a given set of target recipes (.bb files).
106 It is assumed there is a conf/bblayers.conf available in cwd or in BBPATH which
107 will provide the layer, BBFILES and other configuration information.""")
108
109 parser.add_option("-b", "--buildfile", help = "Execute tasks from a specific .bb recipe directly. WARNING: Does not handle any dependencies from other recipes.",
110 action = "store", dest = "buildfile", default = None)
111
112 parser.add_option("-k", "--continue", help = "Continue as much as possible after an error. While the target that failed and anything depending on it cannot be built, as much as possible will be built before stopping.",
113 action = "store_false", dest = "abort", default = True)
114
115 parser.add_option("-a", "--tryaltconfigs", help = "Continue with builds by trying to use alternative providers where possible.",
116 action = "store_true", dest = "tryaltconfigs", default = False)
117
118 parser.add_option("-f", "--force", help = "Force the specified targets/task to run (invalidating any existing stamp file).",
119 action = "store_true", dest = "force", default = False)
120
121 parser.add_option("-c", "--cmd", help = "Specify the task to execute. The exact options available depend on the metadata. Some examples might be 'compile' or 'populate_sysroot' or 'listtasks' may give a list of the tasks available.",
122 action = "store", dest = "cmd")
123
124 parser.add_option("-C", "--clear-stamp", help = "Invalidate the stamp for the specified task such as 'compile' and then run the default task for the specified target(s).",
125 action = "store", dest = "invalidate_stamp")
126
127 parser.add_option("-r", "--read", help = "Read the specified file before bitbake.conf.",
128 action = "append", dest = "prefile", default = [])
129
130 parser.add_option("-R", "--postread", help = "Read the specified file after bitbake.conf.",
131 action = "append", dest = "postfile", default = [])
132
133 parser.add_option("-v", "--verbose", help = "Output more log message data to the terminal.",
134 action = "store_true", dest = "verbose", default = False)
135
136 parser.add_option("-D", "--debug", help = "Increase the debug level. You can specify this more than once.",
137 action = "count", dest="debug", default = 0)
138
139 parser.add_option("-n", "--dry-run", help = "Don't execute, just go through the motions.",
140 action = "store_true", dest = "dry_run", default = False)
141
142 parser.add_option("-S", "--dump-signatures", help = "Dump out the signature construction information, with no task execution. The SIGNATURE_HANDLER parameter is passed to the handler. Two common values are none and printdiff but the handler may define more/less. none means only dump the signature, printdiff means compare the dumped signature with the cached one.",
143 action = "append", dest = "dump_signatures", default = [], metavar="SIGNATURE_HANDLER")
144
145 parser.add_option("-p", "--parse-only", help = "Quit after parsing the BB recipes.",
146 action = "store_true", dest = "parse_only", default = False)
147
148 parser.add_option("-s", "--show-versions", help = "Show current and preferred versions of all recipes.",
149 action = "store_true", dest = "show_versions", default = False)
150
151 parser.add_option("-e", "--environment", help = "Show the global or per-recipe environment complete with information about where variables were set/changed.",
152 action = "store_true", dest = "show_environment", default = False)
153
154 parser.add_option("-g", "--graphviz", help = "Save dependency tree information for the specified targets in the dot syntax.",
155 action = "store_true", dest = "dot_graph", default = False)
156
157 parser.add_option("-I", "--ignore-deps", help = """Assume these dependencies don't exist and are already provided (equivalent to ASSUME_PROVIDED). Useful to make dependency graphs more appealing""",
158 action = "append", dest = "extra_assume_provided", default = [])
159
160 parser.add_option("-l", "--log-domains", help = """Show debug logging for the specified logging domains""",
161 action = "append", dest = "debug_domains", default = [])
162
163 parser.add_option("-P", "--profile", help = "Profile the command and save reports.",
164 action = "store_true", dest = "profile", default = False)
165
166 parser.add_option("-u", "--ui", help = "The user interface to use (e.g. knotty, hob, depexp).",
167 action = "store", dest = "ui")
168
169 parser.add_option("-t", "--servertype", help = "Choose which server to use, process or xmlrpc.",
170 action = "store", dest = "servertype")
171
172 parser.add_option("", "--token", help = "Specify the connection token to be used when connecting to a remote server.",
173 action = "store", dest = "xmlrpctoken")
174
175 parser.add_option("", "--revisions-changed", help = "Set the exit code depending on whether upstream floating revisions have changed or not.",
176 action = "store_true", dest = "revisions_changed", default = False)
177
178 parser.add_option("", "--server-only", help = "Run bitbake without a UI, only starting a server (cooker) process.",
179 action = "store_true", dest = "server_only", default = False)
180
181 parser.add_option("-B", "--bind", help = "The name/address for the bitbake server to bind to.",
182 action = "store", dest = "bind", default = False)
183
184 parser.add_option("", "--no-setscene", help = "Do not run any setscene tasks. sstate will be ignored and everything needed, built.",
185 action = "store_true", dest = "nosetscene", default = False)
186
187 parser.add_option("", "--remote-server", help = "Connect to the specified server.",
188 action = "store", dest = "remote_server", default = False)
189
190 parser.add_option("-m", "--kill-server", help = "Terminate the remote server.",
191 action = "store_true", dest = "kill_server", default = False)
192
193 parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client.",
194 action = "store_true", dest = "observe_only", default = False)
195
196 parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.",
197 action = "store_true", dest = "status_only", default = False)
198
199 parser.add_option("-w", "--write-log", help = "Writes the event log of the build to a bitbake event json file. Use '' (empty string) to assign the name automatically.",
200 action = "store", dest = "writeeventlog")
201
202 options, targets = parser.parse_args(sys.argv)
203
204 # some environmental variables set also configuration options
205 if "BBSERVER" in os.environ:
206 options.servertype = "xmlrpc"
207 options.remote_server = os.environ["BBSERVER"]
208
209 if "BBTOKEN" in os.environ:
210 options.xmlrpctoken = os.environ["BBTOKEN"]
211
212 if "BBEVENTLOG" is os.environ:
213 options.writeeventlog = os.environ["BBEVENTLOG"]
214
215 # fill in proper log name if not supplied
216 if options.writeeventlog is not None and len(options.writeeventlog) == 0:
217 import datetime
218 options.writeeventlog = "bitbake_eventlog_%s.json" % datetime.datetime.now().strftime("%Y%m%d%H%M%S")
219
220 # if BBSERVER says to autodetect, let's do that
221 if options.remote_server:
222 [host, port] = options.remote_server.split(":", 2)
223 port = int(port)
224 # use automatic port if port set to -1, means read it from
225 # the bitbake.lock file; this is a bit tricky, but we always expect
226 # to be in the base of the build directory if we need to have a
227 # chance to start the server later, anyway
228 if port == -1:
229 lock_location = "./bitbake.lock"
230 # we try to read the address at all times; if the server is not started,
231 # we'll try to start it after the first connect fails, below
232 try:
233 lf = open(lock_location, 'r')
234 remotedef = lf.readline()
235 [host, port] = remotedef.split(":")
236 port = int(port)
237 lf.close()
238 options.remote_server = remotedef
239 except Exception as e:
240 sys.exit("Failed to read bitbake.lock (%s), invalid port" % str(e))
241
242 return options, targets[1:]
243
244
245def start_server(servermodule, configParams, configuration, features):
246 server = servermodule.BitBakeServer()
247 if configParams.bind:
248 (host, port) = configParams.bind.split(':')
249 server.initServer((host, int(port)))
250 configuration.interface = [ server.serverImpl.host, server.serverImpl.port ]
251 else:
252 server.initServer()
253 configuration.interface = []
254
255 try:
256 configuration.setServerRegIdleCallback(server.getServerIdleCB())
257
258 cooker = bb.cooker.BBCooker(configuration, features)
259
260 server.addcooker(cooker)
261 server.saveConnectionDetails()
262 except Exception as e:
263 exc_info = sys.exc_info()
264 while hasattr(server, "event_queue"):
265 try:
266 import queue
267 except ImportError:
268 import Queue as queue
269 try:
270 event = server.event_queue.get(block=False)
271 except (queue.Empty, IOError):
272 break
273 if isinstance(event, logging.LogRecord):
274 logger.handle(event)
275 raise exc_info[1], None, exc_info[2]
276 server.detach()
277 return server
278
279
280def main():
281
282 configParams = BitBakeConfigParameters()
283 configuration = cookerdata.CookerConfiguration()
284 configuration.setConfigParameters(configParams)
285
286 ui_module = get_ui(configParams)
287
288 # Server type can be xmlrpc or process currently, if nothing is specified,
289 # the default server is process
290 if configParams.servertype:
291 server_type = configParams.servertype
292 else:
293 server_type = 'process'
294
295 try:
296 module = __import__("bb.server", fromlist = [server_type])
297 servermodule = getattr(module, server_type)
298 except AttributeError:
299 sys.exit("FATAL: Invalid server type '%s' specified.\n"
300 "Valid interfaces: xmlrpc, process [default]." % server_type)
301
302 if configParams.server_only:
303 if configParams.servertype != "xmlrpc":
304 sys.exit("FATAL: If '--server-only' is defined, we must set the servertype as 'xmlrpc'.\n")
305 if not configParams.bind:
306 sys.exit("FATAL: The '--server-only' option requires a name/address to bind to with the -B option.\n")
307 if configParams.remote_server:
308 sys.exit("FATAL: The '--server-only' option conflicts with %s.\n" %
309 ("the BBSERVER environment variable" if "BBSERVER" in os.environ else "the '--remote-server' option" ))
310
311 if configParams.bind and configParams.servertype != "xmlrpc":
312 sys.exit("FATAL: If '-B' or '--bind' is defined, we must set the servertype as 'xmlrpc'.\n")
313
314 if configParams.remote_server and configParams.servertype != "xmlrpc":
315 sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
316
317 if configParams.observe_only and (not configParams.remote_server or configParams.bind):
318 sys.exit("FATAL: '--observe-only' can only be used by UI clients connecting to a server.\n")
319
320 if configParams.kill_server and not configParams.remote_server:
321 sys.exit("FATAL: '--kill-server' can only be used to terminate a remote server")
322
323 if "BBDEBUG" in os.environ:
324 level = int(os.environ["BBDEBUG"])
325 if level > configuration.debug:
326 configuration.debug = level
327
328 bb.msg.init_msgconfig(configParams.verbose, configuration.debug,
329 configuration.debug_domains)
330
331 # Ensure logging messages get sent to the UI as events
332 handler = bb.event.LogHandler()
333 if not configParams.status_only:
334 # In status only mode there are no logs and no UI
335 logger.addHandler(handler)
336
337 # Clear away any spurious environment variables while we stoke up the cooker
338 cleanedvars = bb.utils.clean_environment()
339
340 featureset = []
341 if not configParams.server_only:
342 # Collect the feature set for the UI
343 featureset = getattr(ui_module, "featureSet", [])
344
345 if not configParams.remote_server:
346 # we start a server with a given configuration
347 server = start_server(servermodule, configParams, configuration, featureset)
348 bb.event.ui_queue = []
349 else:
350 # we start a stub server that is actually a XMLRPClient that connects to a real server
351 server = servermodule.BitBakeXMLRPCClient(configParams.observe_only, configParams.xmlrpctoken)
352 server.saveConnectionDetails(configParams.remote_server)
353
354
355 if not configParams.server_only:
356 try:
357 server_connection = server.establishConnection(featureset)
358 except Exception as e:
359 if configParams.kill_server:
360 sys.exit(0)
361 bb.fatal("Could not connect to server %s: %s" % (configParams.remote_server, str(e)))
362
363 # Restore the environment in case the UI needs it
364 for k in cleanedvars:
365 os.environ[k] = cleanedvars[k]
366
367 logger.removeHandler(handler)
368
369
370 if configParams.status_only:
371 server_connection.terminate()
372 sys.exit(0)
373
374 if configParams.kill_server:
375 server_connection.connection.terminateServer()
376 bb.event.ui_queue = []
377 sys.exit(0)
378
379 try:
380 return ui_module.main(server_connection.connection, server_connection.events, configParams)
381 finally:
382 bb.event.ui_queue = []
383 server_connection.terminate()
384 else:
385 print("server address: %s, server port: %s" % (server.serverImpl.host, server.serverImpl.port))
386 return 0
387
388 return 1
389
390if __name__ == "__main__":
391 try:
392 ret = main()
393 except bb.BBHandledException:
394 ret = 1
395 except Exception:
396 ret = 1
397 import traceback
398 traceback.print_exc()
399 sys.exit(ret)
400
diff --git a/bitbake/bin/bitbake-diffsigs b/bitbake/bin/bitbake-diffsigs
new file mode 100755
index 0000000000..08ae00db0f
--- /dev/null
+++ b/bitbake/bin/bitbake-diffsigs
@@ -0,0 +1,122 @@
1#!/usr/bin/env python
2
3# bitbake-diffsigs
4# BitBake task signature data comparison utility
5#
6# Copyright (C) 2012-2013 Intel Corporation
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import os
22import sys
23import warnings
24import fnmatch
25import optparse
26import logging
27
28sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
29
30import bb.tinfoil
31import bb.siggen
32
33def logger_create(name, output=sys.stderr):
34 logger = logging.getLogger(name)
35 console = logging.StreamHandler(output)
36 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
37 if output.isatty():
38 format.enable_color()
39 console.setFormatter(format)
40 logger.addHandler(console)
41 logger.setLevel(logging.INFO)
42 return logger
43
44logger = logger_create('bitbake-diffsigs')
45
46def find_compare_task(bbhandler, pn, taskname):
47 """ Find the most recent signature files for the specified PN/task and compare them """
48
49 if not hasattr(bb.siggen, 'find_siginfo'):
50 logger.error('Metadata does not support finding signature data files')
51 sys.exit(1)
52
53 if not taskname.startswith('do_'):
54 taskname = 'do_%s' % taskname
55
56 filedates = bb.siggen.find_siginfo(pn, taskname, None, bbhandler.config_data)
57 latestfiles = sorted(filedates.keys(), key=lambda f: filedates[f])[-2:]
58 if not latestfiles:
59 logger.error('No sigdata files found matching %s %s' % (pn, taskname))
60 sys.exit(1)
61 elif len(latestfiles) < 2:
62 logger.error('Only one matching sigdata file found for the specified task (%s %s)' % (pn, taskname))
63 sys.exit(1)
64 else:
65 # Define recursion callback
66 def recursecb(key, hash1, hash2):
67 hashes = [hash1, hash2]
68 hashfiles = bb.siggen.find_siginfo(key, None, hashes, bbhandler.config_data)
69
70 recout = []
71 if len(hashfiles) == 2:
72 out2 = bb.siggen.compare_sigfiles(hashfiles[hash1], hashfiles[hash2], recursecb)
73 recout.extend(list(' ' + l for l in out2))
74 else:
75 recout.append("Unable to find matching sigdata for %s with hashes %s or %s" % (key, hash1, hash2))
76
77 return recout
78
79 # Recurse into signature comparison
80 output = bb.siggen.compare_sigfiles(latestfiles[0], latestfiles[1], recursecb)
81 if output:
82 print '\n'.join(output)
83 sys.exit(0)
84
85
86
87parser = optparse.OptionParser(
88 description = "Compares siginfo/sigdata files written out by BitBake",
89 usage = """
90 %prog -t recipename taskname
91 %prog sigdatafile1 sigdatafile2
92 %prog sigdatafile1""")
93
94parser.add_option("-t", "--task",
95 help = "find the signature data files for last two runs of the specified task and compare them",
96 action="store", dest="taskargs", nargs=2, metavar='recipename taskname')
97
98options, args = parser.parse_args(sys.argv)
99
100if options.taskargs:
101 tinfoil = bb.tinfoil.Tinfoil()
102 tinfoil.prepare(config_only = True)
103 find_compare_task(tinfoil, options.taskargs[0], options.taskargs[1])
104else:
105 if len(args) == 1:
106 parser.print_help()
107 else:
108 import cPickle
109 try:
110 if len(args) == 2:
111 output = bb.siggen.dump_sigfile(sys.argv[1])
112 else:
113 output = bb.siggen.compare_sigfiles(sys.argv[1], sys.argv[2])
114 except IOError as e:
115 logger.error(str(e))
116 sys.exit(1)
117 except cPickle.UnpicklingError, EOFError:
118 logger.error('Invalid signature data - ensure you are specifying sigdata/siginfo files')
119 sys.exit(1)
120
121 if output:
122 print '\n'.join(output)
diff --git a/bitbake/bin/bitbake-dumpsig b/bitbake/bin/bitbake-dumpsig
new file mode 100755
index 0000000000..656d93a5ac
--- /dev/null
+++ b/bitbake/bin/bitbake-dumpsig
@@ -0,0 +1,65 @@
1#!/usr/bin/env python
2
3# bitbake-dumpsig
4# BitBake task signature dump utility
5#
6# Copyright (C) 2013 Intel Corporation
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import os
22import sys
23import warnings
24import optparse
25import logging
26
27sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
28
29import bb.siggen
30
31def logger_create(name, output=sys.stderr):
32 logger = logging.getLogger(name)
33 console = logging.StreamHandler(output)
34 format = bb.msg.BBLogFormatter("%(levelname)s: %(message)s")
35 if output.isatty():
36 format.enable_color()
37 console.setFormatter(format)
38 logger.addHandler(console)
39 logger.setLevel(logging.INFO)
40 return logger
41
42logger = logger_create('bitbake-dumpsig')
43
44parser = optparse.OptionParser(
45 description = "Dumps siginfo/sigdata files written out by BitBake",
46 usage = """
47 %prog sigdatafile""")
48
49options, args = parser.parse_args(sys.argv)
50
51if len(args) == 1:
52 parser.print_help()
53else:
54 import cPickle
55 try:
56 output = bb.siggen.dump_sigfile(args[1])
57 except IOError as e:
58 logger.error(str(e))
59 sys.exit(1)
60 except cPickle.UnpicklingError, EOFError:
61 logger.error('Invalid signature data - ensure you are specifying a sigdata/siginfo file')
62 sys.exit(1)
63
64 if output:
65 print '\n'.join(output)
diff --git a/bitbake/bin/bitbake-layers b/bitbake/bin/bitbake-layers
new file mode 100755
index 0000000000..9964040bf7
--- /dev/null
+++ b/bitbake/bin/bitbake-layers
@@ -0,0 +1,758 @@
1#!/usr/bin/env python
2
3# This script has subcommands which operate against your bitbake layers, either
4# displaying useful information, or acting against them.
5# See the help output for details on available commands.
6
7# Copyright (C) 2011 Mentor Graphics Corporation
8# Copyright (C) 2012 Intel Corporation
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License version 2 as
12# published by the Free Software Foundation.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
23import cmd
24import logging
25import os
26import sys
27import fnmatch
28from collections import defaultdict
29import re
30
31bindir = os.path.dirname(__file__)
32topdir = os.path.dirname(bindir)
33sys.path[0:0] = [os.path.join(topdir, 'lib')]
34
35import bb.cache
36import bb.cooker
37import bb.providers
38import bb.utils
39import bb.tinfoil
40
41
42logger = logging.getLogger('BitBake')
43
44
45def main(args):
46 cmds = Commands()
47 if args:
48 # Allow user to specify e.g. show-layers instead of show_layers
49 args = [args[0].replace('-', '_')] + args[1:]
50 cmds.onecmd(' '.join(args))
51 else:
52 cmds.do_help('')
53 return cmds.returncode
54
55
56class Commands(cmd.Cmd):
57 def __init__(self):
58 self.bbhandler = None
59 self.returncode = 0
60 self.bblayers = []
61 cmd.Cmd.__init__(self)
62
63 def init_bbhandler(self, config_only = False):
64 if not self.bbhandler:
65 self.bbhandler = bb.tinfoil.Tinfoil()
66 self.bblayers = (self.bbhandler.config_data.getVar('BBLAYERS', True) or "").split()
67 self.bbhandler.prepare(config_only)
68
69 def default(self, line):
70 """Handle unrecognised commands"""
71 sys.stderr.write("Unrecognised command or option\n")
72 self.do_help('')
73
74 def do_help(self, topic):
75 """display general help or help on a specified command"""
76 if topic:
77 sys.stdout.write('%s: ' % topic)
78 cmd.Cmd.do_help(self, topic.replace('-', '_'))
79 else:
80 sys.stdout.write("usage: bitbake-layers <command> [arguments]\n\n")
81 sys.stdout.write("Available commands:\n")
82 procnames = list(set(self.get_names()))
83 for procname in procnames:
84 if procname[:3] == 'do_':
85 sys.stdout.write(" %s\n" % procname[3:].replace('_', '-'))
86 doc = getattr(self, procname).__doc__
87 if doc:
88 sys.stdout.write(" %s\n" % doc.splitlines()[0])
89
90 def do_show_layers(self, args):
91 """show current configured layers"""
92 self.init_bbhandler(config_only = True)
93 logger.plain("%s %s %s" % ("layer".ljust(20), "path".ljust(40), "priority"))
94 logger.plain('=' * 74)
95 for layerdir in self.bblayers:
96 layername = self.get_layer_name(layerdir)
97 layerpri = 0
98 for layer, _, regex, pri in self.bbhandler.cooker.recipecache.bbfile_config_priorities:
99 if regex.match(os.path.join(layerdir, 'test')):
100 layerpri = pri
101 break
102
103 logger.plain("%s %s %d" % (layername.ljust(20), layerdir.ljust(40), layerpri))
104
105
106 def version_str(self, pe, pv, pr = None):
107 verstr = "%s" % pv
108 if pr:
109 verstr = "%s-%s" % (verstr, pr)
110 if pe:
111 verstr = "%s:%s" % (pe, verstr)
112 return verstr
113
114
115 def do_show_overlayed(self, args):
116 """list overlayed recipes (where the same recipe exists in another layer)
117
118usage: show-overlayed [-f] [-s]
119
120Lists the names of overlayed recipes and the available versions in each
121layer, with the preferred version first. Note that skipped recipes that
122are overlayed will also be listed, with a " (skipped)" suffix.
123
124Options:
125 -f instead of the default formatting, list filenames of higher priority
126 recipes with the ones they overlay indented underneath
127 -s only list overlayed recipes where the version is the same
128"""
129 self.init_bbhandler()
130
131 show_filenames = False
132 show_same_ver_only = False
133 for arg in args.split():
134 if arg == '-f':
135 show_filenames = True
136 elif arg == '-s':
137 show_same_ver_only = True
138 else:
139 sys.stderr.write("show-overlayed: invalid option %s\n" % arg)
140 self.do_help('')
141 return
142
143 items_listed = self.list_recipes('Overlayed recipes', None, True, show_same_ver_only, show_filenames, True)
144
145 # Check for overlayed .bbclass files
146 classes = defaultdict(list)
147 for layerdir in self.bblayers:
148 classdir = os.path.join(layerdir, 'classes')
149 if os.path.exists(classdir):
150 for classfile in os.listdir(classdir):
151 if os.path.splitext(classfile)[1] == '.bbclass':
152 classes[classfile].append(classdir)
153
154 # Locating classes and other files is a bit more complicated than recipes -
155 # layer priority is not a factor; instead BitBake uses the first matching
156 # file in BBPATH, which is manipulated directly by each layer's
157 # conf/layer.conf in turn, thus the order of layers in bblayers.conf is a
158 # factor - however, each layer.conf is free to either prepend or append to
159 # BBPATH (or indeed do crazy stuff with it). Thus the order in BBPATH might
160 # not be exactly the order present in bblayers.conf either.
161 bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True))
162 overlayed_class_found = False
163 for (classfile, classdirs) in classes.items():
164 if len(classdirs) > 1:
165 if not overlayed_class_found:
166 logger.plain('=== Overlayed classes ===')
167 overlayed_class_found = True
168
169 mainfile = bb.utils.which(bbpath, os.path.join('classes', classfile))
170 if show_filenames:
171 logger.plain('%s' % mainfile)
172 else:
173 # We effectively have to guess the layer here
174 logger.plain('%s:' % classfile)
175 mainlayername = '?'
176 for layerdir in self.bblayers:
177 classdir = os.path.join(layerdir, 'classes')
178 if mainfile.startswith(classdir):
179 mainlayername = self.get_layer_name(layerdir)
180 logger.plain(' %s' % mainlayername)
181 for classdir in classdirs:
182 fullpath = os.path.join(classdir, classfile)
183 if fullpath != mainfile:
184 if show_filenames:
185 print(' %s' % fullpath)
186 else:
187 print(' %s' % self.get_layer_name(os.path.dirname(classdir)))
188
189 if overlayed_class_found:
190 items_listed = True;
191
192 if not items_listed:
193 logger.plain('No overlayed files found.')
194
195
196 def do_show_recipes(self, args):
197 """list available recipes, showing the layer they are provided by
198
199usage: show-recipes [-f] [-m] [pnspec]
200
201Lists the names of overlayed recipes and the available versions in each
202layer, with the preferred version first. Optionally you may specify
203pnspec to match a specified recipe name (supports wildcards). Note that
204skipped recipes will also be listed, with a " (skipped)" suffix.
205
206Options:
207 -f instead of the default formatting, list filenames of higher priority
208 recipes with other available recipes indented underneath
209 -m only list where multiple recipes (in the same layer or different
210 layers) exist for the same recipe name
211"""
212 self.init_bbhandler()
213
214 show_filenames = False
215 show_multi_provider_only = False
216 pnspec = None
217 title = 'Available recipes:'
218 for arg in args.split():
219 if arg == '-f':
220 show_filenames = True
221 elif arg == '-m':
222 show_multi_provider_only = True
223 elif not arg.startswith('-'):
224 pnspec = arg
225 title = 'Available recipes matching %s:' % pnspec
226 else:
227 sys.stderr.write("show-recipes: invalid option %s\n" % arg)
228 self.do_help('')
229 return
230 self.list_recipes(title, pnspec, False, False, show_filenames, show_multi_provider_only)
231
232
233 def list_recipes(self, title, pnspec, show_overlayed_only, show_same_ver_only, show_filenames, show_multi_provider_only):
234 pkg_pn = self.bbhandler.cooker.recipecache.pkg_pn
235 (latest_versions, preferred_versions) = bb.providers.findProviders(self.bbhandler.config_data, self.bbhandler.cooker.recipecache, pkg_pn)
236 allproviders = bb.providers.allProviders(self.bbhandler.cooker.recipecache)
237
238 # Ensure we list skipped recipes
239 # We are largely guessing about PN, PV and the preferred version here,
240 # but we have no choice since skipped recipes are not fully parsed
241 skiplist = self.bbhandler.cooker.skiplist.keys()
242 skiplist.sort( key=lambda fileitem: self.bbhandler.cooker.collection.calc_bbfile_priority(fileitem) )
243 skiplist.reverse()
244 for fn in skiplist:
245 recipe_parts = os.path.splitext(os.path.basename(fn))[0].split('_')
246 p = recipe_parts[0]
247 if len(recipe_parts) > 1:
248 ver = (None, recipe_parts[1], None)
249 else:
250 ver = (None, 'unknown', None)
251 allproviders[p].append((ver, fn))
252 if not p in pkg_pn:
253 pkg_pn[p] = 'dummy'
254 preferred_versions[p] = (ver, fn)
255
256 def print_item(f, pn, ver, layer, ispref):
257 if f in skiplist:
258 skipped = ' (skipped)'
259 else:
260 skipped = ''
261 if show_filenames:
262 if ispref:
263 logger.plain("%s%s", f, skipped)
264 else:
265 logger.plain(" %s%s", f, skipped)
266 else:
267 if ispref:
268 logger.plain("%s:", pn)
269 logger.plain(" %s %s%s", layer.ljust(20), ver, skipped)
270
271 preffiles = []
272 items_listed = False
273 for p in sorted(pkg_pn):
274 if pnspec:
275 if not fnmatch.fnmatch(p, pnspec):
276 continue
277
278 if len(allproviders[p]) > 1 or not show_multi_provider_only:
279 pref = preferred_versions[p]
280 preffile = bb.cache.Cache.virtualfn2realfn(pref[1])[0]
281 if preffile not in preffiles:
282 preflayer = self.get_file_layer(preffile)
283 multilayer = False
284 same_ver = True
285 provs = []
286 for prov in allproviders[p]:
287 provfile = bb.cache.Cache.virtualfn2realfn(prov[1])[0]
288 provlayer = self.get_file_layer(provfile)
289 provs.append((provfile, provlayer, prov[0]))
290 if provlayer != preflayer:
291 multilayer = True
292 if prov[0] != pref[0]:
293 same_ver = False
294
295 if (multilayer or not show_overlayed_only) and (same_ver or not show_same_ver_only):
296 if not items_listed:
297 logger.plain('=== %s ===' % title)
298 items_listed = True
299 print_item(preffile, p, self.version_str(pref[0][0], pref[0][1]), preflayer, True)
300 for (provfile, provlayer, provver) in provs:
301 if provfile != preffile:
302 print_item(provfile, p, self.version_str(provver[0], provver[1]), provlayer, False)
303 # Ensure we don't show two entries for BBCLASSEXTENDed recipes
304 preffiles.append(preffile)
305
306 return items_listed
307
308
309 def do_flatten(self, args):
310 """flattens layer configuration into a separate output directory.
311
312usage: flatten [layer1 layer2 [layer3]...] <outputdir>
313
314Takes the specified layers (or all layers in the current layer
315configuration if none are specified) and builds a "flattened" directory
316containing the contents of all layers, with any overlayed recipes removed
317and bbappends appended to the corresponding recipes. Note that some manual
318cleanup may still be necessary afterwards, in particular:
319
320* where non-recipe files (such as patches) are overwritten (the flatten
321 command will show a warning for these)
322* where anything beyond the normal layer setup has been added to
323 layer.conf (only the lowest priority number layer's layer.conf is used)
324* overridden/appended items from bbappends will need to be tidied up
325* when the flattened layers do not have the same directory structure (the
326 flatten command should show a warning when this will cause a problem)
327
328Warning: if you flatten several layers where another layer is intended to
329be used "inbetween" them (in layer priority order) such that recipes /
330bbappends in the layers interact, and then attempt to use the new output
331layer together with that other layer, you may no longer get the same
332build results (as the layer priority order has effectively changed).
333"""
334 arglist = args.split()
335 if len(arglist) < 1:
336 logger.error('Please specify an output directory')
337 self.do_help('flatten')
338 return
339
340 if len(arglist) == 2:
341 logger.error('If you specify layers to flatten you must specify at least two')
342 self.do_help('flatten')
343 return
344
345 outputdir = arglist[-1]
346 if os.path.exists(outputdir) and os.listdir(outputdir):
347 logger.error('Directory %s exists and is non-empty, please clear it out first' % outputdir)
348 return
349
350 self.init_bbhandler()
351 layers = self.bblayers
352 if len(arglist) > 2:
353 layernames = arglist[:-1]
354 found_layernames = []
355 found_layerdirs = []
356 for layerdir in layers:
357 layername = self.get_layer_name(layerdir)
358 if layername in layernames:
359 found_layerdirs.append(layerdir)
360 found_layernames.append(layername)
361
362 for layername in layernames:
363 if not layername in found_layernames:
364 logger.error('Unable to find layer %s in current configuration, please run "%s show-layers" to list configured layers' % (layername, os.path.basename(sys.argv[0])))
365 return
366 layers = found_layerdirs
367 else:
368 layernames = []
369
370 # Ensure a specified path matches our list of layers
371 def layer_path_match(path):
372 for layerdir in layers:
373 if path.startswith(os.path.join(layerdir, '')):
374 return layerdir
375 return None
376
377 appended_recipes = []
378 for layer in layers:
379 overlayed = []
380 for f in self.bbhandler.cooker.collection.overlayed.iterkeys():
381 for of in self.bbhandler.cooker.collection.overlayed[f]:
382 if of.startswith(layer):
383 overlayed.append(of)
384
385 logger.plain('Copying files from %s...' % layer )
386 for root, dirs, files in os.walk(layer):
387 for f1 in files:
388 f1full = os.sep.join([root, f1])
389 if f1full in overlayed:
390 logger.plain(' Skipping overlayed file %s' % f1full )
391 else:
392 ext = os.path.splitext(f1)[1]
393 if ext != '.bbappend':
394 fdest = f1full[len(layer):]
395 fdest = os.path.normpath(os.sep.join([outputdir,fdest]))
396 bb.utils.mkdirhier(os.path.dirname(fdest))
397 if os.path.exists(fdest):
398 if f1 == 'layer.conf' and root.endswith('/conf'):
399 logger.plain(' Skipping layer config file %s' % f1full )
400 continue
401 else:
402 logger.warn('Overwriting file %s', fdest)
403 bb.utils.copyfile(f1full, fdest)
404 if ext == '.bb':
405 if f1 in self.bbhandler.cooker.collection.appendlist:
406 appends = self.bbhandler.cooker.collection.appendlist[f1]
407 if appends:
408 logger.plain(' Applying appends to %s' % fdest )
409 for appendname in appends:
410 if layer_path_match(appendname):
411 self.apply_append(appendname, fdest)
412 appended_recipes.append(f1)
413
414 # Take care of when some layers are excluded and yet we have included bbappends for those recipes
415 for recipename in self.bbhandler.cooker.collection.appendlist.iterkeys():
416 if recipename not in appended_recipes:
417 appends = self.bbhandler.cooker.collection.appendlist[recipename]
418 first_append = None
419 for appendname in appends:
420 layer = layer_path_match(appendname)
421 if layer:
422 if first_append:
423 self.apply_append(appendname, first_append)
424 else:
425 fdest = appendname[len(layer):]
426 fdest = os.path.normpath(os.sep.join([outputdir,fdest]))
427 bb.utils.mkdirhier(os.path.dirname(fdest))
428 bb.utils.copyfile(appendname, fdest)
429 first_append = fdest
430
431 # Get the regex for the first layer in our list (which is where the conf/layer.conf file will
432 # have come from)
433 first_regex = None
434 layerdir = layers[0]
435 for layername, pattern, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities:
436 if regex.match(os.path.join(layerdir, 'test')):
437 first_regex = regex
438 break
439
440 if first_regex:
441 # Find the BBFILES entries that match (which will have come from this conf/layer.conf file)
442 bbfiles = str(self.bbhandler.config_data.getVar('BBFILES', True)).split()
443 bbfiles_layer = []
444 for item in bbfiles:
445 if first_regex.match(item):
446 newpath = os.path.join(outputdir, item[len(layerdir)+1:])
447 bbfiles_layer.append(newpath)
448
449 if bbfiles_layer:
450 # Check that all important layer files match BBFILES
451 for root, dirs, files in os.walk(outputdir):
452 for f1 in files:
453 ext = os.path.splitext(f1)[1]
454 if ext in ['.bb', '.bbappend']:
455 f1full = os.sep.join([root, f1])
456 entry_found = False
457 for item in bbfiles_layer:
458 if fnmatch.fnmatch(f1full, item):
459 entry_found = True
460 break
461 if not entry_found:
462 logger.warning("File %s does not match the flattened layer's BBFILES setting, you may need to edit conf/layer.conf or move the file elsewhere" % f1full)
463
464 def get_file_layer(self, filename):
465 for layer, _, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities:
466 if regex.match(filename):
467 for layerdir in self.bblayers:
468 if regex.match(os.path.join(layerdir, 'test')) and re.match(layerdir, filename):
469 return self.get_layer_name(layerdir)
470 return "?"
471
472 def get_file_layerdir(self, filename):
473 for layer, _, regex, _ in self.bbhandler.cooker.recipecache.bbfile_config_priorities:
474 if regex.match(filename):
475 for layerdir in self.bblayers:
476 if regex.match(os.path.join(layerdir, 'test')) and re.match(layerdir, filename):
477 return layerdir
478 return "?"
479
480 def remove_layer_prefix(self, f):
481 """Remove the layer_dir prefix, e.g., f = /path/to/layer_dir/foo/blah, the
482 return value will be: layer_dir/foo/blah"""
483 f_layerdir = self.get_file_layerdir(f)
484 prefix = os.path.join(os.path.dirname(f_layerdir), '')
485 return f[len(prefix):] if f.startswith(prefix) else f
486
487 def get_layer_name(self, layerdir):
488 return os.path.basename(layerdir.rstrip(os.sep))
489
490 def apply_append(self, appendname, recipename):
491 appendfile = open(appendname, 'r')
492 recipefile = open(recipename, 'a')
493 recipefile.write('\n')
494 recipefile.write('##### bbappended from %s #####\n' % self.get_file_layer(appendname))
495 recipefile.writelines(appendfile.readlines())
496 recipefile.close()
497 appendfile.close()
498
499 def do_show_appends(self, args):
500 """list bbappend files and recipe files they apply to
501
502usage: show-appends
503
504Recipes are listed with the bbappends that apply to them as subitems.
505"""
506 self.init_bbhandler()
507 if not self.bbhandler.cooker.collection.appendlist:
508 logger.plain('No append files found')
509 return
510
511 logger.plain('=== Appended recipes ===')
512
513 pnlist = list(self.bbhandler.cooker_data.pkg_pn.keys())
514 pnlist.sort()
515 for pn in pnlist:
516 self.show_appends_for_pn(pn)
517
518 self.show_appends_for_skipped()
519
520 def show_appends_for_pn(self, pn):
521 filenames = self.bbhandler.cooker_data.pkg_pn[pn]
522
523 best = bb.providers.findBestProvider(pn,
524 self.bbhandler.config_data,
525 self.bbhandler.cooker_data,
526 self.bbhandler.cooker_data.pkg_pn)
527 best_filename = os.path.basename(best[3])
528
529 self.show_appends_output(filenames, best_filename)
530
531 def show_appends_for_skipped(self):
532 filenames = [os.path.basename(f)
533 for f in self.bbhandler.cooker.skiplist.iterkeys()]
534 self.show_appends_output(filenames, None, " (skipped)")
535
536 def show_appends_output(self, filenames, best_filename, name_suffix = ''):
537 appended, missing = self.get_appends_for_files(filenames)
538 if appended:
539 for basename, appends in appended:
540 logger.plain('%s%s:', basename, name_suffix)
541 for append in appends:
542 logger.plain(' %s', append)
543
544 if best_filename:
545 if best_filename in missing:
546 logger.warn('%s: missing append for preferred version',
547 best_filename)
548 self.returncode |= 1
549
550
551 def get_appends_for_files(self, filenames):
552 appended, notappended = [], []
553 for filename in filenames:
554 _, cls = bb.cache.Cache.virtualfn2realfn(filename)
555 if cls:
556 continue
557
558 basename = os.path.basename(filename)
559 appends = self.bbhandler.cooker.collection.get_file_appends(basename)
560 if appends:
561 appended.append((basename, list(appends)))
562 else:
563 notappended.append(basename)
564 return appended, notappended
565
566 def do_show_cross_depends(self, args):
567 """figure out the dependency between recipes that crosses a layer boundary.
568
569usage: show-cross-depends [-f] [-i layer1[,layer2[,layer3...]]]
570
571Figure out the dependency between recipes that crosses a layer boundary.
572
573Options:
574 -f show full file path
575 -i ignore dependencies on items in the specified layer(s)
576
577NOTE:
578The .bbappend file can impact the dependency.
579"""
580 import optparse
581
582 parser = optparse.OptionParser(usage="show-cross-depends [-f] [-i layer1[,layer2[,layer3...]]]")
583 parser.add_option("-f", "",
584 action="store_true", dest="show_filenames")
585 parser.add_option("-i", "",
586 action="store", dest="ignore_layers", default="")
587
588 options, args = parser.parse_args(sys.argv)
589 ignore_layers = options.ignore_layers.split(',')
590
591 self.init_bbhandler()
592
593 pkg_fn = self.bbhandler.cooker_data.pkg_fn
594 bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True))
595 self.require_re = re.compile(r"require\s+(.+)")
596 self.include_re = re.compile(r"include\s+(.+)")
597 self.inherit_re = re.compile(r"inherit\s+(.+)")
598
599 global_inherit = (self.bbhandler.config_data.getVar('INHERIT', True) or "").split()
600
601 # The bb's DEPENDS and RDEPENDS
602 for f in pkg_fn:
603 f = bb.cache.Cache.virtualfn2realfn(f)[0]
604 # Get the layername that the file is in
605 layername = self.get_file_layer(f)
606
607 # The DEPENDS
608 deps = self.bbhandler.cooker_data.deps[f]
609 for pn in deps:
610 if pn in self.bbhandler.cooker_data.pkg_pn:
611 best = bb.providers.findBestProvider(pn,
612 self.bbhandler.config_data,
613 self.bbhandler.cooker_data,
614 self.bbhandler.cooker_data.pkg_pn)
615 self.check_cross_depends("DEPENDS", layername, f, best[3], options.show_filenames, ignore_layers)
616
617 # The RDPENDS
618 all_rdeps = self.bbhandler.cooker_data.rundeps[f].values()
619 # Remove the duplicated or null one.
620 sorted_rdeps = {}
621 # The all_rdeps is the list in list, so we need two for loops
622 for k1 in all_rdeps:
623 for k2 in k1:
624 sorted_rdeps[k2] = 1
625 all_rdeps = sorted_rdeps.keys()
626 for rdep in all_rdeps:
627 all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rdep)
628 if all_p:
629 if f in all_p:
630 # The recipe provides this one itself, ignore
631 continue
632 best = bb.providers.filterProvidersRunTime(all_p, rdep,
633 self.bbhandler.config_data,
634 self.bbhandler.cooker_data)[0][0]
635 self.check_cross_depends("RDEPENDS", layername, f, best, options.show_filenames, ignore_layers)
636
637 # The RRECOMMENDS
638 all_rrecs = self.bbhandler.cooker_data.runrecs[f].values()
639 # Remove the duplicated or null one.
640 sorted_rrecs = {}
641 # The all_rrecs is the list in list, so we need two for loops
642 for k1 in all_rrecs:
643 for k2 in k1:
644 sorted_rrecs[k2] = 1
645 all_rrecs = sorted_rrecs.keys()
646 for rrec in all_rrecs:
647 all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rrec)
648 if all_p:
649 if f in all_p:
650 # The recipe provides this one itself, ignore
651 continue
652 best = bb.providers.filterProvidersRunTime(all_p, rrec,
653 self.bbhandler.config_data,
654 self.bbhandler.cooker_data)[0][0]
655 self.check_cross_depends("RRECOMMENDS", layername, f, best, options.show_filenames, ignore_layers)
656
657 # The inherit class
658 cls_re = re.compile('classes/')
659 if f in self.bbhandler.cooker_data.inherits:
660 inherits = self.bbhandler.cooker_data.inherits[f]
661 for cls in inherits:
662 # The inherits' format is [classes/cls, /path/to/classes/cls]
663 # ignore the classes/cls.
664 if not cls_re.match(cls):
665 classname = os.path.splitext(os.path.basename(cls))[0]
666 if classname in global_inherit:
667 continue
668 inherit_layername = self.get_file_layer(cls)
669 if inherit_layername != layername and not inherit_layername in ignore_layers:
670 if not options.show_filenames:
671 f_short = self.remove_layer_prefix(f)
672 cls = self.remove_layer_prefix(cls)
673 else:
674 f_short = f
675 logger.plain("%s inherits %s" % (f_short, cls))
676
677 # The 'require/include xxx' in the bb file
678 pv_re = re.compile(r"\${PV}")
679 fnfile = open(f, 'r')
680 line = fnfile.readline()
681 while line:
682 m, keyword = self.match_require_include(line)
683 # Found the 'require/include xxxx'
684 if m:
685 needed_file = m.group(1)
686 # Replace the ${PV} with the real PV
687 if pv_re.search(needed_file) and f in self.bbhandler.cooker_data.pkg_pepvpr:
688 pv = self.bbhandler.cooker_data.pkg_pepvpr[f][1]
689 needed_file = re.sub(r"\${PV}", pv, needed_file)
690 self.print_cross_files(bbpath, keyword, layername, f, needed_file, options.show_filenames, ignore_layers)
691 line = fnfile.readline()
692 fnfile.close()
693
694 # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass
695 conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$")
696 inc_re = re.compile(".*\.inc$")
697 # The "inherit xxx" in .bbclass
698 bbclass_re = re.compile(".*\.bbclass$")
699 for layerdir in self.bblayers:
700 layername = self.get_layer_name(layerdir)
701 for dirpath, dirnames, filenames in os.walk(layerdir):
702 for name in filenames:
703 f = os.path.join(dirpath, name)
704 s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f)
705 if s:
706 ffile = open(f, 'r')
707 line = ffile.readline()
708 while line:
709 m, keyword = self.match_require_include(line)
710 # Only bbclass has the "inherit xxx" here.
711 bbclass=""
712 if not m and f.endswith(".bbclass"):
713 m, keyword = self.match_inherit(line)
714 bbclass=".bbclass"
715 # Find a 'require/include xxxx'
716 if m:
717 self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, options.show_filenames, ignore_layers)
718 line = ffile.readline()
719 ffile.close()
720
721 def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames, ignore_layers):
722 """Print the depends that crosses a layer boundary"""
723 needed_file = bb.utils.which(bbpath, needed_filename)
724 if needed_file:
725 # Which layer is this file from
726 needed_layername = self.get_file_layer(needed_file)
727 if needed_layername != layername and not needed_layername in ignore_layers:
728 if not show_filenames:
729 f = self.remove_layer_prefix(f)
730 needed_file = self.remove_layer_prefix(needed_file)
731 logger.plain("%s %s %s" %(f, keyword, needed_file))
732
733 def match_inherit(self, line):
734 """Match the inherit xxx line"""
735 return (self.inherit_re.match(line), "inherits")
736
737 def match_require_include(self, line):
738 """Match the require/include xxx line"""
739 m = self.require_re.match(line)
740 keyword = "requires"
741 if not m:
742 m = self.include_re.match(line)
743 keyword = "includes"
744 return (m, keyword)
745
746 def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames, ignore_layers):
747 """Print the DEPENDS/RDEPENDS file that crosses a layer boundary"""
748 best_realfn = bb.cache.Cache.virtualfn2realfn(needed_file)[0]
749 needed_layername = self.get_file_layer(best_realfn)
750 if needed_layername != layername and not needed_layername in ignore_layers:
751 if not show_filenames:
752 f = self.remove_layer_prefix(f)
753 best_realfn = self.remove_layer_prefix(best_realfn)
754
755 logger.plain("%s %s %s" % (f, keyword, best_realfn))
756
757if __name__ == '__main__':
758 sys.exit(main(sys.argv[1:]) or 0)
diff --git a/bitbake/bin/bitbake-prserv b/bitbake/bin/bitbake-prserv
new file mode 100755
index 0000000000..a8d7acb4c2
--- /dev/null
+++ b/bitbake/bin/bitbake-prserv
@@ -0,0 +1,55 @@
1#!/usr/bin/env python
2import os
3import sys,logging
4import optparse
5
6sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)),'lib'))
7
8import prserv
9import prserv.serv
10
11__version__="1.0.0"
12
13PRHOST_DEFAULT='0.0.0.0'
14PRPORT_DEFAULT=8585
15
16def main():
17 parser = optparse.OptionParser(
18 version="Bitbake PR Service Core version %s, %%prog version %s" % (prserv.__version__, __version__),
19 usage = "%prog < --start | --stop > [options]")
20
21 parser.add_option("-f", "--file", help="database filename(default: prserv.sqlite3)", action="store",
22 dest="dbfile", type="string", default="prserv.sqlite3")
23 parser.add_option("-l", "--log", help="log filename(default: prserv.log)", action="store",
24 dest="logfile", type="string", default="prserv.log")
25 parser.add_option("--loglevel", help="logging level, i.e. CRITICAL, ERROR, WARNING, INFO, DEBUG",
26 action = "store", type="string", dest="loglevel", default = "INFO")
27 parser.add_option("--start", help="start daemon",
28 action="store_true", dest="start")
29 parser.add_option("--stop", help="stop daemon",
30 action="store_true", dest="stop")
31 parser.add_option("--host", help="ip address to bind", action="store",
32 dest="host", type="string", default=PRHOST_DEFAULT)
33 parser.add_option("--port", help="port number(default: 8585)", action="store",
34 dest="port", type="int", default=PRPORT_DEFAULT)
35
36 options, args = parser.parse_args(sys.argv)
37 prserv.init_logger(os.path.abspath(options.logfile),options.loglevel)
38
39 if options.start:
40 ret=prserv.serv.start_daemon(options.dbfile, options.host, options.port,os.path.abspath(options.logfile))
41 elif options.stop:
42 ret=prserv.serv.stop_daemon(options.host, options.port)
43 else:
44 ret=parser.print_help()
45 return ret
46
47if __name__ == "__main__":
48 try:
49 ret = main()
50 except Exception:
51 ret = 1
52 import traceback
53 traceback.print_exc(5)
54 sys.exit(ret)
55
diff --git a/bitbake/bin/bitbake-selftest b/bitbake/bin/bitbake-selftest
new file mode 100755
index 0000000000..81e4c3c05d
--- /dev/null
+++ b/bitbake/bin/bitbake-selftest
@@ -0,0 +1,49 @@
1#!/usr/bin/env python
2#
3# Copyright (C) 2012 Richard Purdie
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18import os
19import sys, logging
20sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
21
22import unittest
23try:
24 import bb
25except RuntimeError as exc:
26 sys.exit(str(exc))
27
28def usage():
29 print('usage: %s [testname1 [testname2]...]' % os.path.basename(sys.argv[0]))
30
31if len(sys.argv) > 1:
32 if '--help' in sys.argv[1:]:
33 usage()
34 sys.exit(0)
35
36 tests = sys.argv[1:]
37else:
38 tests = ["bb.tests.codeparser",
39 "bb.tests.cow",
40 "bb.tests.data",
41 "bb.tests.fetch",
42 "bb.tests.utils"]
43
44for t in tests:
45 t = '.'.join(t.split('.')[:3])
46 __import__(t)
47
48unittest.main(argv=["bitbake-selftest"] + tests)
49
diff --git a/bitbake/bin/bitbake-worker b/bitbake/bin/bitbake-worker
new file mode 100755
index 0000000000..8a24161250
--- /dev/null
+++ b/bitbake/bin/bitbake-worker
@@ -0,0 +1,415 @@
1#!/usr/bin/env python
2
3import os
4import sys
5import warnings
6sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
7from bb import fetch2
8import logging
9import bb
10import select
11import errno
12import signal
13
14# Users shouldn't be running this code directly
15if len(sys.argv) != 2 or not sys.argv[1].startswith("decafbad"):
16 print("bitbake-worker is meant for internal execution by bitbake itself, please don't use it standalone.")
17 sys.exit(1)
18
19profiling = False
20if sys.argv[1] == "decafbadbad":
21 profiling = True
22 try:
23 import cProfile as profile
24 except:
25 import profile
26
27logger = logging.getLogger("BitBake")
28
29try:
30 import cPickle as pickle
31except ImportError:
32 import pickle
33 bb.msg.note(1, bb.msg.domain.Cache, "Importing cPickle failed. Falling back to a very slow implementation.")
34
35
36worker_pipe = sys.stdout.fileno()
37bb.utils.nonblockingfd(worker_pipe)
38
39handler = bb.event.LogHandler()
40logger.addHandler(handler)
41
42if 0:
43 # Code to write out a log file of all events passing through the worker
44 logfilename = "/tmp/workerlogfile"
45 format_str = "%(levelname)s: %(message)s"
46 conlogformat = bb.msg.BBLogFormatter(format_str)
47 consolelog = logging.FileHandler(logfilename)
48 bb.msg.addDefaultlogFilter(consolelog)
49 consolelog.setFormatter(conlogformat)
50 logger.addHandler(consolelog)
51
52worker_queue = ""
53
54def worker_fire(event, d):
55 data = "<event>" + pickle.dumps(event) + "</event>"
56 worker_fire_prepickled(data)
57
58def worker_fire_prepickled(event):
59 global worker_queue
60
61 worker_queue = worker_queue + event
62 worker_flush()
63
64def worker_flush():
65 global worker_queue, worker_pipe
66
67 if not worker_queue:
68 return
69
70 try:
71 written = os.write(worker_pipe, worker_queue)
72 worker_queue = worker_queue[written:]
73 except (IOError, OSError) as e:
74 if e.errno != errno.EAGAIN:
75 raise
76
77def worker_child_fire(event, d):
78 global worker_pipe
79
80 data = "<event>" + pickle.dumps(event) + "</event>"
81 worker_pipe.write(data)
82
83bb.event.worker_fire = worker_fire
84
85lf = None
86#lf = open("/tmp/workercommandlog", "w+")
87def workerlog_write(msg):
88 if lf:
89 lf.write(msg)
90 lf.flush()
91
92def sigterm_handler(signum, frame):
93 signal.signal(signal.SIGTERM, signal.SIG_DFL)
94 os.killpg(0, signal.SIGTERM)
95 sys.exit()
96
97def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdata, quieterrors=False):
98 # We need to setup the environment BEFORE the fork, since
99 # a fork() or exec*() activates PSEUDO...
100
101 envbackup = {}
102 fakeenv = {}
103 umask = None
104
105 taskdep = workerdata["taskdeps"][fn]
106 if 'umask' in taskdep and taskname in taskdep['umask']:
107 # umask might come in as a number or text string..
108 try:
109 umask = int(taskdep['umask'][taskname],8)
110 except TypeError:
111 umask = taskdep['umask'][taskname]
112
113 # We can't use the fakeroot environment in a dry run as it possibly hasn't been built
114 if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not cfg.dry_run:
115 envvars = (workerdata["fakerootenv"][fn] or "").split()
116 for key, value in (var.split('=') for var in envvars):
117 envbackup[key] = os.environ.get(key)
118 os.environ[key] = value
119 fakeenv[key] = value
120
121 fakedirs = (workerdata["fakerootdirs"][fn] or "").split()
122 for p in fakedirs:
123 bb.utils.mkdirhier(p)
124 logger.debug(2, 'Running %s:%s under fakeroot, fakedirs: %s' %
125 (fn, taskname, ', '.join(fakedirs)))
126 else:
127 envvars = (workerdata["fakerootnoenv"][fn] or "").split()
128 for key, value in (var.split('=') for var in envvars):
129 envbackup[key] = os.environ.get(key)
130 os.environ[key] = value
131 fakeenv[key] = value
132
133 sys.stdout.flush()
134 sys.stderr.flush()
135
136 try:
137 pipein, pipeout = os.pipe()
138 pipein = os.fdopen(pipein, 'rb', 4096)
139 pipeout = os.fdopen(pipeout, 'wb', 0)
140 pid = os.fork()
141 except OSError as e:
142 bb.msg.fatal("RunQueue", "fork failed: %d (%s)" % (e.errno, e.strerror))
143
144 if pid == 0:
145 def child():
146 global worker_pipe
147 pipein.close()
148
149 signal.signal(signal.SIGTERM, sigterm_handler)
150 # Let SIGHUP exit as SIGTERM
151 signal.signal(signal.SIGHUP, sigterm_handler)
152
153 # Save out the PID so that the event can include it the
154 # events
155 bb.event.worker_pid = os.getpid()
156 bb.event.worker_fire = worker_child_fire
157 worker_pipe = pipeout
158
159 # Make the child the process group leader and ensure no
160 # child process will be controlled by the current terminal
161 # This ensures signals sent to the controlling terminal like Ctrl+C
162 # don't stop the child processes.
163 os.setsid()
164 # No stdin
165 newsi = os.open(os.devnull, os.O_RDWR)
166 os.dup2(newsi, sys.stdin.fileno())
167
168 if umask:
169 os.umask(umask)
170
171 data.setVar("BB_WORKERCONTEXT", "1")
172 data.setVar("BB_TASKDEPDATA", taskdepdata)
173 data.setVar("BUILDNAME", workerdata["buildname"])
174 data.setVar("DATE", workerdata["date"])
175 data.setVar("TIME", workerdata["time"])
176 bb.parse.siggen.set_taskdata(workerdata["sigdata"])
177 ret = 0
178 try:
179 the_data = bb.cache.Cache.loadDataFull(fn, appends, data)
180 the_data.setVar('BB_TASKHASH', workerdata["runq_hash"][task])
181
182 # exported_vars() returns a generator which *cannot* be passed to os.environ.update()
183 # successfully. We also need to unset anything from the environment which shouldn't be there
184 exports = bb.data.exported_vars(the_data)
185 bb.utils.empty_environment()
186 for e, v in exports:
187 os.environ[e] = v
188 for e in fakeenv:
189 os.environ[e] = fakeenv[e]
190 the_data.setVar(e, fakeenv[e])
191 the_data.setVarFlag(e, 'export', "1")
192
193 if quieterrors:
194 the_data.setVarFlag(taskname, "quieterrors", "1")
195
196 except Exception as exc:
197 if not quieterrors:
198 logger.critical(str(exc))
199 os._exit(1)
200 try:
201 if cfg.dry_run:
202 return 0
203 return bb.build.exec_task(fn, taskname, the_data, cfg.profile)
204 except:
205 os._exit(1)
206 if not profiling:
207 os._exit(child())
208 else:
209 profname = "profile-%s.log" % (fn.replace("/", "-") + "-" + taskname)
210 prof = profile.Profile()
211 try:
212 ret = profile.Profile.runcall(prof, child)
213 finally:
214 prof.dump_stats(profname)
215 bb.utils.process_profilelog(profname)
216 os._exit(ret)
217 else:
218 for key, value in envbackup.iteritems():
219 if value is None:
220 del os.environ[key]
221 else:
222 os.environ[key] = value
223
224 return pid, pipein, pipeout
225
226class runQueueWorkerPipe():
227 """
228 Abstraction for a pipe between a worker thread and the worker server
229 """
230 def __init__(self, pipein, pipeout):
231 self.input = pipein
232 if pipeout:
233 pipeout.close()
234 bb.utils.nonblockingfd(self.input)
235 self.queue = ""
236
237 def read(self):
238 start = len(self.queue)
239 try:
240 self.queue = self.queue + self.input.read(102400)
241 except (OSError, IOError) as e:
242 if e.errno != errno.EAGAIN:
243 raise
244
245 end = len(self.queue)
246 index = self.queue.find("</event>")
247 while index != -1:
248 worker_fire_prepickled(self.queue[:index+8])
249 self.queue = self.queue[index+8:]
250 index = self.queue.find("</event>")
251 return (end > start)
252
253 def close(self):
254 while self.read():
255 continue
256 if len(self.queue) > 0:
257 print("Warning, worker child left partial message: %s" % self.queue)
258 self.input.close()
259
260normalexit = False
261
262class BitbakeWorker(object):
263 def __init__(self, din):
264 self.input = din
265 bb.utils.nonblockingfd(self.input)
266 self.queue = ""
267 self.cookercfg = None
268 self.databuilder = None
269 self.data = None
270 self.build_pids = {}
271 self.build_pipes = {}
272
273 signal.signal(signal.SIGTERM, self.sigterm_exception)
274 # Let SIGHUP exit as SIGTERM
275 signal.signal(signal.SIGHUP, self.sigterm_exception)
276
277 def sigterm_exception(self, signum, stackframe):
278 if signum == signal.SIGTERM:
279 bb.warn("Worker recieved SIGTERM, shutting down...")
280 elif signum == signal.SIGHUP:
281 bb.warn("Worker recieved SIGHUP, shutting down...")
282 self.handle_finishnow(None)
283 signal.signal(signal.SIGTERM, signal.SIG_DFL)
284 os.kill(os.getpid(), signal.SIGTERM)
285
286 def serve(self):
287 while True:
288 (ready, _, _) = select.select([self.input] + [i.input for i in self.build_pipes.values()], [] , [], 1)
289 if self.input in ready or len(self.queue):
290 start = len(self.queue)
291 try:
292 self.queue = self.queue + self.input.read()
293 except (OSError, IOError):
294 pass
295 end = len(self.queue)
296 self.handle_item("cookerconfig", self.handle_cookercfg)
297 self.handle_item("workerdata", self.handle_workerdata)
298 self.handle_item("runtask", self.handle_runtask)
299 self.handle_item("finishnow", self.handle_finishnow)
300 self.handle_item("ping", self.handle_ping)
301 self.handle_item("quit", self.handle_quit)
302
303 for pipe in self.build_pipes:
304 self.build_pipes[pipe].read()
305 if len(self.build_pids):
306 self.process_waitpid()
307 worker_flush()
308
309
310 def handle_item(self, item, func):
311 if self.queue.startswith("<" + item + ">"):
312 index = self.queue.find("</" + item + ">")
313 while index != -1:
314 func(self.queue[(len(item) + 2):index])
315 self.queue = self.queue[(index + len(item) + 3):]
316 index = self.queue.find("</" + item + ">")
317
318 def handle_cookercfg(self, data):
319 self.cookercfg = pickle.loads(data)
320 self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True)
321 self.databuilder.parseBaseConfiguration()
322 self.data = self.databuilder.data
323
324 def handle_workerdata(self, data):
325 self.workerdata = pickle.loads(data)
326 bb.msg.loggerDefaultDebugLevel = self.workerdata["logdefaultdebug"]
327 bb.msg.loggerDefaultVerbose = self.workerdata["logdefaultverbose"]
328 bb.msg.loggerVerboseLogs = self.workerdata["logdefaultverboselogs"]
329 bb.msg.loggerDefaultDomains = self.workerdata["logdefaultdomain"]
330 self.data.setVar("PRSERV_HOST", self.workerdata["prhost"])
331
332 def handle_ping(self, _):
333 workerlog_write("Handling ping\n")
334
335 logger.warn("Pong from bitbake-worker!")
336
337 def handle_quit(self, data):
338 workerlog_write("Handling quit\n")
339
340 global normalexit
341 normalexit = True
342 sys.exit(0)
343
344 def handle_runtask(self, data):
345 fn, task, taskname, quieterrors, appends, taskdepdata = pickle.loads(data)
346 workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname))
347
348 pid, pipein, pipeout = fork_off_task(self.cookercfg, self.data, self.workerdata, fn, task, taskname, appends, taskdepdata, quieterrors)
349
350 self.build_pids[pid] = task
351 self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout)
352
353 def process_waitpid(self):
354 """
355 Return none is there are no processes awaiting result collection, otherwise
356 collect the process exit codes and close the information pipe.
357 """
358 try:
359 pid, status = os.waitpid(-1, os.WNOHANG)
360 if pid == 0 or os.WIFSTOPPED(status):
361 return None
362 except OSError:
363 return None
364
365 workerlog_write("Exit code of %s for pid %s\n" % (status, pid))
366
367 if os.WIFEXITED(status):
368 status = os.WEXITSTATUS(status)
369 elif os.WIFSIGNALED(status):
370 # Per shell conventions for $?, when a process exits due to
371 # a signal, we return an exit code of 128 + SIGNUM
372 status = 128 + os.WTERMSIG(status)
373
374 task = self.build_pids[pid]
375 del self.build_pids[pid]
376
377 self.build_pipes[pid].close()
378 del self.build_pipes[pid]
379
380 worker_fire_prepickled("<exitcode>" + pickle.dumps((task, status)) + "</exitcode>")
381
382 def handle_finishnow(self, _):
383 if self.build_pids:
384 logger.info("Sending SIGTERM to remaining %s tasks", len(self.build_pids))
385 for k, v in self.build_pids.iteritems():
386 try:
387 os.kill(-k, signal.SIGTERM)
388 os.waitpid(-1, 0)
389 except:
390 pass
391 for pipe in self.build_pipes:
392 self.build_pipes[pipe].read()
393
394try:
395 worker = BitbakeWorker(sys.stdin)
396 if not profiling:
397 worker.serve()
398 else:
399 profname = "profile-worker.log"
400 prof = profile.Profile()
401 try:
402 profile.Profile.runcall(prof, worker.serve)
403 finally:
404 prof.dump_stats(profname)
405 bb.utils.process_profilelog(profname)
406except BaseException as e:
407 if not normalexit:
408 import traceback
409 sys.stderr.write(traceback.format_exc())
410 sys.stderr.write(str(e))
411while len(worker_queue):
412 worker_flush()
413workerlog_write("exitting")
414sys.exit(0)
415
diff --git a/bitbake/bin/bitdoc b/bitbake/bin/bitdoc
new file mode 100755
index 0000000000..576d88b574
--- /dev/null
+++ b/bitbake/bin/bitdoc
@@ -0,0 +1,531 @@
1#!/usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# Copyright (C) 2005 Holger Hans Peter Freyther
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20import optparse, os, sys
21
22# bitbake
23sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__), 'lib'))
24import bb
25import bb.parse
26from string import split, join
27
28__version__ = "0.0.2"
29
30class HTMLFormatter:
31 """
32 Simple class to help to generate some sort of HTML files. It is
33 quite inferior solution compared to docbook, gtkdoc, doxygen but it
34 should work for now.
35 We've a global introduction site (index.html) and then one site for
36 the list of keys (alphabetical sorted) and one for the list of groups,
37 one site for each key with links to the relations and groups.
38
39 index.html
40 all_keys.html
41 all_groups.html
42 groupNAME.html
43 keyNAME.html
44 """
45
46 def replace(self, text, *pairs):
47 """
48 From pydoc... almost identical at least
49 """
50 while pairs:
51 (a, b) = pairs[0]
52 text = join(split(text, a), b)
53 pairs = pairs[1:]
54 return text
55 def escape(self, text):
56 """
57 Escape string to be conform HTML
58 """
59 return self.replace(text,
60 ('&', '&amp;'),
61 ('<', '&lt;' ),
62 ('>', '&gt;' ) )
63 def createNavigator(self):
64 """
65 Create the navgiator
66 """
67 return """<table class="navigation" width="100%" summary="Navigation header" cellpadding="2" cellspacing="2">
68<tr valign="middle">
69<td><a accesskey="g" href="index.html">Home</a></td>
70<td><a accesskey="n" href="all_groups.html">Groups</a></td>
71<td><a accesskey="u" href="all_keys.html">Keys</a></td>
72</tr></table>
73"""
74
75 def relatedKeys(self, item):
76 """
77 Create HTML to link to foreign keys
78 """
79
80 if len(item.related()) == 0:
81 return ""
82
83 txt = "<p><b>See also:</b><br>"
84 txts = []
85 for it in item.related():
86 txts.append("""<a href="key%(it)s.html">%(it)s</a>""" % vars() )
87
88 return txt + ",".join(txts)
89
90 def groups(self, item):
91 """
92 Create HTML to link to related groups
93 """
94
95 if len(item.groups()) == 0:
96 return ""
97
98
99 txt = "<p><b>See also:</b><br>"
100 txts = []
101 for group in item.groups():
102 txts.append( """<a href="group%s.html">%s</a> """ % (group, group) )
103
104 return txt + ",".join(txts)
105
106
107 def createKeySite(self, item):
108 """
109 Create a site for a key. It contains the header/navigator, a heading,
110 the description, links to related keys and to the groups.
111 """
112
113 return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
114<html><head><title>Key %s</title></head>
115<link rel="stylesheet" href="style.css" type="text/css">
116<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
117%s
118<h2><span class="refentrytitle">%s</span></h2>
119
120<div class="refsynopsisdiv">
121<h2>Synopsis</h2>
122<p>
123%s
124</p>
125</div>
126
127<div class="refsynopsisdiv">
128<h2>Related Keys</h2>
129<p>
130%s
131</p>
132</div>
133
134<div class="refsynopsisdiv">
135<h2>Groups</h2>
136<p>
137%s
138</p>
139</div>
140
141
142</body>
143""" % (item.name(), self.createNavigator(), item.name(),
144 self.escape(item.description()), self.relatedKeys(item), self.groups(item))
145
146 def createGroupsSite(self, doc):
147 """
148 Create the Group Overview site
149 """
150
151 groups = ""
152 sorted_groups = sorted(doc.groups())
153 for group in sorted_groups:
154 groups += """<a href="group%s.html">%s</a><br>""" % (group, group)
155
156 return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
157<html><head><title>Group overview</title></head>
158<link rel="stylesheet" href="style.css" type="text/css">
159<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
160%s
161<h2>Available Groups</h2>
162%s
163</body>
164""" % (self.createNavigator(), groups)
165
166 def createIndex(self):
167 """
168 Create the index file
169 """
170
171 return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
172<html><head><title>Bitbake Documentation</title></head>
173<link rel="stylesheet" href="style.css" type="text/css">
174<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
175%s
176<h2>Documentation Entrance</h2>
177<a href="all_groups.html">All available groups</a><br>
178<a href="all_keys.html">All available keys</a><br>
179</body>
180""" % self.createNavigator()
181
182 def createKeysSite(self, doc):
183 """
184 Create Overview of all avilable keys
185 """
186 keys = ""
187 sorted_keys = sorted(doc.doc_keys())
188 for key in sorted_keys:
189 keys += """<a href="key%s.html">%s</a><br>""" % (key, key)
190
191 return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
192<html><head><title>Key overview</title></head>
193<link rel="stylesheet" href="style.css" type="text/css">
194<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
195%s
196<h2>Available Keys</h2>
197%s
198</body>
199""" % (self.createNavigator(), keys)
200
201 def createGroupSite(self, gr, items, _description = None):
202 """
203 Create a site for a group:
204 Group the name of the group, items contain the name of the keys
205 inside this group
206 """
207 groups = ""
208 description = ""
209
210 # create a section with the group descriptions
211 if _description:
212 description += "<h2 Description of Grozp %s</h2>" % gr
213 description += _description
214
215 items.sort(lambda x, y:cmp(x.name(), y.name()))
216 for group in items:
217 groups += """<a href="key%s.html">%s</a><br>""" % (group.name(), group.name())
218
219 return """<!doctype html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
220<html><head><title>Group %s</title></head>
221<link rel="stylesheet" href="style.css" type="text/css">
222<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
223%s
224%s
225<div class="refsynopsisdiv">
226<h2>Keys in Group %s</h2>
227<pre class="synopsis">
228%s
229</pre>
230</div>
231</body>
232""" % (gr, self.createNavigator(), description, gr, groups)
233
234
235
236 def createCSS(self):
237 """
238 Create the CSS file
239 """
240 return """.synopsis, .classsynopsis
241{
242 background: #eeeeee;
243 border: solid 1px #aaaaaa;
244 padding: 0.5em;
245}
246.programlisting
247{
248 background: #eeeeff;
249 border: solid 1px #aaaaff;
250 padding: 0.5em;
251}
252.variablelist
253{
254 padding: 4px;
255 margin-left: 3em;
256}
257.variablelist td:first-child
258{
259 vertical-align: top;
260}
261table.navigation
262{
263 background: #ffeeee;
264 border: solid 1px #ffaaaa;
265 margin-top: 0.5em;
266 margin-bottom: 0.5em;
267}
268.navigation a
269{
270 color: #770000;
271}
272.navigation a:visited
273{
274 color: #550000;
275}
276.navigation .title
277{
278 font-size: 200%;
279}
280div.refnamediv
281{
282 margin-top: 2em;
283}
284div.gallery-float
285{
286 float: left;
287 padding: 10px;
288}
289div.gallery-float img
290{
291 border-style: none;
292}
293div.gallery-spacer
294{
295 clear: both;
296}
297a
298{
299 text-decoration: none;
300}
301a:hover
302{
303 text-decoration: underline;
304 color: #FF0000;
305}
306"""
307
308
309
310class DocumentationItem:
311 """
312 A class to hold information about a configuration
313 item. It contains the key name, description, a list of related names,
314 and the group this item is contained in.
315 """
316
317 def __init__(self):
318 self._groups = []
319 self._related = []
320 self._name = ""
321 self._desc = ""
322
323 def groups(self):
324 return self._groups
325
326 def name(self):
327 return self._name
328
329 def description(self):
330 return self._desc
331
332 def related(self):
333 return self._related
334
335 def setName(self, name):
336 self._name = name
337
338 def setDescription(self, desc):
339 self._desc = desc
340
341 def addGroup(self, group):
342 self._groups.append(group)
343
344 def addRelation(self, relation):
345 self._related.append(relation)
346
347 def sort(self):
348 self._related.sort()
349 self._groups.sort()
350
351
352class Documentation:
353 """
354 Holds the documentation... with mappings from key to items...
355 """
356
357 def __init__(self):
358 self.__keys = {}
359 self.__groups = {}
360
361 def insert_doc_item(self, item):
362 """
363 Insert the Doc Item into the internal list
364 of representation
365 """
366 item.sort()
367 self.__keys[item.name()] = item
368
369 for group in item.groups():
370 if not group in self.__groups:
371 self.__groups[group] = []
372 self.__groups[group].append(item)
373 self.__groups[group].sort()
374
375
376 def doc_item(self, key):
377 """
378 Return the DocumentationInstance describing the key
379 """
380 try:
381 return self.__keys[key]
382 except KeyError:
383 return None
384
385 def doc_keys(self):
386 """
387 Return the documented KEYS (names)
388 """
389 return self.__keys.keys()
390
391 def groups(self):
392 """
393 Return the names of available groups
394 """
395 return self.__groups.keys()
396
397 def group_content(self, group_name):
398 """
399 Return a list of keys/names that are in a specefic
400 group or the empty list
401 """
402 try:
403 return self.__groups[group_name]
404 except KeyError:
405 return []
406
407
408def parse_cmdline(args):
409 """
410 Parse the CMD line and return the result as a n-tuple
411 """
412
413 parser = optparse.OptionParser( version = "Bitbake Documentation Tool Core version %s, %%prog version %s" % (bb.__version__, __version__))
414 usage = """%prog [options]
415
416Create a set of html pages (documentation) for a bitbake.conf....
417"""
418
419 # Add the needed options
420 parser.add_option( "-c", "--config", help = "Use the specified configuration file as source",
421 action = "store", dest = "config", default = os.path.join("conf", "documentation.conf") )
422
423 parser.add_option( "-o", "--output", help = "Output directory for html files",
424 action = "store", dest = "output", default = "html/" )
425
426 parser.add_option( "-D", "--debug", help = "Increase the debug level",
427 action = "count", dest = "debug", default = 0 )
428
429 parser.add_option( "-v", "--verbose", help = "output more chit-char to the terminal",
430 action = "store_true", dest = "verbose", default = False )
431
432 options, args = parser.parse_args( sys.argv )
433
434 bb.msg.init_msgconfig(options.verbose, options.debug)
435
436 return options.config, options.output
437
438def main():
439 """
440 The main Method
441 """
442
443 (config_file, output_dir) = parse_cmdline( sys.argv )
444
445 # right to let us load the file now
446 try:
447 documentation = bb.parse.handle( config_file, bb.data.init() )
448 except IOError:
449 bb.fatal( "Unable to open %s" % config_file )
450 except bb.parse.ParseError:
451 bb.fatal( "Unable to parse %s" % config_file )
452
453 if isinstance(documentation, dict):
454 documentation = documentation[""]
455
456 # Assuming we've the file loaded now, we will initialize the 'tree'
457 doc = Documentation()
458
459 # defined states
460 state_begin = 0
461 state_see = 1
462 state_group = 2
463
464 for key in bb.data.keys(documentation):
465 data = documentation.getVarFlag(key, "doc")
466 if not data:
467 continue
468
469 # The Documentation now starts
470 doc_ins = DocumentationItem()
471 doc_ins.setName(key)
472
473
474 tokens = data.split(' ')
475 state = state_begin
476 string= ""
477 for token in tokens:
478 token = token.strip(',')
479
480 if not state == state_see and token == "@see":
481 state = state_see
482 continue
483 elif not state == state_group and token == "@group":
484 state = state_group
485 continue
486
487 if state == state_begin:
488 string += " %s" % token
489 elif state == state_see:
490 doc_ins.addRelation(token)
491 elif state == state_group:
492 doc_ins.addGroup(token)
493
494 # set the description
495 doc_ins.setDescription(string)
496 doc.insert_doc_item(doc_ins)
497
498 # let us create the HTML now
499 bb.utils.mkdirhier(output_dir)
500 os.chdir(output_dir)
501
502 # Let us create the sites now. We do it in the following order
503 # Start with the index.html. It will point to sites explaining all
504 # keys and groups
505 html_slave = HTMLFormatter()
506
507 f = file('style.css', 'w')
508 print >> f, html_slave.createCSS()
509
510 f = file('index.html', 'w')
511 print >> f, html_slave.createIndex()
512
513 f = file('all_groups.html', 'w')
514 print >> f, html_slave.createGroupsSite(doc)
515
516 f = file('all_keys.html', 'w')
517 print >> f, html_slave.createKeysSite(doc)
518
519 # now for each group create the site
520 for group in doc.groups():
521 f = file('group%s.html' % group, 'w')
522 print >> f, html_slave.createGroupSite(group, doc.group_content(group))
523
524 # now for the keys
525 for key in doc.doc_keys():
526 f = file('key%s.html' % doc.doc_item(key).name(), 'w')
527 print >> f, html_slave.createKeySite(doc.doc_item(key))
528
529
530if __name__ == "__main__":
531 main()
diff --git a/bitbake/bin/image-writer b/bitbake/bin/image-writer
new file mode 100755
index 0000000000..86c38b5769
--- /dev/null
+++ b/bitbake/bin/image-writer
@@ -0,0 +1,122 @@
1#!/usr/bin/env python
2
3# Copyright (c) 2012 Wind River Systems, Inc.
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12# See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18import os
19import sys
20sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( \
21 os.path.abspath(__file__))), 'lib'))
22try:
23 import bb
24except RuntimeError as exc:
25 sys.exit(str(exc))
26
27import gtk
28import optparse
29import pygtk
30
31from bb.ui.crumbs.hobwidget import HobAltButton, HobButton
32from bb.ui.crumbs.hig.crumbsmessagedialog import CrumbsMessageDialog
33from bb.ui.crumbs.hig.deployimagedialog import DeployImageDialog
34from bb.ui.crumbs.hig.imageselectiondialog import ImageSelectionDialog
35
36# I put all the fs bitbake supported here. Need more test.
37DEPLOYABLE_IMAGE_TYPES = ["jffs2", "cramfs", "ext2", "ext3", "btrfs", "squashfs", "ubi", "vmdk"]
38Title = "USB Image Writer"
39
40class DeployWindow(gtk.Window):
41 def __init__(self, image_path=''):
42 super(DeployWindow, self).__init__()
43
44 if len(image_path) > 0:
45 valid = True
46 if not os.path.exists(image_path):
47 valid = False
48 lbl = "<b>Invalid image file path: %s.</b>\nPress <b>Select Image</b> to select an image." % image_path
49 else:
50 image_path = os.path.abspath(image_path)
51 extend_name = os.path.splitext(image_path)[1][1:]
52 if extend_name not in DEPLOYABLE_IMAGE_TYPES:
53 valid = False
54 lbl = "<b>Undeployable imge type: %s</b>\nPress <b>Select Image</b> to select an image." % extend_name
55
56 if not valid:
57 image_path = ''
58 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
59 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
60 HobButton.style_button(button)
61 crumbs_dialog.run()
62 crumbs_dialog.destroy()
63
64 self.deploy_dialog = DeployImageDialog(Title, image_path, self,
65 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
66 | gtk.DIALOG_NO_SEPARATOR, None, standalone=True)
67 close_button = self.deploy_dialog.add_button("Close", gtk.RESPONSE_NO)
68 HobAltButton.style_button(close_button)
69 close_button.connect('clicked', gtk.main_quit)
70
71 write_button = self.deploy_dialog.add_button("Write USB image", gtk.RESPONSE_YES)
72 HobAltButton.style_button(write_button)
73
74 self.deploy_dialog.connect('select_image_clicked', self.select_image_clicked_cb)
75 self.deploy_dialog.connect('destroy', gtk.main_quit)
76 response = self.deploy_dialog.show()
77
78 def select_image_clicked_cb(self, dialog):
79 cwd = os.getcwd()
80 dialog = ImageSelectionDialog(cwd, DEPLOYABLE_IMAGE_TYPES, Title, self, gtk.FILE_CHOOSER_ACTION_SAVE )
81 button = dialog.add_button("Cancel", gtk.RESPONSE_NO)
82 HobAltButton.style_button(button)
83 button = dialog.add_button("Open", gtk.RESPONSE_YES)
84 HobAltButton.style_button(button)
85 response = dialog.run()
86
87 if response == gtk.RESPONSE_YES:
88 if not dialog.image_names:
89 lbl = "<b>No selections made</b>\nClicked the radio button to select a image."
90 crumbs_dialog = CrumbsMessageDialog(self, lbl, gtk.STOCK_DIALOG_INFO)
91 button = crumbs_dialog.add_button("Close", gtk.RESPONSE_OK)
92 HobButton.style_button(button)
93 crumbs_dialog.run()
94 crumbs_dialog.destroy()
95 dialog.destroy()
96 return
97
98 # get the full path of image
99 image_path = os.path.join(dialog.image_folder, dialog.image_names[0])
100 self.deploy_dialog.set_image_text_buffer(image_path)
101 self.deploy_dialog.set_image_path(image_path)
102
103 dialog.destroy()
104
105def main():
106 parser = optparse.OptionParser(
107 usage = """%prog [-h] [image_file]
108
109%prog writes bootable images to USB devices. You can
110provide the image file on the command line or select it using the GUI.""")
111
112 options, args = parser.parse_args(sys.argv)
113 image_file = args[1] if len(args) > 1 else ''
114 dw = DeployWindow(image_file)
115
116if __name__ == '__main__':
117 try:
118 main()
119 gtk.main()
120 except Exception:
121 import traceback
122 traceback.print_exc(3)
diff --git a/bitbake/bin/toaster b/bitbake/bin/toaster
new file mode 100755
index 0000000000..75c7a076b1
--- /dev/null
+++ b/bitbake/bin/toaster
@@ -0,0 +1,267 @@
1#!/bin/bash
2# (c) 2013 Intel Corp.
3
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program; if not, write to the Free Software
16# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19# This script can be run in two modes.
20
21# When used with "source", from a build directory,
22# it enables toaster event logging and starts the bitbake resident server.
23# use as: source toaster [start|stop] [noweb] [noui]
24
25# When it is called as a stand-alone script, it starts just the
26# web server, and the building shall be done through the web interface.
27# As script, it will not return to the command prompt. Stop with Ctrl-C.
28
29# Helper function to kill a background toaster development server
30
31function webserverKillAll()
32{
33 local pidfile
34 for pidfile in ${BUILDDIR}/.toastermain.pid; do
35 if [ -f ${pidfile} ]; then
36 while kill -0 $(< ${pidfile}) 2>/dev/null; do
37 kill -SIGTERM -$(< ${pidfile}) 2>/dev/null
38 sleep 1;
39 # Kill processes if they are still running - may happen in interactive shells
40 ps fux | grep "python.*manage.py" | awk '{print $2}' | xargs kill
41 done;
42 rm ${pidfile}
43 fi
44 done
45}
46
47function webserverStartAll()
48{
49 # do not start if toastermain points to a valid process
50 if ! cat "${BUILDDIR}/.toastermain.pid" 2>/dev/null | xargs -I{} kill -0 {} ; then
51 retval=1
52 rm "${BUILDDIR}/.toastermain.pid"
53 fi
54
55 retval=0
56 python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1
57 python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=2
58 if [ $retval -eq 1 ]; then
59 echo "Failed db sync, stopping system start" 1>&2
60 elif [ $retval -eq 2 ]; then
61 echo -e "\nError on migration, trying to recover... \n"
62 python $BBBASEDIR/lib/toaster/manage.py migrate orm 0001_initial --fake
63 retval=0
64 python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=1
65 fi
66 if [ "x$TOASTER_MANAGED" == "x1" ]; then
67 python $BBBASEDIR/lib/toaster/manage.py migrate bldcontrol || retval=1
68 python $BBBASEDIR/lib/toaster/manage.py checksettings --traceback || retval=1
69 fi
70 if [ $retval -eq 0 ]; then
71 echo "Starting webserver"
72 python $BBBASEDIR/lib/toaster/manage.py runserver 0.0.0.0:8000 </dev/null >${BUILDDIR}/toaster_web.log 2>&1 & echo $! >${BUILDDIR}/.toastermain.pid
73 sleep 1
74 if ! cat "${BUILDDIR}/.toastermain.pid" | xargs -I{} kill -0 {} ; then
75 retval=1
76 rm "${BUILDDIR}/.toastermain.pid"
77 fi
78 fi
79 return $retval
80}
81
82# Helper functions to add a special configuration file
83
84function addtoConfiguration()
85{
86 echo "#Created by toaster start script" > ${BUILDDIR}/conf/$2
87 echo $1 >> ${BUILDDIR}/conf/$2
88}
89
90INSTOPSYSTEM=0
91
92# define the stop command
93function stop_system()
94{
95 # prevent reentry
96 if [ $INSTOPSYSTEM == 1 ]; then return; fi
97 INSTOPSYSTEM=1
98 if [ -f ${BUILDDIR}/.toasterui.pid ]; then
99 kill $(< ${BUILDDIR}/.toasterui.pid ) 2>/dev/null
100 rm ${BUILDDIR}/.toasterui.pid
101 fi
102 BBSERVER=0.0.0.0:8200 bitbake -m
103 unset BBSERVER
104 webserverKillAll
105 # force stop any misbehaving bitbake server
106 lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 -r kill
107 trap - SIGHUP
108 #trap - SIGCHLD
109 INSTOPSYSTEM=0
110}
111
112function check_pidbyfile() {
113 [ -e $1 ] && kill -0 $(< $1) 2>/dev/null
114}
115
116
117function notify_chldexit() {
118 if [ $NOTOASTERUI == 0 ]; then
119 check_pidbyfile ${BUILDDIR}/.toasterui.pid && return
120 stop_system
121 fi
122}
123
124
125BBBASEDIR=`dirname ${BASH_SOURCE}`/..
126RUNNING=0
127
128if [ -z "$ZSH_NAME" ] && [ `basename \"$0\"` = `basename \"$BASH_SOURCE\"` ]; then
129 # We are called as standalone. We refuse to run in a build environment - we need the interactive mode for that.
130 # Start just the web server, point the web browser to the interface, and start any Django services.
131
132 if [ -n "$BUILDDIR" ]; then
133 echo -e "Error: build/ directory detected. Toaster will not start in managed mode if a build environment is detected.\nUse a clean terminal to start Toaster." 1>&2;
134 exit 1;
135 fi
136
137 # Define a fake builddir where only the pid files are actually created. No real builds will take place here.
138 BUILDDIR=/tmp
139 RUNNING=1
140 function trap_ctrlc() {
141 echo "** Stopping system"
142 webserverKillAll
143 RUNNING=0
144 }
145 TOASTER_MANAGED=1
146 export TOASTER_MANAGED=1
147 if ! webserverStartAll; then
148 echo "Failed to start the web server, stopping" 1>&2;
149 exit 1;
150 fi
151 xdg-open http://0.0.0.0:8000/ >/dev/null 2>&1 &
152 trap trap_ctrlc SIGINT
153 echo "Running. Stop with Ctrl-C"
154 while [ $RUNNING -gt 0 ]; do
155 python $BBBASEDIR/lib/toaster/manage.py runbuilds
156 sleep 1
157 done
158 echo "**** Exit"
159 exit 0
160fi
161
162# We make sure we're running in the current shell and in a good environment
163if [ -z "$BUILDDIR" ] || [ -z `which bitbake` ]; then
164 echo "Error: Build environment is not setup or bitbake is not in path." 1>&2;
165 return 2
166fi
167
168
169
170# Verify prerequisites
171
172if ! echo "import django; print (1,) == django.VERSION[0:1] and django.VERSION[1:2][0] in (5,6)" | python 2>/dev/null | grep True >/dev/null; then
173 echo -e "This program needs Django 1.5 or 1.6. Please install with\n\npip install django==1.6"
174 return 2
175fi
176
177if ! echo "import south; print [0,8,4] == map(int,south.__version__.split(\".\"))" | python 2>/dev/null | grep True >/dev/null; then
178 echo -e "This program needs South 0.8.4. Please install with\n\npip install south==0.8.4"
179 return 2
180fi
181
182
183
184
185
186# Determine the action. If specified by arguments, fine, if not, toggle it
187if [ "x$1" == "xstart" ] || [ "x$1" == "xstop" ]; then
188 CMD="$1"
189else
190 if [ -z "$BBSERVER" ]; then
191 CMD="start"
192 else
193 CMD="stop"
194 fi;
195fi
196
197NOTOASTERUI=0
198WEBSERVER=1
199for param in $*; do
200 case $param in
201 noui )
202 NOTOASTERUI=1
203 ;;
204 noweb )
205 WEBSERVER=0
206 ;;
207 esac
208done
209
210echo "The system will $CMD."
211
212# Make sure it's safe to run by checking bitbake lock
213
214lock=1
215if [ -e $BUILDDIR/bitbake.lock ]; then
216 (flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0
217fi
218
219if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/.toastermain.pid ] ); then
220 echo "Error: bitbake lock state error. File locks show that the system is on." 2>&1
221 echo "If you see problems, stop and then start the system again." 2>&1
222 return 3
223fi
224
225
226# Execute the commands
227
228case $CMD in
229 start )
230 start_success=1
231 addtoConfiguration "INHERIT+=\"toaster buildhistory\"" toaster.conf
232 if [ $WEBSERVER -gt 0 ] && ! webserverStartAll; then
233 echo "Failed ${CMD}."
234 return 4
235 fi
236 unset BBSERVER
237 bitbake --postread conf/toaster.conf --server-only -t xmlrpc -B 0.0.0.0:8200
238 if [ $? -ne 0 ]; then
239 start_success=0
240 echo "Bitbake server start failed"
241 else
242 export BBSERVER=0.0.0.0:8200
243 if [ $NOTOASTERUI == 0 ]; then # we start the TOASTERUI only if not inhibited
244 bitbake --observe-only -u toasterui >${BUILDDIR}/toaster_ui.log 2>&1 & echo $! >${BUILDDIR}/.toasterui.pid
245 fi
246 fi
247 if [ $start_success -eq 1 ]; then
248 # set fail safe stop system on terminal exit
249 trap stop_system SIGHUP
250 echo "Successful ${CMD}."
251 else
252 # failed start, do stop
253 stop_system
254 echo "Failed ${CMD}."
255 fi
256 # stop system on terminal exit
257 set -o monitor
258 trap stop_system SIGHUP
259 #trap notify_chldexit SIGCHLD
260 ;;
261 stop )
262 stop_system
263 echo "Successful ${CMD}."
264 ;;
265esac
266
267
diff --git a/bitbake/bin/toaster-eventreplay b/bitbake/bin/toaster-eventreplay
new file mode 100755
index 0000000000..624829aea0
--- /dev/null
+++ b/bitbake/bin/toaster-eventreplay
@@ -0,0 +1,179 @@
1#!/usr/bin/env python
2# ex:ts=4:sw=4:sts=4:et
3# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4#
5# Copyright (C) 2014 Alex Damian
6#
7# This file re-uses code spread throughout other Bitbake source files.
8# As such, all other copyrights belong to their own right holders.
9#
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License version 2 as
13# published by the Free Software Foundation.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24
25# This command takes a filename as a single parameter. The filename is read
26# as a build eventlog, and the ToasterUI is used to process events in the file
27# and log data in the database
28
29import os
30import sys, logging
31
32# mangle syspath to allow easy import of modules
33sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
34 'lib'))
35
36
37import bb.cooker
38from bb.ui import toasterui
39import sys
40import logging
41
42logger = logging.getLogger(__name__)
43console = logging.StreamHandler(sys.stdout)
44format_str = "%(levelname)s: %(message)s"
45logging.basicConfig(format=format_str)
46
47
48import json, pickle
49
50
51class FileReadEventsServerConnection():
52 """ Emulates a connection to a bitbake server that feeds
53 events coming actually read from a saved log file.
54 """
55
56 class MockConnection():
57 """ fill-in for the proxy to the server. we just return generic data
58 """
59 def __init__(self, sc):
60 self._sc = sc
61
62 def runCommand(self, commandArray):
63 """ emulates running a command on the server; only read-only commands are accepted """
64 command_name = commandArray[0]
65
66 if command_name == "getVariable":
67 if commandArray[1] in self._sc._variables:
68 return (self._sc._variables[commandArray[1]]['v'], None)
69 return (None, "Missing variable")
70
71 elif command_name == "getAllKeysWithFlags":
72 dump = {}
73 flaglist = commandArray[1]
74 for k in self._sc._variables.keys():
75 try:
76 if not k.startswith("__"):
77 v = self._sc._variables[k]['v']
78 dump[k] = {
79 'v' : v ,
80 'history' : self._sc._variables[k]['history'],
81 }
82 for d in flaglist:
83 dump[k][d] = self._sc._variables[k][d]
84 except Exception as e:
85 print(e)
86 return (dump, None)
87 else:
88 raise Exception("Command %s not implemented" % commandArray[0])
89
90 def terminateServer(self):
91 """ do not do anything """
92 pass
93
94
95
96 class EventReader():
97 def __init__(self, sc):
98 self._sc = sc
99 self.firstraise = 0
100
101 def _create_event(self, line):
102 def _import_class(name):
103 assert len(name) > 0
104 assert "." in name, name
105
106 components = name.strip().split(".")
107 modulename = ".".join(components[:-1])
108 moduleklass = components[-1]
109
110 module = __import__(modulename, fromlist=[str(moduleklass)])
111 return getattr(module, moduleklass)
112
113 # we build a toaster event out of current event log line
114 try:
115 event_data = json.loads(line.strip())
116 event_class = _import_class(event_data['class'])
117 event_object = pickle.loads(json.loads(event_data['vars']))
118 except ValueError as e:
119 print("Failed loading ", line)
120 raise e
121
122 if not isinstance(event_object, event_class):
123 raise Exception("Error loading objects %s class %s ", event_object, event_class)
124
125 return event_object
126
127 def waitEvent(self, timeout):
128
129 nextline = self._sc._eventfile.readline()
130 if len(nextline) == 0:
131 # the build data ended, while toasterui still waits for events.
132 # this happens when the server was abruptly stopped, so we simulate this
133 self.firstraise += 1
134 if self.firstraise == 1:
135 raise KeyboardInterrupt()
136 else:
137 return None
138 else:
139 self._sc.lineno += 1
140 return self._create_event(nextline)
141
142
143 def _readVariables(self, variableline):
144 self._variables = json.loads(variableline.strip())['allvariables']
145
146
147 def __init__(self, file_name):
148 self.connection = FileReadEventsServerConnection.MockConnection(self)
149 self._eventfile = open(file_name, "r")
150
151 # we expect to have the variable dump at the start of the file
152 self.lineno = 1
153 self._readVariables(self._eventfile.readline())
154
155 self.events = FileReadEventsServerConnection.EventReader(self)
156
157
158
159
160
161class MockConfigParameters():
162 """ stand-in for cookerdata.ConfigParameters; as we don't really config a cooker, this
163 serves just to supply needed interfaces for the toaster ui to work """
164 def __init__(self):
165 self.observe_only = True # we can only read files
166
167
168# run toaster ui on our mock bitbake class
169if __name__ == "__main__":
170 if len(sys.argv) < 2:
171 logger.error("Usage: %s event.log " % sys.argv[0])
172 sys.exit(1)
173
174 file_name = sys.argv[-1]
175 mock_connection = FileReadEventsServerConnection(file_name)
176 configParams = MockConfigParameters()
177
178 # run the main program
179 toasterui.main(mock_connection.connection, mock_connection.events, configParams)