summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
authorRichard Purdie <rpurdie@linux.intel.com>2010-01-20 18:46:02 +0000
committerRichard Purdie <rpurdie@linux.intel.com>2010-01-20 18:46:02 +0000
commit22c29d8651668195f72e2f6a8e059d625eb511c3 (patch)
treedd1dd43f0ec47a9964c8a766eb8b3ad75cf51a64 /bitbake
parent1bfd6edef9db9c9175058ae801d1b601e4f15263 (diff)
downloadast2050-yocto-poky-22c29d8651668195f72e2f6a8e059d625eb511c3.zip
ast2050-yocto-poky-22c29d8651668195f72e2f6a8e059d625eb511c3.tar.gz
bitbake: Switch to bitbake-dev version (bitbake master upstream)
Signed-off-by: Richard Purdie <rpurdie@linux.intel.com>
Diffstat (limited to 'bitbake')
-rw-r--r--bitbake/AUTHORS2
-rw-r--r--bitbake/ChangeLog217
-rw-r--r--bitbake/MANIFEST53
-rwxr-xr-xbitbake/bin/bitbake97
-rwxr-xr-xbitbake/bin/bitdoc2
-rw-r--r--bitbake/contrib/vim/syntax/bitbake.vim35
-rw-r--r--bitbake/doc/bitbake.14
-rw-r--r--bitbake/doc/manual/usermanual.xml28
-rw-r--r--bitbake/lib/bb/__init__.py3
-rw-r--r--bitbake/lib/bb/build.py226
-rw-r--r--bitbake/lib/bb/cache.py89
-rw-r--r--bitbake/lib/bb/command.py271
-rw-r--r--bitbake/lib/bb/cooker.py761
-rw-r--r--bitbake/lib/bb/daemonize.py191
-rw-r--r--bitbake/lib/bb/data.py2
-rw-r--r--bitbake/lib/bb/event.py211
-rw-r--r--bitbake/lib/bb/fetch/__init__.py42
-rw-r--r--bitbake/lib/bb/fetch/cvs.py2
-rw-r--r--bitbake/lib/bb/fetch/git.py73
-rw-r--r--bitbake/lib/bb/fetch/local.py4
-rw-r--r--bitbake/lib/bb/fetch/svk.py2
-rw-r--r--bitbake/lib/bb/fetch/wget.py2
-rw-r--r--bitbake/lib/bb/msg.py26
-rw-r--r--bitbake/lib/bb/parse/parse_py/BBHandler.py32
-rw-r--r--bitbake/lib/bb/parse/parse_py/ConfHandler.py13
-rw-r--r--bitbake/lib/bb/providers.py4
-rw-r--r--bitbake/lib/bb/runqueue.py341
-rw-r--r--bitbake/lib/bb/server/__init__.py2
-rw-r--r--bitbake/lib/bb/server/none.py181
-rw-r--r--bitbake/lib/bb/server/xmlrpc.py187
-rw-r--r--bitbake/lib/bb/shell.py19
-rw-r--r--bitbake/lib/bb/taskdata.py38
-rw-r--r--bitbake/lib/bb/ui/__init__.py18
-rw-r--r--bitbake/lib/bb/ui/crumbs/__init__.py18
-rw-r--r--bitbake/lib/bb/ui/crumbs/buildmanager.py457
-rw-r--r--bitbake/lib/bb/ui/crumbs/puccho.glade606
-rw-r--r--bitbake/lib/bb/ui/crumbs/runningbuild.py180
-rw-r--r--bitbake/lib/bb/ui/depexp.py272
-rw-r--r--bitbake/lib/bb/ui/goggle.py77
-rw-r--r--bitbake/lib/bb/ui/knotty.py162
-rw-r--r--bitbake/lib/bb/ui/ncurses.py335
-rw-r--r--bitbake/lib/bb/ui/puccho.py425
-rw-r--r--bitbake/lib/bb/ui/uievent.py125
-rw-r--r--bitbake/lib/bb/ui/uihelper.py49
-rw-r--r--bitbake/lib/bb/utils.py20
45 files changed, 4948 insertions, 956 deletions
diff --git a/bitbake/AUTHORS b/bitbake/AUTHORS
index 9d59260..a4014b1 100644
--- a/bitbake/AUTHORS
+++ b/bitbake/AUTHORS
@@ -2,7 +2,7 @@ Tim Ansell <mithro@mithis.net>
Phil Blundell <pb@handhelds.org>
Seb Frankengul <seb@frankengul.org>
Holger Freyther <zecke@handhelds.org>
-Marcin Juszkiewicz <marcin@haerwu.biz>
+Marcin Juszkiewicz <marcin@juszkiewicz.com.pl>
Chris Larson <kergoth@handhelds.org>
Ulrich Luckas <luckas@musoft.de>
Mickey Lauer <mickey@Vanille.de>
diff --git a/bitbake/ChangeLog b/bitbake/ChangeLog
index 9fe3bf3..22124cb 100644
--- a/bitbake/ChangeLog
+++ b/bitbake/ChangeLog
@@ -1,8 +1,99 @@
-Changes in BitBake 1.8.x:
- - Add bb.utils.prune_suffix function
-
-Changes in BitBake 1.8.12:
- - Fix -f (force) in conjunction with -b
+Changes in Bitbake 1.9.x:
+ - Add PE (Package Epoch) support from Philipp Zabel (pH5)
+ - Treat python functions the same as shell functions for logging
+ - Use TMPDIR/anonfunc as a __anonfunc temp directory (T)
+ - Catch truncated cache file errors
+ - Allow operations other than assignment on flag variables
+ - Add code to handle inter-task dependencies
+ - Fix cache errors when generation dotGraphs
+ - Make sure __inherit_cache is updated before calling include() (from Michael Krelin)
+ - Fix bug when target was in ASSUME_PROVIDED (#2236)
+ - Raise ParseError for filenames with multiple underscores instead of infinitely looping (#2062)
+ - Fix invalid regexp in BBMASK error handling (missing import) (#1124)
+ - Promote certain warnings from debug to note 2 level
+ - Update manual
+ - Correctly redirect stdin when forking
+ - If parsing errors are found, exit, too many users miss the errors
+ - Remove supriours PREFERRED_PROVIDER warnings
+ - svn fetcher: Add _buildsvncommand function
+ - Improve certain error messages
+ - Rewrite svn fetcher to make adding extra operations easier
+ as part of future SRCDATE="now" fixes
+ (requires new FETCHCMD_svn definition in bitbake.conf)
+ - Change SVNDIR layout to be more unique (fixes #2644 and #2624)
+ - Add ConfigParsed Event after configuration parsing is complete
+ - Add SRCREV support for svn fetcher
+ - data.emit_var() - only call getVar if we need the variable
+ - Stop generating the A variable (seems to be legacy code)
+ - Make sure intertask depends get processed correcting in recursive depends
+ - Add pn-PN to overrides when evaluating PREFERRED_VERSION
+ - Improve the progress indicator by skipping tasks that have
+ already run before starting the build rather than during it
+ - Add profiling option (-P)
+ - Add BB_SRCREV_POLICY variable (clear or cache) to control SRCREV cache
+ - Add SRCREV_FORMAT support
+ - Fix local fetcher's localpath return values
+ - Apply OVERRIDES before performing immediate expansions
+ - Allow the -b -e option combination to take regular expressions
+ - Fix handling of variables with expansion in the name using _append/_prepend
+ e.g. RRECOMMENDS_${PN}_append_xyz = "abc"
+ - Add plain message function to bb.msg
+ - Sort the list of providers before processing so dependency problems are
+ reproducible rather than effectively random
+ - Fix/improve bitbake -s output
+ - Add locking for fetchers so only one tries to fetch a given file at a given time
+ - Fix int(0)/None confusion in runqueue.py which causes random gaps in dependency chains
+ - Expand data in addtasks
+ - Print the list of missing DEPENDS,RDEPENDS for the "No buildable providers available for required...."
+ error message.
+ - Rework add_task to be more efficient (6% speedup, 7% number of function calls reduction)
+ - Sort digraph output to make builds more reproducible
+ - Split expandKeys into two for loops to benefit from the expand_cache (12% speedup)
+ - runqueue.py: Fix idepends handling to avoid dependency errors
+ - Clear the terminal TOSTOP flag if set (and warn the user)
+ - Fix regression from r653 and make SRCDATE/CVSDATE work for packages again
+ - Fix a bug in bb.decodeurl where http://some.where.com/somefile.tgz decoded to host="" (#1530)
+ - Warn about malformed PREFERRED_PROVIDERS (#1072)
+ - Add support for BB_NICE_LEVEL option (#1627)
+ - Psyco is used only on x86 as there is no support for other architectures.
+ - Sort initial providers list by default preference (#1145, #2024)
+ - Improve provider sorting so prefered versions have preference over latest versions (#768)
+ - Detect builds of tasks with overlapping providers and warn (will become a fatal error) (#1359)
+ - Add MULTI_PROVIDER_WHITELIST variable to allow known safe multiple providers to be listed
+ - Handle paths in svn fetcher module parameter
+ - Support the syntax "export VARIABLE"
+ - Add bzr fetcher
+ - Add support for cleaning directories before a task in the form:
+ do_taskname[cleandirs] = "dir"
+ - bzr fetcher tweaks from Robert Schuster (#2913)
+ - Add mercurial (hg) fetcher from Robert Schuster (#2913)
+ - Don't add duplicates to BBPATH
+ - Fix preferred_version return values (providers.py)
+ - Fix 'depends' flag splitting
+ - Fix unexport handling (#3135)
+ - Add bb.copyfile function similar to bb.movefile (and improve movefile error reporting)
+ - Allow multiple options for deptask flag
+ - Use git-fetch instead of git-pull removing any need for merges when
+ fetching (we don't care about the index). Fixes fetch errors.
+ - Add BB_GENERATE_MIRROR_TARBALLS option, set to 0 to make git fetches
+ faster at the expense of not creating mirror tarballs.
+ - SRCREV handling updates, improvements and fixes from Poky
+ - Add bb.utils.lockfile() and bb.utils.unlockfile() from Poky
+ - Add support for task selfstamp and lockfiles flags
+ - Disable task number acceleration since it can allow the tasks to run
+ out of sequence
+ - Improve runqueue code comments
+ - Add task scheduler abstraction and some example schedulers
+ - Improve circular dependency chain debugging code and user feedback
+ - Don't give a stacktrace for invalid tasks, have a user friendly message (#3431)
+ - Add support for "-e target" (#3432)
+ - Fix shell showdata command (#3259)
+ - Fix shell data updating problems (#1880)
+ - Properly raise errors for invalid source URI protocols
+ - Change the wget fetcher failure handling to avoid lockfile problems
+ - Add support for branches in git fetcher (Otavio Salvador, Michael Lauer)
+ - Make taskdata and runqueue errors more user friendly
+ - Add norecurse and fullpath options to cvs fetcher
- Fix exit code for build failures in --continue mode
- Fix git branch tags fetching
- Change parseConfigurationFile so it works on real data, not a copy
@@ -27,8 +118,10 @@ Changes in BitBake 1.8.12:
how extensively stamps are looked at for validity
- When handling build target failures make sure idepends are checked and
failed where needed. Fixes --continue mode crashes.
+ - Fix -f (force) in conjunction with -b
- Fix problems with recrdeptask handling where some idepends weren't handled
correctly.
+ - Handle exit codes correctly (from pH5)
- Work around refs/HEAD issues with git over http (#3410)
- Add proxy support to the CVS fetcher (from Cyril Chemparathy)
- Improve runfetchcmd so errors are seen and various GIT variables are exported
@@ -44,7 +137,6 @@ Changes in BitBake 1.8.12:
- Add PERSISTENT_DIR to store the PersistData in a persistent
directory != the cache dir.
- Add md5 and sha256 checksum generation functions to utils.py
- - Make sure Build Completed events are generated even when tasks fail
- Correctly handle '-' characters in class names (#2958)
- Make sure expandKeys has been called on the data dictonary before running tasks
- Correctly add a task override in the form task-TASKNAME.
@@ -63,6 +155,7 @@ Changes in BitBake 1.8.12:
used instead of the internal bitbake one. Alternatively, BB_ENV_EXTRAWHITE can be used
to extend the internal whitelist.
- Perforce fetcher fix to use commandline options instead of being overriden by the environment
+ - bb.utils.prunedir can cope with symlinks to directoriees without exceptions
- use @rev when doing a svn checkout
- Add osc fetcher (from Joshua Lock in Poky)
- When SRCREV autorevisioning for a recipe is in use, don't cache the recipe
@@ -76,109 +169,15 @@ Changes in BitBake 1.8.12:
proxies to work better. (from Poky)
- Also allow user and pswd options in SRC_URIs globally (from Poky)
- Improve proxy handling when using mirrors (from Poky)
-
-Changes in BitBake 1.8.10:
- - Psyco is available only for x86 - do not use it on other architectures.
- - Fix a bug in bb.decodeurl where http://some.where.com/somefile.tgz decoded to host="" (#1530)
- - Warn about malformed PREFERRED_PROVIDERS (#1072)
- - Add support for BB_NICE_LEVEL option (#1627)
- - Sort initial providers list by default preference (#1145, #2024)
- - Improve provider sorting so prefered versions have preference over latest versions (#768)
- - Detect builds of tasks with overlapping providers and warn (will become a fatal error) (#1359)
- - Add MULTI_PROVIDER_WHITELIST variable to allow known safe multiple providers to be listed
- - Handle paths in svn fetcher module parameter
- - Support the syntax "export VARIABLE"
- - Add bzr fetcher
- - Add support for cleaning directories before a task in the form:
- do_taskname[cleandirs] = "dir"
- - bzr fetcher tweaks from Robert Schuster (#2913)
- - Add mercurial (hg) fetcher from Robert Schuster (#2913)
- - Fix bogus preferred_version return values
- - Fix 'depends' flag splitting
- - Fix unexport handling (#3135)
- - Add bb.copyfile function similar to bb.movefile (and improve movefile error reporting)
- - Allow multiple options for deptask flag
- - Use git-fetch instead of git-pull removing any need for merges when
- fetching (we don't care about the index). Fixes fetch errors.
- - Add BB_GENERATE_MIRROR_TARBALLS option, set to 0 to make git fetches
- faster at the expense of not creating mirror tarballs.
- - SRCREV handling updates, improvements and fixes from Poky
- - Add bb.utils.lockfile() and bb.utils.unlockfile() from Poky
- - Add support for task selfstamp and lockfiles flags
- - Disable task number acceleration since it can allow the tasks to run
- out of sequence
- - Improve runqueue code comments
- - Add task scheduler abstraction and some example schedulers
- - Improve circular dependency chain debugging code and user feedback
- - Don't give a stacktrace for invalid tasks, have a user friendly message (#3431)
- - Add support for "-e target" (#3432)
- - Fix shell showdata command (#3259)
- - Fix shell data updating problems (#1880)
- - Properly raise errors for invalid source URI protocols
- - Change the wget fetcher failure handling to avoid lockfile problems
- - Add git branch support
- - Add support for branches in git fetcher (Otavio Salvador, Michael Lauer)
- - Make taskdata and runqueue errors more user friendly
- - Add norecurse and fullpath options to cvs fetcher
- - bb.utils.prunedir can cope with symlinks to directories without exceptions
-
-Changes in Bitbake 1.8.8:
- - Rewrite svn fetcher to make adding extra operations easier
- as part of future SRCDATE="now" fixes
- (requires new FETCHCMD_svn definition in bitbake.conf)
- - Change SVNDIR layout to be more unique (fixes #2644 and #2624)
- - Import persistent data store from trunk
- - Sync fetcher code with that in trunk, adding SRCREV support for svn
- - Add ConfigParsed Event after configuration parsing is complete
- - data.emit_var() - only call getVar if we need the variable
- - Stop generating the A variable (seems to be legacy code)
- - Make sure intertask depends get processed correcting in recursive depends
- - Add pn-PN to overrides when evaluating PREFERRED_VERSION
- - Improve the progress indicator by skipping tasks that have
- already run before starting the build rather than during it
- - Add profiling option (-P)
- - Add BB_SRCREV_POLICY variable (clear or cache) to control SRCREV cache
- - Add SRCREV_FORMAT support
- - Fix local fetcher's localpath return values
- - Apply OVERRIDES before performing immediate expansions
- - Allow the -b -e option combination to take regular expressions
- - Add plain message function to bb.msg
- - Sort the list of providers before processing so dependency problems are
- reproducible rather than effectively random
- - Add locking for fetchers so only one tries to fetch a given file at a given time
- - Fix int(0)/None confusion in runqueue.py which causes random gaps in dependency chains
- - Fix handling of variables with expansion in the name using _append/_prepend
- e.g. RRECOMMENDS_${PN}_append_xyz = "abc"
- - Expand data in addtasks
- - Print the list of missing DEPENDS,RDEPENDS for the "No buildable providers available for required...."
- error message.
- - Rework add_task to be more efficient (6% speedup, 7% number of function calls reduction)
- - Sort digraph output to make builds more reproducible
- - Split expandKeys into two for loops to benefit from the expand_cache (12% speedup)
- - runqueue.py: Fix idepends handling to avoid dependency errors
- - Clear the terminal TOSTOP flag if set (and warn the user)
- - Fix regression from r653 and make SRCDATE/CVSDATE work for packages again
-
-Changes in Bitbake 1.8.6:
- - Correctly redirect stdin when forking
- - If parsing errors are found, exit, too many users miss the errors
- - Remove supriours PREFERRED_PROVIDER warnings
-
-Changes in Bitbake 1.8.4:
- - Make sure __inherit_cache is updated before calling include() (from Michael Krelin)
- - Fix bug when target was in ASSUME_PROVIDED (#2236)
- - Raise ParseError for filenames with multiple underscores instead of infinitely looping (#2062)
- - Fix invalid regexp in BBMASK error handling (missing import) (#1124)
- - Don't run build sanity checks on incomplete builds
- - Promote certain warnings from debug to note 2 level
- - Update manual
-
-Changes in Bitbake 1.8.2:
- - Catch truncated cache file errors
- - Add PE (Package Epoch) support from Philipp Zabel (pH5)
- - Add code to handle inter-task dependencies
- - Allow operations other than assignment on flag variables
- - Fix cache errors when generation dotGraphs
+ - Add bb.utils.prune_suffix function
+ - Fix hg checkouts of specific revisions (from Poky)
+ - Fix wget fetching of urls with parameters specified (from Poky)
+ - Add username handling to git fetcher (from Poky)
+ - Set HOME environmental variable when running fetcher commands (from Poky)
+ - Make sure allowed variables inherited from the environment are exported again (from Poky)
+ - When running a stage task in bbshell, run populate_staging, not the stage task (from Poky)
+ - Fix + character escaping from PACKAGES_DYNAMIC (thanks Otavio Salvador)
+ - Addition of BBCLASSEXTEND support for allowing one recipe to provide multiple targets (from Poky)
Changes in Bitbake 1.8.0:
- Release 1.7.x as a stable series
diff --git a/bitbake/MANIFEST b/bitbake/MANIFEST
deleted file mode 100644
index 39e8017..0000000
--- a/bitbake/MANIFEST
+++ /dev/null
@@ -1,53 +0,0 @@
-AUTHORS
-COPYING
-ChangeLog
-MANIFEST
-setup.py
-bin/bitdoc
-bin/bbimage
-bin/bitbake
-lib/bb/__init__.py
-lib/bb/build.py
-lib/bb/cache.py
-lib/bb/cooker.py
-lib/bb/COW.py
-lib/bb/data.py
-lib/bb/data_smart.py
-lib/bb/event.py
-lib/bb/fetch/__init__.py
-lib/bb/fetch/bzr.py
-lib/bb/fetch/cvs.py
-lib/bb/fetch/git.py
-lib/bb/fetch/hg.py
-lib/bb/fetch/local.py
-lib/bb/fetch/osc.py
-lib/bb/fetch/perforce.py
-lib/bb/fetch/ssh.py
-lib/bb/fetch/svk.py
-lib/bb/fetch/svn.py
-lib/bb/fetch/wget.py
-lib/bb/manifest.py
-lib/bb/methodpool.py
-lib/bb/msg.py
-lib/bb/parse/__init__.py
-lib/bb/parse/parse_py/__init__.py
-lib/bb/parse/parse_py/BBHandler.py
-lib/bb/parse/parse_py/ConfHandler.py
-lib/bb/persist_data.py
-lib/bb/providers.py
-lib/bb/runqueue.py
-lib/bb/shell.py
-lib/bb/taskdata.py
-lib/bb/utils.py
-setup.py
-doc/COPYING.GPL
-doc/COPYING.MIT
-doc/bitbake.1
-doc/manual/html.css
-doc/manual/Makefile
-doc/manual/usermanual.xml
-contrib/bbdev.sh
-contrib/vim/syntax/bitbake.vim
-contrib/vim/ftdetect/bitbake.vim
-conf/bitbake.conf
-classes/base.bbclass
diff --git a/bitbake/bin/bitbake b/bitbake/bin/bitbake
index 842ba04..23c9d73 100755
--- a/bitbake/bin/bitbake
+++ b/bitbake/bin/bitbake
@@ -22,12 +22,18 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import sys, os, getopt, re, time, optparse
+import sys, os, getopt, re, time, optparse, xmlrpclib
sys.path.insert(0,os.path.join(os.path.dirname(os.path.dirname(sys.argv[0])), 'lib'))
import bb
from bb import cooker
+from bb import ui
-__version__ = "1.8.13"
+
+__version__ = "1.9.0"
+
+if sys.hexversion < 0x020500F0:
+ print "Sorry, python 2.5 or later is required for this version of bitbake"
+ sys.exit(1)
#============================================================================#
# BBOptions
@@ -41,11 +47,28 @@ class BBConfiguration( object ):
setattr( self, key, val )
+def print_exception(exc, value, tb):
+ """
+ Print the exception to stderr, only showing the traceback if bitbake
+ debugging is enabled.
+ """
+ if not bb.msg.debug_level['default']:
+ tb = None
+
+ sys.__excepthook__(exc, value, tb)
+
+
#============================================================================#
# main
#============================================================================#
def main():
+ return_value = 0
+ pythonver = sys.version_info
+ if pythonver[0] < 2 or (pythonver[0] == 2 and pythonver[1] < 5):
+ print "Sorry, bitbake needs python 2.5 or later."
+ sys.exit(1)
+
parser = optparse.OptionParser( version = "BitBake Build Tool Core version %s, %%prog version %s" % ( bb.__version__, __version__ ),
usage = """%prog [options] [package ...]
@@ -99,8 +122,8 @@ Default BBFILES are the .bb files in the current directory.""" )
parser.add_option( "-g", "--graphviz", help = "emit the dependency trees of the specified packages in the dot syntax",
action = "store_true", dest = "dot_graph", default = False )
- parser.add_option( "-I", "--ignore-deps", help = """Stop processing at the given list of dependencies when generating dependency graphs. This can help to make the graph more appealing""",
- action = "append", dest = "ignored_dot_deps", default = [] )
+ 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""",
+ action = "append", dest = "extra_assume_provided", default = [] )
parser.add_option( "-l", "--log-domains", help = """Show debug logging for the specified logging domains""",
action = "append", dest = "debug_domains", default = [] )
@@ -108,6 +131,9 @@ Default BBFILES are the .bb files in the current directory.""" )
parser.add_option( "-P", "--profile", help = "profile the command and print a report",
action = "store_true", dest = "profile", default = False )
+ parser.add_option( "-u", "--ui", help = "userinterface to use",
+ action = "store", dest = "ui")
+
parser.add_option( "", "--revisions-changed", help = "Set the exit code depending on whether upstream floating revisions have changed or not",
action = "store_true", dest = "revisions_changed", default = False )
@@ -117,30 +143,53 @@ Default BBFILES are the .bb files in the current directory.""" )
configuration.pkgs_to_build = []
configuration.pkgs_to_build.extend(args[1:])
- cooker = bb.cooker.BBCooker(configuration)
+ #server = bb.server.xmlrpc
+ server = bb.server.none
+
+ # Save a logfile for cooker into the current working directory. When the
+ # server is daemonized this logfile will be truncated.
+ cooker_logfile = os.path.join (os.getcwd(), "cooker.log")
+
+ cooker = bb.cooker.BBCooker(configuration, server)
# Clear away any spurious environment variables. But don't wipe the
- # environment totally.
+ # environment totally. This is necessary to ensure the correct operation
+ # of the UIs (e.g. for DISPLAY, etc.)
bb.utils.clean_environment()
- cooker.parseConfiguration()
-
- if configuration.profile:
- try:
- import cProfile as profile
- except:
- import profile
-
- profile.runctx("cooker.cook()", globals(), locals(), "profile.log")
- import pstats
- p = pstats.Stats('profile.log')
- p.sort_stats('time')
- p.print_stats()
- p.print_callers()
- p.sort_stats('cumulative')
- p.print_stats()
+ cooker.parseCommandLine()
+
+ serverinfo = server.BitbakeServerInfo(cooker.server)
+
+ server.BitBakeServerFork(serverinfo, cooker.serve, cooker_logfile)
+ del cooker
+
+ sys.excepthook = print_exception
+
+ # Setup a connection to the server (cooker)
+ serverConnection = server.BitBakeServerConnection(serverinfo)
+
+ # Launch the UI
+ if configuration.ui:
+ ui = configuration.ui
else:
- cooker.cook()
+ ui = "knotty"
+
+ try:
+ # Dynamically load the UI based on the ui name. Although we
+ # suggest a fixed set this allows you to have flexibility in which
+ # ones are available.
+ exec "from bb.ui import " + ui
+ exec "return_value = " + ui + ".init(serverConnection.connection, serverConnection.events)"
+ except ImportError:
+ print "FATAL: Invalid user interface '%s' specified. " % ui
+ print "Valid interfaces are 'ncurses', 'depexp' or the default, 'knotty'."
+ except Exception, e:
+ print "FATAL: Unable to start to '%s' UI: %s." % (configuration.ui, e.message)
+ finally:
+ serverConnection.terminate()
+ return return_value
if __name__ == "__main__":
- main()
+ ret = main()
+ sys.exit(ret)
diff --git a/bitbake/bin/bitdoc b/bitbake/bin/bitdoc
index 3bcc9b3..4940f66 100755
--- a/bitbake/bin/bitdoc
+++ b/bitbake/bin/bitdoc
@@ -453,6 +453,8 @@ def main():
except bb.parse.ParseError:
bb.fatal( "Unable to parse %s" % config_file )
+ if isinstance(documentation, dict):
+ documentation = documentation[""]
# Assuming we've the file loaded now, we will initialize the 'tree'
doc = Documentation()
diff --git a/bitbake/contrib/vim/syntax/bitbake.vim b/bitbake/contrib/vim/syntax/bitbake.vim
index 43a1990..be55980 100644
--- a/bitbake/contrib/vim/syntax/bitbake.vim
+++ b/bitbake/contrib/vim/syntax/bitbake.vim
@@ -16,12 +16,17 @@ endif
syn case match
-
" Catch incorrect syntax (only matches if nothing else does)
"
syn match bbUnmatched "."
+syn include @python syntax/python.vim
+if exists("b:current_syntax")
+ unlet b:current_syntax
+endif
+
+
" Other
syn match bbComment "^#.*$" display contains=bbTodo
@@ -34,21 +39,25 @@ syn match bbArrayBrackets "[\[\]]" contained
" BitBake strings
syn match bbContinue "\\$"
-syn region bbString matchgroup=bbQuote start=/"/ skip=/\\$/ excludenl end=/"/ contained keepend contains=bbTodo,bbContinue,bbVarDeref
-syn region bbString matchgroup=bbQuote start=/'/ skip=/\\$/ excludenl end=/'/ contained keepend contains=bbTodo,bbContinue,bbVarDeref
-
+syn region bbString matchgroup=bbQuote start=/"/ skip=/\\$/ excludenl end=/"/ contained keepend contains=bbTodo,bbContinue,bbVarInlinePy,bbVarDeref
+syn region bbString matchgroup=bbQuote start=/'/ skip=/\\$/ excludenl end=/'/ contained keepend contains=bbTodo,bbContinue,bbVarInlinePy,bbVarDeref
" BitBake variable metadata
+syn match bbVarBraces "[\${}]"
+syn region bbVarDeref matchgroup=bbVarBraces start="${" end="}" contained
+" syn region bbVarDeref start="${" end="}" contained
+" syn region bbVarInlinePy start="${@" end="}" contained contains=@python
+syn region bbVarInlinePy matchgroup=bbVarBraces start="${@" end="}" contained contains=@python
+
syn keyword bbExportFlag export contained nextgroup=bbIdentifier skipwhite
-syn match bbVarDeref "${[a-zA-Z0-9\-_\.]\+}" contained
-syn match bbVarDef "^\(export\s*\)\?\([a-zA-Z0-9\-_\.]\+\(_[${}a-zA-Z0-9\-_\.]\+\)\?\)\s*\(:=\|+=\|=+\|\.=\|=\.\|?=\|=\)\@=" contains=bbExportFlag,bbIdentifier,bbVarDeref nextgroup=bbVarEq
+" syn match bbVarDeref "${[a-zA-Z0-9\-_\.]\+}" contained
+syn match bbVarDef "^\(export\s*\)\?\([a-zA-Z0-9\-_\.]\+\(_[${}a-zA/-Z0-9\-_\.]\+\)\?\)\s*\(:=\|+=\|=+\|\.=\|=\.\|?=\|=\)\@=" contains=bbExportFlag,bbIdentifier,bbVarDeref nextgroup=bbVarEq
-syn match bbIdentifier "[a-zA-Z0-9\-_\.]\+" display contained
+syn match bbIdentifier "[a-zA-Z0-9\-_\./]\+" display contained
"syn keyword bbVarEq = display contained nextgroup=bbVarValue
syn match bbVarEq "\(:=\|+=\|=+\|\.=\|=\.\|?=\|=\)" contained nextgroup=bbVarValue
-syn match bbVarValue ".*$" contained contains=bbString,bbVarDeref
-
+syn match bbVarValue ".*$" contained contains=bbString
" BitBake variable metadata flags
syn match bbVarFlagDef "^\([a-zA-Z0-9\-_\.]\+\)\(\[[a-zA-Z0-9\-_\.]\+\]\)\@=" contains=bbIdentifier nextgroup=bbVarFlagFlag
@@ -61,10 +70,6 @@ syn match bbFunction "\h\w*" display contained
" BitBake python metadata
-syn include @python syntax/python.vim
-if exists("b:current_syntax")
- unlet b:current_syntax
-endif
syn keyword bbPythonFlag python contained nextgroup=bbFunction
syn match bbPythonFuncDef "^\(python\s\+\)\(\w\+\)\?\(\s*()\s*\)\({\)\@=" contains=bbPythonFlag,bbFunction,bbDelimiter nextgroup=bbPythonFuncRegion skipwhite
@@ -98,7 +103,6 @@ syn match bbStatementRest ".*$" contained contains=bbString,bbVarDeref
"
hi def link bbArrayBrackets Statement
hi def link bbUnmatched Error
-hi def link bbVarDeref String
hi def link bbContinue Special
hi def link bbDef Statement
hi def link bbPythonFlag Type
@@ -116,5 +120,8 @@ hi def link bbIdentifier Identifier
hi def link bbVarEq Operator
hi def link bbQuote String
hi def link bbVarValue String
+" hi def link bbVarInlinePy PreProc
+hi def link bbVarDeref PreProc
+hi def link bbVarBraces PreProc
let b:current_syntax = "bb"
diff --git a/bitbake/doc/bitbake.1 b/bitbake/doc/bitbake.1
index e687f0a..036402e 100644
--- a/bitbake/doc/bitbake.1
+++ b/bitbake/doc/bitbake.1
@@ -32,7 +32,7 @@ command.
\fBbitbake\fP is a program that executes the specified task (default is 'build')
for a given set of BitBake files.
.br
-It expects that BBFILES is defined, which is a space seperated list of files to
+It expects that BBFILES is defined, which is a space separated list of files to
be executed. BBFILES does support wildcards.
.br
Default BBFILES are the .bb files in the current directory.
@@ -67,7 +67,7 @@ drop into the interactive mode also called the BitBake shell.
Specify task to execute. Note that this only executes the specified task for
the providee and the packages it depends on, i.e. 'compile' does not implicitly
call stage for the dependencies (IOW: use only if you know what you are doing).
-Depending on the base.bbclass a listtaks tasks is defined and will show
+Depending on the base.bbclass a listtasks task is defined and will show
available tasks.
.TP
.B \-rFILE, \-\-read=FILE
diff --git a/bitbake/doc/manual/usermanual.xml b/bitbake/doc/manual/usermanual.xml
index a01801e..cdd0599 100644
--- a/bitbake/doc/manual/usermanual.xml
+++ b/bitbake/doc/manual/usermanual.xml
@@ -119,7 +119,7 @@ will be introduced.</para>
</section>
<section>
<title>Conditional metadata set</title>
- <para>OVERRIDES is a <quote>:</quote> seperated variable containing each item you want to satisfy conditions. So, if you have a variable which is conditional on <quote>arm</quote>, and <quote>arm</quote> is in OVERRIDES, then the <quote>arm</quote> specific version of the variable is used rather than the non-conditional version. Example:</para>
+ <para>OVERRIDES is a <quote>:</quote> separated variable containing each item you want to satisfy conditions. So, if you have a variable which is conditional on <quote>arm</quote>, and <quote>arm</quote> is in OVERRIDES, then the <quote>arm</quote> specific version of the variable is used rather than the non-conditional version. Example:</para>
<para><screen><varname>OVERRIDES</varname> = "architecture:os:machine"
<varname>TEST</varname> = "defaultvalue"
<varname>TEST_os</varname> = "osspecificvalue"
@@ -184,7 +184,7 @@ include</literal> directive.</para>
<section>
<title>Inheritance</title>
<para><emphasis>NOTE:</emphasis> This is only supported in .bb and .bbclass files.</para>
- <para>The <literal>inherit</literal> directive is a means of specifying what classes of functionality your .bb requires. It is a rudamentary form of inheritence. For example, you can easily abstract out the tasks involved in building a package that uses autoconf and automake, and put that into a bbclass for your packages to make use of. A given bbclass is located by searching for classes/filename.oeclass in <envar>BBPATH</envar>, where filename is what you inherited.</para>
+ <para>The <literal>inherit</literal> directive is a means of specifying what classes of functionality your .bb requires. It is a rudimentary form of inheritance. For example, you can easily abstract out the tasks involved in building a package that uses autoconf and automake, and put that into a bbclass for your packages to make use of. A given bbclass is located by searching for classes/filename.oeclass in <envar>BBPATH</envar>, where filename is what you inherited.</para>
</section>
<section>
<title>Tasks</title>
@@ -263,11 +263,11 @@ of the event and the content of the <varname>FILE</varname> variable.</para>
</section>
<section>
<title>Classes</title>
- <para>BitBake classes are our rudamentary inheritence mechanism. As briefly mentioned in the metadata introduction, they're parsed when an <literal>inherit</literal> directive is encountered, and they are located in classes/ relative to the dirs in <envar>BBPATH</envar>.</para>
+ <para>BitBake classes are our rudimentary inheritance mechanism. As briefly mentioned in the metadata introduction, they're parsed when an <literal>inherit</literal> directive is encountered, and they are located in classes/ relative to the dirs in <envar>BBPATH</envar>.</para>
</section>
<section>
<title>.bb Files</title>
- <para>A BitBake (.bb) file is a logical unit of tasks to be executed. Normally this is a package to be built. Inter-.bb dependencies are obeyed. The files themselves are located via the <varname>BBFILES</varname> variable, which is set to a space seperated list of .bb files, and does handle wildcards.</para>
+ <para>A BitBake (.bb) file is a logical unit of tasks to be executed. Normally this is a package to be built. Inter-.bb dependencies are obeyed. The files themselves are located via the <varname>BBFILES</varname> variable, which is set to a space separated list of .bb files, and does handle wildcards.</para>
</section>
</section>
</chapter>
@@ -352,15 +352,7 @@ will be tried first when fetching a file if that fails the actual file will be t
<chapter>
- <title>Commands</title>
- <section>
- <title>bbread</title>
- <para>bbread is a command for displaying BitBake metadata. When run with no arguments, it has the core parse 'conf/bitbake.conf', as located in BBPATH, and displays that. If you supply a file on the commandline, such as a .bb, then it parses that afterwards, using the aforementioned configuration metadata.</para>
- <para><emphasis>NOTE: the stand a lone bbread command was removed. Instead of bbread use bitbake -e.
- </emphasis></para>
- </section>
- <section>
- <title>bitbake</title>
+ <title>The bitbake command</title>
<section>
<title>Introduction</title>
<para>bitbake is the primary command in the system. It facilitates executing tasks in a single .bb file, or executing a given task on a set of multiple .bb files, accounting for interdependencies amongst them.</para>
@@ -372,7 +364,7 @@ will be tried first when fetching a file if that fails the actual file will be t
usage: bitbake [options] [package ...]
Executes the specified task (default is 'build') for a given set of BitBake files.
-It expects that BBFILES is defined, which is a space seperated list of files to
+It expects that BBFILES is defined, which is a space separated list of files to
be executed. BBFILES does support wildcards.
Default BBFILES are the .bb files in the current directory.
@@ -394,7 +386,7 @@ options:
it depends on, i.e. 'compile' does not implicitly call
stage for the dependencies (IOW: use only if you know
what you are doing). Depending on the base.bbclass a
- listtasks tasks is defined and will show available
+ listtasks task is defined and will show available
tasks
-r FILE, --read=FILE read the specified file before bitbake.conf
-v, --verbose output more chit-chat to the terminal
@@ -417,6 +409,7 @@ options:
Show debug logging for the specified logging domains
-P, --profile profile the command and print a report
+
</screen>
</para>
<para>
@@ -462,12 +455,12 @@ Two files will be written into the current working directory, <emphasis>depends.
</section>
<section>
<title>Metadata</title>
- <para>As you may have seen in the usage information, or in the information about .bb files, the BBFILES variable is how the bitbake tool locates its files. This variable is a space seperated list of files that are available, and supports wildcards.
+ <para>As you may have seen in the usage information, or in the information about .bb files, the BBFILES variable is how the bitbake tool locates its files. This variable is a space separated list of files that are available, and supports wildcards.
<example>
<title>Setting BBFILES</title>
<programlisting><varname>BBFILES</varname> = "/path/to/bbfiles/*.bb"</programlisting>
</example></para>
- <para>With regard to dependencies, it expects the .bb to define a <varname>DEPENDS</varname> variable, which contains a space seperated list of <quote>package names</quote>, which themselves are the <varname>PN</varname> variable. The <varname>PN</varname> variable is, in general, by default, set to a component of the .bb filename.</para>
+ <para>With regard to dependencies, it expects the .bb to define a <varname>DEPENDS</varname> variable, which contains a space separated list of <quote>package names</quote>, which themselves are the <varname>PN</varname> variable. The <varname>PN</varname> variable is, in general, by default, set to a component of the .bb filename.</para>
<example>
<title>Depending on another .bb</title>
<para>a.bb:
@@ -514,6 +507,5 @@ BBFILE_PRIORITY_upstream = "5"
BBFILE_PRIORITY_local = "10"</screen>
</example>
</section>
- </section>
</chapter>
</book>
diff --git a/bitbake/lib/bb/__init__.py b/bitbake/lib/bb/__init__.py
index b8f7c7f..f2f8f65 100644
--- a/bitbake/lib/bb/__init__.py
+++ b/bitbake/lib/bb/__init__.py
@@ -21,7 +21,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-__version__ = "1.8.13"
+__version__ = "1.9.0"
__all__ = [
@@ -54,6 +54,7 @@ __all__ = [
# modules
"parse",
"data",
+ "command",
"event",
"build",
"fetch",
diff --git a/bitbake/lib/bb/build.py b/bitbake/lib/bb/build.py
index 1d6742b..6d80b4b 100644
--- a/bitbake/lib/bb/build.py
+++ b/bitbake/lib/bb/build.py
@@ -25,8 +25,8 @@
#
#Based on functions from the base bb module, Copyright 2003 Holger Schurig
-from bb import data, fetch, event, mkdirhier, utils
-import bb, os
+from bb import data, event, mkdirhier, utils
+import bb, os, sys
# When we execute a python function we'd like certain things
# in all namespaces, hence we add them to __builtins__
@@ -37,7 +37,11 @@ __builtins__['os'] = os
# events
class FuncFailed(Exception):
- """Executed function failed"""
+ """
+ Executed function failed
+ First parameter a message
+ Second paramter is a logfile (optional)
+ """
class EventException(Exception):
"""Exception which is associated with an Event."""
@@ -50,7 +54,9 @@ class TaskBase(event.Event):
def __init__(self, t, d ):
self._task = t
- event.Event.__init__(self, d)
+ self._package = bb.data.getVar("PF", d, 1)
+ event.Event.__init__(self)
+ self._message = "package %s: task %s: %s" % (bb.data.getVar("PF", d, 1), t, bb.event.getName(self)[4:])
def getTask(self):
return self._task
@@ -68,6 +74,10 @@ class TaskSucceeded(TaskBase):
class TaskFailed(TaskBase):
"""Task execution failed"""
+ def __init__(self, msg, logfile, t, d ):
+ self.logfile = logfile
+ self.msg = msg
+ TaskBase.__init__(self, t, d)
class InvalidTask(TaskBase):
"""Invalid Task"""
@@ -104,42 +114,116 @@ def exec_func(func, d, dirs = None):
else:
adir = data.getVar('B', d, 1)
+ # Save current directory
try:
prevdir = os.getcwd()
except OSError:
prevdir = data.getVar('TOPDIR', d, True)
+
+ # Setup logfiles
+ t = data.getVar('T', d, 1)
+ if not t:
+ bb.msg.fatal(bb.msg.domain.Build, "T not set")
+ mkdirhier(t)
+ # Gross hack, FIXME
+ import random
+ logfile = "%s/log.%s.%s.%s" % (t, func, str(os.getpid()),random.random())
+ runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
+
+ # Change to correct directory (if specified)
if adir and os.access(adir, os.F_OK):
os.chdir(adir)
+ # Handle logfiles
+ si = file('/dev/null', 'r')
+ try:
+ if bb.msg.debug_level['default'] > 0 or ispython:
+ so = os.popen("tee \"%s\"" % logfile, "w")
+ else:
+ so = file(logfile, 'w')
+ except OSError, e:
+ bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
+ pass
+
+ se = so
+
+ # Dup the existing fds so we dont lose them
+ osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
+ oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
+ ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
+
+ # Replace those fds with our own
+ os.dup2(si.fileno(), osi[1])
+ os.dup2(so.fileno(), oso[1])
+ os.dup2(se.fileno(), ose[1])
+
locks = []
lockfiles = (data.expand(flags['lockfiles'], d) or "").split()
for lock in lockfiles:
locks.append(bb.utils.lockfile(lock))
- if flags['python']:
- exec_func_python(func, d)
- else:
- exec_func_shell(func, d, flags)
+ try:
+ # Run the function
+ if ispython:
+ exec_func_python(func, d, runfile, logfile)
+ else:
+ exec_func_shell(func, d, runfile, logfile, flags)
+
+ # Restore original directory
+ try:
+ os.chdir(prevdir)
+ except:
+ pass
- for lock in locks:
- bb.utils.unlockfile(lock)
+ finally:
- if os.path.exists(prevdir):
- os.chdir(prevdir)
+ # Unlock any lockfiles
+ for lock in locks:
+ bb.utils.unlockfile(lock)
+
+ # Restore the backup fds
+ os.dup2(osi[0], osi[1])
+ os.dup2(oso[0], oso[1])
+ os.dup2(ose[0], ose[1])
+
+ # Close our logs
+ si.close()
+ so.close()
+ se.close()
-def exec_func_python(func, d):
+ if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
+ bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
+ os.remove(logfile)
+
+ # Close the backup fds
+ os.close(osi[0])
+ os.close(oso[0])
+ os.close(ose[0])
+
+def exec_func_python(func, d, runfile, logfile):
"""Execute a python BB 'function'"""
- import re
+ import re, os
bbfile = bb.data.getVar('FILE', d, 1)
tmp = "def " + func + "():\n%s" % data.getVar(func, d)
tmp += '\n' + func + '()'
+
+ f = open(runfile, "w")
+ f.write(tmp)
comp = utils.better_compile(tmp, func, bbfile)
g = {} # globals
g['d'] = d
- utils.better_exec(comp, g, tmp, bbfile)
+ try:
+ utils.better_exec(comp, g, tmp, bbfile)
+ except:
+ (t,value,tb) = sys.exc_info()
+
+ if t in [bb.parse.SkipPackage, bb.build.FuncFailed]:
+ raise
+ bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
+ raise FuncFailed("function %s failed" % func, logfile)
-def exec_func_shell(func, d, flags):
+def exec_func_shell(func, d, runfile, logfile, flags):
"""Execute a shell BB 'function' Returns true if execution was successful.
For this, it creates a bash shell script in the tmp dectory, writes the local
@@ -149,23 +233,13 @@ def exec_func_shell(func, d, flags):
of the directories you need created prior to execution. The last
item in the list is where we will chdir/cd to.
"""
- import sys
deps = flags['deps']
check = flags['check']
- interact = flags['interactive']
if check in globals():
if globals()[check](func, deps):
return
- global logfile
- t = data.getVar('T', d, 1)
- if not t:
- return 0
- mkdirhier(t)
- logfile = "%s/log.%s.%s" % (t, func, str(os.getpid()))
- runfile = "%s/run.%s.%s" % (t, func, str(os.getpid()))
-
f = open(runfile, "w")
f.write("#!/bin/sh -e\n")
if bb.msg.debug_level['default'] > 0: f.write("set -x\n")
@@ -177,91 +251,21 @@ def exec_func_shell(func, d, flags):
os.chmod(runfile, 0775)
if not func:
bb.msg.error(bb.msg.domain.Build, "Function not specified")
- raise FuncFailed()
-
- # open logs
- si = file('/dev/null', 'r')
- try:
- if bb.msg.debug_level['default'] > 0:
- so = os.popen("tee \"%s\"" % logfile, "w")
- else:
- so = file(logfile, 'w')
- except OSError, e:
- bb.msg.error(bb.msg.domain.Build, "opening log file: %s" % e)
- pass
-
- se = so
-
- if not interact:
- # dup the existing fds so we dont lose them
- osi = [os.dup(sys.stdin.fileno()), sys.stdin.fileno()]
- oso = [os.dup(sys.stdout.fileno()), sys.stdout.fileno()]
- ose = [os.dup(sys.stderr.fileno()), sys.stderr.fileno()]
-
- # replace those fds with our own
- os.dup2(si.fileno(), osi[1])
- os.dup2(so.fileno(), oso[1])
- os.dup2(se.fileno(), ose[1])
+ raise FuncFailed("Function not specified for exec_func_shell")
# execute function
- prevdir = os.getcwd()
if flags['fakeroot']:
maybe_fakeroot = "PATH=\"%s\" fakeroot " % bb.data.getVar("PATH", d, 1)
else:
maybe_fakeroot = ''
lang_environment = "LC_ALL=C "
ret = os.system('%s%ssh -e %s' % (lang_environment, maybe_fakeroot, runfile))
- try:
- os.chdir(prevdir)
- except:
- pass
-
- if not interact:
- # restore the backups
- os.dup2(osi[0], osi[1])
- os.dup2(oso[0], oso[1])
- os.dup2(ose[0], ose[1])
- # close our logs
- si.close()
- so.close()
- se.close()
-
- if os.path.exists(logfile) and os.path.getsize(logfile) == 0:
- bb.msg.debug(2, bb.msg.domain.Build, "Zero size logfile %s, removing" % logfile)
- os.remove(logfile)
-
- # close the backup fds
- os.close(osi[0])
- os.close(oso[0])
- os.close(ose[0])
-
- if ret==0:
- if bb.msg.debug_level['default'] > 0:
- os.remove(runfile)
-# os.remove(logfile)
+ if ret == 0:
return
- else:
- bb.msg.error(bb.msg.domain.Build, "function %s failed" % func)
- if data.getVar("BBINCLUDELOGS", d):
- bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
- number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
- if number_of_lines:
- os.system('tail -n%s %s' % (number_of_lines, logfile))
- elif os.path.exists(logfile):
- f = open(logfile, "r")
- while True:
- l = f.readline()
- if l == '':
- break
- l = l.rstrip()
- print '| %s' % l
- f.close()
- else:
- bb.msg.error(bb.msg.domain.Build, "There was no logfile output")
- else:
- bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
- raise FuncFailed( logfile )
+
+ bb.msg.error(bb.msg.domain.Build, "Function %s failed" % func)
+ raise FuncFailed("function %s failed" % func, logfile)
def exec_task(task, d):
@@ -282,14 +286,20 @@ def exec_task(task, d):
data.setVar('OVERRIDES', 'task-%s:%s' % (task[3:], old_overrides), localdata)
data.update_data(localdata)
data.expandKeys(localdata)
- event.fire(TaskStarted(task, localdata))
+ event.fire(TaskStarted(task, localdata), localdata)
exec_func(task, localdata)
- event.fire(TaskSucceeded(task, localdata))
- except FuncFailed, reason:
- bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % reason )
- failedevent = TaskFailed(task, d)
- event.fire(failedevent)
- raise EventException("Function failed in task: %s" % reason, failedevent)
+ event.fire(TaskSucceeded(task, localdata), localdata)
+ except FuncFailed, message:
+ # Try to extract the optional logfile
+ try:
+ (msg, logfile) = message
+ except:
+ logfile = None
+ msg = message
+ bb.msg.note(1, bb.msg.domain.Build, "Task failed: %s" % message )
+ failedevent = TaskFailed(msg, logfile, task, d)
+ event.fire(failedevent, d)
+ raise EventException("Function failed in task: %s" % message, failedevent)
# make stamp, or cause event and raise exception
if not data.getVarFlag(task, 'nostamp', d) and not data.getVarFlag(task, 'selfstamp', d):
diff --git a/bitbake/lib/bb/cache.py b/bitbake/lib/bb/cache.py
index d30d57d..2f1b8fa 100644
--- a/bitbake/lib/bb/cache.py
+++ b/bitbake/lib/bb/cache.py
@@ -134,7 +134,18 @@ class Cache:
self.data = data
# Make sure __depends makes the depends_cache
- self.getVar("__depends", virtualfn, True)
+ # If we're a virtual class we need to make sure all our depends are appended
+ # to the depends of fn.
+ depends = self.getVar("__depends", virtualfn, True) or []
+ if "__depends" not in self.depends_cache[fn] or not self.depends_cache[fn]["__depends"]:
+ self.depends_cache[fn]["__depends"] = depends
+ for dep in depends:
+ if dep not in self.depends_cache[fn]["__depends"]:
+ self.depends_cache[fn]["__depends"].append(dep)
+
+ # Make sure BBCLASSEXTEND always makes the cache too
+ self.getVar('BBCLASSEXTEND', virtualfn, True)
+
self.depends_cache[virtualfn]["CACHETIMESTAMP"] = bb.parse.cached_mtime(fn)
def virtualfn2realfn(self, virtualfn):
@@ -170,11 +181,8 @@ class Cache:
bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s (full)" % fn)
- bb_data, skipped = self.load_bbfile(fn, cfgData)
- if isinstance(bb_data, dict):
- return bb_data[cls]
-
- return bb_data
+ bb_data = self.load_bbfile(fn, cfgData)
+ return bb_data[cls]
def loadData(self, fn, cfgData, cacheData):
"""
@@ -184,42 +192,39 @@ class Cache:
to record the variables accessed.
Return the cache status and whether the file was skipped when parsed
"""
+ skipped = 0
+ virtuals = 0
+
if fn not in self.checked:
self.cacheValidUpdate(fn)
+
if self.cacheValid(fn):
- if "SKIPPED" in self.depends_cache[fn]:
- return True, True
- self.handle_data(fn, cacheData)
multi = self.getVar('BBCLASSEXTEND', fn, True)
- if multi:
- for cls in multi.split():
- virtualfn = self.realfn2virtual(fn, cls)
- # Pretend we're clean so getVar works
- self.clean[virtualfn] = ""
- self.handle_data(virtualfn, cacheData)
- return True, False
+ for cls in (multi or "").split() + [""]:
+ virtualfn = self.realfn2virtual(fn, cls)
+ if self.depends_cache[virtualfn]["__SKIPPED"]:
+ skipped += 1
+ bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
+ continue
+ self.handle_data(virtualfn, cacheData)
+ virtuals += 1
+ return True, skipped, virtuals
bb.msg.debug(1, bb.msg.domain.Cache, "Parsing %s" % fn)
- bb_data, skipped = self.load_bbfile(fn, cfgData)
-
- if skipped:
- if isinstance(bb_data, dict):
- self.setData(fn, fn, bb_data[""])
- else:
- self.setData(fn, fn, bb_data)
- return False, skipped
+ bb_data = self.load_bbfile(fn, cfgData)
- if isinstance(bb_data, dict):
- for data in bb_data:
- virtualfn = self.realfn2virtual(fn, data)
- self.setData(virtualfn, fn, bb_data[data])
+ for data in bb_data:
+ virtualfn = self.realfn2virtual(fn, data)
+ self.setData(virtualfn, fn, bb_data[data])
+ if self.getVar("__SKIPPED", virtualfn, True):
+ skipped += 1
+ bb.msg.debug(1, bb.msg.domain.Cache, "Skipping %s" % virtualfn)
+ else:
self.handle_data(virtualfn, cacheData)
- return False, skipped
+ virtuals += 1
+ return False, skipped, virtuals
- self.setData(fn, fn, bb_data)
- self.handle_data(fn, cacheData)
- return False, skipped
def cacheValid(self, fn):
"""
@@ -286,16 +291,13 @@ class Cache:
if not fn in self.clean:
self.clean[fn] = ""
- return True
+ # Mark extended class data as clean too
+ multi = self.getVar('BBCLASSEXTEND', fn, True)
+ for cls in (multi or "").split():
+ virtualfn = self.realfn2virtual(fn, cls)
+ self.clean[virtualfn] = ""
- def skip(self, fn):
- """
- Mark a fn as skipped
- Called from the parser
- """
- if not fn in self.depends_cache:
- self.depends_cache[fn] = {}
- self.depends_cache[fn]["SKIPPED"] = "1"
+ return True
def remove(self, fn):
"""
@@ -462,10 +464,7 @@ class Cache:
try:
bb_data = parse.handle(bbfile, bb_data) # read .bb data
os.chdir(oldpath)
- return bb_data, False
- except bb.parse.SkipPackage:
- os.chdir(oldpath)
- return bb_data, True
+ return bb_data
except:
os.chdir(oldpath)
raise
diff --git a/bitbake/lib/bb/command.py b/bitbake/lib/bb/command.py
new file mode 100644
index 0000000..2bb5365
--- /dev/null
+++ b/bitbake/lib/bb/command.py
@@ -0,0 +1,271 @@
+"""
+BitBake 'Command' module
+
+Provide an interface to interact with the bitbake server through 'commands'
+"""
+
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+The bitbake server takes 'commands' from its UI/commandline.
+Commands are either synchronous or asynchronous.
+Async commands return data to the client in the form of events.
+Sync commands must only return data through the function return value
+and must not trigger events, directly or indirectly.
+Commands are queued in a CommandQueue
+"""
+
+import bb
+
+async_cmds = {}
+sync_cmds = {}
+
+class Command:
+ """
+ A queue of asynchronous commands for bitbake
+ """
+ def __init__(self, cooker):
+
+ self.cooker = cooker
+ self.cmds_sync = CommandsSync()
+ self.cmds_async = CommandsAsync()
+
+ # FIXME Add lock for this
+ self.currentAsyncCommand = None
+
+ for attr in CommandsSync.__dict__:
+ command = attr[:].lower()
+ method = getattr(CommandsSync, attr)
+ sync_cmds[command] = (method)
+
+ for attr in CommandsAsync.__dict__:
+ command = attr[:].lower()
+ method = getattr(CommandsAsync, attr)
+ async_cmds[command] = (method)
+
+ def runCommand(self, commandline):
+ try:
+ command = commandline.pop(0)
+ if command in CommandsSync.__dict__:
+ # Can run synchronous commands straight away
+ return getattr(CommandsSync, command)(self.cmds_sync, self, commandline)
+ if self.currentAsyncCommand is not None:
+ return "Busy (%s in progress)" % self.currentAsyncCommand[0]
+ if command not in CommandsAsync.__dict__:
+ return "No such command"
+ self.currentAsyncCommand = (command, commandline)
+ self.cooker.server.register_idle_function(self.cooker.runCommands, self.cooker)
+ return True
+ except:
+ import traceback
+ return traceback.format_exc()
+
+ def runAsyncCommand(self):
+ try:
+ if self.currentAsyncCommand is not None:
+ (command, options) = self.currentAsyncCommand
+ commandmethod = getattr(CommandsAsync, command)
+ needcache = getattr( commandmethod, "needcache" )
+ if needcache and self.cooker.cookerState != bb.cooker.cookerParsed:
+ self.cooker.updateCache()
+ return True
+ else:
+ commandmethod(self.cmds_async, self, options)
+ return False
+ else:
+ return False
+ except:
+ import traceback
+ self.finishAsyncCommand(traceback.format_exc())
+ return False
+
+ def finishAsyncCommand(self, error = None):
+ if error:
+ bb.event.fire(bb.command.CookerCommandFailed(error), self.cooker.configuration.event_data)
+ else:
+ bb.event.fire(bb.command.CookerCommandCompleted(), self.cooker.configuration.event_data)
+ self.currentAsyncCommand = None
+
+
+class CommandsSync:
+ """
+ A class of synchronous commands
+ These should run quickly so as not to hurt interactive performance.
+ These must not influence any running synchronous command.
+ """
+
+ def stateShutdown(self, command, params):
+ """
+ Trigger cooker 'shutdown' mode
+ """
+ command.cooker.cookerAction = bb.cooker.cookerShutdown
+
+ def stateStop(self, command, params):
+ """
+ Stop the cooker
+ """
+ command.cooker.cookerAction = bb.cooker.cookerStop
+
+ def getCmdLineAction(self, command, params):
+ """
+ Get any command parsed from the commandline
+ """
+ return command.cooker.commandlineAction
+
+ def getVariable(self, command, params):
+ """
+ Read the value of a variable from configuration.data
+ """
+ varname = params[0]
+ expand = True
+ if len(params) > 1:
+ expand = params[1]
+
+ return bb.data.getVar(varname, command.cooker.configuration.data, expand)
+
+ def setVariable(self, command, params):
+ """
+ Set the value of variable in configuration.data
+ """
+ varname = params[0]
+ value = params[1]
+ bb.data.setVar(varname, value, command.cooker.configuration.data)
+
+
+class CommandsAsync:
+ """
+ A class of asynchronous commands
+ These functions communicate via generated events.
+ Any function that requires metadata parsing should be here.
+ """
+
+ def buildFile(self, command, params):
+ """
+ Build a single specified .bb file
+ """
+ bfile = params[0]
+ task = params[1]
+
+ command.cooker.buildFile(bfile, task)
+ buildFile.needcache = False
+
+ def buildTargets(self, command, params):
+ """
+ Build a set of targets
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.buildTargets(pkgs_to_build, task)
+ buildTargets.needcache = True
+
+ def generateDepTreeEvent(self, command, params):
+ """
+ Generate an event containing the dependency information
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.generateDepTreeEvent(pkgs_to_build, task)
+ command.finishAsyncCommand()
+ generateDepTreeEvent.needcache = True
+
+ def generateDotGraph(self, command, params):
+ """
+ Dump dependency information to disk as .dot files
+ """
+ pkgs_to_build = params[0]
+ task = params[1]
+
+ command.cooker.generateDotGraphFiles(pkgs_to_build, task)
+ command.finishAsyncCommand()
+ generateDotGraph.needcache = True
+
+ def showVersions(self, command, params):
+ """
+ Show the currently selected versions
+ """
+ command.cooker.showVersions()
+ command.finishAsyncCommand()
+ showVersions.needcache = True
+
+ def showEnvironmentTarget(self, command, params):
+ """
+ Print the environment of a target recipe
+ (needs the cache to work out which recipe to use)
+ """
+ pkg = params[0]
+
+ command.cooker.showEnvironment(None, pkg)
+ command.finishAsyncCommand()
+ showEnvironmentTarget.needcache = True
+
+ def showEnvironment(self, command, params):
+ """
+ Print the standard environment
+ or if specified the environment for a specified recipe
+ """
+ bfile = params[0]
+
+ command.cooker.showEnvironment(bfile)
+ command.finishAsyncCommand()
+ showEnvironment.needcache = False
+
+ def parseFiles(self, command, params):
+ """
+ Parse the .bb files
+ """
+ command.cooker.updateCache()
+ command.finishAsyncCommand()
+ parseFiles.needcache = True
+
+ def compareRevisions(self, command, params):
+ """
+ Parse the .bb files
+ """
+ command.cooker.compareRevisions()
+ command.finishAsyncCommand()
+ compareRevisions.needcache = True
+
+#
+# Events
+#
+class CookerCommandCompleted(bb.event.Event):
+ """
+ Cooker command completed
+ """
+ def __init__(self):
+ bb.event.Event.__init__(self)
+
+
+class CookerCommandFailed(bb.event.Event):
+ """
+ Cooker command completed
+ """
+ def __init__(self, error):
+ bb.event.Event.__init__(self)
+ self.error = error
+
+class CookerCommandSetExitCode(bb.event.Event):
+ """
+ Set the exit code for a cooker command
+ """
+ def __init__(self, exitcode):
+ bb.event.Event.__init__(self)
+ self.exitcode = int(exitcode)
+
+
+
diff --git a/bitbake/lib/bb/cooker.py b/bitbake/lib/bb/cooker.py
index 14ccfb5..8036d7e 100644
--- a/bitbake/lib/bb/cooker.py
+++ b/bitbake/lib/bb/cooker.py
@@ -7,7 +7,7 @@
# Copyright (C) 2003 - 2005 Michael 'Mickey' Lauer
# Copyright (C) 2005 Holger Hans Peter Freyther
# Copyright (C) 2005 ROAD GmbH
-# Copyright (C) 2006 Richard Purdie
+# Copyright (C) 2006 - 2007 Richard Purdie
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -25,9 +25,35 @@
import sys, os, getopt, glob, copy, os.path, re, time
import bb
from bb import utils, data, parse, event, cache, providers, taskdata, runqueue
+from bb import command
+import bb.server.xmlrpc
import itertools, sre_constants
-parsespin = itertools.cycle( r'|/-\\' )
+class MultipleMatches(Exception):
+ """
+ Exception raised when multiple file matches are found
+ """
+
+class ParsingErrorsFound(Exception):
+ """
+ Exception raised when parsing errors are found
+ """
+
+class NothingToBuild(Exception):
+ """
+ Exception raised when there is nothing to build
+ """
+
+
+# Different states cooker can be in
+cookerClean = 1
+cookerParsing = 2
+cookerParsed = 3
+
+# Different action states the cooker can be in
+cookerRun = 1 # Cooker is running normally
+cookerShutdown = 2 # Active tasks should be brought to a controlled stop
+cookerStop = 3 # Stop, now!
#============================================================================#
# BBCooker
@@ -37,12 +63,14 @@ class BBCooker:
Manages one bitbake build run
"""
- def __init__(self, configuration):
+ def __init__(self, configuration, server):
self.status = None
self.cache = None
self.bb_cache = None
+ self.server = server.BitBakeServer(self)
+
self.configuration = configuration
if self.configuration.verbose:
@@ -58,17 +86,15 @@ class BBCooker:
self.configuration.data = bb.data.init()
- def parseConfiguration(self):
-
bb.data.inheritFromOS(self.configuration.data)
- # Add conf/bitbake.conf to the list of configuration files to read
- self.configuration.file.append( os.path.join( "conf", "bitbake.conf" ) )
+ for f in self.configuration.file:
+ self.parseConfigurationFile( f )
- self.parseConfigurationFile(self.configuration.file)
+ self.parseConfigurationFile( os.path.join( "conf", "bitbake.conf" ) )
if not self.configuration.cmd:
- self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data) or "build"
+ self.configuration.cmd = bb.data.getVar("BB_DEFAULT_TASK", self.configuration.data, True) or "build"
bbpkgs = bb.data.getVar('BBPKGS', self.configuration.data, True)
if bbpkgs and len(self.configuration.pkgs_to_build) == 0:
@@ -80,9 +106,7 @@ class BBCooker:
self.configuration.event_data = bb.data.createCopy(self.configuration.data)
bb.data.update_data(self.configuration.event_data)
- #
# TOSTOP must not be set or our children will hang when they output
- #
fd = sys.stdout.fileno()
if os.isatty(fd):
import termios
@@ -92,40 +116,91 @@ class BBCooker:
tcattr[3] = tcattr[3] & ~termios.TOSTOP
termios.tcsetattr(fd, termios.TCSANOW, tcattr)
+ self.command = bb.command.Command(self)
+ self.cookerState = cookerClean
+ self.cookerAction = cookerRun
+
+ def parseConfiguration(self):
+
+
# Change nice level if we're asked to
nice = bb.data.getVar("BB_NICE_LEVEL", self.configuration.data, True)
if nice:
curnice = os.nice(0)
nice = int(nice) - curnice
bb.msg.note(2, bb.msg.domain.Build, "Renice to %s " % os.nice(nice))
-
+
+ def parseCommandLine(self):
+ # Parse any commandline into actions
+ if self.configuration.show_environment:
+ self.commandlineAction = None
+
+ if 'world' in self.configuration.pkgs_to_build:
+ bb.error("'world' is not a valid target for --environment.")
+ elif len(self.configuration.pkgs_to_build) > 1:
+ bb.error("Only one target can be used with the --environment option.")
+ elif self.configuration.buildfile and len(self.configuration.pkgs_to_build) > 0:
+ bb.error("No target should be used with the --environment and --buildfile options.")
+ elif len(self.configuration.pkgs_to_build) > 0:
+ self.commandlineAction = ["showEnvironmentTarget", self.configuration.pkgs_to_build]
+ else:
+ self.commandlineAction = ["showEnvironment", self.configuration.buildfile]
+ elif self.configuration.buildfile is not None:
+ self.commandlineAction = ["buildFile", self.configuration.buildfile, self.configuration.cmd]
+ elif self.configuration.revisions_changed:
+ self.commandlineAction = ["compareRevisions"]
+ elif self.configuration.show_versions:
+ self.commandlineAction = ["showVersions"]
+ elif self.configuration.parse_only:
+ self.commandlineAction = ["parseFiles"]
+ # FIXME - implement
+ #elif self.configuration.interactive:
+ # self.interactiveMode()
+ elif self.configuration.dot_graph:
+ if self.configuration.pkgs_to_build:
+ self.commandlineAction = ["generateDotGraph", self.configuration.pkgs_to_build, self.configuration.cmd]
+ else:
+ self.commandlineAction = None
+ bb.error("Please specify a package name for dependency graph generation.")
+ else:
+ if self.configuration.pkgs_to_build:
+ self.commandlineAction = ["buildTargets", self.configuration.pkgs_to_build, self.configuration.cmd]
+ else:
+ self.commandlineAction = None
+ bb.error("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.")
+
+ def runCommands(self, server, data, abort):
+ """
+ Run any queued asynchronous command
+ This is done by the idle handler so it runs in true context rather than
+ tied to any UI.
+ """
+
+ return self.command.runAsyncCommand()
def tryBuildPackage(self, fn, item, task, the_data):
"""
Build one task of a package, optionally build following task depends
"""
- bb.event.fire(bb.event.PkgStarted(item, the_data))
try:
if not self.configuration.dry_run:
bb.build.exec_task('do_%s' % task, the_data)
- bb.event.fire(bb.event.PkgSucceeded(item, the_data))
return True
except bb.build.FuncFailed:
bb.msg.error(bb.msg.domain.Build, "task stack execution failed")
- bb.event.fire(bb.event.PkgFailed(item, the_data))
raise
except bb.build.EventException, e:
event = e.args[1]
bb.msg.error(bb.msg.domain.Build, "%s event exception, aborting" % bb.event.getName(event))
- bb.event.fire(bb.event.PkgFailed(item, the_data))
raise
- def tryBuild(self, fn):
+ def tryBuild(self, fn, task):
"""
Build a provider and its dependencies.
build_depends is a list of previous build dependencies (not runtime)
If build_depends is empty, we're dealing with a runtime depends
"""
+
the_data = self.bb_cache.loadDataFull(fn, self.configuration.data)
item = self.status.pkg_fn[fn]
@@ -133,9 +208,13 @@ class BBCooker:
#if bb.build.stamp_is_current('do_%s' % self.configuration.cmd, the_data):
# return True
- return self.tryBuildPackage(fn, item, self.configuration.cmd, the_data)
+ return self.tryBuildPackage(fn, item, task, the_data)
def showVersions(self):
+
+ # Need files parsed
+ self.updateCache()
+
pkg_pn = self.status.pkg_pn
preferred_versions = {}
latest_versions = {}
@@ -149,43 +228,36 @@ class BBCooker:
pkg_list = pkg_pn.keys()
pkg_list.sort()
+ bb.msg.plain("%-35s %25s %25s" % ("Package Name", "Latest Version", "Preferred Version"))
+ bb.msg.plain("%-35s %25s %25s\n" % ("============", "==============", "================="))
+
for p in pkg_list:
pref = preferred_versions[p]
latest = latest_versions[p]
- if pref != latest:
- prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
- else:
+ prefstr = pref[0][0] + ":" + pref[0][1] + '-' + pref[0][2]
+ lateststr = latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2]
+
+ if pref == latest:
prefstr = ""
- print "%-30s %20s %20s" % (p, latest[0][0] + ":" + latest[0][1] + "-" + latest[0][2],
- prefstr)
+ bb.msg.plain("%-35s %25s %25s" % (p, lateststr, prefstr))
+ def compareRevisions(self):
+ ret = bb.fetch.fetcher_compare_revisons(self.configuration.data)
+ bb.event.fire(bb.command.CookerCommandSetExitCode(ret), self.configuration.event_data)
- def showEnvironment(self , buildfile = None, pkgs_to_build = []):
+ def showEnvironment(self, buildfile = None, pkgs_to_build = []):
"""
Show the outer or per-package environment
"""
fn = None
envdata = None
- if 'world' in pkgs_to_build:
- print "'world' is not a valid target for --environment."
- sys.exit(1)
-
- if len(pkgs_to_build) > 1:
- print "Only one target can be used with the --environment option."
- sys.exit(1)
-
if buildfile:
- if len(pkgs_to_build) > 0:
- print "No target should be used with the --environment and --buildfile options."
- sys.exit(1)
self.cb = None
self.bb_cache = bb.cache.init(self)
fn = self.matchFile(buildfile)
- if not fn:
- sys.exit(1)
elif len(pkgs_to_build) == 1:
self.updateCache()
@@ -193,13 +265,9 @@ class BBCooker:
bb.data.update_data(localdata)
bb.data.expandKeys(localdata)
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
-
- try:
- taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
+ taskdata.add_provider(localdata, self.status, pkgs_to_build[0])
+ taskdata.add_unresolved(localdata, self.status)
targetid = taskdata.getbuild_id(pkgs_to_build[0])
fnid = taskdata.build_targets[targetid][0]
@@ -211,55 +279,69 @@ class BBCooker:
try:
envdata = self.bb_cache.loadDataFull(fn, self.configuration.data)
except IOError, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e))
+ bb.msg.error(bb.msg.domain.Parsing, "Unable to read %s: %s" % (fn, e))
+ raise
except Exception, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
+ bb.msg.error(bb.msg.domain.Parsing, "%s" % e)
+ raise
+
+ class dummywrite:
+ def __init__(self):
+ self.writebuf = ""
+ def write(self, output):
+ self.writebuf = self.writebuf + output
# emit variables and shell functions
try:
- data.update_data( envdata )
- data.emit_env(sys.__stdout__, envdata, True)
+ data.update_data(envdata)
+ wb = dummywrite()
+ data.emit_env(wb, envdata, True)
+ bb.msg.plain(wb.writebuf)
except Exception, e:
bb.msg.fatal(bb.msg.domain.Parsing, "%s" % e)
# emit the metadata which isnt valid shell
- data.expandKeys( envdata )
+ data.expandKeys(envdata)
for e in envdata.keys():
if data.getVarFlag( e, 'python', envdata ):
- sys.__stdout__.write("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1)))
+ bb.msg.plain("\npython %s () {\n%s}\n" % (e, data.getVar(e, envdata, 1)))
- def generateDotGraph( self, pkgs_to_build, ignore_deps ):
+ def generateDepTreeData(self, pkgs_to_build, task):
"""
- Generate a task dependency graph.
-
- pkgs_to_build A list of packages that needs to be built
- ignore_deps A list of names where processing of dependencies
- should be stopped. e.g. dependencies that get
+ Create a dependency tree of pkgs_to_build, returning the data.
"""
- for dep in ignore_deps:
- self.status.ignored_dependencies.add(dep)
+ # Need files parsed
+ self.updateCache()
+
+ # If we are told to do the None task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
+
+ pkgs_to_build = self.checkPackages(pkgs_to_build)
localdata = data.createCopy(self.configuration.data)
bb.data.update_data(localdata)
bb.data.expandKeys(localdata)
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
runlist = []
- try:
- for k in pkgs_to_build:
- taskdata.add_provider(localdata, self.status, k)
- runlist.append([k, "do_%s" % self.configuration.cmd])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ for k in pkgs_to_build:
+ taskdata.add_provider(localdata, self.status, k)
+ runlist.append([k, "do_%s" % task])
+ taskdata.add_unresolved(localdata, self.status)
+
rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
rq.prepare_runqueue()
seen_fnids = []
- depends_file = file('depends.dot', 'w' )
- tdepends_file = file('task-depends.dot', 'w' )
- print >> depends_file, "digraph depends {"
- print >> tdepends_file, "digraph depends {"
+ depend_tree = {}
+ depend_tree["depends"] = {}
+ depend_tree["tdepends"] = {}
+ depend_tree["pn"] = {}
+ depend_tree["rdepends-pn"] = {}
+ depend_tree["packages"] = {}
+ depend_tree["rdepends-pkg"] = {}
+ depend_tree["rrecs-pkg"] = {}
for task in range(len(rq.runq_fnid)):
taskname = rq.runq_task[task]
@@ -267,43 +349,118 @@ class BBCooker:
fn = taskdata.fn_index[fnid]
pn = self.status.pkg_fn[fn]
version = "%s:%s-%s" % self.status.pkg_pepvpr[fn]
- print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
+ if pn not in depend_tree["pn"]:
+ depend_tree["pn"][pn] = {}
+ depend_tree["pn"][pn]["filename"] = fn
+ depend_tree["pn"][pn]["version"] = version
for dep in rq.runq_depends[task]:
depfn = taskdata.fn_index[rq.runq_fnid[dep]]
deppn = self.status.pkg_fn[depfn]
- print >> tdepends_file, '"%s.%s" -> "%s.%s"' % (pn, rq.runq_task[task], deppn, rq.runq_task[dep])
+ dotname = "%s.%s" % (pn, rq.runq_task[task])
+ if not dotname in depend_tree["tdepends"]:
+ depend_tree["tdepends"][dotname] = []
+ depend_tree["tdepends"][dotname].append("%s.%s" % (deppn, rq.runq_task[dep]))
if fnid not in seen_fnids:
seen_fnids.append(fnid)
packages = []
- print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
- for depend in self.status.deps[fn]:
- print >> depends_file, '"%s" -> "%s"' % (pn, depend)
+
+ depend_tree["depends"][pn] = []
+ for dep in taskdata.depids[fnid]:
+ depend_tree["depends"][pn].append(taskdata.build_names_index[dep])
+
+ depend_tree["rdepends-pn"][pn] = []
+ for rdep in taskdata.rdepids[fnid]:
+ depend_tree["rdepends-pn"][pn].append(taskdata.run_names_index[rdep])
+
rdepends = self.status.rundeps[fn]
for package in rdepends:
- for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rdepends[package]):
- print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1])
+ depend_tree["rdepends-pkg"][package] = []
+ for rdepend in rdepends[package]:
+ depend_tree["rdepends-pkg"][package].append(rdepend)
packages.append(package)
+
rrecs = self.status.runrecs[fn]
for package in rrecs:
- for rdepend in re.findall("([\w.-]+)(\ \(.+\))?", rrecs[package]):
- print >> depends_file, '"%s" -> "%s%s" [style=dashed]' % (package, rdepend[0], rdepend[1])
+ depend_tree["rrecs-pkg"][package] = []
+ for rdepend in rrecs[package]:
+ depend_tree["rrecs-pkg"][package].append(rdepend)
if not package in packages:
packages.append(package)
+
for package in packages:
- if package != pn:
- print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
- for depend in self.status.deps[fn]:
- print >> depends_file, '"%s" -> "%s"' % (package, depend)
- # Prints a flattened form of the above where subpackages of a package are merged into the main pn
- #print >> depends_file, '"%s" [label="%s %s\\n%s\\n%s"]' % (pn, pn, taskname, version, fn)
- #for rdep in taskdata.rdepids[fnid]:
- # print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, taskdata.run_names_index[rdep])
- #for dep in taskdata.depids[fnid]:
- # print >> depends_file, '"%s" -> "%s"' % (pn, taskdata.build_names_index[dep])
+ if package not in depend_tree["packages"]:
+ depend_tree["packages"][package] = {}
+ depend_tree["packages"][package]["pn"] = pn
+ depend_tree["packages"][package]["filename"] = fn
+ depend_tree["packages"][package]["version"] = version
+
+ return depend_tree
+
+
+ def generateDepTreeEvent(self, pkgs_to_build, task):
+ """
+ Create a task dependency graph of pkgs_to_build.
+ Generate an event with the result
+ """
+ depgraph = self.generateDepTreeData(pkgs_to_build, task)
+ bb.event.fire(bb.event.DepTreeGenerated(depgraph), self.configuration.data)
+
+ def generateDotGraphFiles(self, pkgs_to_build, task):
+ """
+ Create a task dependency graph of pkgs_to_build.
+ Save the result to a set of .dot files.
+ """
+
+ depgraph = self.generateDepTreeData(pkgs_to_build, task)
+
+ # Prints a flattened form of package-depends below where subpackages of a package are merged into the main pn
+ depends_file = file('pn-depends.dot', 'w' )
+ print >> depends_file, "digraph depends {"
+ for pn in depgraph["pn"]:
+ fn = depgraph["pn"][pn]["filename"]
+ version = depgraph["pn"][pn]["version"]
+ print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
+ for pn in depgraph["depends"]:
+ for depend in depgraph["depends"][pn]:
+ print >> depends_file, '"%s" -> "%s"' % (pn, depend)
+ for pn in depgraph["rdepends-pn"]:
+ for rdepend in depgraph["rdepends-pn"][pn]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (pn, rdepend)
+ print >> depends_file, "}"
+ bb.msg.plain("PN dependencies saved to 'pn-depends.dot'")
+
+ depends_file = file('package-depends.dot', 'w' )
+ print >> depends_file, "digraph depends {"
+ for package in depgraph["packages"]:
+ pn = depgraph["packages"][package]["pn"]
+ fn = depgraph["packages"][package]["filename"]
+ version = depgraph["packages"][package]["version"]
+ if package == pn:
+ print >> depends_file, '"%s" [label="%s %s\\n%s"]' % (pn, pn, version, fn)
+ else:
+ print >> depends_file, '"%s" [label="%s(%s) %s\\n%s"]' % (package, package, pn, version, fn)
+ for depend in depgraph["depends"][pn]:
+ print >> depends_file, '"%s" -> "%s"' % (package, depend)
+ for package in depgraph["rdepends-pkg"]:
+ for rdepend in depgraph["rdepends-pkg"][package]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
+ for package in depgraph["rrecs-pkg"]:
+ for rdepend in depgraph["rrecs-pkg"][package]:
+ print >> depends_file, '"%s" -> "%s" [style=dashed]' % (package, rdepend)
print >> depends_file, "}"
+ bb.msg.plain("Package dependencies saved to 'package-depends.dot'")
+
+ tdepends_file = file('task-depends.dot', 'w' )
+ print >> tdepends_file, "digraph depends {"
+ for task in depgraph["tdepends"]:
+ (pn, taskname) = task.rsplit(".", 1)
+ fn = depgraph["pn"][pn]["filename"]
+ version = depgraph["pn"][pn]["version"]
+ print >> tdepends_file, '"%s.%s" [label="%s %s\\n%s\\n%s"]' % (pn, taskname, pn, taskname, version, fn)
+ for dep in depgraph["tdepends"][task]:
+ print >> tdepends_file, '"%s" -> "%s"' % (task, dep)
print >> tdepends_file, "}"
- bb.msg.note(1, bb.msg.domain.Collection, "Dependencies saved to 'depends.dot'")
- bb.msg.note(1, bb.msg.domain.Collection, "Task dependencies saved to 'task-depends.dot'")
+ bb.msg.plain("Task dependencies saved to 'task-depends.dot'")
def buildDepgraph( self ):
all_depends = self.status.all_depends
@@ -324,7 +481,7 @@ class BBCooker:
try:
(providee, provider) = p.split(':')
except:
- bb.msg.error(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
+ bb.msg.fatal(bb.msg.domain.Provider, "Malformed option in PREFERRED_PROVIDERS variable: %s" % p)
continue
if providee in self.status.preferred and self.status.preferred[providee] != provider:
bb.msg.error(bb.msg.domain.Provider, "conflicting preferences for %s: both %s and %s specified" % (providee, provider, self.status.preferred[providee]))
@@ -362,19 +519,6 @@ class BBCooker:
self.status.possible_world = None
self.status.all_depends = None
- def myProgressCallback( self, x, y, f, from_cache ):
- """Update any tty with the progress change"""
- if os.isatty(sys.stdout.fileno()):
- sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
- sys.stdout.flush()
- else:
- if x == 1:
- sys.stdout.write("Parsing .bb files, please wait...")
- sys.stdout.flush()
- if x == y:
- sys.stdout.write("done.")
- sys.stdout.flush()
-
def interactiveMode( self ):
"""Drop off into a shell"""
try:
@@ -383,12 +527,10 @@ class BBCooker:
bb.msg.fatal(bb.msg.domain.Parsing, "Sorry, shell not available (%s)" % details )
else:
shell.start( self )
- sys.exit( 0 )
- def parseConfigurationFile( self, afiles ):
+ def parseConfigurationFile( self, afile ):
try:
- for afile in afiles:
- self.configuration.data = bb.parse.handle( afile, self.configuration.data )
+ self.configuration.data = bb.parse.handle( afile, self.configuration.data )
# Handle any INHERITs and inherit the base class
inherits = ["base"] + (bb.data.getVar('INHERIT', self.configuration.data, True ) or "").split()
@@ -402,10 +544,10 @@ class BBCooker:
bb.fetch.fetcher_init(self.configuration.data)
- bb.event.fire(bb.event.ConfigParsed(self.configuration.data))
+ bb.event.fire(bb.event.ConfigParsed(), self.configuration.data)
except IOError, e:
- bb.msg.fatal(bb.msg.domain.Parsing, "IO Error: %s" % str(e) )
+ bb.msg.fatal(bb.msg.domain.Parsing, "Error when parsing %s: %s" % (afile, str(e)))
except bb.parse.ParseError, details:
bb.msg.fatal(bb.msg.domain.Parsing, "Unable to parse %s (%s)" % (afile, details) )
@@ -439,17 +581,17 @@ class BBCooker:
"""
if not bb.data.getVar("BUILDNAME", self.configuration.data):
bb.data.setVar("BUILDNAME", os.popen('date +%Y%m%d%H%M').readline().strip(), self.configuration.data)
- bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()),self.configuration.data)
+ bb.data.setVar("BUILDSTART", time.strftime('%m/%d/%Y %H:%M:%S',time.gmtime()), self.configuration.data)
- def matchFile(self, buildfile):
+ def matchFiles(self, buildfile):
"""
- Convert the fragment buildfile into a real file
- Error if there are too many matches
+ Find the .bb files which match the expression in 'buildfile'.
"""
+
bf = os.path.abspath(buildfile)
try:
os.stat(bf)
- return bf
+ return [bf]
except OSError:
(filelist, masked) = self.collect_bbfiles()
regexp = re.compile(buildfile)
@@ -458,27 +600,41 @@ class BBCooker:
if regexp.search(f) and os.path.isfile(f):
bf = f
matches.append(f)
- if len(matches) != 1:
- bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
- for f in matches:
- bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
- return False
- return matches[0]
+ return matches
- def buildFile(self, buildfile):
+ def matchFile(self, buildfile):
+ """
+ Find the .bb file which matches the expression in 'buildfile'.
+ Raise an error if multiple files
+ """
+ matches = self.matchFiles(buildfile)
+ if len(matches) != 1:
+ bb.msg.error(bb.msg.domain.Parsing, "Unable to match %s (%s matches found):" % (buildfile, len(matches)))
+ for f in matches:
+ bb.msg.error(bb.msg.domain.Parsing, " %s" % f)
+ raise MultipleMatches
+ return matches[0]
+
+ def buildFile(self, buildfile, task):
"""
Build the file matching regexp buildfile
"""
- # Make sure our target is a fully qualified filename
+ # Parse the configuration here. We need to do it explicitly here since
+ # buildFile() doesn't use the cache
+ self.parseConfiguration()
+
+ # If we are told to do the None task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
+
fn = self.matchFile(buildfile)
- if not fn:
- return False
+ self.buildSetVars()
# Load data into the cache for fn and parse the loaded cache data
self.bb_cache = bb.cache.init(self)
self.status = bb.cache.CacheData()
- self.bb_cache.loadData(fn, self.configuration.data, self.status)
+ self.bb_cache.loadData(fn, self.configuration.data, self.status)
# Tweak some variables
item = self.bb_cache.getVar('PN', fn, True)
@@ -493,159 +649,157 @@ class BBCooker:
# Remove stamp for target if force mode active
if self.configuration.force:
- bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (self.configuration.cmd, fn))
- bb.build.del_stamp('do_%s' % self.configuration.cmd, self.configuration.data)
+ bb.msg.note(2, bb.msg.domain.RunQueue, "Remove stamp %s, %s" % (task, fn))
+ bb.build.del_stamp('do_%s' % task, self.status, fn)
# Setup taskdata structure
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
taskdata.add_provider(self.configuration.data, self.status, item)
buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
- bb.event.fire(bb.event.BuildStarted(buildname, [item], self.configuration.event_data))
+ bb.event.fire(bb.event.BuildStarted(buildname, [item]), self.configuration.event_data)
# Execute the runqueue
- runlist = [[item, "do_%s" % self.configuration.cmd]]
+ runlist = [[item, "do_%s" % task]]
+
rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- rq.prepare_runqueue()
- try:
- failures = rq.execute_runqueue()
- except runqueue.TaskFailure, fnids:
+
+ def buildFileIdle(server, rq, abort):
+
+ if abort or self.cookerAction == cookerStop:
+ rq.finish_runqueue(True)
+ elif self.cookerAction == cookerShutdown:
+ rq.finish_runqueue(False)
failures = 0
- for fnid in fnids:
- bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
- failures = failures + 1
- bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures))
- return False
- bb.event.fire(bb.event.BuildCompleted(buildname, [item], self.configuration.event_data, failures))
- return True
+ try:
+ retval = rq.execute_runqueue()
+ except runqueue.TaskFailure, fnids:
+ for fnid in fnids:
+ bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
+ failures = failures + 1
+ retval = False
+ if not retval:
+ self.command.finishAsyncCommand()
+ bb.event.fire(bb.event.BuildCompleted(buildname, item, failures), self.configuration.event_data)
+ return False
+ return 0.5
+
+ self.server.register_idle_function(buildFileIdle, rq)
- def buildTargets(self, targets):
+ def buildTargets(self, targets, task):
"""
Attempt to build the targets specified
"""
- buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
- bb.event.fire(bb.event.BuildStarted(buildname, targets, self.configuration.event_data))
+ # Need files parsed
+ self.updateCache()
- localdata = data.createCopy(self.configuration.data)
- bb.data.update_data(localdata)
- bb.data.expandKeys(localdata)
+ # If we are told to do the NULL task then query the default task
+ if (task == None):
+ task = self.configuration.cmd
- taskdata = bb.taskdata.TaskData(self.configuration.abort, self.configuration.tryaltconfigs)
+ targets = self.checkPackages(targets)
- runlist = []
- try:
- for k in targets:
- taskdata.add_provider(localdata, self.status, k)
- runlist.append([k, "do_%s" % self.configuration.cmd])
- taskdata.add_unresolved(localdata, self.status)
- except bb.providers.NoProvider:
- sys.exit(1)
+ def buildTargetsIdle(server, rq, abort):
- rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- rq.prepare_runqueue()
- try:
- failures = rq.execute_runqueue()
- except runqueue.TaskFailure, fnids:
+ if abort or self.cookerAction == cookerStop:
+ rq.finish_runqueue(True)
+ elif self.cookerAction == cookerShutdown:
+ rq.finish_runqueue(False)
failures = 0
- for fnid in fnids:
- bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
- failures = failures + 1
- bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures))
- sys.exit(1)
- bb.event.fire(bb.event.BuildCompleted(buildname, targets, self.configuration.event_data, failures))
+ try:
+ retval = rq.execute_runqueue()
+ except runqueue.TaskFailure, fnids:
+ for fnid in fnids:
+ bb.msg.error(bb.msg.domain.Build, "'%s' failed" % taskdata.fn_index[fnid])
+ failures = failures + 1
+ retval = False
+ if not retval:
+ self.command.finishAsyncCommand()
+ bb.event.fire(bb.event.BuildCompleted(buildname, targets, failures), self.configuration.event_data)
+ return None
+ return 0.5
- sys.exit(0)
+ self.buildSetVars()
- def updateCache(self):
- # Import Psyco if available and not disabled
- import platform
- if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
- if not self.configuration.disable_psyco:
- try:
- import psyco
- except ImportError:
- bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
- else:
- psyco.bind( self.parse_bbfiles )
- else:
- bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
+ buildname = bb.data.getVar("BUILDNAME", self.configuration.data)
+ bb.event.fire(bb.event.BuildStarted(buildname, targets), self.configuration.event_data)
- self.status = bb.cache.CacheData()
+ localdata = data.createCopy(self.configuration.data)
+ bb.data.update_data(localdata)
+ bb.data.expandKeys(localdata)
- ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
- self.status.ignored_dependencies = set( ignore.split() )
+ taskdata = bb.taskdata.TaskData(self.configuration.abort)
- self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
+ runlist = []
+ for k in targets:
+ taskdata.add_provider(localdata, self.status, k)
+ runlist.append([k, "do_%s" % task])
+ taskdata.add_unresolved(localdata, self.status)
- bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
- (filelist, masked) = self.collect_bbfiles()
- bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
- self.parse_bbfiles(filelist, masked, self.myProgressCallback)
- bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
+ rq = bb.runqueue.RunQueue(self, self.configuration.data, self.status, taskdata, runlist)
- self.buildDepgraph()
+ self.server.register_idle_function(buildTargetsIdle, rq)
- def cook(self):
- """
- We are building stuff here. We do the building
- from here. By default we try to execute task
- build.
- """
+ def updateCache(self):
- # Wipe the OS environment
- bb.utils.empty_environment()
+ if self.cookerState == cookerParsed:
+ return
- if self.configuration.show_environment:
- self.showEnvironment(self.configuration.buildfile, self.configuration.pkgs_to_build)
- sys.exit( 0 )
+ if self.cookerState != cookerParsing:
- self.buildSetVars()
+ self.parseConfiguration ()
- if self.configuration.interactive:
- self.interactiveMode()
+ # Import Psyco if available and not disabled
+ import platform
+ if platform.machine() in ['i386', 'i486', 'i586', 'i686']:
+ if not self.configuration.disable_psyco:
+ try:
+ import psyco
+ except ImportError:
+ bb.msg.note(1, bb.msg.domain.Collection, "Psyco JIT Compiler (http://psyco.sf.net) not available. Install it to increase performance.")
+ else:
+ psyco.bind( CookerParser.parse_next )
+ else:
+ bb.msg.note(1, bb.msg.domain.Collection, "You have disabled Psyco. This decreases performance.")
- if self.configuration.buildfile is not None:
- if not self.buildFile(self.configuration.buildfile):
- sys.exit(1)
- sys.exit(0)
+ self.status = bb.cache.CacheData()
- # initialise the parsing status now we know we will need deps
- self.updateCache()
+ ignore = bb.data.getVar("ASSUME_PROVIDED", self.configuration.data, 1) or ""
+ self.status.ignored_dependencies = set(ignore.split())
+
+ for dep in self.configuration.extra_assume_provided:
+ self.status.ignored_dependencies.add(dep)
+
+ self.handleCollections( bb.data.getVar("BBFILE_COLLECTIONS", self.configuration.data, 1) )
- if self.configuration.revisions_changed:
- sys.exit(bb.fetch.fetcher_compare_revisons(self.configuration.data))
+ bb.msg.debug(1, bb.msg.domain.Collection, "collecting .bb files")
+ (filelist, masked) = self.collect_bbfiles()
+ bb.data.renameVar("__depends", "__base_depends", self.configuration.data)
- if self.configuration.parse_only:
- bb.msg.note(1, bb.msg.domain.Collection, "Requested parsing .bb files only. Exiting.")
- return 0
+ self.parser = CookerParser(self, filelist, masked)
+ self.cookerState = cookerParsing
- pkgs_to_build = self.configuration.pkgs_to_build
+ if not self.parser.parse_next():
+ bb.msg.debug(1, bb.msg.domain.Collection, "parsing complete")
+ self.buildDepgraph()
+ self.cookerState = cookerParsed
+ return None
- if len(pkgs_to_build) == 0 and not self.configuration.show_versions:
- print "Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help'"
- print "for usage information."
- sys.exit(0)
+ return True
- try:
- if self.configuration.show_versions:
- self.showVersions()
- sys.exit( 0 )
- if 'world' in pkgs_to_build:
- self.buildWorldTargetList()
- pkgs_to_build.remove('world')
- for t in self.status.world_target:
- pkgs_to_build.append(t)
+ def checkPackages(self, pkgs_to_build):
- if self.configuration.dot_graph:
- self.generateDotGraph( pkgs_to_build, self.configuration.ignored_dot_deps )
- sys.exit( 0 )
+ if len(pkgs_to_build) == 0:
+ raise NothingToBuild
- return self.buildTargets(pkgs_to_build)
+ if 'world' in pkgs_to_build:
+ self.buildWorldTargetList()
+ pkgs_to_build.remove('world')
+ for t in self.status.world_target:
+ pkgs_to_build.append(t)
- except KeyboardInterrupt:
- bb.msg.note(1, bb.msg.domain.Collection, "KeyboardInterrupt - Build not completed.")
- sys.exit(1)
+ return pkgs_to_build
def get_bbfiles( self, path = os.getcwd() ):
"""Get list of default .bb files by reading out the current directory"""
@@ -717,59 +871,108 @@ class BBCooker:
return (finalfiles, masked)
- def parse_bbfiles(self, filelist, masked, progressCallback = None):
- parsed, cached, skipped, error = 0, 0, 0, 0
- for i in xrange( len( filelist ) ):
- f = filelist[i]
+ def serve(self):
- #bb.msg.debug(1, bb.msg.domain.Collection, "parsing %s" % f)
+ # Empty the environment. The environment will be populated as
+ # necessary from the data store.
+ bb.utils.empty_environment()
- # read a file's metadata
+ if self.configuration.profile:
try:
- fromCache, skip = self.bb_cache.loadData(f, self.configuration.data, self.status)
- if skip:
- skipped += 1
- bb.msg.debug(2, bb.msg.domain.Collection, "skipping %s" % f)
- self.bb_cache.skip(f)
- continue
- elif fromCache: cached += 1
- else: parsed += 1
-
- # Disabled by RP as was no longer functional
- # allow metadata files to add items to BBFILES
- #data.update_data(self.pkgdata[f])
- #addbbfiles = self.bb_cache.getVar('BBFILES', f, False) or None
- #if addbbfiles:
- # for aof in addbbfiles.split():
- # if not files.count(aof):
- # if not os.path.isabs(aof):
- # aof = os.path.join(os.path.dirname(f),aof)
- # files.append(aof)
-
- # now inform the caller
- if progressCallback is not None:
- progressCallback( i + 1, len( filelist ), f, fromCache )
+ import cProfile as profile
+ except:
+ import profile
+
+ profile.runctx("self.server.serve_forever()", globals(), locals(), "profile.log")
+
+ # Redirect stdout to capture profile information
+ pout = open('profile.log.processed', 'w')
+ so = sys.stdout.fileno()
+ os.dup2(pout.fileno(), so)
+
+ import pstats
+ p = pstats.Stats('profile.log')
+ p.sort_stats('time')
+ p.print_stats()
+ p.print_callers()
+ p.sort_stats('cumulative')
+ p.print_stats()
+
+ os.dup2(so, pout.fileno())
+ pout.flush()
+ pout.close()
+ else:
+ self.server.serve_forever()
+
+ bb.event.fire(CookerExit(), self.configuration.event_data)
+
+class CookerExit(bb.event.Event):
+ """
+ Notify clients of the Cooker shutdown
+ """
+
+ def __init__(self):
+ bb.event.Event.__init__(self)
+
+class CookerParser:
+ def __init__(self, cooker, filelist, masked):
+ # Internal data
+ self.filelist = filelist
+ self.cooker = cooker
+
+ # Accounting statistics
+ self.parsed = 0
+ self.cached = 0
+ self.error = 0
+ self.masked = masked
+ self.total = len(filelist)
+
+ self.skipped = 0
+ self.virtuals = 0
+
+ # Pointer to the next file to parse
+ self.pointer = 0
+
+ def parse_next(self):
+ if self.pointer < len(self.filelist):
+ f = self.filelist[self.pointer]
+ cooker = self.cooker
+
+ try:
+ fromCache, skipped, virtuals = cooker.bb_cache.loadData(f, cooker.configuration.data, cooker.status)
+ if fromCache:
+ self.cached += 1
+ else:
+ self.parsed += 1
+
+ self.skipped += skipped
+ self.virtuals += virtuals
except IOError, e:
- self.bb_cache.remove(f)
+ self.error += 1
+ cooker.bb_cache.remove(f)
bb.msg.error(bb.msg.domain.Collection, "opening %s: %s" % (f, e))
pass
except KeyboardInterrupt:
- self.bb_cache.sync()
+ cooker.bb_cache.remove(f)
+ cooker.bb_cache.sync()
raise
except Exception, e:
- error += 1
- self.bb_cache.remove(f)
+ self.error += 1
+ cooker.bb_cache.remove(f)
bb.msg.error(bb.msg.domain.Collection, "%s while parsing %s" % (e, f))
except:
- self.bb_cache.remove(f)
+ cooker.bb_cache.remove(f)
raise
+ finally:
+ bb.event.fire(bb.event.ParseProgress(self.cached, self.parsed, self.skipped, self.masked, self.virtuals, self.error, self.total), cooker.configuration.event_data)
- if progressCallback is not None:
- print "\r" # need newline after Handling Bitbake files message
- bb.msg.note(1, bb.msg.domain.Collection, "Parsing finished. %d cached, %d parsed, %d skipped, %d masked." % ( cached, parsed, skipped, masked ))
+ self.pointer += 1
- self.bb_cache.sync()
+ if self.pointer >= self.total:
+ cooker.bb_cache.sync()
+ if self.error > 0:
+ raise ParsingErrorsFound
+ return False
+ return True
- if error > 0:
- bb.msg.fatal(bb.msg.domain.Collection, "Parsing errors found, exiting...")
diff --git a/bitbake/lib/bb/daemonize.py b/bitbake/lib/bb/daemonize.py
new file mode 100644
index 0000000..1a8bb37
--- /dev/null
+++ b/bitbake/lib/bb/daemonize.py
@@ -0,0 +1,191 @@
+"""
+Python Deamonizing helper
+
+Configurable daemon behaviors:
+
+ 1.) The current working directory set to the "/" directory.
+ 2.) The current file creation mode mask set to 0.
+ 3.) Close all open files (1024).
+ 4.) Redirect standard I/O streams to "/dev/null".
+
+A failed call to fork() now raises an exception.
+
+References:
+ 1) Advanced Programming in the Unix Environment: W. Richard Stevens
+ 2) Unix Programming Frequently Asked Questions:
+ http://www.erlenstar.demon.co.uk/unix/faq_toc.html
+
+Modified to allow a function to be daemonized and return for
+bitbake use by Richard Purdie
+"""
+
+__author__ = "Chad J. Schroeder"
+__copyright__ = "Copyright (C) 2005 Chad J. Schroeder"
+__version__ = "0.2"
+
+# Standard Python modules.
+import os # Miscellaneous OS interfaces.
+import sys # System-specific parameters and functions.
+
+# Default daemon parameters.
+# File mode creation mask of the daemon.
+# For BitBake's children, we do want to inherit the parent umask.
+UMASK = None
+
+# Default maximum for the number of available file descriptors.
+MAXFD = 1024
+
+# The standard I/O file descriptors are redirected to /dev/null by default.
+if (hasattr(os, "devnull")):
+ REDIRECT_TO = os.devnull
+else:
+ REDIRECT_TO = "/dev/null"
+
+def createDaemon(function, logfile):
+ """
+ Detach a process from the controlling terminal and run it in the
+ background as a daemon, returning control to the caller.
+ """
+
+ try:
+ # Fork a child process so the parent can exit. This returns control to
+ # the command-line or shell. It also guarantees that the child will not
+ # be a process group leader, since the child receives a new process ID
+ # and inherits the parent's process group ID. This step is required
+ # to insure that the next call to os.setsid is successful.
+ pid = os.fork()
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if (pid == 0): # The first child.
+ # To become the session leader of this new session and the process group
+ # leader of the new process group, we call os.setsid(). The process is
+ # also guaranteed not to have a controlling terminal.
+ os.setsid()
+
+ # Is ignoring SIGHUP necessary?
+ #
+ # It's often suggested that the SIGHUP signal should be ignored before
+ # the second fork to avoid premature termination of the process. The
+ # reason is that when the first child terminates, all processes, e.g.
+ # the second child, in the orphaned group will be sent a SIGHUP.
+ #
+ # "However, as part of the session management system, there are exactly
+ # two cases where SIGHUP is sent on the death of a process:
+ #
+ # 1) When the process that dies is the session leader of a session that
+ # is attached to a terminal device, SIGHUP is sent to all processes
+ # in the foreground process group of that terminal device.
+ # 2) When the death of a process causes a process group to become
+ # orphaned, and one or more processes in the orphaned group are
+ # stopped, then SIGHUP and SIGCONT are sent to all members of the
+ # orphaned group." [2]
+ #
+ # The first case can be ignored since the child is guaranteed not to have
+ # a controlling terminal. The second case isn't so easy to dismiss.
+ # The process group is orphaned when the first child terminates and
+ # POSIX.1 requires that every STOPPED process in an orphaned process
+ # group be sent a SIGHUP signal followed by a SIGCONT signal. Since the
+ # second child is not STOPPED though, we can safely forego ignoring the
+ # SIGHUP signal. In any case, there are no ill-effects if it is ignored.
+ #
+ # import signal # Set handlers for asynchronous events.
+ # signal.signal(signal.SIGHUP, signal.SIG_IGN)
+
+ try:
+ # Fork a second child and exit immediately to prevent zombies. This
+ # causes the second child process to be orphaned, making the init
+ # process responsible for its cleanup. And, since the first child is
+ # a session leader without a controlling terminal, it's possible for
+ # it to acquire one by opening a terminal in the future (System V-
+ # based systems). This second fork guarantees that the child is no
+ # longer a session leader, preventing the daemon from ever acquiring
+ # a controlling terminal.
+ pid = os.fork() # Fork a second child.
+ except OSError, e:
+ raise Exception, "%s [%d]" % (e.strerror, e.errno)
+
+ if (pid == 0): # The second child.
+ # We probably don't want the file mode creation mask inherited from
+ # the parent, so we give the child complete control over permissions.
+ if UMASK is not None:
+ os.umask(UMASK)
+ else:
+ # Parent (the first child) of the second child.
+ os._exit(0)
+ else:
+ # exit() or _exit()?
+ # _exit is like exit(), but it doesn't call any functions registered
+ # with atexit (and on_exit) or any registered signal handlers. It also
+ # closes any open file descriptors. Using exit() may cause all stdio
+ # streams to be flushed twice and any temporary files may be unexpectedly
+ # removed. It's therefore recommended that child branches of a fork()
+ # and the parent branch(es) of a daemon use _exit().
+ return
+
+ # Close all open file descriptors. This prevents the child from keeping
+ # open any file descriptors inherited from the parent. There is a variety
+ # of methods to accomplish this task. Three are listed below.
+ #
+ # Try the system configuration variable, SC_OPEN_MAX, to obtain the maximum
+ # number of open file descriptors to close. If it doesn't exists, use
+ # the default value (configurable).
+ #
+ # try:
+ # maxfd = os.sysconf("SC_OPEN_MAX")
+ # except (AttributeError, ValueError):
+ # maxfd = MAXFD
+ #
+ # OR
+ #
+ # if (os.sysconf_names.has_key("SC_OPEN_MAX")):
+ # maxfd = os.sysconf("SC_OPEN_MAX")
+ # else:
+ # maxfd = MAXFD
+ #
+ # OR
+ #
+ # Use the getrlimit method to retrieve the maximum file descriptor number
+ # that can be opened by this process. If there is not limit on the
+ # resource, use the default value.
+ #
+ import resource # Resource usage information.
+ maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+ if (maxfd == resource.RLIM_INFINITY):
+ maxfd = MAXFD
+
+ # Iterate through and close all file descriptors.
+# for fd in range(0, maxfd):
+# try:
+# os.close(fd)
+# except OSError: # ERROR, fd wasn't open to begin with (ignored)
+# pass
+
+ # Redirect the standard I/O file descriptors to the specified file. Since
+ # the daemon has no controlling terminal, most daemons redirect stdin,
+ # stdout, and stderr to /dev/null. This is done to prevent side-effects
+ # from reads and writes to the standard I/O file descriptors.
+
+ # This call to open is guaranteed to return the lowest file descriptor,
+ # which will be 0 (stdin), since it was closed above.
+# os.open(REDIRECT_TO, os.O_RDWR) # standard input (0)
+
+ # Duplicate standard input to standard output and standard error.
+# os.dup2(0, 1) # standard output (1)
+# os.dup2(0, 2) # standard error (2)
+
+
+ si = file('/dev/null', 'r')
+ so = file(logfile, 'w')
+ se = so
+
+
+ # Replace those fds with our own
+ os.dup2(si.fileno(), sys.stdin.fileno())
+ os.dup2(so.fileno(), sys.stdout.fileno())
+ os.dup2(se.fileno(), sys.stderr.fileno())
+
+ function()
+
+ os._exit(0)
+
diff --git a/bitbake/lib/bb/data.py b/bitbake/lib/bb/data.py
index f424ac7..d3058b9 100644
--- a/bitbake/lib/bb/data.py
+++ b/bitbake/lib/bb/data.py
@@ -37,7 +37,7 @@ the speed is more critical here.
#
#Based on functions from the base bb module, Copyright 2003 Holger Schurig
-import sys, os, re, time, types
+import sys, os, re, types
if sys.argv[0][-5:] == "pydoc":
path = os.path.dirname(os.path.dirname(sys.argv[1]))
else:
diff --git a/bitbake/lib/bb/event.py b/bitbake/lib/bb/event.py
index 9d7341f..7251d78 100644
--- a/bitbake/lib/bb/event.py
+++ b/bitbake/lib/bb/event.py
@@ -24,21 +24,18 @@ BitBake build tools.
import os, re
import bb.utils
+import pickle
+
+# This is the pid for which we should generate the event. This is set when
+# the runqueue forks off.
+worker_pid = 0
+worker_pipe = None
class Event:
"""Base class for events"""
- type = "Event"
-
- def __init__(self, d):
- self._data = d
-
- def getData(self):
- return self._data
-
- def setData(self, data):
- self._data = data
- data = property(getData, setData, None, "data property")
+ def __init__(self):
+ self.pid = worker_pid
NotHandled = 0
Handled = 1
@@ -47,75 +44,83 @@ Registered = 10
AlreadyRegistered = 14
# Internal
-_handlers = []
-_handlers_dict = {}
+_handlers = {}
+_ui_handlers = {}
+_ui_handler_seq = 0
-def tmpHandler(event):
- """Default handler for code events"""
- return NotHandled
+def fire(event, d):
+ """Fire off an Event"""
-def defaultTmpHandler():
- tmp = "def tmpHandler(e):\n\t\"\"\"heh\"\"\"\n\treturn NotHandled"
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event.defaultTmpHandler")
- return comp
+ if worker_pid != 0:
+ worker_fire(event, d)
+ return
-def fire(event):
- """Fire off an Event"""
- for h in _handlers:
+ for handler in _handlers:
+ h = _handlers[handler]
+ event.data = d
if type(h).__name__ == "code":
exec(h)
- if tmpHandler(event) == Handled:
- return Handled
+ tmpHandler(event)
else:
- if h(event) == Handled:
- return Handled
- return NotHandled
+ h(event)
+ del event.data
+
+ errors = []
+ for h in _ui_handlers:
+ #print "Sending event %s" % event
+ try:
+ # We use pickle here since it better handles object instances
+ # which xmlrpc's marshaller does not. Events *must* be serializable
+ # by pickle.
+ _ui_handlers[h].event.send((pickle.dumps(event)))
+ except:
+ errors.append(h)
+ for h in errors:
+ del _ui_handlers[h]
+
+def worker_fire(event, d):
+ data = "<event>" + pickle.dumps(event) + "</event>"
+ if os.write(worker_pipe, data) != len (data):
+ print "Error sending event to server (short write)"
+
+def fire_from_worker(event, d):
+ if not event.startswith("<event>") or not event.endswith("</event>"):
+ print "Error, not an event"
+ return
+ event = pickle.loads(event[7:-8])
+ bb.event.fire(event, d)
def register(name, handler):
"""Register an Event handler"""
# already registered
- if name in _handlers_dict:
+ if name in _handlers:
return AlreadyRegistered
if handler is not None:
-# handle string containing python code
+ # handle string containing python code
if type(handler).__name__ == "str":
- _registerCode(handler)
+ tmp = "def tmpHandler(e):\n%s" % handler
+ comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
+ _handlers[name] = comp
else:
- _handlers.append(handler)
+ _handlers[name] = handler
- _handlers_dict[name] = 1
return Registered
-def _registerCode(handlerStr):
- """Register a 'code' Event.
- Deprecated interface; call register instead.
-
- Expects to be passed python code as a string, which will
- be passed in turn to compile() and then exec(). Note that
- the code will be within a function, so should have had
- appropriate tabbing put in place."""
- tmp = "def tmpHandler(e):\n%s" % handlerStr
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._registerCode")
-# prevent duplicate registration
- _handlers.append(comp)
-
def remove(name, handler):
"""Remove an Event handler"""
+ _handlers.pop(name)
- _handlers_dict.pop(name)
- if type(handler).__name__ == "str":
- return _removeCode(handler)
- else:
- _handlers.remove(handler)
+def register_UIHhandler(handler):
+ bb.event._ui_handler_seq = bb.event._ui_handler_seq + 1
+ _ui_handlers[_ui_handler_seq] = handler
+ return _ui_handler_seq
-def _removeCode(handlerStr):
- """Remove a 'code' Event handler
- Deprecated interface; call remove instead."""
- tmp = "def tmpHandler(e):\n%s" % handlerStr
- comp = bb.utils.better_compile(tmp, "tmpHandler(e)", "bb.event._removeCode")
- _handlers.remove(comp)
+def unregister_UIHhandler(handlerNum):
+ if handlerNum in _ui_handlers:
+ del _ui_handlers[handlerNum]
+ return
def getName(e):
"""Returns the name of a class or class instance"""
@@ -130,17 +135,17 @@ class ConfigParsed(Event):
class RecipeParsed(Event):
""" Recipe Parsing Complete """
- def __init__(self, fn, d):
+ def __init__(self, fn):
self.fn = fn
- Event.__init__(self, d)
+ Event.__init__(self)
class StampUpdate(Event):
"""Trigger for any adjustment of the stamp files to happen"""
- def __init__(self, targets, stampfns, d):
+ def __init__(self, targets, stampfns):
self._targets = targets
self._stampfns = stampfns
- Event.__init__(self, d)
+ Event.__init__(self)
def getStampPrefix(self):
return self._stampfns
@@ -151,29 +156,13 @@ class StampUpdate(Event):
stampPrefix = property(getStampPrefix)
targets = property(getTargets)
-class PkgBase(Event):
- """Base class for package events"""
-
- def __init__(self, t, d):
- self._pkg = t
- Event.__init__(self, d)
-
- def getPkg(self):
- return self._pkg
-
- def setPkg(self, pkg):
- self._pkg = pkg
-
- pkg = property(getPkg, setPkg, None, "pkg property")
-
-
class BuildBase(Event):
"""Base class for bbmake run events"""
- def __init__(self, n, p, c, failures = 0):
+ def __init__(self, n, p, failures = 0):
self._name = n
self._pkgs = p
- Event.__init__(self, c)
+ Event.__init__(self)
self._failures = failures
def getPkgs(self):
@@ -205,33 +194,8 @@ class BuildBase(Event):
cfg = property(getCfg, setCfg, None, "cfg property")
-class DepBase(PkgBase):
- """Base class for dependency events"""
-
- def __init__(self, t, data, d):
- self._dep = d
- PkgBase.__init__(self, t, data)
-
- def getDep(self):
- return self._dep
-
- def setDep(self, dep):
- self._dep = dep
-
- dep = property(getDep, setDep, None, "dep property")
-
-
-class PkgStarted(PkgBase):
- """Package build started"""
-class PkgFailed(PkgBase):
- """Package build failed"""
-
-
-class PkgSucceeded(PkgBase):
- """Package build completed"""
-
class BuildStarted(BuildBase):
"""bbmake build run started"""
@@ -241,18 +205,13 @@ class BuildCompleted(BuildBase):
"""bbmake build run completed"""
-class UnsatisfiedDep(DepBase):
- """Unsatisfied Dependency"""
-class RecursiveDep(DepBase):
- """Recursive Dependency"""
-
class NoProvider(Event):
"""No Provider for an Event"""
- def __init__(self, item, data,runtime=False):
- Event.__init__(self, data)
+ def __init__(self, item, runtime=False):
+ Event.__init__(self)
self._item = item
self._runtime = runtime
@@ -265,8 +224,8 @@ class NoProvider(Event):
class MultipleProviders(Event):
"""Multiple Providers"""
- def __init__(self, item, candidates, data, runtime = False):
- Event.__init__(self, data)
+ def __init__(self, item, candidates, runtime = False):
+ Event.__init__(self)
self._item = item
self._candidates = candidates
self._is_runtime = runtime
@@ -288,3 +247,29 @@ class MultipleProviders(Event):
Get the possible Candidates for a PROVIDER.
"""
return self._candidates
+
+class ParseProgress(Event):
+ """
+ Parsing Progress Event
+ """
+
+ def __init__(self, cached, parsed, skipped, masked, virtuals, errors, total):
+ Event.__init__(self)
+ self.cached = cached
+ self.parsed = parsed
+ self.skipped = skipped
+ self.virtuals = virtuals
+ self.masked = masked
+ self.errors = errors
+ self.sofar = cached + parsed
+ self.total = total
+
+class DepTreeGenerated(Event):
+ """
+ Event when a dependency tree has been generated
+ """
+
+ def __init__(self, depgraph):
+ Event.__init__(self)
+ self._depgraph = depgraph
+
diff --git a/bitbake/lib/bb/fetch/__init__.py b/bitbake/lib/bb/fetch/__init__.py
index 7326ed0..ab4658b 100644
--- a/bitbake/lib/bb/fetch/__init__.py
+++ b/bitbake/lib/bb/fetch/__init__.py
@@ -99,6 +99,11 @@ def fetcher_init(d):
pd.delDomain("BB_URI_HEADREVS")
else:
bb.msg.fatal(bb.msg.domain.Fetcher, "Invalid SRCREV cache policy of: %s" % srcrev_policy)
+
+ for m in methods:
+ if hasattr(m, "init"):
+ m.init(d)
+
# Make sure our domains exist
pd.addDomain("BB_URI_HEADREVS")
pd.addDomain("BB_URI_LOCALCOUNT")
@@ -467,6 +472,23 @@ class Fetch(object):
srcrev_internal_helper = staticmethod(srcrev_internal_helper)
+ def localcount_internal_helper(ud, d):
+ """
+ Return:
+ a) a locked localcount if specified
+ b) None otherwise
+ """
+
+ localcount= None
+ if 'name' in ud.parm:
+ pn = data.getVar("PN", d, 1)
+ localcount = data.getVar("LOCALCOUNT_" + ud.parm['name'], d, 1)
+ if not localcount:
+ localcount = data.getVar("LOCALCOUNT", d, 1)
+ return localcount
+
+ localcount_internal_helper = staticmethod(localcount_internal_helper)
+
def try_mirror(d, tarfn):
"""
Try to use a mirrored version of the sources. We do this
@@ -555,12 +577,7 @@ class Fetch(object):
"""
"""
- has_sortable_valid = hasattr(self, "_sortable_revision_valid")
- has_sortable = hasattr(self, "_sortable_revision")
-
- if has_sortable and not has_sortable_valid:
- return self._sortable_revision(url, ud, d)
- elif has_sortable and self._sortable_revision_valid(url, ud, d):
+ if hasattr(self, "_sortable_revision"):
return self._sortable_revision(url, ud, d)
pd = persist_data.PersistData(d)
@@ -568,13 +585,24 @@ class Fetch(object):
latest_rev = self._build_revision(url, ud, d)
last_rev = pd.getValue("BB_URI_LOCALCOUNT", key + "_rev")
- count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
+ uselocalcount = bb.data.getVar("BB_LOCALCOUNT_OVERRIDE", d, True) or False
+ count = None
+ if uselocalcount:
+ count = Fetch.localcount_internal_helper(ud, d)
+ if count is None:
+ count = pd.getValue("BB_URI_LOCALCOUNT", key + "_count")
if last_rev == latest_rev:
return str(count + "+" + latest_rev)
+ buildindex_provided = hasattr(self, "_sortable_buildindex")
+ if buildindex_provided:
+ count = self._sortable_buildindex(url, ud, d, latest_rev)
+
if count is None:
count = "0"
+ elif uselocalcount or buildindex_provided:
+ count = str(count)
else:
count = str(int(count) + 1)
diff --git a/bitbake/lib/bb/fetch/cvs.py b/bitbake/lib/bb/fetch/cvs.py
index d8bd4ea..90a0065 100644
--- a/bitbake/lib/bb/fetch/cvs.py
+++ b/bitbake/lib/bb/fetch/cvs.py
@@ -41,7 +41,7 @@ class Cvs(Fetch):
"""
Check to see if a given url can be fetched with cvs.
"""
- return ud.type in ['cvs', 'pserver']
+ return ud.type in ['cvs']
def localpath(self, url, ud, d):
if not "module" in ud.parm:
diff --git a/bitbake/lib/bb/fetch/git.py b/bitbake/lib/bb/fetch/git.py
index 3016f0f..0e68325 100644
--- a/bitbake/lib/bb/fetch/git.py
+++ b/bitbake/lib/bb/fetch/git.py
@@ -28,6 +28,12 @@ from bb.fetch import runfetchcmd
class Git(Fetch):
"""Class to fetch a module or modules from git repositories"""
+ def init(self, d):
+ #
+ # Only enable _sortable revision if the key is set
+ #
+ if bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True):
+ self._sortable_buildindex = self._sortable_buildindex_disabled
def supports(self, url, ud, d):
"""
Check to see if a given url can be fetched with git.
@@ -58,10 +64,18 @@ class Git(Fetch):
if not ud.tag or ud.tag == "master":
ud.tag = self.latest_revision(url, ud, d)
+ subdir = ud.parm.get("subpath", "")
+ if subdir != "":
+ if subdir.endswith("/"):
+ subdir = subdir[:-1]
+ subdirpath = os.path.join(ud.path, subdir);
+ else:
+ subdirpath = ud.path;
+
if 'fullclone' in ud.parm:
ud.localfile = ud.mirrortarball
else:
- ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, ud.path.replace('/', '.'), ud.tag), d)
+ ud.localfile = data.expand('git_%s%s_%s.tar.gz' % (ud.host, subdirpath.replace('/', '.'), ud.tag), d)
return os.path.join(data.getVar("DL_DIR", d, True), ud.localfile)
@@ -111,10 +125,27 @@ class Git(Fetch):
if os.path.exists(codir):
bb.utils.prunedir(codir)
+ subdir = ud.parm.get("subpath", "")
+ if subdir != "":
+ if subdir.endswith("/"):
+ subdirbase = os.path.basename(subdir[:-1])
+ else:
+ subdirbase = os.path.basename(subdir)
+ else:
+ subdirbase = ""
+
+ if subdir != "":
+ readpathspec = ":%s" % (subdir)
+ codir = os.path.join(codir, "git")
+ coprefix = os.path.join(codir, subdirbase, "")
+ else:
+ readpathspec = ""
+ coprefix = os.path.join(codir, "git", "")
+
bb.mkdirhier(codir)
os.chdir(ud.clonedir)
- runfetchcmd("git read-tree %s" % (ud.tag), d)
- runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (os.path.join(codir, "git", "")), d)
+ runfetchcmd("git read-tree %s%s" % (ud.tag, readpathspec), d)
+ runfetchcmd("git checkout-index -q -f --prefix=%s -a" % (coprefix), d)
os.chdir(codir)
bb.msg.note(1, bb.msg.domain.Fetcher, "Creating tarball of git checkout")
@@ -154,42 +185,32 @@ class Git(Fetch):
def _build_revision(self, url, ud, d):
return ud.tag
- def _sortable_revision_valid(self, url, ud, d):
- return bb.data.getVar("BB_GIT_CLONE_FOR_SRCREV", d, True) or False
-
- def _sortable_revision(self, url, ud, d):
+ def _sortable_buildindex_disabled(self, url, ud, d, rev):
"""
- This is only called when _sortable_revision_valid called true
-
- We will have to get the updated revision.
+ Return a suitable buildindex for the revision specified. This is done by counting revisions
+ using "git rev-list" which may or may not work in different circumstances.
"""
- key = "GIT_CACHED_REVISION-%s-%s" % (gitsrcname, ud.tag)
- if bb.data.getVar(key, d):
- return bb.data.getVar(key, d)
-
-
- # Runtime warning on wrongly configured sources
- if ud.tag == "1":
- bb.msg.error(1, bb.msg.domain.Fetcher, "SRCREV is '1'. This indicates a configuration error of %s" % url)
- return "0+1"
-
cwd = os.getcwd()
# Check if we have the rev already
+
if not os.path.exists(ud.clonedir):
print "no repo"
self.go(None, ud, d)
+ if not os.path.exists(ud.clonedir):
+ bb.msg.error(bb.msg.domain.Fetcher, "GIT repository for %s doesn't exist in %s, cannot get sortable buildnumber, using old value" % (url, ud.clonedir))
+ return None
+
os.chdir(ud.clonedir)
- if not self._contains_ref(ud.tag, d):
+ if not self._contains_ref(rev, d):
self.go(None, ud, d)
- output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % ud.tag, d, quiet=True)
+ output = runfetchcmd("git rev-list %s -- 2> /dev/null | wc -l" % rev, d, quiet=True)
os.chdir(cwd)
- sortable_revision = "%s+%s" % (output.split()[0], ud.tag)
- bb.data.setVar(key, sortable_revision, d)
- return sortable_revision
-
+ buildindex = "%s" % output.split()[0]
+ bb.msg.debug(1, bb.msg.domain.Fetcher, "GIT repository for %s in %s is returning %s revisions in rev-list before %s" % (url, repodir, buildindex, rev))
+ return buildindex
diff --git a/bitbake/lib/bb/fetch/local.py b/bitbake/lib/bb/fetch/local.py
index 577774e..f9bdf58 100644
--- a/bitbake/lib/bb/fetch/local.py
+++ b/bitbake/lib/bb/fetch/local.py
@@ -33,9 +33,9 @@ from bb.fetch import Fetch
class Local(Fetch):
def supports(self, url, urldata, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url represents a local fetch.
"""
- return urldata.type in ['file','patch']
+ return urldata.type in ['file']
def localpath(self, url, urldata, d):
"""
diff --git a/bitbake/lib/bb/fetch/svk.py b/bitbake/lib/bb/fetch/svk.py
index 442f858..120dad9 100644
--- a/bitbake/lib/bb/fetch/svk.py
+++ b/bitbake/lib/bb/fetch/svk.py
@@ -36,7 +36,7 @@ class Svk(Fetch):
"""Class to fetch a module or modules from svk repositories"""
def supports(self, url, ud, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url can be fetched with svk.
"""
return ud.type in ['svk']
diff --git a/bitbake/lib/bb/fetch/wget.py b/bitbake/lib/bb/fetch/wget.py
index a0dca94..fd93c7e 100644
--- a/bitbake/lib/bb/fetch/wget.py
+++ b/bitbake/lib/bb/fetch/wget.py
@@ -36,7 +36,7 @@ class Wget(Fetch):
"""Class to fetch urls via 'wget'"""
def supports(self, url, ud, d):
"""
- Check to see if a given url can be fetched with cvs.
+ Check to see if a given url can be fetched with wget.
"""
return ud.type in ['http','https','ftp']
diff --git a/bitbake/lib/bb/msg.py b/bitbake/lib/bb/msg.py
index a1b31e5..3fcf709 100644
--- a/bitbake/lib/bb/msg.py
+++ b/bitbake/lib/bb/msg.py
@@ -22,8 +22,8 @@ Message handling infrastructure for bitbake
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import sys, os, re, bb
-from bb import utils, event
+import sys, bb
+from bb import event
debug_level = {}
@@ -47,9 +47,9 @@ domain = bb.utils.Enum(
class MsgBase(bb.event.Event):
"""Base class for messages"""
- def __init__(self, msg, d ):
+ def __init__(self, msg):
self._message = msg
- event.Event.__init__(self, d)
+ event.Event.__init__(self)
class MsgDebug(MsgBase):
"""Debug Message"""
@@ -97,33 +97,29 @@ def set_debug_domains(domains):
#
def debug(level, domain, msg, fn = None):
- bb.event.fire(MsgDebug(msg, None))
if not domain:
domain = 'default'
if debug_level[domain] >= level:
- print 'DEBUG: ' + msg
+ bb.event.fire(MsgDebug(msg), None)
def note(level, domain, msg, fn = None):
- bb.event.fire(MsgNote(msg, None))
if not domain:
domain = 'default'
if level == 1 or verbose or debug_level[domain] >= 1:
- print 'NOTE: ' + msg
+ bb.event.fire(MsgNote(msg), None)
def warn(domain, msg, fn = None):
- bb.event.fire(MsgWarn(msg, None))
- print 'WARNING: ' + msg
+ bb.event.fire(MsgWarn(msg), None)
def error(domain, msg, fn = None):
- bb.event.fire(MsgError(msg, None))
+ bb.event.fire(MsgError(msg), None)
print 'ERROR: ' + msg
def fatal(domain, msg, fn = None):
- bb.event.fire(MsgFatal(msg, None))
- print 'ERROR: ' + msg
+ bb.event.fire(MsgFatal(msg), None)
+ print 'FATAL: ' + msg
sys.exit(1)
def plain(msg, fn = None):
- bb.event.fire(MsgPlain(msg, None))
- print msg
+ bb.event.fire(MsgPlain(msg), None)
diff --git a/bitbake/lib/bb/parse/parse_py/BBHandler.py b/bitbake/lib/bb/parse/parse_py/BBHandler.py
index 915db21..86fa18e 100644
--- a/bitbake/lib/bb/parse/parse_py/BBHandler.py
+++ b/bitbake/lib/bb/parse/parse_py/BBHandler.py
@@ -94,7 +94,7 @@ def finalise(fn, d):
for f in anonfuncs:
code = code + " %s(d)\n" % f
data.setVar("__anonfunc", code, d)
- build.exec_func_python("__anonfunc", d)
+ build.exec_func("__anonfunc", d)
data.delVar('T', d)
if t:
data.setVar('T', t, d)
@@ -114,7 +114,7 @@ def finalise(fn, d):
tasklist = data.getVar('__BBTASKS', d) or []
bb.build.add_tasks(tasklist, d)
- bb.event.fire(bb.event.RecipeParsed(fn, d))
+ bb.event.fire(bb.event.RecipeParsed(fn), d)
def handle(fn, d, include = 0):
@@ -185,18 +185,26 @@ def handle(fn, d, include = 0):
multi = data.getVar('BBCLASSEXTEND', d, 1)
if multi:
based = bb.data.createCopy(d)
+ else:
+ based = d
+ try:
finalise(fn, based)
- darray = {"": based}
- for cls in multi.split():
- pn = data.getVar('PN', d, True)
- based = bb.data.createCopy(d)
- data.setVar('PN', pn + '-' + cls, based)
- inherit([cls], based)
+ except bb.parse.SkipPackage:
+ bb.data.setVar("__SKIPPED", True, based)
+ darray = {"": based}
+
+ for cls in (multi or "").split():
+ pn = data.getVar('PN', d, True)
+ based = bb.data.createCopy(d)
+ data.setVar('PN', pn + '-' + cls, based)
+ inherit([cls], based)
+ try:
finalise(fn, based)
- darray[cls] = based
- return darray
- else:
- finalise(fn, d)
+ except bb.parse.SkipPackage:
+ bb.data.setVar("__SKIPPED", True, based)
+ darray[cls] = based
+ return darray
+
bbpath.pop(0)
if oldfile:
bb.data.setVar("FILE", oldfile, d)
diff --git a/bitbake/lib/bb/parse/parse_py/ConfHandler.py b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
index c9f1ea1..23316ad 100644
--- a/bitbake/lib/bb/parse/parse_py/ConfHandler.py
+++ b/bitbake/lib/bb/parse/parse_py/ConfHandler.py
@@ -34,10 +34,17 @@ __require_regexp__ = re.compile( r"require\s+(.+)" )
__export_regexp__ = re.compile( r"export\s+(.+)" )
def init(data):
- if not bb.data.getVar('TOPDIR', data):
- bb.data.setVar('TOPDIR', os.getcwd(), data)
+ topdir = bb.data.getVar('TOPDIR', data)
+ if not topdir:
+ topdir = os.getcwd()
+ bb.data.setVar('TOPDIR', topdir, data)
if not bb.data.getVar('BBPATH', data):
- bb.data.setVar('BBPATH', os.path.join(sys.prefix, 'share', 'bitbake'), data)
+ from pkg_resources import Requirement, resource_filename
+ bitbake = Requirement.parse("bitbake")
+ datadir = resource_filename(bitbake, "../share/bitbake")
+ basedir = resource_filename(bitbake, "..")
+ bb.data.setVar('BBPATH', '%s:%s:%s' % (topdir, datadir, basedir), data)
+
def supports(fn, d):
return localpath(fn, d)[-5:] == ".conf"
diff --git a/bitbake/lib/bb/providers.py b/bitbake/lib/bb/providers.py
index 001281a..8617251 100644
--- a/bitbake/lib/bb/providers.py
+++ b/bitbake/lib/bb/providers.py
@@ -21,7 +21,7 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import os, re
+import re
from bb import data, utils
import bb
@@ -203,7 +203,7 @@ def _filterProviders(providers, item, cfgData, dataCache):
eligible.append(preferred_versions[pn][1])
# Now add latest verisons
- for pn in pkg_pn.keys():
+ for pn in sortpkg_pn.keys():
if pn in preferred_versions and preferred_versions[pn][1]:
continue
preferred_versions[pn] = findLatestProvider(pn, cfgData, dataCache, sortpkg_pn[pn][0])
diff --git a/bitbake/lib/bb/runqueue.py b/bitbake/lib/bb/runqueue.py
index cce5da4..c3ad442 100644
--- a/bitbake/lib/bb/runqueue.py
+++ b/bitbake/lib/bb/runqueue.py
@@ -37,20 +37,38 @@ class RunQueueStats:
"""
Holds statistics on the tasks handled by the associated runQueue
"""
- def __init__(self):
+ def __init__(self, total):
self.completed = 0
self.skipped = 0
self.failed = 0
+ self.active = 0
+ self.total = total
def taskFailed(self):
+ self.active = self.active - 1
self.failed = self.failed + 1
def taskCompleted(self, number = 1):
+ self.active = self.active - number
self.completed = self.completed + number
def taskSkipped(self, number = 1):
+ self.active = self.active + number
self.skipped = self.skipped + number
+ def taskActive(self):
+ self.active = self.active + 1
+
+# These values indicate the next step due to be run in the
+# runQueue state machine
+runQueuePrepare = 2
+runQueueRunInit = 3
+runQueueRunning = 4
+runQueueFailed = 6
+runQueueCleanUp = 7
+runQueueComplete = 8
+runQueueChildProcess = 9
+
class RunQueueScheduler:
"""
Control the order tasks are scheduled in.
@@ -142,9 +160,9 @@ class RunQueue:
self.cooker = cooker
self.dataCache = dataCache
self.taskData = taskData
+ self.cfgData = cfgData
self.targets = targets
- self.cfgdata = cfgData
self.number_tasks = int(bb.data.getVar("BB_NUMBER_THREADS", cfgData, 1) or 1)
self.multi_provider_whitelist = (bb.data.getVar("MULTI_PROVIDER_WHITELIST", cfgData, 1) or "").split()
self.scheduler = bb.data.getVar("BB_SCHEDULER", cfgData, 1) or "speed"
@@ -152,12 +170,13 @@ class RunQueue:
self.stampwhitelist = bb.data.getVar("BB_STAMP_WHITELIST", cfgData, 1) or ""
def reset_runqueue(self):
-
self.runq_fnid = []
self.runq_task = []
self.runq_depends = []
self.runq_revdeps = []
+ self.state = runQueuePrepare
+
def get_user_idstring(self, task):
fn = self.taskData.fn_index[self.runq_fnid[task]]
taskname = self.runq_task[task]
@@ -653,6 +672,8 @@ class RunQueue:
#self.dump_data(taskData)
+ self.state = runQueueRunInit
+
def check_stamps(self):
unchecked = {}
current = []
@@ -796,39 +817,51 @@ class RunQueue:
(if the abort on failure configuration option isn't set)
"""
- failures = 0
- while 1:
- failed_fnids = []
- try:
- self.execute_runqueue_internal()
- finally:
- if self.master_process:
- failed_fnids = self.finish_runqueue()
- if len(failed_fnids) == 0:
- return failures
+ if self.state is runQueuePrepare:
+ self.prepare_runqueue()
+
+ if self.state is runQueueRunInit:
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
+ self.execute_runqueue_initVars()
+
+ if self.state is runQueueRunning:
+ self.execute_runqueue_internal()
+
+ if self.state is runQueueCleanUp:
+ self.finish_runqueue()
+
+ if self.state is runQueueFailed:
if not self.taskData.tryaltconfigs:
- raise bb.runqueue.TaskFailure(failed_fnids)
- for fnid in failed_fnids:
- #print "Failure: %s %s %s" % (fnid, self.taskData.fn_index[fnid], self.runq_task[fnid])
+ raise bb.runqueue.TaskFailure(self.failed_fnids)
+ for fnid in self.failed_fnids:
self.taskData.fail_fnid(fnid)
- failures = failures + 1
self.reset_runqueue()
- self.prepare_runqueue()
+
+ if self.state is runQueueComplete:
+ # All done
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
+ return False
+
+ if self.state is runQueueChildProcess:
+ print "Child process"
+ return False
+
+ # Loop
+ return True
def execute_runqueue_initVars(self):
- self.stats = RunQueueStats()
+ self.stats = RunQueueStats(len(self.runq_fnid))
- self.active_builds = 0
self.runq_buildable = []
self.runq_running = []
self.runq_complete = []
self.build_pids = {}
+ self.build_pipes = {}
self.failed_fnids = []
- self.master_process = True
# Mark initial buildable tasks
- for task in range(len(self.runq_fnid)):
+ for task in range(self.stats.total):
self.runq_running.append(0)
self.runq_complete.append(0)
if len(self.runq_depends[task]) == 0:
@@ -836,6 +869,10 @@ class RunQueue:
else:
self.runq_buildable.append(0)
+ self.state = runQueueRunning
+
+ event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp), self.cfgData)
+
def task_complete(self, task):
"""
Mark a task as completed
@@ -858,26 +895,32 @@ class RunQueue:
taskname = self.runq_task[revdep]
bb.msg.debug(1, bb.msg.domain.RunQueue, "Marking task %s (%s, %s) as buildable" % (revdep, fn, taskname))
+ def task_fail(self, task, exitcode):
+ """
+ Called when a task has failed
+ Updates the state engine with the failure
+ """
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed with %s" % (task, self.get_user_idstring(task), exitcode))
+ self.stats.taskFailed()
+ fnid = self.runq_fnid[task]
+ self.failed_fnids.append(fnid)
+ bb.event.fire(runQueueTaskFailed(task, self.stats, self), self.cfgData)
+ if self.taskData.abort:
+ self.state = runQueueCleanup
+
def execute_runqueue_internal(self):
"""
Run the tasks in a queue prepared by prepare_runqueue
"""
- bb.msg.note(1, bb.msg.domain.RunQueue, "Executing runqueue")
-
- self.execute_runqueue_initVars()
-
- if len(self.runq_fnid) == 0:
+ if self.stats.total == 0:
# nothing to do
- return []
-
- def sigint_handler(signum, frame):
- raise KeyboardInterrupt
-
- event.fire(bb.event.StampUpdate(self.target_pairs, self.dataCache.stamp, self.cfgdata))
+ self.state = runQueueCleanup
while True:
- task = self.sched.next()
+ task = None
+ if self.stats.active < self.number_tasks:
+ task = self.sched.next()
if task is not None:
fn = self.taskData.fn_index[self.runq_fnid[task]]
@@ -885,107 +928,143 @@ class RunQueue:
if self.check_stamp_task(task):
bb.msg.debug(2, bb.msg.domain.RunQueue, "Stamp current task %s (%s)" % (task, self.get_user_idstring(task)))
self.runq_running[task] = 1
+ self.runq_buildable[task] = 1
self.task_complete(task)
self.stats.taskCompleted()
self.stats.taskSkipped()
continue
- bb.msg.note(1, bb.msg.domain.RunQueue, "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.active_builds + 1, len(self.runq_fnid), task, self.get_user_idstring(task)))
sys.stdout.flush()
sys.stderr.flush()
- try:
+ try:
+ pipein, pipeout = os.pipe()
pid = os.fork()
except OSError, e:
bb.msg.fatal(bb.msg.domain.RunQueue, "fork failed: %d (%s)" % (e.errno, e.strerror))
if pid == 0:
- # Bypass master process' handling
- self.master_process = False
- # Stop Ctrl+C being sent to children
- # signal.signal(signal.SIGINT, signal.SIG_IGN)
+ os.close(pipein)
+ # Save out the PID so that the event can include it the
+ # events
+ bb.event.worker_pid = os.getpid()
+ bb.event.worker_pipe = pipeout
+
+ self.state = runQueueChildProcess
# Make the child the process group leader
os.setpgid(0, 0)
+ # No stdin
newsi = os.open('/dev/null', os.O_RDWR)
os.dup2(newsi, sys.stdin.fileno())
- self.cooker.configuration.cmd = taskname[3:]
+
+ bb.event.fire(runQueueTaskStarted(task, self.stats, self), self.cfgData)
+ bb.msg.note(1, bb.msg.domain.RunQueue,
+ "Running task %d of %d (ID: %s, %s)" % (self.stats.completed + self.stats.active + 1,
+ self.stats.total,
+ task,
+ self.get_user_idstring(task)))
+
bb.data.setVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", self, self.cooker.configuration.data)
try:
- self.cooker.tryBuild(fn)
+ self.cooker.tryBuild(fn, taskname[3:])
except bb.build.EventException:
bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
- sys.exit(1)
+ os._exit(1)
except:
bb.msg.error(bb.msg.domain.Build, "Build of " + fn + " " + taskname + " failed")
- raise
- sys.exit(0)
+ os._exit(1)
+ os._exit(0)
+
self.build_pids[pid] = task
+ self.build_pipes[pid] = runQueuePipe(pipein, pipeout, self.cfgData)
self.runq_running[task] = 1
- self.active_builds = self.active_builds + 1
- if self.active_builds < self.number_tasks:
+ self.stats.taskActive()
+ if self.stats.active < self.number_tasks:
continue
- if self.active_builds > 0:
- result = os.waitpid(-1, 0)
- self.active_builds = self.active_builds - 1
+
+ for pipe in self.build_pipes:
+ self.build_pipes[pipe].read()
+
+ if self.stats.active > 0:
+ result = os.waitpid(-1, os.WNOHANG)
+ if result[0] is 0 and result[1] is 0:
+ return
task = self.build_pids[result[0]]
+ del self.build_pids[result[0]]
+ self.build_pipes[result[0]].close()
+ del self.build_pipes[result[0]]
if result[1] != 0:
- del self.build_pids[result[0]]
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
- self.failed_fnids.append(self.runq_fnid[task])
- self.stats.taskFailed()
- if not self.taskData.abort:
- continue
- break
+ self.task_fail(task, result[1])
+ return
self.task_complete(task)
self.stats.taskCompleted()
- del self.build_pids[result[0]]
+ bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
continue
+
+ if len(self.failed_fnids) != 0:
+ self.state = runQueueFailed
+ return
+
+ # Sanity Checks
+ for task in range(self.stats.total):
+ if self.runq_buildable[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
+ if self.runq_running[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
+ if self.runq_complete[task] == 0:
+ bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
+ self.state = runQueueComplete
return
- def finish_runqueue(self):
+ def finish_runqueue_now(self):
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.stats.active)
+ for k, v in self.build_pids.iteritems():
+ try:
+ os.kill(-k, signal.SIGINT)
+ except:
+ pass
+ for pipe in self.build_pipes:
+ self.build_pipes[pipe].read()
+
+ def finish_runqueue(self, now = False):
+ self.state = runQueueCleanUp
+ if now:
+ self.finish_runqueue_now()
try:
- while self.active_builds > 0:
- bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.active_builds)
+ while self.stats.active > 0:
+ bb.event.fire(runQueueExitWait(self.stats.active), self.cfgData)
+ bb.msg.note(1, bb.msg.domain.RunQueue, "Waiting for %s active tasks to finish" % self.stats.active)
tasknum = 1
for k, v in self.build_pids.iteritems():
- bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
- tasknum = tasknum + 1
- result = os.waitpid(-1, 0)
+ bb.msg.note(1, bb.msg.domain.RunQueue, "%s: %s (%s)" % (tasknum, self.get_user_idstring(v), k))
+ tasknum = tasknum + 1
+ result = os.waitpid(-1, os.WNOHANG)
+ if result[0] is 0 and result[1] is 0:
+ return
task = self.build_pids[result[0]]
- if result[1] != 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s (%s) failed" % (task, self.get_user_idstring(task)))
- self.failed_fnids.append(self.runq_fnid[task])
- self.stats.taskFailed()
del self.build_pids[result[0]]
- self.active_builds = self.active_builds - 1
- bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
- return self.failed_fnids
- except KeyboardInterrupt:
- bb.msg.note(1, bb.msg.domain.RunQueue, "Sending SIGINT to remaining %s tasks" % self.active_builds)
- for k, v in self.build_pids.iteritems():
- try:
- os.kill(-k, signal.SIGINT)
- except:
- pass
+ self.build_pipes[result[0]].close()
+ del self.build_pipes[result[0]]
+ if result[1] != 0:
+ self.task_fail(task, result[1])
+ else:
+ self.stats.taskCompleted()
+ bb.event.fire(runQueueTaskCompleted(task, self.stats, self), self.cfgData)
+ except:
+ self.finish_runqueue_now()
raise
- # Sanity Checks
- for task in range(len(self.runq_fnid)):
- if self.runq_buildable[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never buildable!" % task)
- if self.runq_running[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never ran!" % task)
- if self.runq_complete[task] == 0:
- bb.msg.error(bb.msg.domain.RunQueue, "Task %s never completed!" % task)
-
- bb.msg.note(1, bb.msg.domain.RunQueue, "Tasks Summary: Attempted %d tasks of which %d didn't need to be rerun and %d failed." % (self.stats.completed, self.stats.skipped, self.stats.failed))
+ if len(self.failed_fnids) != 0:
+ self.state = runQueueFailed
+ return
- return self.failed_fnids
+ self.state = runQueueComplete
+ return
def dump_data(self, taskQueue):
"""
Dump some debug information on the internal data structures
"""
bb.msg.debug(3, bb.msg.domain.RunQueue, "run_tasks:")
- for task in range(len(self.runq_fnid)):
+ for task in range(len(self.runq_task)):
bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
taskQueue.fn_index[self.runq_fnid[task]],
self.runq_task[task],
@@ -994,7 +1073,7 @@ class RunQueue:
self.runq_revdeps[task]))
bb.msg.debug(3, bb.msg.domain.RunQueue, "sorted_tasks:")
- for task1 in range(len(self.runq_fnid)):
+ for task1 in range(len(self.runq_task)):
if task1 in self.prio_map:
task = self.prio_map[task1]
bb.msg.debug(3, bb.msg.domain.RunQueue, " (%s)%s - %s: %s Deps %s RevDeps %s" % (task,
@@ -1005,6 +1084,58 @@ class RunQueue:
self.runq_revdeps[task]))
+class TaskFailure(Exception):
+ """
+ Exception raised when a task in a runqueue fails
+ """
+ def __init__(self, x):
+ self.args = x
+
+
+class runQueueExitWait(bb.event.Event):
+ """
+ Event when waiting for task processes to exit
+ """
+
+ def __init__(self, remain):
+ self.remain = remain
+ self.message = "Waiting for %s active tasks to finish" % remain
+ bb.event.Event.__init__(self)
+
+class runQueueEvent(bb.event.Event):
+ """
+ Base runQueue event class
+ """
+ def __init__(self, task, stats, rq):
+ self.taskid = task
+ self.taskstring = rq.get_user_idstring(task)
+ self.stats = stats
+ bb.event.Event.__init__(self)
+
+class runQueueTaskStarted(runQueueEvent):
+ """
+ Event notifing a task was started
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Running task %s (%d of %d) (%s)" % (task, stats.completed + stats.active + 1, self.stats.total, self.taskstring)
+
+class runQueueTaskFailed(runQueueEvent):
+ """
+ Event notifing a task failed
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Task %s failed (%s)" % (task, self.taskstring)
+
+class runQueueTaskCompleted(runQueueEvent):
+ """
+ Event notifing a task completed
+ """
+ def __init__(self, task, stats, rq):
+ runQueueEvent.__init__(self, task, stats, rq)
+ self.message = "Task %s completed (%s)" % (task, self.taskstring)
+
def check_stamp_fn(fn, taskname, d):
rq = bb.data.getVar("__RUNQUEUE_DO_NOT_USE_EXTERNALLY", d)
fnid = rq.taskData.getfn_id(fn)
@@ -1013,3 +1144,31 @@ def check_stamp_fn(fn, taskname, d):
return rq.check_stamp_task(taskid)
return None
+class runQueuePipe():
+ """
+ Abstraction for a pipe between a worker thread and the server
+ """
+ def __init__(self, pipein, pipeout, d):
+ self.fd = pipein
+ os.close(pipeout)
+ self.queue = ""
+ self.d = d
+
+ def read(self):
+ start = len(self.queue)
+ self.queue = self.queue + os.read(self.fd, 1024)
+ end = len(self.queue)
+ index = self.queue.find("</event>")
+ while index != -1:
+ bb.event.fire_from_worker(self.queue[:index+8], self.d)
+ self.queue = self.queue[index+8:]
+ index = self.queue.find("</event>")
+ return (end > start)
+
+ def close(self):
+ while self.read():
+ continue
+ if len(self.queue) > 0:
+ print "Warning, worker left partial message"
+ os.close(self.fd)
+
diff --git a/bitbake/lib/bb/server/__init__.py b/bitbake/lib/bb/server/__init__.py
new file mode 100644
index 0000000..1a73223
--- /dev/null
+++ b/bitbake/lib/bb/server/__init__.py
@@ -0,0 +1,2 @@
+import xmlrpc
+import none
diff --git a/bitbake/lib/bb/server/none.py b/bitbake/lib/bb/server/none.py
new file mode 100644
index 0000000..ebda111
--- /dev/null
+++ b/bitbake/lib/bb/server/none.py
@@ -0,0 +1,181 @@
+#
+# BitBake 'dummy' Passthrough Server
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ This module implements an xmlrpc server for BitBake.
+
+ Use this by deriving a class from BitBakeXMLRPCServer and then adding
+ methods which you want to "export" via XMLRPC. If the methods have the
+ prefix xmlrpc_, then registering those function will happen automatically,
+ if not, you need to call register_function.
+
+ Use register_idle_function() to add a function which the xmlrpc server
+ calls from within server_forever when no requests are pending. Make sure
+ that those functions are non-blocking or else you will introduce latency
+ in the server's main loop.
+"""
+
+import time
+import bb
+from bb.ui import uievent
+import xmlrpclib
+import pickle
+
+DEBUG = False
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import inspect, select
+
+class BitBakeServerCommands():
+ def __init__(self, server, cooker):
+ self.cooker = cooker
+ self.server = server
+
+ def runCommand(self, command):
+ """
+ Run a cooker command on the server
+ """
+ #print "Running Command %s" % command
+ return self.cooker.command.runCommand(command)
+
+ def terminateServer(self):
+ """
+ Trigger the server to quit
+ """
+ self.server.server_exit()
+ #print "Server (cooker) exitting"
+ return
+
+ def ping(self):
+ """
+ Dummy method which can be used to check the server is still alive
+ """
+ return True
+
+eventQueue = []
+
+class BBUIEventQueue:
+ class event:
+ def __init__(self, parent):
+ self.parent = parent
+ @staticmethod
+ def send(event):
+ bb.server.none.eventQueue.append(pickle.loads(event))
+ @staticmethod
+ def quit():
+ return
+
+ def __init__(self, BBServer):
+ self.eventQueue = bb.server.none.eventQueue
+ self.BBServer = BBServer
+ self.EventHandle = bb.event.register_UIHhandler(self)
+
+ def getEvent(self):
+ if len(self.eventQueue) == 0:
+ return None
+
+ return self.eventQueue.pop(0)
+
+ def waitEvent(self, delay):
+ event = self.getEvent()
+ if event:
+ return event
+ self.BBServer.idle_commands(delay)
+ return self.getEvent()
+
+ def queue_event(self, event):
+ self.eventQueue.append(event)
+
+ def system_quit( self ):
+ bb.event.unregister_UIHhandler(self.EventHandle)
+
+class BitBakeServer():
+ # remove this when you're done with debugging
+ # allow_reuse_address = True
+
+ def __init__(self, cooker):
+ self._idlefuns = {}
+ self.commands = BitBakeServerCommands(self, cooker)
+
+ def register_idle_function(self, function, data):
+ """Register a function to be called while the server is idle"""
+ assert callable(function)
+ self._idlefuns[function] = data
+
+ def idle_commands(self, delay):
+ #print "Idle queue length %s" % len(self._idlefuns)
+ #print "Idle timeout, running idle functions"
+ #if len(self._idlefuns) == 0:
+ nextsleep = delay
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, False)
+ #print "Idle function returned %s" % (retval)
+ if retval is False:
+ del self._idlefuns[function]
+ elif retval is True:
+ nextsleep = None
+ elif nextsleep is None:
+ continue
+ elif retval < nextsleep:
+ nextsleep = retval
+ except SystemExit:
+ raise
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+ if nextsleep is not None:
+ #print "Sleeping for %s (%s)" % (nextsleep, delay)
+ time.sleep(nextsleep)
+
+ def server_exit(self):
+ # Tell idle functions we're exiting
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, True)
+ except:
+ pass
+
+class BitbakeServerInfo():
+ def __init__(self, server):
+ self.server = server
+ self.commands = server.commands
+
+class BitBakeServerFork():
+ def __init__(self, serverinfo, command, logfile):
+ serverinfo.forkCommand = command
+ serverinfo.logfile = logfile
+
+class BitBakeServerConnection():
+ def __init__(self, serverinfo):
+ self.server = serverinfo.server
+ self.connection = serverinfo.commands
+ self.events = bb.server.none.BBUIEventQueue(self.server)
+
+ def terminate(self):
+ try:
+ self.events.system_quit()
+ except:
+ pass
+ try:
+ self.connection.terminateServer()
+ except:
+ pass
+
diff --git a/bitbake/lib/bb/server/xmlrpc.py b/bitbake/lib/bb/server/xmlrpc.py
new file mode 100644
index 0000000..3364918
--- /dev/null
+++ b/bitbake/lib/bb/server/xmlrpc.py
@@ -0,0 +1,187 @@
+#
+# BitBake XMLRPC Server
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2008 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ This module implements an xmlrpc server for BitBake.
+
+ Use this by deriving a class from BitBakeXMLRPCServer and then adding
+ methods which you want to "export" via XMLRPC. If the methods have the
+ prefix xmlrpc_, then registering those function will happen automatically,
+ if not, you need to call register_function.
+
+ Use register_idle_function() to add a function which the xmlrpc server
+ calls from within server_forever when no requests are pending. Make sure
+ that those functions are non-blocking or else you will introduce latency
+ in the server's main loop.
+"""
+
+import bb
+import xmlrpclib, sys
+from bb import daemonize
+from bb.ui import uievent
+
+DEBUG = False
+
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+import inspect, select
+
+if sys.hexversion < 0x020600F0:
+ print "Sorry, python 2.6 or later is required for bitbake's XMLRPC mode"
+ sys.exit(1)
+
+class BitBakeServerCommands():
+ def __init__(self, server, cooker):
+ self.cooker = cooker
+ self.server = server
+
+ def registerEventHandler(self, host, port):
+ """
+ Register a remote UI Event Handler
+ """
+ s = xmlrpclib.Server("http://%s:%d" % (host, port), allow_none=True)
+ return bb.event.register_UIHhandler(s)
+
+ def unregisterEventHandler(self, handlerNum):
+ """
+ Unregister a remote UI Event Handler
+ """
+ return bb.event.unregister_UIHhandler(handlerNum)
+
+ def runCommand(self, command):
+ """
+ Run a cooker command on the server
+ """
+ return self.cooker.command.runCommand(command)
+
+ def terminateServer(self):
+ """
+ Trigger the server to quit
+ """
+ self.server.quit = True
+ print "Server (cooker) exitting"
+ return
+
+ def ping(self):
+ """
+ Dummy method which can be used to check the server is still alive
+ """
+ return True
+
+class BitBakeServer(SimpleXMLRPCServer):
+ # remove this when you're done with debugging
+ # allow_reuse_address = True
+
+ def __init__(self, cooker, interface = ("localhost", 0)):
+ """
+ Constructor
+ """
+ SimpleXMLRPCServer.__init__(self, interface,
+ requestHandler=SimpleXMLRPCRequestHandler,
+ logRequests=False, allow_none=True)
+ self._idlefuns = {}
+ self.host, self.port = self.socket.getsockname()
+ #self.register_introspection_functions()
+ commands = BitBakeServerCommands(self, cooker)
+ self.autoregister_all_functions(commands, "")
+
+ def autoregister_all_functions(self, context, prefix):
+ """
+ Convenience method for registering all functions in the scope
+ of this class that start with a common prefix
+ """
+ methodlist = inspect.getmembers(context, inspect.ismethod)
+ for name, method in methodlist:
+ if name.startswith(prefix):
+ self.register_function(method, name[len(prefix):])
+
+ def register_idle_function(self, function, data):
+ """Register a function to be called while the server is idle"""
+ assert callable(function)
+ self._idlefuns[function] = data
+
+ def serve_forever(self):
+ """
+ Serve Requests. Overloaded to honor a quit command
+ """
+ self.quit = False
+ self.timeout = 0 # Run Idle calls for our first callback
+ while not self.quit:
+ #print "Idle queue length %s" % len(self._idlefuns)
+ self.handle_request()
+ #print "Idle timeout, running idle functions"
+ nextsleep = None
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, False)
+ if retval is False:
+ del self._idlefuns[function]
+ elif retval is True:
+ nextsleep = 0
+ elif nextsleep is 0:
+ continue
+ elif nextsleep is None:
+ nextsleep = retval
+ elif retval < nextsleep:
+ nextsleep = retval
+ except SystemExit:
+ raise
+ except:
+ import traceback
+ traceback.print_exc()
+ pass
+ if nextsleep is None and len(self._idlefuns) > 0:
+ nextsleep = 0
+ self.timeout = nextsleep
+ # Tell idle functions we're exiting
+ for function, data in self._idlefuns.items():
+ try:
+ retval = function(self, data, True)
+ except:
+ pass
+
+ self.server_close()
+ return
+
+class BitbakeServerInfo():
+ def __init__(self, server):
+ self.host = server.host
+ self.port = server.port
+
+class BitBakeServerFork():
+ def __init__(self, serverinfo, command, logfile):
+ daemonize.createDaemon(command, logfile)
+
+class BitBakeServerConnection():
+ def __init__(self, serverinfo):
+ self.connection = xmlrpclib.Server("http://%s:%s" % (serverinfo.host, serverinfo.port), allow_none=True)
+ self.events = uievent.BBUIEventQueue(self.connection)
+
+ def terminate(self):
+ # Don't wait for server indefinitely
+ import socket
+ socket.setdefaulttimeout(2)
+ try:
+ self.events.system_quit()
+ except:
+ pass
+ try:
+ self.connection.terminateServer()
+ except:
+ pass
+
diff --git a/bitbake/lib/bb/shell.py b/bitbake/lib/bb/shell.py
index b1ad783..66e5171 100644
--- a/bitbake/lib/bb/shell.py
+++ b/bitbake/lib/bb/shell.py
@@ -151,9 +151,6 @@ class BitBakeShellCommands:
if len( names ) == 0: names = [ globexpr ]
print "SHELL: Building %s" % ' '.join( names )
- oldcmd = cooker.configuration.cmd
- cooker.configuration.cmd = cmd
-
td = taskdata.TaskData(cooker.configuration.abort)
localdata = data.createCopy(cooker.configuration.data)
data.update_data(localdata)
@@ -168,7 +165,7 @@ class BitBakeShellCommands:
if len(providers) == 0:
raise Providers.NoProvider
- tasks.append([name, "do_%s" % cooker.configuration.cmd])
+ tasks.append([name, "do_%s" % cmd])
td.add_unresolved(localdata, cooker.status)
@@ -189,7 +186,6 @@ class BitBakeShellCommands:
print "ERROR: Couldn't build '%s'" % names
last_exception = e
- cooker.configuration.cmd = oldcmd
build.usage = "<providee>"
@@ -208,6 +204,11 @@ class BitBakeShellCommands:
self.build( params, "configure" )
configure.usage = "<providee>"
+ def install( self, params ):
+ """Execute 'install' on a providee"""
+ self.build( params, "install" )
+ install.usage = "<providee>"
+
def edit( self, params ):
"""Call $EDITOR on a providee"""
name = params[0]
@@ -240,18 +241,14 @@ class BitBakeShellCommands:
bf = completeFilePath( name )
print "SHELL: Calling '%s' on '%s'" % ( cmd, bf )
- oldcmd = cooker.configuration.cmd
- cooker.configuration.cmd = cmd
-
try:
- cooker.buildFile(bf)
+ cooker.buildFile(bf, cmd)
except parse.ParseError:
print "ERROR: Unable to open or parse '%s'" % bf
except build.EventException, e:
print "ERROR: Couldn't build '%s'" % name
last_exception = e
- cooker.configuration.cmd = oldcmd
fileBuild.usage = "<bbfile>"
def fileClean( self, params ):
@@ -493,7 +490,7 @@ SRC_URI = ""
interpreter.interact( "SHELL: Expert Mode - BitBake Python %s\nType 'help' for more information, press CTRL-D to switch back to BBSHELL." % sys.version )
def showdata( self, params ):
- """Show the parsed metadata for a given providee"""
+ """Execute 'showdata' on a providee"""
cooker.showEnvironment(None, params)
showdata.usage = "<providee>"
diff --git a/bitbake/lib/bb/taskdata.py b/bitbake/lib/bb/taskdata.py
index 976e0ca..4a88e75 100644
--- a/bitbake/lib/bb/taskdata.py
+++ b/bitbake/lib/bb/taskdata.py
@@ -23,8 +23,20 @@ Task data collection and handling
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from bb import data, event, mkdirhier, utils
-import bb, os
+import bb
+
+def re_match_strings(target, strings):
+ """
+ Whether or not the string 'target' matches
+ any one string of the strings which can be regular expression string
+ """
+ import re
+
+ for name in strings:
+ if (name==target or
+ re.search(name,target)!=None):
+ return True
+ return False
class TaskData:
"""
@@ -264,7 +276,7 @@ class TaskData:
"""
unresolved = []
for target in self.build_names_index:
- if target in dataCache.ignored_dependencies:
+ if re_match_strings(target, dataCache.ignored_dependencies):
continue
if self.build_names_index.index(target) in self.failed_deps:
continue
@@ -279,7 +291,7 @@ class TaskData:
"""
unresolved = []
for target in self.run_names_index:
- if target in dataCache.ignored_dependencies:
+ if re_match_strings(target, dataCache.ignored_dependencies):
continue
if self.run_names_index.index(target) in self.failed_rdeps:
continue
@@ -359,7 +371,7 @@ class TaskData:
added internally during dependency resolution
"""
- if item in dataCache.ignored_dependencies:
+ if re_match_strings(item, dataCache.ignored_dependencies):
return
if not item in dataCache.providers:
@@ -367,7 +379,7 @@ class TaskData:
bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s' (but '%s' DEPENDS on or otherwise requires it)" % (item, self.get_dependees_str(item)))
else:
bb.msg.note(2, bb.msg.domain.Provider, "Nothing PROVIDES '%s'" % (item))
- bb.event.fire(bb.event.NoProvider(item, cfgData))
+ bb.event.fire(bb.event.NoProvider(item), cfgData)
raise bb.providers.NoProvider(item)
if self.have_build_target(item):
@@ -380,7 +392,7 @@ class TaskData:
if not eligible:
bb.msg.note(2, bb.msg.domain.Provider, "No buildable provider PROVIDES '%s' but '%s' DEPENDS on or otherwise requires it. Enable debugging and see earlier logs to find unbuildable providers." % (item, self.get_dependees_str(item)))
- bb.event.fire(bb.event.NoProvider(item, cfgData))
+ bb.event.fire(bb.event.NoProvider(item), cfgData)
raise bb.providers.NoProvider(item)
if len(eligible) > 1 and foundUnique == False:
@@ -390,7 +402,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(1, bb.msg.domain.Provider, "multiple providers are available for %s (%s);" % (item, ", ".join(providers_list)))
bb.msg.note(1, bb.msg.domain.Provider, "consider defining PREFERRED_PROVIDER_%s" % item)
- bb.event.fire(bb.event.MultipleProviders(item, providers_list, cfgData))
+ bb.event.fire(bb.event.MultipleProviders(item, providers_list), cfgData)
self.consider_msgs_cache.append(item)
for fn in eligible:
@@ -410,7 +422,7 @@ class TaskData:
(takes item names from RDEPENDS/PACKAGES namespace)
"""
- if item in dataCache.ignored_dependencies:
+ if re_match_strings(item, dataCache.ignored_dependencies):
return
if self.have_runtime_target(item):
@@ -420,7 +432,7 @@ class TaskData:
if not all_p:
bb.msg.error(bb.msg.domain.Provider, "'%s' RDEPENDS/RRECOMMENDS or otherwise requires the runtime entity '%s' but it wasn't found in any PACKAGE or RPROVIDES variables" % (self.get_rdependees_str(item), item))
- bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
+ bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
raise bb.providers.NoRProvider(item)
eligible, numberPreferred = bb.providers.filterProvidersRunTime(all_p, item, cfgData, dataCache)
@@ -428,7 +440,7 @@ class TaskData:
if not eligible:
bb.msg.error(bb.msg.domain.Provider, "'%s' RDEPENDS/RRECOMMENDS or otherwise requires the runtime entity '%s' but it wasn't found in any PACKAGE or RPROVIDES variables of any buildable targets.\nEnable debugging and see earlier logs to find unbuildable targets." % (self.get_rdependees_str(item), item))
- bb.event.fire(bb.event.NoProvider(item, cfgData, runtime=True))
+ bb.event.fire(bb.event.NoProvider(item, runtime=True), cfgData)
raise bb.providers.NoRProvider(item)
if len(eligible) > 1 and numberPreferred == 0:
@@ -438,7 +450,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (%s);" % (item, ", ".join(providers_list)))
bb.msg.note(2, bb.msg.domain.Provider, "consider defining a PREFERRED_PROVIDER entry to match runtime %s" % item)
- bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
+ bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
self.consider_msgs_cache.append(item)
if numberPreferred > 1:
@@ -448,7 +460,7 @@ class TaskData:
providers_list.append(dataCache.pkg_fn[fn])
bb.msg.note(2, bb.msg.domain.Provider, "multiple providers are available for runtime %s (top %s entries preferred) (%s);" % (item, numberPreferred, ", ".join(providers_list)))
bb.msg.note(2, bb.msg.domain.Provider, "consider defining only one PREFERRED_PROVIDER entry to match runtime %s" % item)
- bb.event.fire(bb.event.MultipleProviders(item,providers_list, cfgData, runtime=True))
+ bb.event.fire(bb.event.MultipleProviders(item,providers_list, runtime=True), cfgData)
self.consider_msgs_cache.append(item)
# run through the list until we find one that we can build
diff --git a/bitbake/lib/bb/ui/__init__.py b/bitbake/lib/bb/ui/__init__.py
new file mode 100644
index 0000000..c6a377a
--- /dev/null
+++ b/bitbake/lib/bb/ui/__init__.py
@@ -0,0 +1,18 @@
+#
+# BitBake UI Implementation
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
diff --git a/bitbake/lib/bb/ui/crumbs/__init__.py b/bitbake/lib/bb/ui/crumbs/__init__.py
new file mode 100644
index 0000000..c6a377a
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/__init__.py
@@ -0,0 +1,18 @@
+#
+# BitBake UI Implementation
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
diff --git a/bitbake/lib/bb/ui/crumbs/buildmanager.py b/bitbake/lib/bb/ui/crumbs/buildmanager.py
new file mode 100644
index 0000000..f89e8ee
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/buildmanager.py
@@ -0,0 +1,457 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import threading
+import os
+import datetime
+import time
+
+class BuildConfiguration:
+ """ Represents a potential *or* historic *or* concrete build. It
+ encompasses all the things that we need to tell bitbake to do to make it
+ build what we want it to build.
+
+ It also stored the metadata URL and the set of possible machines (and the
+ distros / images / uris for these. Apart from the metdata URL these are
+ not serialised to file (since they may be transient). In some ways this
+ functionality might be shifted to the loader class."""
+
+ def __init__ (self):
+ self.metadata_url = None
+
+ # Tuple of (distros, image, urls)
+ self.machine_options = {}
+
+ self.machine = None
+ self.distro = None
+ self.image = None
+ self.urls = []
+ self.extra_urls = []
+ self.extra_pkgs = []
+
+ def get_machines_model (self):
+ model = gtk.ListStore (gobject.TYPE_STRING)
+ for machine in self.machine_options.keys():
+ model.append ([machine])
+
+ return model
+
+ def get_distro_and_images_models (self, machine):
+ distro_model = gtk.ListStore (gobject.TYPE_STRING)
+
+ for distro in self.machine_options[machine][0]:
+ distro_model.append ([distro])
+
+ image_model = gtk.ListStore (gobject.TYPE_STRING)
+
+ for image in self.machine_options[machine][1]:
+ image_model.append ([image])
+
+ return (distro_model, image_model)
+
+ def get_repos (self):
+ self.urls = self.machine_options[self.machine][2]
+ return self.urls
+
+ # It might be a lot lot better if we stored these in like, bitbake conf
+ # file format.
+ @staticmethod
+ def load_from_file (filename):
+ f = open (filename, "r")
+
+ conf = BuildConfiguration()
+ for line in f.readlines():
+ data = line.split (";")[1]
+ if (line.startswith ("metadata-url;")):
+ conf.metadata_url = data.strip()
+ continue
+ if (line.startswith ("url;")):
+ conf.urls += [data.strip()]
+ continue
+ if (line.startswith ("extra-url;")):
+ conf.extra_urls += [data.strip()]
+ continue
+ if (line.startswith ("machine;")):
+ conf.machine = data.strip()
+ continue
+ if (line.startswith ("distribution;")):
+ conf.distro = data.strip()
+ continue
+ if (line.startswith ("image;")):
+ conf.image = data.strip()
+ continue
+
+ f.close ()
+ return conf
+
+ # Serialise to a file. This is part of the build process and we use this
+ # to be able to repeat a given build (using the same set of parameters)
+ # but also so that we can include the details of the image / machine /
+ # distro in the build manager tree view.
+ def write_to_file (self, filename):
+ f = open (filename, "w")
+
+ lines = []
+
+ if (self.metadata_url):
+ lines += ["metadata-url;%s\n" % (self.metadata_url)]
+
+ for url in self.urls:
+ lines += ["url;%s\n" % (url)]
+
+ for url in self.extra_urls:
+ lines += ["extra-url;%s\n" % (url)]
+
+ if (self.machine):
+ lines += ["machine;%s\n" % (self.machine)]
+
+ if (self.distro):
+ lines += ["distribution;%s\n" % (self.distro)]
+
+ if (self.image):
+ lines += ["image;%s\n" % (self.image)]
+
+ f.writelines (lines)
+ f.close ()
+
+class BuildResult(gobject.GObject):
+ """ Represents an historic build. Perhaps not successful. But it includes
+ things such as the files that are in the directory (the output from the
+ build) as well as a deserialised BuildConfiguration file that is stored in
+ ".conf" in the directory for the build.
+
+ This is GObject so that it can be included in the TreeStore."""
+
+ (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
+ (0, 1, 2)
+
+ def __init__ (self, parent, identifier):
+ gobject.GObject.__init__ (self)
+ self.date = None
+
+ self.files = []
+ self.status = None
+ self.identifier = identifier
+ self.path = os.path.join (parent, identifier)
+
+ # Extract the date, since the directory name is of the
+ # format build-<year><month><day>-<ordinal> we can easily
+ # pull it out.
+ # TODO: Better to stat a file?
+ (_ , date, revision) = identifier.split ("-")
+ print date
+
+ year = int (date[0:4])
+ month = int (date[4:6])
+ day = int (date[6:8])
+
+ self.date = datetime.date (year, month, day)
+
+ self.conf = None
+
+ # By default builds are STATE_FAILED unless we find a "complete" file
+ # in which case they are STATE_COMPLETE
+ self.state = BuildResult.STATE_FAILED
+ for file in os.listdir (self.path):
+ if (file.startswith (".conf")):
+ conffile = os.path.join (self.path, file)
+ self.conf = BuildConfiguration.load_from_file (conffile)
+ elif (file.startswith ("complete")):
+ self.state = BuildResult.STATE_COMPLETE
+ else:
+ self.add_file (file)
+
+ def add_file (self, file):
+ # Just add the file for now. Don't care about the type.
+ self.files += [(file, None)]
+
+class BuildManagerModel (gtk.TreeStore):
+ """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
+ but it abstracts nicely what the columns mean and the setup of the columns
+ in the model. """
+
+ (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
+ (0, 1, 2, 3, 4, 5, 6)
+
+ def __init__ (self):
+ gtk.TreeStore.__init__ (self,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_OBJECT,
+ gobject.TYPE_INT64,
+ gobject.TYPE_INT)
+
+class BuildManager (gobject.GObject):
+ """ This class manages the historic builds that have been found in the
+ "results" directory but is also used for starting a new build."""
+
+ __gsignals__ = {
+ 'population-finished' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'populate-error' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ())
+ }
+
+ def update_build_result (self, result, iter):
+ # Convert the date into something we can sort by.
+ date = long (time.mktime (result.date.timetuple()))
+
+ # Add a top level entry for the build
+
+ self.model.set (iter,
+ BuildManagerModel.COL_IDENT, result.identifier,
+ BuildManagerModel.COL_DESC, result.conf.image,
+ BuildManagerModel.COL_MACHINE, result.conf.machine,
+ BuildManagerModel.COL_DISTRO, result.conf.distro,
+ BuildManagerModel.COL_BUILD_RESULT, result,
+ BuildManagerModel.COL_DATE, date,
+ BuildManagerModel.COL_STATE, result.state)
+
+ # And then we use the files in the directory as the children for the
+ # top level iter.
+ for file in result.files:
+ self.model.append (iter, (None, file[0], None, None, None, date, -1))
+
+ # This function is called as an idle by the BuildManagerPopulaterThread
+ def add_build_result (self, result):
+ gtk.gdk.threads_enter()
+ self.known_builds += [result]
+
+ self.update_build_result (result, self.model.append (None))
+
+ gtk.gdk.threads_leave()
+
+ def notify_build_finished (self):
+ # This is a bit of a hack. If we have a running build running then we
+ # will have a row in the model in STATE_ONGOING. Find it and make it
+ # as if it was a proper historic build (well, it is completed now....)
+
+ # We need to use the iters here rather than the Python iterator
+ # interface to the model since we need to pass it into
+ # update_build_result
+
+ iter = self.model.get_iter_first()
+
+ while (iter):
+ (ident, state) = self.model.get(iter,
+ BuildManagerModel.COL_IDENT,
+ BuildManagerModel.COL_STATE)
+
+ if state == BuildResult.STATE_ONGOING:
+ result = BuildResult (self.results_directory, ident)
+ self.update_build_result (result, iter)
+ iter = self.model.iter_next(iter)
+
+ def notify_build_succeeded (self):
+ # Write the "complete" file so that when we create the BuildResult
+ # object we put into the model
+
+ complete_file_path = os.path.join (self.cur_build_directory, "complete")
+ f = file (complete_file_path, "w")
+ f.close()
+ self.notify_build_finished()
+
+ def notify_build_failed (self):
+ # Without a "complete" file then this will mark the build as failed:
+ self.notify_build_finished()
+
+ # This function is called as an idle
+ def emit_population_finished_signal (self):
+ gtk.gdk.threads_enter()
+ self.emit ("population-finished")
+ gtk.gdk.threads_leave()
+
+ class BuildManagerPopulaterThread (threading.Thread):
+ def __init__ (self, manager, directory):
+ threading.Thread.__init__ (self)
+ self.manager = manager
+ self.directory = directory
+
+ def run (self):
+ # For each of the "build-<...>" directories ..
+
+ if os.path.exists (self.directory):
+ for directory in os.listdir (self.directory):
+
+ if not directory.startswith ("build-"):
+ continue
+
+ build_result = BuildResult (self.directory, directory)
+ self.manager.add_build_result (build_result)
+
+ gobject.idle_add (BuildManager.emit_population_finished_signal,
+ self.manager)
+
+ def __init__ (self, server, results_directory):
+ gobject.GObject.__init__ (self)
+
+ # The builds that we've found from walking the result directory
+ self.known_builds = []
+
+ # Save out the bitbake server, we need this for issuing commands to
+ # the cooker:
+ self.server = server
+
+ # The TreeStore that we use
+ self.model = BuildManagerModel ()
+
+ # The results directory is where we create (and look for) the
+ # build-<xyz>-<n> directories. We need to populate ourselves from
+ # directory
+ self.results_directory = results_directory
+ self.populate_from_directory (self.results_directory)
+
+ def populate_from_directory (self, directory):
+ thread = BuildManager.BuildManagerPopulaterThread (self, directory)
+ thread.start()
+
+ # Come up with the name for the next build ident by combining "build-"
+ # with the date formatted as yyyymmdd and then an ordinal. We do this by
+ # an optimistic algorithm incrementing the ordinal if we find that it
+ # already exists.
+ def get_next_build_ident (self):
+ today = datetime.date.today ()
+ datestr = str (today.year) + str (today.month) + str (today.day)
+
+ revision = 0
+ test_name = "build-%s-%d" % (datestr, revision)
+ test_path = os.path.join (self.results_directory, test_name)
+
+ while (os.path.exists (test_path)):
+ revision += 1
+ test_name = "build-%s-%d" % (datestr, revision)
+ test_path = os.path.join (self.results_directory, test_name)
+
+ return test_name
+
+ # Take a BuildConfiguration and then try and build it based on the
+ # parameters of that configuration. S
+ def do_build (self, conf):
+ server = self.server
+
+ # Work out the build directory. Note we actually create the
+ # directories here since we need to write the ".conf" file. Otherwise
+ # we could have relied on bitbake's builder thread to actually make
+ # the directories as it proceeds with the build.
+ ident = self.get_next_build_ident ()
+ build_directory = os.path.join (self.results_directory,
+ ident)
+ self.cur_build_directory = build_directory
+ os.makedirs (build_directory)
+
+ conffile = os.path.join (build_directory, ".conf")
+ conf.write_to_file (conffile)
+
+ # Add a row to the model representing this ongoing build. It's kinda a
+ # fake entry. If this build completes or fails then this gets updated
+ # with the real stuff like the historic builds
+ date = long (time.time())
+ self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
+ None, date, BuildResult.STATE_ONGOING))
+ try:
+ server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
+ server.runCommand(["setVariable", "MACHINE", conf.machine])
+ server.runCommand(["setVariable", "DISTRO", conf.distro])
+ server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
+ server.runCommand(["setVariable", "BBFILES", \
+ """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
+ server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
+ server.runCommand(["setVariable", "IPK_FEED_URIS", \
+ " ".join(conf.get_repos())])
+ server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
+ build_directory])
+ server.runCommand(["buildTargets", [conf.image], "rootfs"])
+
+ except Exception, e:
+ print e
+
+class BuildManagerTreeView (gtk.TreeView):
+ """ The tree view for the build manager. This shows the historic builds
+ and so forth. """
+
+ # We use this function to control what goes in the cell since we store
+ # the date in the model as seconds since the epoch (for sorting) and so we
+ # need to make it human readable.
+ def date_format_custom_cell_data_func (self, col, cell, model, iter):
+ date = model.get (iter, BuildManagerModel.COL_DATE)[0]
+ datestr = time.strftime("%A %d %B %Y", time.localtime(date))
+ cell.set_property ("text", datestr)
+
+ # This format function controls what goes in the cell. We use this to map
+ # the integer state to a string and also to colourise the text
+ def state_format_custom_cell_data_fun (self, col, cell, model, iter):
+ state = model.get (iter, BuildManagerModel.COL_STATE)[0]
+
+ if (state == BuildResult.STATE_ONGOING):
+ cell.set_property ("text", "Active")
+ cell.set_property ("foreground", "#000000")
+ elif (state == BuildResult.STATE_FAILED):
+ cell.set_property ("text", "Failed")
+ cell.set_property ("foreground", "#ff0000")
+ elif (state == BuildResult.STATE_COMPLETE):
+ cell.set_property ("text", "Complete")
+ cell.set_property ("foreground", "#00ff00")
+ else:
+ cell.set_property ("text", "")
+
+ def __init__ (self):
+ gtk.TreeView.__init__(self)
+
+ # Misc descriptiony thing
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn (None, renderer,
+ text=BuildManagerModel.COL_DESC)
+ self.append_column (col)
+
+ # Machine
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Machine", renderer,
+ text=BuildManagerModel.COL_MACHINE)
+ self.append_column (col)
+
+ # distro
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Distribution", renderer,
+ text=BuildManagerModel.COL_DISTRO)
+ self.append_column (col)
+
+ # date (using a custom function for formatting the cell contents it
+ # takes epoch -> human readable string)
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Date", renderer,
+ text=BuildManagerModel.COL_DATE)
+ self.append_column (col)
+ col.set_cell_data_func (renderer,
+ self.date_format_custom_cell_data_func)
+
+ # For status.
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Status", renderer,
+ text = BuildManagerModel.COL_STATE)
+ self.append_column (col)
+ col.set_cell_data_func (renderer,
+ self.state_format_custom_cell_data_fun)
+
diff --git a/bitbake/lib/bb/ui/crumbs/puccho.glade b/bitbake/lib/bb/ui/crumbs/puccho.glade
new file mode 100644
index 0000000..d7553a6
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/puccho.glade
@@ -0,0 +1,606 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.5 on Mon Nov 10 12:24:12 2008 -->
+<glade-interface>
+ <widget class="GtkDialog" id="build_dialog">
+ <property name="title" translatable="yes">Start a build</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="build_table">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">5</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkAlignment" id="status_alignment">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkHBox" id="status_hbox">
+ <property name="spacing">6</property>
+ <child>
+ <widget class="GtkImage" id="status_image">
+ <property name="visible">True</property>
+ <property name="no_show_all">True</property>
+ <property name="xalign">0</property>
+ <property name="stock">gtk-dialog-error</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="status_label">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">If you see this text something is wrong...</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Build configuration&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="image_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="image_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Image:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="distribution_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="distribution_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Distribution:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkComboBox" id="machine_combo">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="machine_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Machine:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="refresh_button">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-refresh</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="location_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">32</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repository&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkDialog" id="dialog2">
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <widget class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">7</property>
+ <property name="n_columns">3</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <widget class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Repositories&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">&lt;b&gt;Additional packages&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Location: </property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="xalign">1</property>
+ <property name="xscale">0</property>
+ <child>
+ <widget class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="spacing">5</property>
+ <child>
+ <widget class="GtkButton" id="button7">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-remove</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button6">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-edit</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkButton" id="button5">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-add</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ <property name="xpad">12</property>
+ <property name="label" translatable="yes">Search:</property>
+ </widget>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkEntry" id="entry2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </widget>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="left_padding">12</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="treeview2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="right_attach">3</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <widget class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="response_id">0</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkWindow" id="main_window">
+ <child>
+ <widget class="GtkVBox" id="main_window_vbox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolbar" id="main_toolbar">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolButton" id="main_toolbutton_build">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Build</property>
+ <property name="stock_id">gtk-execute</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="results_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="progress_scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/bitbake/lib/bb/ui/crumbs/runningbuild.py b/bitbake/lib/bb/ui/crumbs/runningbuild.py
new file mode 100644
index 0000000..4015592
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/runningbuild.py
@@ -0,0 +1,180 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+
+class RunningBuildModel (gtk.TreeStore):
+ (COL_TYPE, COL_PACKAGE, COL_TASK, COL_MESSAGE, COL_ICON, COL_ACTIVE) = (0, 1, 2, 3, 4, 5)
+ def __init__ (self):
+ gtk.TreeStore.__init__ (self,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN)
+
+class RunningBuild (gobject.GObject):
+ __gsignals__ = {
+ 'build-succeeded' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'build-failed' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ())
+ }
+ pids_to_task = {}
+ tasks_to_iter = {}
+
+ def __init__ (self):
+ gobject.GObject.__init__ (self)
+ self.model = RunningBuildModel()
+
+ def handle_event (self, event):
+ # Handle an event from the event queue, this may result in updating
+ # the model and thus the UI. Or it may be to tell us that the build
+ # has finished successfully (or not, as the case may be.)
+
+ parent = None
+ pid = 0
+ package = None
+ task = None
+
+ # If we have a pid attached to this message/event try and get the
+ # (package, task) pair for it. If we get that then get the parent iter
+ # for the message.
+ if hassattr(event, 'pid'):
+ pid = event.pid
+ if self.pids_to_task.has_key(pid):
+ (package, task) = self.pids_to_task[pid]
+ parent = self.tasks_to_iter[(package, task)]
+
+ if isinstance(event, bb.msg.Msg):
+ # Set a pretty icon for the message based on it's type.
+ if isinstance(event, bb.msg.MsgWarn):
+ icon = "dialog-warning"
+ elif isinstance(event, bb.msg.MsgErr):
+ icon = "dialog-error"
+ else:
+ icon = None
+
+ # Ignore the "Running task i of n .." messages
+ if (event._message.startswith ("Running task")):
+ return
+
+ # Add the message to the tree either at the top level if parent is
+ # None otherwise as a descendent of a task.
+ self.model.append (parent,
+ (event.__name__.split()[-1], # e.g. MsgWarn, MsgError
+ package,
+ task,
+ event._message,
+ icon,
+ False))
+ elif isinstance(event, bb.build.TaskStarted):
+ (package, task) = (event._package, event._task)
+
+ # Save out this PID.
+ self.pids_to_task[pid] = (package,task)
+
+ # Check if we already have this package in our model. If so then
+ # that can be the parent for the task. Otherwise we create a new
+ # top level for the package.
+ if (self.tasks_to_iter.has_key ((package, None))):
+ parent = self.tasks_to_iter[(package, None)]
+ else:
+ parent = self.model.append (None, (None,
+ package,
+ None,
+ "Package: %s" % (package),
+ None,
+ False))
+ self.tasks_to_iter[(package, None)] = parent
+
+ # Because this parent package now has an active child mark it as
+ # such.
+ self.model.set(parent, self.model.COL_ICON, "gtk-execute")
+
+ # Add an entry in the model for this task
+ i = self.model.append (parent, (None,
+ package,
+ task,
+ "Task: %s" % (task),
+ None,
+ False))
+
+ # Save out the iter so that we can find it when we have a message
+ # that we need to attach to a task.
+ self.tasks_to_iter[(package, task)] = i
+
+ # Mark this task as active.
+ self.model.set(i, self.model.COL_ICON, "gtk-execute")
+
+ elif isinstance(event, bb.build.Task):
+
+ if isinstance(event, bb.build.TaskFailed):
+ # Mark the task as failed
+ i = self.tasks_to_iter[(package, task)]
+ self.model.set(i, self.model.COL_ICON, "dialog-error")
+
+ # Mark the parent package as failed
+ i = self.tasks_to_iter[(package, None)]
+ self.model.set(i, self.model.COL_ICON, "dialog-error")
+ else:
+ # Mark the task as inactive
+ i = self.tasks_to_iter[(package, task)]
+ self.model.set(i, self.model.COL_ICON, None)
+
+ # Mark the parent package as inactive
+ i = self.tasks_to_iter[(package, None)]
+ self.model.set(i, self.model.COL_ICON, None)
+
+
+ # Clear the iters and the pids since when the task goes away the
+ # pid will no longer be used for messages
+ del self.tasks_to_iter[(package, task)]
+ del self.pids_to_task[pid]
+
+ elif isinstance(event, bb.event.BuildCompleted):
+ failures = int (event._failures)
+
+ # Emit the appropriate signal depending on the number of failures
+ if (failures > 1):
+ self.emit ("build-failed")
+ else:
+ self.emit ("build-succeeded")
+
+class RunningBuildTreeView (gtk.TreeView):
+ def __init__ (self):
+ gtk.TreeView.__init__ (self)
+
+ # The icon that indicates whether we're building or failed.
+ renderer = gtk.CellRendererPixbuf ()
+ col = gtk.TreeViewColumn ("Status", renderer)
+ col.add_attribute (renderer, "icon-name", 4)
+ self.append_column (col)
+
+ # The message of the build.
+ renderer = gtk.CellRendererText ()
+ col = gtk.TreeViewColumn ("Message", renderer, text=3)
+ self.append_column (col)
+
+
diff --git a/bitbake/lib/bb/ui/depexp.py b/bitbake/lib/bb/ui/depexp.py
new file mode 100644
index 0000000..cfa5b65
--- /dev/null
+++ b/bitbake/lib/bb/ui/depexp.py
@@ -0,0 +1,272 @@
+#
+# BitBake Graphical GTK based Dependency Explorer
+#
+# Copyright (C) 2007 Ross Burton
+# Copyright (C) 2007 - 2008 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import threading
+import xmlrpclib
+
+# Package Model
+(COL_PKG_NAME) = (0)
+
+# Dependency Model
+(TYPE_DEP, TYPE_RDEP) = (0, 1)
+(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2)
+
+class PackageDepView(gtk.TreeView):
+ def __init__(self, model, dep_type, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.dep_type = dep_type
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ #self.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PACKAGE))
+
+ def _filter(self, model, iter):
+ (this_type, package) = model.get(iter, COL_DEP_TYPE, COL_DEP_PARENT)
+ if this_type != self.dep_type: return False
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+class PackageReverseDepView(gtk.TreeView):
+ def __init__(self, model, label):
+ gtk.TreeView.__init__(self)
+ self.current = None
+ self.filter_model = model.filter_new()
+ self.filter_model.set_visible_func(self._filter)
+ self.set_model(self.filter_model)
+ self.append_column(gtk.TreeViewColumn(label, gtk.CellRendererText(), text=COL_DEP_PARENT))
+
+ def _filter(self, model, iter):
+ package = model.get_value(iter, COL_DEP_PACKAGE)
+ return package == self.current
+
+ def set_current_package(self, package):
+ self.current = package
+ self.filter_model.refilter()
+
+class DepExplorer(gtk.Window):
+ def __init__(self):
+ gtk.Window.__init__(self)
+ self.set_title("Dependency Explorer")
+ self.set_default_size(500, 500)
+ self.connect("delete-event", gtk.main_quit)
+
+ # Create the data models
+ self.pkg_model = gtk.ListStore(gobject.TYPE_STRING)
+ self.depends_model = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
+
+ pane = gtk.HPaned()
+ pane.set_position(250)
+ self.add(pane)
+
+ # The master list of packages
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.pkg_treeview = gtk.TreeView(self.pkg_model)
+ self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed)
+ self.pkg_treeview.append_column(gtk.TreeViewColumn("Package", gtk.CellRendererText(), text=COL_PKG_NAME))
+ pane.add1(scrolled)
+ scrolled.add(self.pkg_treeview)
+
+ box = gtk.VBox(homogeneous=True, spacing=4)
+
+ # Runtime Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.rdep_treeview = PackageDepView(self.depends_model, TYPE_RDEP, "Runtime Depends")
+ self.rdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.rdep_treeview)
+ box.add(scrolled)
+
+ # Build Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Build Depends")
+ self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE)
+ scrolled.add(self.dep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ # Reverse Depends
+ scrolled = gtk.ScrolledWindow()
+ scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ scrolled.set_shadow_type(gtk.SHADOW_IN)
+ self.revdep_treeview = PackageReverseDepView(self.depends_model, "Reverse Depends")
+ self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT)
+ scrolled.add(self.revdep_treeview)
+ box.add(scrolled)
+ pane.add2(box)
+
+ self.show_all()
+
+ def on_package_activated(self, treeview, path, column, data_col):
+ model = treeview.get_model()
+ package = model.get_value(model.get_iter(path), data_col)
+
+ pkg_path = []
+ def finder(model, path, iter, needle):
+ package = model.get_value(iter, COL_PKG_NAME)
+ if package == needle:
+ pkg_path.append(path)
+ return True
+ else:
+ return False
+ self.pkg_model.foreach(finder, package)
+ if pkg_path:
+ self.pkg_treeview.get_selection().select_path(pkg_path[0])
+ self.pkg_treeview.scroll_to_cell(pkg_path[0])
+
+ def on_cursor_changed(self, selection):
+ (model, it) = selection.get_selected()
+ if iter is None:
+ current_package = None
+ else:
+ current_package = model.get_value(it, COL_PKG_NAME)
+ self.rdep_treeview.set_current_package(current_package)
+ self.dep_treeview.set_current_package(current_package)
+ self.revdep_treeview.set_current_package(current_package)
+
+
+def parse(depgraph, pkg_model, depends_model):
+
+ for package in depgraph["pn"]:
+ pkg_model.set(pkg_model.append(), COL_PKG_NAME, package)
+
+ for package in depgraph["depends"]:
+ for depend in depgraph["depends"][package]:
+ depends_model.set (depends_model.append(),
+ COL_DEP_TYPE, TYPE_DEP,
+ COL_DEP_PARENT, package,
+ COL_DEP_PACKAGE, depend)
+
+ for package in depgraph["rdepends-pn"]:
+ for rdepend in depgraph["rdepends-pn"][package]:
+ depends_model.set (depends_model.append(),
+ COL_DEP_TYPE, TYPE_RDEP,
+ COL_DEP_PARENT, package,
+ COL_DEP_PACKAGE, rdepend)
+
+class ProgressBar(gtk.Window):
+ def __init__(self):
+
+ gtk.Window.__init__(self)
+ self.set_title("Parsing .bb files, please wait...")
+ self.set_default_size(500, 0)
+ self.connect("delete-event", gtk.main_quit)
+
+ self.progress = gtk.ProgressBar()
+ self.add(self.progress)
+ self.show_all()
+
+class gtkthread(threading.Thread):
+ quit = threading.Event()
+ def __init__(self, shutdown):
+ threading.Thread.__init__(self)
+ self.setDaemon(True)
+ self.shutdown = shutdown
+
+ def run(self):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+ gtk.main()
+ gtkthread.quit.set()
+
+def init(server, eventHandler):
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ if not cmdline or cmdline[0] != "generateDotGraph":
+ print "This UI is only compatible with the -g option"
+ return
+ ret = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]])
+ if ret != True:
+ print "Couldn't run command! %s" % ret
+ return
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return
+
+ shutdown = 0
+
+ gtkgui = gtkthread(shutdown)
+ gtkgui.start()
+
+ gtk.gdk.threads_enter()
+ pbar = ProgressBar()
+ dep = DepExplorer()
+ gtk.gdk.threads_leave()
+
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if gtkthread.quit.isSet():
+ break
+
+ if event is None:
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ if x == y:
+ print("\nParsing finished. %d cached, %d parsed, %d skipped, %d masked, %d errors."
+ % ( event.cached, event.parsed, event.skipped, event.masked, event.errors))
+ pbar.hide()
+ gtk.gdk.threads_enter()
+ pbar.progress.set_fraction(float(x)/float(y))
+ pbar.progress.set_text("%d/%d (%2d %%)" % (x, y, x*100/y))
+ gtk.gdk.threads_leave()
+ continue
+
+ if isinstance(event, bb.event.DepTreeGenerated):
+ gtk.gdk.threads_enter()
+ parse(event._depgraph, dep.pkg_model, dep.depends_model)
+ gtk.gdk.threads_leave()
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ continue
+ if isinstance(event, bb.command.CookerCommandFailed):
+ print "Command execution failed: %s" % event.error
+ break
+ if isinstance(event, bb.cooker.CookerExit):
+ break
+
+ continue
+
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ print "\nThird Keyboard Interrupt, exit.\n"
+ break
+ if shutdown == 1:
+ print "\nSecond Keyboard Interrupt, stopping...\n"
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ print "\nKeyboard Interrupt, closing down...\n"
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+
diff --git a/bitbake/lib/bb/ui/goggle.py b/bitbake/lib/bb/ui/goggle.py
new file mode 100644
index 0000000..94995d8
--- /dev/null
+++ b/bitbake/lib/bb/ui/goggle.py
@@ -0,0 +1,77 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gobject
+import gtk
+import xmlrpclib
+from bb.ui.crumbs.runningbuild import RunningBuildTreeView, RunningBuild
+
+def event_handle_idle_func (eventHandler, build):
+
+ # Consume as many messages as we can in the time available to us
+ event = eventHandler.getEvent()
+ while event:
+ build.handle_event (event)
+ event = eventHandler.getEvent()
+
+ return True
+
+class MainWindow (gtk.Window):
+ def __init__ (self):
+ gtk.Window.__init__ (self, gtk.WINDOW_TOPLEVEL)
+
+ # Setup tree view and the scrolled window
+ scrolled_window = gtk.ScrolledWindow ()
+ self.add (scrolled_window)
+ self.cur_build_tv = RunningBuildTreeView()
+ scrolled_window.add (self.cur_build_tv)
+
+def init (server, eventHandler):
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+
+ window = MainWindow ()
+ window.show_all ()
+
+ # Create the object for the current build
+ running_build = RunningBuild ()
+ window.cur_build_tv.set_model (running_build.model)
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ print cmdline
+ if not cmdline:
+ return 1
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandline! %s" % ret
+ return 1
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return 1
+
+ # Use a timeout function for probing the event queue to find out if we
+ # have a message waiting for us.
+ gobject.timeout_add (200,
+ event_handle_idle_func,
+ eventHandler,
+ running_build)
+
+ gtk.main()
+
diff --git a/bitbake/lib/bb/ui/knotty.py b/bitbake/lib/bb/ui/knotty.py
new file mode 100644
index 0000000..c69fd6c
--- /dev/null
+++ b/bitbake/lib/bb/ui/knotty.py
@@ -0,0 +1,162 @@
+#
+# BitBake (No)TTY UI Implementation
+#
+# Handling output to TTYs or files (no TTY)
+#
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+
+import sys
+import itertools
+import xmlrpclib
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+def init(server, eventHandler):
+
+ # Get values of variables which control our output
+ includelogs = server.runCommand(["getVariable", "BBINCLUDELOGS"])
+ loglines = server.runCommand(["getVariable", "BBINCLUDELOGS_LINES"])
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ #print cmdline
+ if not cmdline:
+ return 1
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandline! %s" % ret
+ return 1
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return 1
+
+ shutdown = 0
+ return_value = 0
+ while True:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if event is None:
+ continue
+ #print event
+ if isinstance(event, bb.msg.MsgPlain):
+ print event._message
+ continue
+ if isinstance(event, bb.msg.MsgDebug):
+ print 'DEBUG: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgNote):
+ print 'NOTE: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgWarn):
+ print 'WARNING: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgError):
+ return_value = 1
+ print 'ERROR: ' + event._message
+ continue
+ if isinstance(event, bb.msg.MsgFatal):
+ return_value = 1
+ print 'FATAL: ' + event._message
+ break
+ if isinstance(event, bb.build.TaskFailed):
+ return_value = 1
+ logfile = event.logfile
+ if logfile:
+ print "ERROR: Logfile of failure stored in %s." % logfile
+ if 1 or includelogs:
+ print "Log data follows:"
+ f = open(logfile, "r")
+ lines = []
+ while True:
+ l = f.readline()
+ if l == '':
+ break
+ l = l.rstrip()
+ if loglines:
+ lines.append(' | %s' % l)
+ if len(lines) > int(loglines):
+ lines.pop(0)
+ else:
+ print '| %s' % l
+ f.close()
+ if lines:
+ for line in lines:
+ print line
+ if isinstance(event, bb.build.TaskBase):
+ print "NOTE: %s" % event._message
+ continue
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ if os.isatty(sys.stdout.fileno()):
+ sys.stdout.write("\rNOTE: Handling BitBake files: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
+ sys.stdout.flush()
+ else:
+ if x == 1:
+ sys.stdout.write("Parsing .bb files, please wait...")
+ sys.stdout.flush()
+ if x == y:
+ sys.stdout.write("done.")
+ sys.stdout.flush()
+ if x == y:
+ print("\nParsing of %d .bb files complete (%d cached, %d parsed). %d targets, %d skipped, %d masked, %d errors."
+ % ( event.total, event.cached, event.parsed, event.virtuals, event.skipped, event.masked, event.errors))
+ continue
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ break
+ if isinstance(event, bb.command.CookerCommandSetExitCode):
+ return_value = event.exitcode
+ continue
+ if isinstance(event, bb.command.CookerCommandFailed):
+ return_value = 1
+ print "Command execution failed: %s" % event.error
+ break
+ if isinstance(event, bb.cooker.CookerExit):
+ break
+
+ # ignore
+ if isinstance(event, bb.event.BuildStarted):
+ continue
+ if isinstance(event, bb.event.BuildCompleted):
+ continue
+ if isinstance(event, bb.event.MultipleProviders):
+ continue
+ if isinstance(event, bb.runqueue.runQueueEvent):
+ continue
+ if isinstance(event, bb.event.StampUpdate):
+ continue
+ if isinstance(event, bb.event.ConfigParsed):
+ continue
+ if isinstance(event, bb.event.RecipeParsed):
+ continue
+ print "Unknown Event: %s" % event
+
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ print "\nThird Keyboard Interrupt, exit.\n"
+ break
+ if shutdown == 1:
+ print "\nSecond Keyboard Interrupt, stopping...\n"
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ print "\nKeyboard Interrupt, closing down...\n"
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+ return return_value
diff --git a/bitbake/lib/bb/ui/ncurses.py b/bitbake/lib/bb/ui/ncurses.py
new file mode 100644
index 0000000..14310dc
--- /dev/null
+++ b/bitbake/lib/bb/ui/ncurses.py
@@ -0,0 +1,335 @@
+#
+# BitBake Curses UI Implementation
+#
+# Implements an ncurses frontend for the BitBake utility.
+#
+# Copyright (C) 2006 Michael 'Mickey' Lauer
+# Copyright (C) 2006-2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""
+ We have the following windows:
+
+ 1.) Main Window: Shows what we are ultimately building and how far we are. Includes status bar
+ 2.) Thread Activity Window: Shows one status line for every concurrent bitbake thread.
+ 3.) Command Line Window: Contains an interactive command line where you can interact w/ Bitbake.
+
+ Basic window layout is like that:
+
+ |---------------------------------------------------------|
+ | <Main Window> | <Thread Activity Window> |
+ | | 0: foo do_compile complete|
+ | Building Gtk+-2.6.10 | 1: bar do_patch complete |
+ | Status: 60% | ... |
+ | | ... |
+ | | ... |
+ |---------------------------------------------------------|
+ |<Command Line Window> |
+ |>>> which virtual/kernel |
+ |openzaurus-kernel |
+ |>>> _ |
+ |---------------------------------------------------------|
+
+"""
+
+import os, sys, curses, itertools, time
+import bb
+import xmlrpclib
+from bb import ui
+from bb.ui import uihelper
+
+parsespin = itertools.cycle( r'|/-\\' )
+
+X = 0
+Y = 1
+WIDTH = 2
+HEIGHT = 3
+
+MAXSTATUSLENGTH = 32
+
+class NCursesUI:
+ """
+ NCurses UI Class
+ """
+ class Window:
+ """Base Window Class"""
+ def __init__( self, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ self.win = curses.newwin( height, width, y, x )
+ self.dimensions = ( x, y, width, height )
+ """
+ if curses.has_colors():
+ color = 1
+ curses.init_pair( color, fg, bg )
+ self.win.bkgdset( ord(' '), curses.color_pair(color) )
+ else:
+ self.win.bkgdset( ord(' '), curses.A_BOLD )
+ """
+ self.erase()
+ self.setScrolling()
+ self.win.noutrefresh()
+
+ def erase( self ):
+ self.win.erase()
+
+ def setScrolling( self, b = True ):
+ self.win.scrollok( b )
+ self.win.idlok( b )
+
+ def setBoxed( self ):
+ self.boxed = True
+ self.win.box()
+ self.win.noutrefresh()
+
+ def setText( self, x, y, text, *args ):
+ self.win.addstr( y, x, text, *args )
+ self.win.noutrefresh()
+
+ def appendText( self, text, *args ):
+ self.win.addstr( text, *args )
+ self.win.noutrefresh()
+
+ def drawHline( self, y ):
+ self.win.hline( y, 0, curses.ACS_HLINE, self.dimensions[WIDTH] )
+ self.win.noutrefresh()
+
+ class DecoratedWindow( Window ):
+ """Base class for windows with a box and a title bar"""
+ def __init__( self, title, x, y, width, height, fg=curses.COLOR_BLACK, bg=curses.COLOR_WHITE ):
+ NCursesUI.Window.__init__( self, x+1, y+3, width-2, height-4, fg, bg )
+ self.decoration = NCursesUI.Window( x, y, width, height, fg, bg )
+ self.decoration.setBoxed()
+ self.decoration.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+ self.setTitle( title )
+
+ def setTitle( self, title ):
+ self.decoration.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+# class TitleWindow( Window ):
+ #-------------------------------------------------------------------------#
+# """Title Window"""
+# def __init__( self, x, y, width, height ):
+# NCursesUI.Window.__init__( self, x, y, width, height )
+# version = bb.__version__
+# title = "BitBake %s" % version
+# credit = "(C) 2003-2007 Team BitBake"
+# #self.win.hline( 2, 1, curses.ACS_HLINE, width-2 )
+# self.win.border()
+# self.setText( 1, 1, title.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+# self.setText( 1, 2, credit.center( self.dimensions[WIDTH]-2 ), curses.A_BOLD )
+
+ #-------------------------------------------------------------------------#
+ class ThreadActivityWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Thread Activity Window"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Thread Activity", x, y, width, height )
+
+ def setStatus( self, thread, text ):
+ line = "%02d: %s" % ( thread, text )
+ width = self.dimensions[WIDTH]
+ if ( len(line) > width ):
+ line = line[:width-3] + "..."
+ else:
+ line = line.ljust( width )
+ self.setText( 0, thread, line )
+
+ #-------------------------------------------------------------------------#
+ class MainWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Main Window"""
+ def __init__( self, x, y, width, height ):
+ self.StatusPosition = width - MAXSTATUSLENGTH
+ NCursesUI.DecoratedWindow.__init__( self, None, x, y, width, height )
+ curses.nl()
+
+ def setTitle( self, title ):
+ title = "BitBake %s" % bb.__version__
+ self.decoration.setText( 2, 1, title, curses.A_BOLD )
+ self.decoration.setText( self.StatusPosition - 8, 1, "Status:", curses.A_BOLD )
+
+ def setStatus(self, status):
+ while len(status) < MAXSTATUSLENGTH:
+ status = status + " "
+ self.decoration.setText( self.StatusPosition, 1, status, curses.A_BOLD )
+
+
+ #-------------------------------------------------------------------------#
+ class ShellOutputWindow( DecoratedWindow ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Output"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.DecoratedWindow.__init__( self, "Command Line Window", x, y, width, height )
+
+ #-------------------------------------------------------------------------#
+ class ShellInputWindow( Window ):
+ #-------------------------------------------------------------------------#
+ """Interactive Command Line Input"""
+ def __init__( self, x, y, width, height ):
+ NCursesUI.Window.__init__( self, x, y, width, height )
+
+# put that to the top again from curses.textpad import Textbox
+# self.textbox = Textbox( self.win )
+# t = threading.Thread()
+# t.run = self.textbox.edit
+# t.start()
+
+ #-------------------------------------------------------------------------#
+ def main(self, stdscr, server, eventHandler):
+ #-------------------------------------------------------------------------#
+ height, width = stdscr.getmaxyx()
+
+ # for now split it like that:
+ # MAIN_y + THREAD_y = 2/3 screen at the top
+ # MAIN_x = 2/3 left, THREAD_y = 1/3 right
+ # CLI_y = 1/3 of screen at the bottom
+ # CLI_x = full
+
+ main_left = 0
+ main_top = 0
+ main_height = ( height / 3 * 2 )
+ main_width = ( width / 3 ) * 2
+ clo_left = main_left
+ clo_top = main_top + main_height
+ clo_height = height - main_height - main_top - 1
+ clo_width = width
+ cli_left = main_left
+ cli_top = clo_top + clo_height
+ cli_height = 1
+ cli_width = width
+ thread_left = main_left + main_width
+ thread_top = main_top
+ thread_height = main_height
+ thread_width = width - main_width
+
+ #tw = self.TitleWindow( 0, 0, width, main_top )
+ mw = self.MainWindow( main_left, main_top, main_width, main_height )
+ taw = self.ThreadActivityWindow( thread_left, thread_top, thread_width, thread_height )
+ clo = self.ShellOutputWindow( clo_left, clo_top, clo_width, clo_height )
+ cli = self.ShellInputWindow( cli_left, cli_top, cli_width, cli_height )
+ cli.setText( 0, 0, "BB>" )
+
+ mw.setStatus("Idle")
+
+ helper = uihelper.BBUIHelper()
+ shutdown = 0
+
+ try:
+ cmdline = server.runCommand(["getCmdLineAction"])
+ if not cmdline:
+ return
+ ret = server.runCommand(cmdline)
+ if ret != True:
+ print "Couldn't get default commandlind! %s" % ret
+ return
+ except xmlrpclib.Fault, x:
+ print "XMLRPC Fault getting commandline:\n %s" % x
+ return
+
+ exitflag = False
+ while not exitflag:
+ try:
+ event = eventHandler.waitEvent(0.25)
+ if not event:
+ continue
+ helper.eventHandler(event)
+ #mw.appendText("%s\n" % event[0])
+ if isinstance(event, bb.build.Task):
+ mw.appendText("NOTE: %s\n" % event._message)
+ if isinstance(event, bb.msg.MsgDebug):
+ mw.appendText('DEBUG: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgNote):
+ mw.appendText('NOTE: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgWarn):
+ mw.appendText('WARNING: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgError):
+ mw.appendText('ERROR: ' + event._message + '\n')
+ if isinstance(event, bb.msg.MsgFatal):
+ mw.appendText('FATAL: ' + event._message + '\n')
+ if isinstance(event, bb.event.ParseProgress):
+ x = event.sofar
+ y = event.total
+ if x == y:
+ mw.setStatus("Idle")
+ mw.appendText("Parsing finished. %d cached, %d parsed, %d skipped, %d masked."
+ % ( event.cached, event.parsed, event.skipped, event.masked ))
+ else:
+ mw.setStatus("Parsing: %s (%04d/%04d) [%2d %%]" % ( parsespin.next(), x, y, x*100/y ) )
+# if isinstance(event, bb.build.TaskFailed):
+# if event.logfile:
+# if data.getVar("BBINCLUDELOGS", d):
+# bb.msg.error(bb.msg.domain.Build, "log data follows (%s)" % logfile)
+# number_of_lines = data.getVar("BBINCLUDELOGS_LINES", d)
+# if number_of_lines:
+# os.system('tail -n%s %s' % (number_of_lines, logfile))
+# else:
+# f = open(logfile, "r")
+# while True:
+# l = f.readline()
+# if l == '':
+# break
+# l = l.rstrip()
+# print '| %s' % l
+# f.close()
+# else:
+# bb.msg.error(bb.msg.domain.Build, "see log in %s" % logfile)
+
+ if isinstance(event, bb.command.CookerCommandCompleted):
+ exitflag = True
+ if isinstance(event, bb.command.CookerCommandFailed):
+ mw.appendText("Command execution failed: %s" % event.error)
+ time.sleep(2)
+ exitflag = True
+ if isinstance(event, bb.cooker.CookerExit):
+ exitflag = True
+
+ if helper.needUpdate:
+ activetasks, failedtasks = helper.getTasks()
+ taw.erase()
+ taw.setText(0, 0, "")
+ if activetasks:
+ taw.appendText("Active Tasks:\n")
+ for task in activetasks:
+ taw.appendText(task)
+ if failedtasks:
+ taw.appendText("Failed Tasks:\n")
+ for task in failedtasks:
+ taw.appendText(task)
+
+ curses.doupdate()
+ except KeyboardInterrupt:
+ if shutdown == 2:
+ mw.appendText("Third Keyboard Interrupt, exit.\n")
+ exitflag = True
+ if shutdown == 1:
+ mw.appendText("Second Keyboard Interrupt, stopping...\n")
+ server.runCommand(["stateStop"])
+ if shutdown == 0:
+ mw.appendText("Keyboard Interrupt, closing down...\n")
+ server.runCommand(["stateShutdown"])
+ shutdown = shutdown + 1
+ pass
+
+def init(server, eventHandler):
+ if not os.isatty(sys.stdout.fileno()):
+ print "FATAL: Unable to run 'ncurses' UI without a TTY."
+ return
+ ui = NCursesUI()
+ try:
+ curses.wrapper(ui.main, server, eventHandler)
+ except:
+ import traceback
+ traceback.print_exc()
+
diff --git a/bitbake/lib/bb/ui/puccho.py b/bitbake/lib/bb/ui/puccho.py
new file mode 100644
index 0000000..713aa1f
--- /dev/null
+++ b/bitbake/lib/bb/ui/puccho.py
@@ -0,0 +1,425 @@
+#
+# BitBake Graphical GTK User Interface
+#
+# Copyright (C) 2008 Intel Corporation
+#
+# Authored by Rob Bradford <rob@linux.intel.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import gtk
+import gobject
+import gtk.glade
+import threading
+import urllib2
+import os
+
+from bb.ui.crumbs.buildmanager import BuildManager, BuildConfiguration
+from bb.ui.crumbs.buildmanager import BuildManagerTreeView
+
+from bb.ui.crumbs.runningbuild import RunningBuild, RunningBuildTreeView
+
+# The metadata loader is used by the BuildSetupDialog to download the
+# available options to populate the dialog
+class MetaDataLoader(gobject.GObject):
+ """ This class provides the mechanism for loading the metadata (the
+ fetching and parsing) from a given URL. The metadata encompasses details
+ on what machines are available. The distribution and images available for
+ the machine and the the uris to use for building the given machine."""
+ __gsignals__ = {
+ 'success' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ ()),
+ 'error' : (gobject.SIGNAL_RUN_LAST,
+ gobject.TYPE_NONE,
+ (gobject.TYPE_STRING,))
+ }
+
+ # We use these little helper functions to ensure that we take the gdk lock
+ # when emitting the signal. These functions are called as idles (so that
+ # they happen in the gtk / main thread's main loop.
+ def emit_error_signal (self, remark):
+ gtk.gdk.threads_enter()
+ self.emit ("error", remark)
+ gtk.gdk.threads_leave()
+
+ def emit_success_signal (self):
+ gtk.gdk.threads_enter()
+ self.emit ("success")
+ gtk.gdk.threads_leave()
+
+ def __init__ (self):
+ gobject.GObject.__init__ (self)
+
+ class LoaderThread(threading.Thread):
+ """ This class provides an asynchronous loader for the metadata (by
+ using threads and signals). This is useful since the metadata may be
+ at a remote URL."""
+ class LoaderImportException (Exception):
+ pass
+
+ def __init__(self, loader, url):
+ threading.Thread.__init__ (self)
+ self.url = url
+ self.loader = loader
+
+ def run (self):
+ result = {}
+ try:
+ f = urllib2.urlopen (self.url)
+
+ # Parse the metadata format. The format is....
+ # <machine>;<default distro>|<distro>...;<default image>|<image>...;<type##url>|...
+ for line in f.readlines():
+ components = line.split(";")
+ if (len (components) < 4):
+ raise MetaDataLoader.LoaderThread.LoaderImportException
+ machine = components[0]
+ distros = components[1].split("|")
+ images = components[2].split("|")
+ urls = components[3].split("|")
+
+ result[machine] = (distros, images, urls)
+
+ # Create an object representing this *potential*
+ # configuration. It can become concrete if the machine, distro
+ # and image are all chosen in the UI
+ configuration = BuildConfiguration()
+ configuration.metadata_url = self.url
+ configuration.machine_options = result
+ self.loader.configuration = configuration
+
+ # Emit that we've actually got a configuration
+ gobject.idle_add (MetaDataLoader.emit_success_signal,
+ self.loader)
+
+ except MetaDataLoader.LoaderThread.LoaderImportException, e:
+ gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+ "Repository metadata corrupt")
+ except Exception, e:
+ gobject.idle_add (MetaDataLoader.emit_error_signal, self.loader,
+ "Unable to download repository metadata")
+ print e
+
+ def try_fetch_from_url (self, url):
+ # Try and download the metadata. Firing a signal if successful
+ thread = MetaDataLoader.LoaderThread(self, url)
+ thread.start()
+
+class BuildSetupDialog (gtk.Dialog):
+ RESPONSE_BUILD = 1
+
+ # A little helper method that just sets the states on the widgets based on
+ # whether we've got good metadata or not.
+ def set_configurable (self, configurable):
+ if (self.configurable == configurable):
+ return
+
+ self.configurable = configurable
+ for widget in self.conf_widgets:
+ widget.set_sensitive (configurable)
+
+ if not configurable:
+ self.machine_combo.set_active (-1)
+ self.distribution_combo.set_active (-1)
+ self.image_combo.set_active (-1)
+
+ # GTK widget callbacks
+ def refresh_button_clicked (self, button):
+ # Refresh button clicked.
+
+ url = self.location_entry.get_chars (0, -1)
+ self.loader.try_fetch_from_url(url)
+
+ def repository_entry_editable_changed (self, entry):
+ if (len (entry.get_chars (0, -1)) > 0):
+ self.refresh_button.set_sensitive (True)
+ else:
+ self.refresh_button.set_sensitive (False)
+ self.clear_status_message()
+
+ # If we were previously configurable we are no longer since the
+ # location entry has been changed
+ self.set_configurable (False)
+
+ def machine_combo_changed (self, combobox):
+ active_iter = combobox.get_active_iter()
+
+ if not active_iter:
+ return
+
+ model = combobox.get_model()
+
+ if model:
+ chosen_machine = model.get (active_iter, 0)[0]
+
+ (distros_model, images_model) = \
+ self.loader.configuration.get_distro_and_images_models (chosen_machine)
+
+ self.distribution_combo.set_model (distros_model)
+ self.image_combo.set_model (images_model)
+
+ # Callbacks from the loader
+ def loader_success_cb (self, loader):
+ self.status_image.set_from_icon_name ("info",
+ gtk.ICON_SIZE_BUTTON)
+ self.status_image.show()
+ self.status_label.set_label ("Repository metadata successfully downloaded")
+
+ # Set the models on the combo boxes based on the models generated from
+ # the configuration that the loader has created
+
+ # We just need to set the machine here, that then determines the
+ # distro and image options. Cunning huh? :-)
+
+ self.configuration = self.loader.configuration
+ model = self.configuration.get_machines_model ()
+ self.machine_combo.set_model (model)
+
+ self.set_configurable (True)
+
+ def loader_error_cb (self, loader, message):
+ self.status_image.set_from_icon_name ("error",
+ gtk.ICON_SIZE_BUTTON)
+ self.status_image.show()
+ self.status_label.set_text ("Error downloading repository metadata")
+ for widget in self.conf_widgets:
+ widget.set_sensitive (False)
+
+ def clear_status_message (self):
+ self.status_image.hide()
+ self.status_label.set_label (
+ """<i>Enter the repository location and press _Refresh</i>""")
+
+ def __init__ (self):
+ gtk.Dialog.__init__ (self)
+
+ # Cancel
+ self.add_button (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+
+ # Build
+ button = gtk.Button ("_Build", None, True)
+ image = gtk.Image ()
+ image.set_from_stock (gtk.STOCK_EXECUTE,gtk.ICON_SIZE_BUTTON)
+ button.set_image (image)
+ self.add_action_widget (button, BuildSetupDialog.RESPONSE_BUILD)
+ button.show_all ()
+
+ # Pull in *just* the table from the Glade XML data.
+ gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+ root = "build_table")
+ table = gxml.get_widget ("build_table")
+ self.vbox.pack_start (table, True, False, 0)
+
+ # Grab all the widgets that we need to turn on/off when we refresh...
+ self.conf_widgets = []
+ self.conf_widgets += [gxml.get_widget ("machine_label")]
+ self.conf_widgets += [gxml.get_widget ("distribution_label")]
+ self.conf_widgets += [gxml.get_widget ("image_label")]
+ self.conf_widgets += [gxml.get_widget ("machine_combo")]
+ self.conf_widgets += [gxml.get_widget ("distribution_combo")]
+ self.conf_widgets += [gxml.get_widget ("image_combo")]
+
+ # Grab the status widgets
+ self.status_image = gxml.get_widget ("status_image")
+ self.status_label = gxml.get_widget ("status_label")
+
+ # Grab the refresh button and connect to the clicked signal
+ self.refresh_button = gxml.get_widget ("refresh_button")
+ self.refresh_button.connect ("clicked", self.refresh_button_clicked)
+
+ # Grab the location entry and connect to editable::changed
+ self.location_entry = gxml.get_widget ("location_entry")
+ self.location_entry.connect ("changed",
+ self.repository_entry_editable_changed)
+
+ # Grab the machine combo and hook onto the changed signal. This then
+ # allows us to populate the distro and image combos
+ self.machine_combo = gxml.get_widget ("machine_combo")
+ self.machine_combo.connect ("changed", self.machine_combo_changed)
+
+ # Setup the combo
+ cell = gtk.CellRendererText()
+ self.machine_combo.pack_start(cell, True)
+ self.machine_combo.add_attribute(cell, 'text', 0)
+
+ # Grab the distro and image combos. We need these to populate with
+ # models once the machine is chosen
+ self.distribution_combo = gxml.get_widget ("distribution_combo")
+ cell = gtk.CellRendererText()
+ self.distribution_combo.pack_start(cell, True)
+ self.distribution_combo.add_attribute(cell, 'text', 0)
+
+ self.image_combo = gxml.get_widget ("image_combo")
+ cell = gtk.CellRendererText()
+ self.image_combo.pack_start(cell, True)
+ self.image_combo.add_attribute(cell, 'text', 0)
+
+ # Put the default descriptive text in the status box
+ self.clear_status_message()
+
+ # Mark as non-configurable, this is just greys out the widgets the
+ # user can't yet use
+ self.configurable = False
+ self.set_configurable(False)
+
+ # Show the table
+ table.show_all ()
+
+ # The loader and some signals connected to it to update the status
+ # area
+ self.loader = MetaDataLoader()
+ self.loader.connect ("success", self.loader_success_cb)
+ self.loader.connect ("error", self.loader_error_cb)
+
+ def update_configuration (self):
+ """ A poorly named function but it updates the internal configuration
+ from the widgets. This can make that configuration concrete and can
+ thus be used for building """
+ # Extract the chosen machine from the combo
+ model = self.machine_combo.get_model()
+ active_iter = self.machine_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.machine = model.get(active_iter, 0)[0]
+
+ # Extract the chosen distro from the combo
+ model = self.distribution_combo.get_model()
+ active_iter = self.distribution_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.distro = model.get(active_iter, 0)[0]
+
+ # Extract the chosen image from the combo
+ model = self.image_combo.get_model()
+ active_iter = self.image_combo.get_active_iter()
+ if (active_iter):
+ self.configuration.image = model.get(active_iter, 0)[0]
+
+# This function operates to pull events out from the event queue and then push
+# them into the RunningBuild (which then drives the RunningBuild which then
+# pushes through and updates the progress tree view.)
+#
+# TODO: Should be a method on the RunningBuild class
+def event_handle_timeout (eventHandler, build):
+ # Consume as many messages as we can ...
+ event = eventHandler.getEvent()
+ while event:
+ build.handle_event (event)
+ event = eventHandler.getEvent()
+ return True
+
+class MainWindow (gtk.Window):
+
+ # Callback that gets fired when the user hits a button in the
+ # BuildSetupDialog.
+ def build_dialog_box_response_cb (self, dialog, response_id):
+ conf = None
+ if (response_id == BuildSetupDialog.RESPONSE_BUILD):
+ dialog.update_configuration()
+ print dialog.configuration.machine, dialog.configuration.distro, \
+ dialog.configuration.image
+ conf = dialog.configuration
+
+ dialog.destroy()
+
+ if conf:
+ self.manager.do_build (conf)
+
+ def build_button_clicked_cb (self, button):
+ dialog = BuildSetupDialog ()
+
+ # For some unknown reason Dialog.run causes nice little deadlocks ... :-(
+ dialog.connect ("response", self.build_dialog_box_response_cb)
+ dialog.show()
+
+ def __init__ (self):
+ gtk.Window.__init__ (self)
+
+ # Pull in *just* the main vbox from the Glade XML data and then pack
+ # that inside the window
+ gxml = gtk.glade.XML (os.path.dirname(__file__) + "/crumbs/puccho.glade",
+ root = "main_window_vbox")
+ vbox = gxml.get_widget ("main_window_vbox")
+ self.add (vbox)
+
+ # Create the tree views for the build manager view and the progress view
+ self.build_manager_view = BuildManagerTreeView()
+ self.running_build_view = RunningBuildTreeView()
+
+ # Grab the scrolled windows that we put the tree views into
+ self.results_scrolledwindow = gxml.get_widget ("results_scrolledwindow")
+ self.progress_scrolledwindow = gxml.get_widget ("progress_scrolledwindow")
+
+ # Put the tree views inside ...
+ self.results_scrolledwindow.add (self.build_manager_view)
+ self.progress_scrolledwindow.add (self.running_build_view)
+
+ # Hook up the build button...
+ self.build_button = gxml.get_widget ("main_toolbutton_build")
+ self.build_button.connect ("clicked", self.build_button_clicked_cb)
+
+# I'm not very happy about the current ownership of the RunningBuild. I have
+# my suspicions that this object should be held by the BuildManager since we
+# care about the signals in the manager
+
+def running_build_succeeded_cb (running_build, manager):
+ # Notify the manager that a build has succeeded. This is necessary as part
+ # of the 'hack' that we use for making the row in the model / view
+ # representing the ongoing build change into a row representing the
+ # completed build. Since we know only one build can be running a time then
+ # we can handle this.
+
+ # FIXME: Refactor all this so that the RunningBuild is owned by the
+ # BuildManager. It can then hook onto the signals directly and drive
+ # interesting things it cares about.
+ manager.notify_build_succeeded ()
+ print "build succeeded"
+
+def running_build_failed_cb (running_build, manager):
+ # As above
+ print "build failed"
+ manager.notify_build_failed ()
+
+def init (server, eventHandler):
+ # Initialise threading...
+ gobject.threads_init()
+ gtk.gdk.threads_init()
+
+ main_window = MainWindow ()
+ main_window.show_all ()
+
+ # Set up the build manager stuff in general
+ builds_dir = os.path.join (os.getcwd(), "results")
+ manager = BuildManager (server, builds_dir)
+ main_window.build_manager_view.set_model (manager.model)
+
+ # Do the running build setup
+ running_build = RunningBuild ()
+ main_window.running_build_view.set_model (running_build.model)
+ running_build.connect ("build-succeeded", running_build_succeeded_cb,
+ manager)
+ running_build.connect ("build-failed", running_build_failed_cb, manager)
+
+ # We need to save the manager into the MainWindow so that the toolbar
+ # button can use it.
+ # FIXME: Refactor ?
+ main_window.manager = manager
+
+ # Use a timeout function for probing the event queue to find out if we
+ # have a message waiting for us.
+ gobject.timeout_add (200,
+ event_handle_timeout,
+ eventHandler,
+ running_build)
+
+ gtk.main()
diff --git a/bitbake/lib/bb/ui/uievent.py b/bitbake/lib/bb/ui/uievent.py
new file mode 100644
index 0000000..36302f4
--- /dev/null
+++ b/bitbake/lib/bb/ui/uievent.py
@@ -0,0 +1,125 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+"""
+Use this class to fork off a thread to recieve event callbacks from the bitbake
+server and queue them for the UI to process. This process must be used to avoid
+client/server deadlocks.
+"""
+
+import socket, threading, pickle
+from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
+
+class BBUIEventQueue:
+ def __init__(self, BBServer):
+
+ self.eventQueue = []
+ self.eventQueueLock = threading.Lock()
+ self.eventQueueNotify = threading.Event()
+
+ self.BBServer = BBServer
+
+ self.t = threading.Thread()
+ self.t.setDaemon(True)
+ self.t.run = self.startCallbackHandler
+ self.t.start()
+
+ def getEvent(self):
+
+ self.eventQueueLock.acquire()
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueLock.release()
+ return None
+
+ item = self.eventQueue.pop(0)
+
+ if len(self.eventQueue) == 0:
+ self.eventQueueNotify.clear()
+
+ self.eventQueueLock.release()
+ return item
+
+ def waitEvent(self, delay):
+ self.eventQueueNotify.wait(delay)
+ return self.getEvent()
+
+ def queue_event(self, event):
+ self.eventQueueLock.acquire()
+ self.eventQueue.append(pickle.loads(event))
+ self.eventQueueNotify.set()
+ self.eventQueueLock.release()
+
+ def startCallbackHandler(self):
+
+ server = UIXMLRPCServer()
+ self.host, self.port = server.socket.getsockname()
+
+ server.register_function( self.system_quit, "event.quit" )
+ server.register_function( self.queue_event, "event.send" )
+ server.socket.settimeout(1)
+
+ self.EventHandle = self.BBServer.registerEventHandler(self.host, self.port)
+
+ self.server = server
+ while not server.quit:
+ server.handle_request()
+ server.server_close()
+
+ def system_quit( self ):
+ """
+ Shut down the callback thread
+ """
+ try:
+ self.BBServer.unregisterEventHandler(self.EventHandle)
+ except:
+ pass
+ self.server.quit = True
+
+class UIXMLRPCServer (SimpleXMLRPCServer):
+
+ def __init__( self, interface = ("localhost", 0) ):
+ self.quit = False
+ SimpleXMLRPCServer.__init__( self,
+ interface,
+ requestHandler=SimpleXMLRPCRequestHandler,
+ logRequests=False, allow_none=True)
+
+ def get_request(self):
+ while not self.quit:
+ try:
+ sock, addr = self.socket.accept()
+ sock.settimeout(1)
+ return (sock, addr)
+ except socket.timeout:
+ pass
+ return (None,None)
+
+ def close_request(self, request):
+ if request is None:
+ return
+ SimpleXMLRPCServer.close_request(self, request)
+
+ def process_request(self, request, client_address):
+ if request is None:
+ return
+ SimpleXMLRPCServer.process_request(self, request, client_address)
+
+
diff --git a/bitbake/lib/bb/ui/uihelper.py b/bitbake/lib/bb/ui/uihelper.py
new file mode 100644
index 0000000..151ffc5
--- /dev/null
+++ b/bitbake/lib/bb/ui/uihelper.py
@@ -0,0 +1,49 @@
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Copyright (C) 2006 - 2007 Michael 'Mickey' Lauer
+# Copyright (C) 2006 - 2007 Richard Purdie
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+class BBUIHelper:
+ def __init__(self):
+ self.needUpdate = False
+ self.running_tasks = {}
+ self.failed_tasks = {}
+
+ def eventHandler(self, event):
+ if isinstance(event, bb.build.TaskStarted):
+ self.running_tasks["%s %s\n" % (event._package, event._task)] = ""
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskSucceeded):
+ del self.running_tasks["%s %s\n" % (event._package, event._task)]
+ self.needUpdate = True
+ if isinstance(event, bb.build.TaskFailed):
+ del self.running_tasks["%s %s\n" % (event._package, event._task)]
+ self.failed_tasks["%s %s\n" % (event._package, event._task)] = ""
+ self.needUpdate = True
+
+ # Add runqueue event handling
+ #if isinstance(event, bb.runqueue.runQueueTaskCompleted):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueTaskStarted):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueTaskFailed):
+ # a = 1
+ #if isinstance(event, bb.runqueue.runQueueExitWait):
+ # a = 1
+
+ def getTasks(self):
+ return (self.running_tasks, self.failed_tasks)
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py
index 3017ecf..5fc1463 100644
--- a/bitbake/lib/bb/utils.py
+++ b/bitbake/lib/bb/utils.py
@@ -21,8 +21,9 @@ BitBake Utility Functions
digits = "0123456789"
ascii_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+separators = ".-"
-import re, fcntl, os
+import re, fcntl, os, types
def explode_version(s):
r = []
@@ -39,12 +40,15 @@ def explode_version(s):
r.append(m.group(1))
s = m.group(2)
continue
+ r.append(s[0])
s = s[1:]
return r
def vercmp_part(a, b):
va = explode_version(a)
vb = explode_version(b)
+ sa = False
+ sb = False
while True:
if va == []:
ca = None
@@ -56,6 +60,16 @@ def vercmp_part(a, b):
cb = vb.pop(0)
if ca == None and cb == None:
return 0
+
+ if type(ca) is types.StringType:
+ sa = ca in separators
+ if type(cb) is types.StringType:
+ sb = cb in separators
+ if sa and not sb:
+ return -1
+ if not sa and sb:
+ return 1
+
if ca > cb:
return 1
if ca < cb:
@@ -151,7 +165,7 @@ def better_compile(text, file, realfile):
# split the text into lines again
body = text.split('\n')
- bb.msg.error(bb.msg.domain.Util, "Error in compiling: ", realfile)
+ bb.msg.error(bb.msg.domain.Util, "Error in compiling python function in: ", realfile)
bb.msg.error(bb.msg.domain.Util, "The lines resulting into this error were:")
bb.msg.error(bb.msg.domain.Util, "\t%d:%s:'%s'" % (e.lineno, e.__class__.__name__, body[e.lineno-1]))
@@ -176,7 +190,7 @@ def better_exec(code, context, text, realfile):
raise
# print the Header of the Error Message
- bb.msg.error(bb.msg.domain.Util, "Error in executing: %s" % realfile)
+ bb.msg.error(bb.msg.domain.Util, "Error in executing python function in: %s" % realfile)
bb.msg.error(bb.msg.domain.Util, "Exception:%s Message:%s" % (t,value) )
# let us find the line number now
OpenPOWER on IntegriCloud