summaryrefslogtreecommitdiffstats
path: root/bitbake/bin
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-xbitbake/bin/bitbake362
-rwxr-xr-xbitbake/bin/bitbake-diffsigs122
-rwxr-xr-xbitbake/bin/bitbake-dumpsig65
-rwxr-xr-xbitbake/bin/bitbake-layers726
-rwxr-xr-xbitbake/bin/bitbake-prserv55
-rwxr-xr-xbitbake/bin/bitbake-selftest49
-rwxr-xr-xbitbake/bin/bitbake-worker375
-rwxr-xr-xbitbake/bin/bitdoc531
-rwxr-xr-xbitbake/bin/image-writer122
-rwxr-xr-xbitbake/bin/toaster214
10 files changed, 2621 insertions, 0 deletions
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
new file mode 100755
index 0000000000..846d059319
--- /dev/null
+++ b/bitbake/bin/bitbake
@@ -0,0 +1,362 @@
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.23.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. Parameters are passed to the signature handling code, use 'none' if no specific handler is required.",
143 action = "append", dest = "dump_signatures", default = [])
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-package 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("", "--revisions-changed", help = "Set the exit code depending on whether upstream floating revisions have changed or not.",
173 action = "store_true", dest = "revisions_changed", default = False)
174
175 parser.add_option("", "--server-only", help = "Run bitbake without a UI, only starting a server (cooker) process.",
176 action = "store_true", dest = "server_only", default = False)
177
178 parser.add_option("-B", "--bind", help = "The name/address for the bitbake server to bind to.",
179 action = "store", dest = "bind", default = False)
180
181 parser.add_option("", "--no-setscene", help = "Do not run any setscene tasks. sstate will be ignored and everything needed, built.",
182 action = "store_true", dest = "nosetscene", default = False)
183
184 parser.add_option("", "--remote-server", help = "Connect to the specified server.",
185 action = "store", dest = "remote_server", default = False)
186
187 parser.add_option("-m", "--kill-server", help = "Terminate the remote server.",
188 action = "store_true", dest = "kill_server", default = False)
189
190 parser.add_option("", "--observe-only", help = "Connect to a server as an observing-only client.",
191 action = "store_true", dest = "observe_only", default = False)
192
193 parser.add_option("", "--status-only", help = "Check the status of the remote bitbake server.",
194 action = "store_true", dest = "status_only", default = False)
195
196 options, targets = parser.parse_args(sys.argv)
197
198 # some environmental variables set also configuration options
199 if "BBSERVER" in os.environ:
200 options.servertype = "xmlrpc"
201 options.remote_server = os.environ["BBSERVER"]
202
203 return options, targets[1:]
204
205
206def start_server(servermodule, configParams, configuration, features):
207 server = servermodule.BitBakeServer()
208 if configParams.bind:
209 (host, port) = configParams.bind.split(':')
210 server.initServer((host, int(port)))
211 configuration.interface = [ server.serverImpl.host, server.serverImpl.port ]
212 else:
213 server.initServer()
214 configuration.interface = []
215
216 try:
217 configuration.setServerRegIdleCallback(server.getServerIdleCB())
218
219 cooker = bb.cooker.BBCooker(configuration, features)
220
221 server.addcooker(cooker)
222 server.saveConnectionDetails()
223 except Exception as e:
224 exc_info = sys.exc_info()
225 while hasattr(server, "event_queue"):
226 try:
227 import queue
228 except ImportError:
229 import Queue as queue
230 try:
231 event = server.event_queue.get(block=False)
232 except (queue.Empty, IOError):
233 break
234 if isinstance(event, logging.LogRecord):
235 logger.handle(event)
236 raise exc_info[1], None, exc_info[2]
237 server.detach()
238 return server
239
240
241
242def main():
243
244 configParams = BitBakeConfigParameters()
245 configuration = cookerdata.CookerConfiguration()
246 configuration.setConfigParameters(configParams)
247
248 ui_module = get_ui(configParams)
249
250 # Server type can be xmlrpc or process currently, if nothing is specified,
251 # the default server is process
252 if configParams.servertype:
253 server_type = configParams.servertype
254 else:
255 server_type = 'process'
256
257 try:
258 module = __import__("bb.server", fromlist = [server_type])
259 servermodule = getattr(module, server_type)
260 except AttributeError:
261 sys.exit("FATAL: Invalid server type '%s' specified.\n"
262 "Valid interfaces: xmlrpc, process [default]." % server_type)
263
264 if configParams.server_only:
265 if configParams.servertype != "xmlrpc":
266 sys.exit("FATAL: If '--server-only' is defined, we must set the servertype as 'xmlrpc'.\n")
267 if not configParams.bind:
268 sys.exit("FATAL: The '--server-only' option requires a name/address to bind to with the -B option.\n")
269 if configParams.remote_server:
270 sys.exit("FATAL: The '--server-only' option conflicts with %s.\n" %
271 ("the BBSERVER environment variable" if "BBSERVER" in os.environ else "the '--remote-server' option" ))
272
273 if configParams.bind and configParams.servertype != "xmlrpc":
274 sys.exit("FATAL: If '-B' or '--bind' is defined, we must set the servertype as 'xmlrpc'.\n")
275
276 if configParams.remote_server and configParams.servertype != "xmlrpc":
277 sys.exit("FATAL: If '--remote-server' is defined, we must set the servertype as 'xmlrpc'.\n")
278
279 if configParams.observe_only and (not configParams.remote_server or configParams.bind):
280 sys.exit("FATAL: '--observe-only' can only be used by UI clients connecting to a server.\n")
281
282 if "BBDEBUG" in os.environ:
283 level = int(os.environ["BBDEBUG"])
284 if level > configuration.debug:
285 configuration.debug = level
286
287 bb.msg.init_msgconfig(configParams.verbose, configuration.debug,
288 configuration.debug_domains)
289
290 # Ensure logging messages get sent to the UI as events
291 handler = bb.event.LogHandler()
292 if not configParams.status_only:
293 # In status only mode there are no logs and no UI
294 logger.addHandler(handler)
295
296 # Clear away any spurious environment variables while we stoke up the cooker
297 cleanedvars = bb.utils.clean_environment()
298
299 featureset = []
300 if not configParams.server_only:
301 # Collect the feature set for the UI
302 featureset = getattr(ui_module, "featureSet", [])
303
304 if not configParams.remote_server:
305 # we start a server with a given configuration
306 server = start_server(servermodule, configParams, configuration, featureset)
307 bb.event.ui_queue = []
308 else:
309 # we start a stub server that is actually a XMLRPClient that connects to a real server
310 server = servermodule.BitBakeXMLRPCClient(configParams.observe_only)
311 server.saveConnectionDetails(configParams.remote_server)
312 server.saveConnectionConfigParams(configParams)
313
314 if not configParams.server_only:
315 if configParams.status_only:
316 try:
317 server_connection = server.establishConnection(featureset)
318 except:
319 sys.exit(1)
320 if not server_connection:
321 sys.exit(1)
322 server_connection.terminate()
323 sys.exit(0)
324
325 # Setup a connection to the server (cooker)
326 server_connection = server.establishConnection(featureset)
327 if not server_connection:
328 if configParams.kill_server:
329 bb.fatal("Server already killed")
330 configParams.bind = configParams.remote_server
331 start_server(servermodule, configParams, configuration, featureset)
332 bb.event.ui_queue = []
333 server_connection = server.establishConnection(featureset)
334
335 # Restore the environment in case the UI needs it
336 for k in cleanedvars:
337 os.environ[k] = cleanedvars[k]
338
339 logger.removeHandler(handler)
340
341 try:
342 return ui_module.main(server_connection.connection, server_connection.events, configParams)
343 finally:
344 bb.event.ui_queue = []
345 server_connection.terminate()
346 else:
347 print("server address: %s, server port: %s" % (server.serverImpl.host, server.serverImpl.port))
348 return 0
349
350 return 1
351
352if __name__ == "__main__":
353 try:
354 ret = main()
355 except bb.BBHandledException:
356 ret = 1
357 except Exception:
358 ret = 1
359 import traceback
360 traceback.print_exc()
361 sys.exit(ret)
362
diff --git a/bitbake/bin/bitbake-diffsigs b/bitbake/bin/bitbake-diffsigs
new file mode 100755
index 0000000000..78757b0aae
--- /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..2a7f82998b
--- /dev/null
+++ b/bitbake/bin/bitbake-layers
@@ -0,0 +1,726 @@
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.appendlist.get(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]
570
571Figure out the dependency between recipes that crosses a layer boundary.
572
573Options:
574 -f show full file path
575
576NOTE:
577The .bbappend file can impact the dependency.
578"""
579 self.init_bbhandler()
580
581 show_filenames = False
582 for arg in args.split():
583 if arg == '-f':
584 show_filenames = True
585 else:
586 sys.stderr.write("show-cross-depends: invalid option %s\n" % arg)
587 self.do_help('')
588 return
589
590 pkg_fn = self.bbhandler.cooker_data.pkg_fn
591 bbpath = str(self.bbhandler.config_data.getVar('BBPATH', True))
592 self.require_re = re.compile(r"require\s+(.+)")
593 self.include_re = re.compile(r"include\s+(.+)")
594 self.inherit_re = re.compile(r"inherit\s+(.+)")
595
596 # The bb's DEPENDS and RDEPENDS
597 for f in pkg_fn:
598 f = bb.cache.Cache.virtualfn2realfn(f)[0]
599 # Get the layername that the file is in
600 layername = self.get_file_layer(f)
601
602 # The DEPENDS
603 deps = self.bbhandler.cooker_data.deps[f]
604 for pn in deps:
605 if pn in self.bbhandler.cooker_data.pkg_pn:
606 best = bb.providers.findBestProvider(pn,
607 self.bbhandler.config_data,
608 self.bbhandler.cooker_data,
609 self.bbhandler.cooker_data.pkg_pn)
610 self.check_cross_depends("DEPENDS", layername, f, best[3], show_filenames)
611
612 # The RDPENDS
613 all_rdeps = self.bbhandler.cooker_data.rundeps[f].values()
614 # Remove the duplicated or null one.
615 sorted_rdeps = {}
616 # The all_rdeps is the list in list, so we need two for loops
617 for k1 in all_rdeps:
618 for k2 in k1:
619 sorted_rdeps[k2] = 1
620 all_rdeps = sorted_rdeps.keys()
621 for rdep in all_rdeps:
622 all_p = bb.providers.getRuntimeProviders(self.bbhandler.cooker_data, rdep)
623 if all_p:
624 best = bb.providers.filterProvidersRunTime(all_p, rdep,
625 self.bbhandler.config_data,
626 self.bbhandler.cooker_data)[0][0]
627 self.check_cross_depends("RDEPENDS", layername, f, best, show_filenames)
628
629 # The inherit class
630 cls_re = re.compile('classes/')
631 if f in self.bbhandler.cooker_data.inherits:
632 inherits = self.bbhandler.cooker_data.inherits[f]
633 for cls in inherits:
634 # The inherits' format is [classes/cls, /path/to/classes/cls]
635 # ignore the classes/cls.
636 if not cls_re.match(cls):
637 inherit_layername = self.get_file_layer(cls)
638 if inherit_layername != layername:
639 if not show_filenames:
640 f_short = self.remove_layer_prefix(f)
641 cls = self.remove_layer_prefix(cls)
642 else:
643 f_short = f
644 logger.plain("%s inherits %s" % (f_short, cls))
645
646 # The 'require/include xxx' in the bb file
647 pv_re = re.compile(r"\${PV}")
648 fnfile = open(f, 'r')
649 line = fnfile.readline()
650 while line:
651 m, keyword = self.match_require_include(line)
652 # Found the 'require/include xxxx'
653 if m:
654 needed_file = m.group(1)
655 # Replace the ${PV} with the real PV
656 if pv_re.search(needed_file) and f in self.bbhandler.cooker_data.pkg_pepvpr:
657 pv = self.bbhandler.cooker_data.pkg_pepvpr[f][1]
658 needed_file = re.sub(r"\${PV}", pv, needed_file)
659 self.print_cross_files(bbpath, keyword, layername, f, needed_file, show_filenames)
660 line = fnfile.readline()
661 fnfile.close()
662
663 # The "require/include xxx" in conf/machine/*.conf, .inc and .bbclass
664 conf_re = re.compile(".*/conf/machine/[^\/]*\.conf$")
665 inc_re = re.compile(".*\.inc$")
666 # The "inherit xxx" in .bbclass
667 bbclass_re = re.compile(".*\.bbclass$")
668 for layerdir in self.bblayers:
669 layername = self.get_layer_name(layerdir)
670 for dirpath, dirnames, filenames in os.walk(layerdir):
671 for name in filenames:
672 f = os.path.join(dirpath, name)
673 s = conf_re.match(f) or inc_re.match(f) or bbclass_re.match(f)
674 if s:
675 ffile = open(f, 'r')
676 line = ffile.readline()
677 while line:
678 m, keyword = self.match_require_include(line)
679 # Only bbclass has the "inherit xxx" here.
680 bbclass=""
681 if not m and f.endswith(".bbclass"):
682 m, keyword = self.match_inherit(line)
683 bbclass=".bbclass"
684 # Find a 'require/include xxxx'
685 if m:
686 self.print_cross_files(bbpath, keyword, layername, f, m.group(1) + bbclass, show_filenames)
687 line = ffile.readline()
688 ffile.close()
689
690 def print_cross_files(self, bbpath, keyword, layername, f, needed_filename, show_filenames):
691 """Print the depends that crosses a layer boundary"""
692 needed_file = bb.utils.which(bbpath, needed_filename)
693 if needed_file:
694 # Which layer is this file from
695 needed_layername = self.get_file_layer(needed_file)
696 if needed_layername != layername:
697 if not show_filenames:
698 f = self.remove_layer_prefix(f)
699 needed_file = self.remove_layer_prefix(needed_file)
700 logger.plain("%s %s %s" %(f, keyword, needed_file))
701 def match_inherit(self, line):
702 """Match the inherit xxx line"""
703 return (self.inherit_re.match(line), "inherits")
704
705 def match_require_include(self, line):
706 """Match the require/include xxx line"""
707 m = self.require_re.match(line)
708 keyword = "requires"
709 if not m:
710 m = self.include_re.match(line)
711 keyword = "includes"
712 return (m, keyword)
713
714 def check_cross_depends(self, keyword, layername, f, needed_file, show_filenames):
715 """Print the DEPENDS/RDEPENDS file that crosses a layer boundary"""
716 best_realfn = bb.cache.Cache.virtualfn2realfn(needed_file)[0]
717 needed_layername = self.get_file_layer(best_realfn)
718 if needed_layername != layername:
719 if not show_filenames:
720 f = self.remove_layer_prefix(f)
721 best_realfn = self.remove_layer_prefix(best_realfn)
722
723 logger.plain("%s %s %s" % (f, keyword, best_realfn))
724
725if __name__ == '__main__':
726 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..68e2bf4571
--- /dev/null
+++ b/bitbake/bin/bitbake-worker
@@ -0,0 +1,375 @@
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 sys.argv[1] != "decafbad":
16 print("bitbake-worker is meant for internal execution by bitbake itself, please don't use it standalone.")
17 sys.exit(1)
18
19logger = logging.getLogger("BitBake")
20
21try:
22 import cPickle as pickle
23except ImportError:
24 import pickle
25 bb.msg.note(1, bb.msg.domain.Cache, "Importing cPickle failed. Falling back to a very slow implementation.")
26
27
28worker_pipe = sys.stdout.fileno()
29bb.utils.nonblockingfd(worker_pipe)
30
31handler = bb.event.LogHandler()
32logger.addHandler(handler)
33
34if 0:
35 # Code to write out a log file of all events passing through the worker
36 logfilename = "/tmp/workerlogfile"
37 format_str = "%(levelname)s: %(message)s"
38 conlogformat = bb.msg.BBLogFormatter(format_str)
39 consolelog = logging.FileHandler(logfilename)
40 bb.msg.addDefaultlogFilter(consolelog)
41 consolelog.setFormatter(conlogformat)
42 logger.addHandler(consolelog)
43
44worker_queue = ""
45
46def worker_fire(event, d):
47 data = "<event>" + pickle.dumps(event) + "</event>"
48 worker_fire_prepickled(data)
49
50def worker_fire_prepickled(event):
51 global worker_queue
52
53 worker_queue = worker_queue + event
54 worker_flush()
55
56def worker_flush():
57 global worker_queue, worker_pipe
58
59 if not worker_queue:
60 return
61
62 try:
63 written = os.write(worker_pipe, worker_queue)
64 worker_queue = worker_queue[written:]
65 except (IOError, OSError) as e:
66 if e.errno != errno.EAGAIN:
67 raise
68
69def worker_child_fire(event, d):
70 global worker_pipe
71
72 data = "<event>" + pickle.dumps(event) + "</event>"
73 worker_pipe.write(data)
74
75bb.event.worker_fire = worker_fire
76
77lf = None
78#lf = open("/tmp/workercommandlog", "w+")
79def workerlog_write(msg):
80 if lf:
81 lf.write(msg)
82 lf.flush()
83
84def fork_off_task(cfg, data, workerdata, fn, task, taskname, appends, taskdepdata, quieterrors=False):
85 # We need to setup the environment BEFORE the fork, since
86 # a fork() or exec*() activates PSEUDO...
87
88 envbackup = {}
89 fakeenv = {}
90 umask = None
91
92 taskdep = workerdata["taskdeps"][fn]
93 if 'umask' in taskdep and taskname in taskdep['umask']:
94 # umask might come in as a number or text string..
95 try:
96 umask = int(taskdep['umask'][taskname],8)
97 except TypeError:
98 umask = taskdep['umask'][taskname]
99
100 # We can't use the fakeroot environment in a dry run as it possibly hasn't been built
101 if 'fakeroot' in taskdep and taskname in taskdep['fakeroot'] and not cfg.dry_run:
102 envvars = (workerdata["fakerootenv"][fn] or "").split()
103 for key, value in (var.split('=') for var in envvars):
104 envbackup[key] = os.environ.get(key)
105 os.environ[key] = value
106 fakeenv[key] = value
107
108 fakedirs = (workerdata["fakerootdirs"][fn] or "").split()
109 for p in fakedirs:
110 bb.utils.mkdirhier(p)
111 logger.debug(2, 'Running %s:%s under fakeroot, fakedirs: %s' %
112 (fn, taskname, ', '.join(fakedirs)))
113 else:
114 envvars = (workerdata["fakerootnoenv"][fn] or "").split()
115 for key, value in (var.split('=') for var in envvars):
116 envbackup[key] = os.environ.get(key)
117 os.environ[key] = value
118 fakeenv[key] = value
119
120 sys.stdout.flush()
121 sys.stderr.flush()
122
123 try:
124 pipein, pipeout = os.pipe()
125 pipein = os.fdopen(pipein, 'rb', 4096)
126 pipeout = os.fdopen(pipeout, 'wb', 0)
127 pid = os.fork()
128 except OSError as e:
129 bb.msg.fatal("RunQueue", "fork failed: %d (%s)" % (e.errno, e.strerror))
130
131 if pid == 0:
132 global worker_pipe
133 pipein.close()
134
135 signal.signal(signal.SIGTERM, signal.SIG_DFL)
136
137 # Save out the PID so that the event can include it the
138 # events
139 bb.event.worker_pid = os.getpid()
140 bb.event.worker_fire = worker_child_fire
141 worker_pipe = pipeout
142
143 # Make the child the process group leader
144 os.setpgid(0, 0)
145 # No stdin
146 newsi = os.open(os.devnull, os.O_RDWR)
147 os.dup2(newsi, sys.stdin.fileno())
148
149 if umask:
150 os.umask(umask)
151
152 data.setVar("BB_WORKERCONTEXT", "1")
153 data.setVar("BB_TASKDEPDATA", taskdepdata)
154 data.setVar("BUILDNAME", workerdata["buildname"])
155 data.setVar("DATE", workerdata["date"])
156 data.setVar("TIME", workerdata["time"])
157 bb.parse.siggen.set_taskdata(workerdata["hashes"], workerdata["hash_deps"], workerdata["sigchecksums"])
158 ret = 0
159 try:
160 the_data = bb.cache.Cache.loadDataFull(fn, appends, data)
161 the_data.setVar('BB_TASKHASH', workerdata["runq_hash"][task])
162 for h in workerdata["hashes"]:
163 the_data.setVar("BBHASH_%s" % h, workerdata["hashes"][h])
164 for h in workerdata["hash_deps"]:
165 the_data.setVar("BBHASHDEPS_%s" % h, workerdata["hash_deps"][h])
166
167 # exported_vars() returns a generator which *cannot* be passed to os.environ.update()
168 # successfully. We also need to unset anything from the environment which shouldn't be there
169 exports = bb.data.exported_vars(the_data)
170 bb.utils.empty_environment()
171 for e, v in exports:
172 os.environ[e] = v
173 for e in fakeenv:
174 os.environ[e] = fakeenv[e]
175 the_data.setVar(e, fakeenv[e])
176 the_data.setVarFlag(e, 'export', "1")
177
178 if quieterrors:
179 the_data.setVarFlag(taskname, "quieterrors", "1")
180
181 except Exception as exc:
182 if not quieterrors:
183 logger.critical(str(exc))
184 os._exit(1)
185 try:
186 if not cfg.dry_run:
187 ret = bb.build.exec_task(fn, taskname, the_data, cfg.profile)
188 os._exit(ret)
189 except:
190 os._exit(1)
191 else:
192 for key, value in envbackup.iteritems():
193 if value is None:
194 del os.environ[key]
195 else:
196 os.environ[key] = value
197
198 return pid, pipein, pipeout
199
200class runQueueWorkerPipe():
201 """
202 Abstraction for a pipe between a worker thread and the worker server
203 """
204 def __init__(self, pipein, pipeout):
205 self.input = pipein
206 if pipeout:
207 pipeout.close()
208 bb.utils.nonblockingfd(self.input)
209 self.queue = ""
210
211 def read(self):
212 start = len(self.queue)
213 try:
214 self.queue = self.queue + self.input.read(102400)
215 except (OSError, IOError) as e:
216 if e.errno != errno.EAGAIN:
217 raise
218
219 end = len(self.queue)
220 index = self.queue.find("</event>")
221 while index != -1:
222 worker_fire_prepickled(self.queue[:index+8])
223 self.queue = self.queue[index+8:]
224 index = self.queue.find("</event>")
225 return (end > start)
226
227 def close(self):
228 while self.read():
229 continue
230 if len(self.queue) > 0:
231 print("Warning, worker child left partial message: %s" % self.queue)
232 self.input.close()
233
234normalexit = False
235
236class BitbakeWorker(object):
237 def __init__(self, din):
238 self.input = din
239 bb.utils.nonblockingfd(self.input)
240 self.queue = ""
241 self.cookercfg = None
242 self.databuilder = None
243 self.data = None
244 self.build_pids = {}
245 self.build_pipes = {}
246
247 signal.signal(signal.SIGTERM, self.sigterm_exception)
248
249 def sigterm_exception(self, signum, stackframe):
250 bb.warn("Worker recieved SIGTERM, shutting down...")
251 self.handle_finishnow(None)
252 signal.signal(signal.SIGTERM, signal.SIG_DFL)
253 os.kill(os.getpid(), signal.SIGTERM)
254
255 def serve(self):
256 while True:
257 (ready, _, _) = select.select([self.input] + [i.input for i in self.build_pipes.values()], [] , [], 1)
258 if self.input in ready or len(self.queue):
259 start = len(self.queue)
260 try:
261 self.queue = self.queue + self.input.read()
262 except (OSError, IOError):
263 pass
264 end = len(self.queue)
265 self.handle_item("cookerconfig", self.handle_cookercfg)
266 self.handle_item("workerdata", self.handle_workerdata)
267 self.handle_item("runtask", self.handle_runtask)
268 self.handle_item("finishnow", self.handle_finishnow)
269 self.handle_item("ping", self.handle_ping)
270 self.handle_item("quit", self.handle_quit)
271
272 for pipe in self.build_pipes:
273 self.build_pipes[pipe].read()
274 if len(self.build_pids):
275 self.process_waitpid()
276 worker_flush()
277
278
279 def handle_item(self, item, func):
280 if self.queue.startswith("<" + item + ">"):
281 index = self.queue.find("</" + item + ">")
282 while index != -1:
283 func(self.queue[(len(item) + 2):index])
284 self.queue = self.queue[(index + len(item) + 3):]
285 index = self.queue.find("</" + item + ">")
286
287 def handle_cookercfg(self, data):
288 self.cookercfg = pickle.loads(data)
289 self.databuilder = bb.cookerdata.CookerDataBuilder(self.cookercfg, worker=True)
290 self.databuilder.parseBaseConfiguration()
291 self.data = self.databuilder.data
292
293 def handle_workerdata(self, data):
294 self.workerdata = pickle.loads(data)
295 bb.msg.loggerDefaultDebugLevel = self.workerdata["logdefaultdebug"]
296 bb.msg.loggerDefaultVerbose = self.workerdata["logdefaultverbose"]
297 bb.msg.loggerVerboseLogs = self.workerdata["logdefaultverboselogs"]
298 bb.msg.loggerDefaultDomains = self.workerdata["logdefaultdomain"]
299 self.data.setVar("PRSERV_HOST", self.workerdata["prhost"])
300
301 def handle_ping(self, _):
302 workerlog_write("Handling ping\n")
303
304 logger.warn("Pong from bitbake-worker!")
305
306 def handle_quit(self, data):
307 workerlog_write("Handling quit\n")
308
309 global normalexit
310 normalexit = True
311 sys.exit(0)
312
313 def handle_runtask(self, data):
314 fn, task, taskname, quieterrors, appends, taskdepdata = pickle.loads(data)
315 workerlog_write("Handling runtask %s %s %s\n" % (task, fn, taskname))
316
317 pid, pipein, pipeout = fork_off_task(self.cookercfg, self.data, self.workerdata, fn, task, taskname, appends, taskdepdata, quieterrors)
318
319 self.build_pids[pid] = task
320 self.build_pipes[pid] = runQueueWorkerPipe(pipein, pipeout)
321
322 def process_waitpid(self):
323 """
324 Return none is there are no processes awaiting result collection, otherwise
325 collect the process exit codes and close the information pipe.
326 """
327 try:
328 pid, status = os.waitpid(-1, os.WNOHANG)
329 if pid == 0 or os.WIFSTOPPED(status):
330 return None
331 except OSError:
332 return None
333
334 workerlog_write("Exit code of %s for pid %s\n" % (status, pid))
335
336 if os.WIFEXITED(status):
337 status = os.WEXITSTATUS(status)
338 elif os.WIFSIGNALED(status):
339 # Per shell conventions for $?, when a process exits due to
340 # a signal, we return an exit code of 128 + SIGNUM
341 status = 128 + os.WTERMSIG(status)
342
343 task = self.build_pids[pid]
344 del self.build_pids[pid]
345
346 self.build_pipes[pid].close()
347 del self.build_pipes[pid]
348
349 worker_fire_prepickled("<exitcode>" + pickle.dumps((task, status)) + "</exitcode>")
350
351 def handle_finishnow(self, _):
352 if self.build_pids:
353 logger.info("Sending SIGTERM to remaining %s tasks", len(self.build_pids))
354 for k, v in self.build_pids.iteritems():
355 try:
356 os.kill(-k, signal.SIGTERM)
357 os.waitpid(-1, 0)
358 except:
359 pass
360 for pipe in self.build_pipes:
361 self.build_pipes[pipe].read()
362
363try:
364 worker = BitbakeWorker(sys.stdin)
365 worker.serve()
366except BaseException as e:
367 if not normalexit:
368 import traceback
369 sys.stderr.write(traceback.format_exc())
370 sys.stderr.write(str(e))
371while len(worker_queue):
372 worker_flush()
373workerlog_write("exitting")
374sys.exit(0)
375
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..dea69a4652
--- /dev/null
+++ b/bitbake/bin/toaster
@@ -0,0 +1,214 @@
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 enables toaster event logging and
20# starts bitbake resident server
21# use as: source toaster [start|stop]
22
23# Helper function to kill a background toaster development server
24
25function webserverKillAll()
26{
27 local pidfile
28 for pidfile in ${BUILDDIR}/.toastermain.pid; do
29 if [ -f ${pidfile} ]; then
30 while kill -0 $(< ${pidfile}) 2>/dev/null; do
31 kill -SIGTERM -$(< ${pidfile}) 2>/dev/null
32 sleep 1;
33 done;
34 rm ${pidfile}
35 fi
36 done
37}
38
39function webserverStartAll()
40{
41 retval=0
42 python $BBBASEDIR/lib/toaster/manage.py syncdb || retval=1
43 python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=2
44 if [ $retval -eq 1 ]; then
45 echo "Failed db sync, stopping system start" 1>&2
46 elif [ $retval -eq 2 ]; then
47 echo -e "\nError on migration, trying to recover... \n"
48 python $BBBASEDIR/lib/toaster/manage.py migrate orm 0001_initial --fake
49 retval=0
50 python $BBBASEDIR/lib/toaster/manage.py migrate orm || retval=1
51 fi
52 if [ $retval -eq 0 ]; then
53 python $BBBASEDIR/lib/toaster/manage.py runserver 0.0.0.0:8000 </dev/null >${BUILDDIR}/toaster_web.log 2>&1 & echo $! >${BUILDDIR}/.toastermain.pid
54 sleep 1
55 if ! cat "${BUILDDIR}/.toastermain.pid" | xargs -I{} kill -0 {} ; then
56 retval=1
57 rm "${BUILDDIR}/.toastermain.pid"
58 fi
59 fi
60 return $retval
61}
62
63# Helper functions to add a special configuration file
64
65function addtoConfiguration()
66{
67 echo "#Created by toaster start script" > ${BUILDDIR}/conf/$2
68 echo $1 >> ${BUILDDIR}/conf/$2
69}
70
71INSTOPSYSTEM=0
72
73# define the stop command
74function stop_system()
75{
76 # prevent reentry
77 if [ $INSTOPSYSTEM == 1 ]; then return; fi
78 INSTOPSYSTEM=1
79 if [ -f ${BUILDDIR}/.toasterui.pid ]; then
80 kill $(< ${BUILDDIR}/.toasterui.pid ) 2>/dev/null
81 rm ${BUILDDIR}/.toasterui.pid
82 fi
83 BBSERVER=localhost:8200 bitbake -m
84 unset BBSERVER
85 webserverKillAll
86 # force stop any misbehaving bitbake server
87 lsof bitbake.lock | awk '{print $2}' | grep "[0-9]\+" | xargs -n1 -r kill
88 trap - SIGHUP
89 #trap - SIGCHLD
90 INSTOPSYSTEM=0
91}
92
93function check_pidbyfile() {
94 [ -e $1 ] && kill -0 $(< $1) 2>/dev/null
95}
96
97
98function notify_chldexit() {
99 if [ $NOTOASTERUI == 0 ]; then
100 check_pidbyfile ${BUILDDIR}/.toasterui.pid && return
101 stop_system
102 fi
103}
104
105
106# We make sure we're running in the current shell and in a good environment
107
108if [ -z "$ZSH_NAME" ] && [ `basename \"$0\"` = `basename \"$BASH_SOURCE\"` ]; then
109 echo "Error: This script needs to be sourced. Please run as 'source toaster [start|stop]'" 1>&2;
110 exit 1
111fi
112
113if [ -z "$BUILDDIR" ] || [ -z `which bitbake` ]; then
114 echo "Error: Build environment is not setup or bitbake is not in path." 1>&2;
115 return 2
116fi
117
118BBBASEDIR=`dirname ${BASH_SOURCE}`/..
119
120
121# Verify prerequisites
122
123if ! echo "import django; print (1,5) == django.VERSION[0:2]" | python 2>/dev/null | grep True >/dev/null; then
124 echo -e "This program needs Django 1.5. Please install with\n\nsudo pip install django==1.5"
125 return 2
126fi
127
128if ! echo "import south; print [0,8,4] == map(int,south.__version__.split(\".\"))" | python 2>/dev/null | grep True >/dev/null; then
129 echo -e "This program needs South 0.8.4. Please install with\n\nsudo pip install south==0.8.4"
130 return 2
131fi
132
133
134
135
136
137# Determine the action. If specified by arguments, fine, if not, toggle it
138if [ "x$1" == "xstart" ] || [ "x$1" == "xstop" ]; then
139 CMD="$1"
140else
141 if [ -z "$BBSERVER" ]; then
142 CMD="start"
143 else
144 CMD="stop"
145 fi;
146fi
147
148NOTOASTERUI=0
149for param in $*; do
150 case $param in
151 noui )
152 NOTOASTERUI=1
153 ;;
154 esac
155done
156
157echo "The system will $CMD."
158
159# Make sure it's safe to run by checking bitbake lock
160
161lock=1
162if [ -e $BUILDDIR/bitbake.lock ]; then
163 (flock -n 200 ) 200<$BUILDDIR/bitbake.lock || lock=0
164fi
165
166if [ ${CMD} == "start" ] && ( [ $lock -eq 0 ] || [ -e $BUILDDIR/.toastermain.pid ] ); then
167 echo "Error: bitbake lock state error. File locks show that the system is on." 2>&1
168 echo "If you see problems, stop and then start the system again." 2>&1
169 return 3
170fi
171
172
173# Execute the commands
174
175case $CMD in
176 start )
177 start_success=1
178 addtoConfiguration "INHERIT+=\"toaster buildhistory\"" toaster.conf
179 if ! webserverStartAll; then
180 echo "Failed ${CMD}."
181 return 4
182 fi
183 unset BBSERVER
184 bitbake --postread conf/toaster.conf --server-only -t xmlrpc -B localhost:8200
185 if [ $? -ne 0 ]; then
186 start_success=0
187 echo "Bitbake server start failed"
188 else
189 export BBSERVER=localhost:8200
190 if [ $NOTOASTERUI == 0 ]; then # we start the TOASTERUI only if not inhibited
191 bitbake --observe-only -u toasterui >${BUILDDIR}/toaster_ui.log 2>&1 & echo $! >${BUILDDIR}/.toasterui.pid
192 fi
193 fi
194 if [ $start_success -eq 1 ]; then
195 # set fail safe stop system on terminal exit
196 trap stop_system SIGHUP
197 echo "Successful ${CMD}."
198 else
199 # failed start, do stop
200 stop_system
201 echo "Failed ${CMD}."
202 fi
203 # stop system on terminal exit
204 set -o monitor
205 trap stop_system SIGHUP
206 #trap notify_chldexit SIGCHLD
207 ;;
208 stop )
209 stop_system
210 echo "Successful ${CMD}."
211 ;;
212esac
213
214