diff options
author | Marco Pesenti Gritti <mpgritti@gmail.com> | 2008-10-19 00:50:58 (GMT) |
---|---|---|
committer | Marco Pesenti Gritti <mpgritti@gmail.com> | 2008-10-19 00:50:58 (GMT) |
commit | 336368cbddd5ea79543be7d1b7e2eed142942cab (patch) | |
tree | ad3080b476084f6ab320f9719463832940d379a8 /scripts | |
parent | 865b0335aa30412ce87e6a287d6e8cc699e0f623 (diff) |
Some directory renaming.
Diffstat (limited to 'scripts')
-rw-r--r-- | scripts/bundlemodule.py | 94 | ||||
-rw-r--r-- | scripts/check.py | 142 | ||||
-rw-r--r-- | scripts/clear.py | 18 | ||||
-rw-r--r-- | scripts/config.py | 111 | ||||
-rw-r--r-- | scripts/data/pylintrc | 311 | ||||
-rw-r--r-- | scripts/depscheck.py | 37 | ||||
-rw-r--r-- | scripts/main.py | 116 | ||||
-rwxr-xr-x | scripts/mockbuild | 25 | ||||
-rw-r--r-- | scripts/report.py | 291 | ||||
-rw-r--r-- | scripts/sysdeps.py | 91 |
10 files changed, 1211 insertions, 25 deletions
diff --git a/scripts/bundlemodule.py b/scripts/bundlemodule.py new file mode 100644 index 0000000..2372ba3 --- /dev/null +++ b/scripts/bundlemodule.py @@ -0,0 +1,94 @@ +__metaclass__ = type + +import os + +from jhbuild.errors import BuildStateError +from jhbuild.modtypes import Package, get_branch, register_module_type + +__all__ = [ 'BundleModule' ] + +class BundleModule(Package): + type = 'bundle' + + STATE_CHECKOUT = 'checkout' + STATE_FORCE_CHECKOUT = 'force_checkout' + STATE_BUILD = 'build' + STATE_INSTALL = 'install' + + def __init__(self, name, branch, dependencies=[], after=[]): + Package.__init__(self, name, dependencies, after) + self.branch = branch + + def get_srcdir(self, buildscript): + return self.branch.srcdir + + def get_builddir(self, buildscript): + return self.get_srcdir(buildscript) + + def get_revision(self): + return self.branch.branchname + + def do_start(self, buildscript): + pass + do_start.next_state = STATE_CHECKOUT + do_start.error_states = [] + + def skip_checkout(self, buildscript, last_state): + # skip the checkout stage if the nonetwork flag is set + return buildscript.config.nonetwork + + def do_checkout(self, buildscript): + srcdir = self.get_srcdir(buildscript) + buildscript.set_action('Checking out', self) + self.branch.checkout(buildscript) + # did the checkout succeed? + if not os.path.exists(srcdir): + raise BuildStateError('source directory %s was not created' + % srcdir) + do_checkout.next_state = STATE_BUILD + do_checkout.error_states = [STATE_FORCE_CHECKOUT] + + def skip_force_checkout(self, buildscript, last_state): + return False + + def do_force_checkout(self, buildscript): + buildscript.set_action('Checking out', self) + self.branch.force_checkout(buildscript) + do_force_checkout.next_state = STATE_BUILD + do_force_checkout.error_states = [STATE_FORCE_CHECKOUT] + + def skip_build(self, buildscript, last_state): + return buildscript.config.nobuild + + def do_build(self, buildscript): + buildscript.set_action('Building', self) + srcdir = self.get_srcdir(buildscript) + builddir = self.get_builddir(buildscript) + python = os.environ.get('PYTHON', 'python') + cmd = [python, 'setup.py', 'build'] + buildscript.execute(cmd, cwd=srcdir) + do_build.next_state = STATE_INSTALL + do_build.error_states = [STATE_FORCE_CHECKOUT] + + def skip_install(self, buildscript, last_state): + return buildscript.config.nobuild + + def do_install(self, buildscript): + buildscript.set_action('Installing', self) + srcdir = self.get_srcdir(buildscript) + builddir = self.get_builddir(buildscript) + python = os.environ.get('PYTHON', 'python') + cmd = [python, 'setup.py', 'install'] + cmd.extend(['--prefix', buildscript.config.prefix]) + buildscript.execute(cmd, cwd=srcdir) + buildscript.packagedb.add(self.name, self.get_revision() or '') + do_install.next_state = Package.STATE_DONE + do_install.error_states = [] + + +def parse_bundle(node, config, uri, repositories, default_repo): + id = node.getAttribute('id') + branch = get_branch(node, repositories, default_repo) + return BundleModule(id, branch) + +register_module_type('bundle', parse_bundle) diff --git a/scripts/check.py b/scripts/check.py new file mode 100644 index 0000000..3213f87 --- /dev/null +++ b/scripts/check.py @@ -0,0 +1,142 @@ +import os +import sys +from optparse import make_option +import random +import signal +import subprocess +import time + +from jhbuild.commands import Command, register_command +import jhbuild.config + +scripts_dir = os.path.dirname(__file__) +data_dir = os.path.join(scripts_dir, 'data') +pylintrc_path = os.path.join(data_dir, 'pylintrc') + +def check_display(display): + result = subprocess.call(['xdpyinfo', '-display', ':%d' % display], + stdout=open(os.devnull, "w"), + stderr=open(os.devnull, "w")) + return result == 0 + +class UICheck(object): + def start(self): + self._mainloop = gobject.MainLoop() + self._exit_code = 0 + + self._start_shell() + + gobject.idle_add(self._start_ui_check_cb) + + self._mainloop.run() + + return self._exit_code + + def _start_shell(self): + print 'Launch the shell.' + + env = os.environ + env['SUGAR_PROFILE'] = 'uicheck' + env['SUGAR_PROFILE_NAME'] = 'uicheck' + + args = ['dbus-launch', '--exit-with-session', 'sugar'] + shell_process = subprocess.Popen(args, env=env) + + def _ui_check_exit_cb(self, pid, condition): + self._exit_code = os.WEXITSTATUS(condition) + self._mainloop.quit() + + def _ui_check_read_cb(self, fd, condition): + sys.stdout.write(os.read(fd, 1024)) + return True + + def _start_ui_check_cb(self): + print 'Launch UI check.' + + pid, stdin, stdout, stderr = gobject.spawn_async( + ['sugar-ui-check'], standard_output=True, standard_error=True, + flags=gobject.SPAWN_SEARCH_PATH | gobject.SPAWN_DO_NOT_REAP_CHILD) + + gobject.io_add_watch(stdout, gobject.IO_IN, self._ui_check_read_cb) + gobject.io_add_watch(stderr, gobject.IO_IN, self._ui_check_read_cb) + + gobject.child_watch_add(pid, self._ui_check_exit_cb) + +class cmd_check(Command): + + name = 'check' + usage_args = '' + + def __init__(self): + Command.__init__(self, [ + make_option('-t', '--type', action='store', + dest='type', default=None, + help='specify the check type') + ]) + + def start_xvfb(self): + result = None + + for port in range(100, 1000): + if not check_display(port): + try: + p = subprocess.Popen(['Xvfb', '-ac', ':%d' % port], + stdout=open(os.devnull, "w"), + stderr=subprocess.STDOUT) + + tries = 10 + while tries > 0: + if check_display(port): + os.environ['DISPLAY'] = ':%d' % port + return p.pid + else: + tries -= 1 + time.sleep(0.1) + except OSError: + break + + print 'Cannot execute xfvb, will use the default display.' + return None + + def lint(self, module): + xvfb_pid = self.start_xvfb() + try: + subprocess.call(['pylint', module, '--rcfile=%s' % pylintrc_path]) + finally: + os.kill(xvfb_pid, signal.SIGTERM) + + def check_ui(self, config): + xvfb_pid = self.start_xvfb() + try: + result = UICheck().start() + finally: + os.kill(xvfb_pid, signal.SIGTERM) + + return result + + def check_pylint(self, config): + self.lint('sugar') + self.lint('jarabe') + + ext_path = os.path.join(config.prefix, 'share', 'sugar', 'extensions') + jhbuild.config.addpath('PYTHONPATH', ext_path) + + self.lint('cpsection') + self.lint('deviceicon') + + def run(self, config, options, args): + result = 0 + + if not options.type or options.type == 'pylint': + self.check_pylint(config) + if not options.type or options.type == 'ui': + result = self.check_ui(config) + + return result + +try: + import gobject + + register_command(cmd_check) +except ImportError: + print 'Disable check, pygobject is not installed.' diff --git a/scripts/clear.py b/scripts/clear.py new file mode 100644 index 0000000..cc25e34 --- /dev/null +++ b/scripts/clear.py @@ -0,0 +1,18 @@ +import os +import shutil + +from jhbuild.commands import Command, register_command + +class cmd_clear(Command): + + name = 'clear' + usage_args = '' + + def run(self, config, options, args): + if os.path.exists(config.checkoutroot): + shutil.rmtree(config.checkoutroot) + + if os.path.exists(config.prefix): + shutil.rmtree(config.prefix) + +register_command(cmd_clear) diff --git a/scripts/config.py b/scripts/config.py new file mode 100644 index 0000000..b9ccb19 --- /dev/null +++ b/scripts/config.py @@ -0,0 +1,111 @@ +import os +import sys + +import jhbuild.config + +import sysdeps + +class Config(jhbuild.config.Config): + def __init__(self, rc_file): + scripts_dir = os.path.dirname(__file__) + self._base_dir = os.path.dirname(scripts_dir) + self._set_dir = os.path.join(self._base_dir, 'config', 'modulesets') + + jhbuild.config.Config.__init__(self, self._ensure_rc_file(rc_file)) + + self._setup() + + def get_user_path(self): + user_path = os.path.expanduser('~/.sugar-jhbuild') + if not os.path.exists(user_path): + os.mkdir(user_path) + return user_path + + def _ensure_rc_file(self, rc_file): + if not os.path.exists(rc_file): + f = open(rc_file, 'w') + f.write('# Created by sugar-jhbuild') + f.close + return rc_file + + def _setup(self): + self.autogenargs = '' + + self.moduleset = [] + self._add_moduleset('glucose-external.modules') + self._add_moduleset('tools.modules') + self._add_moduleset('extra.modules') + self._add_moduleset('extra-activities.modules') + + release_dir = None + for f in os.listdir(self._set_dir): + if f.startswith('release-'): + release_dir = f + + self._add_moduleset('glucose.modules', release_dir) + self._add_moduleset('fructose.modules', release_dir) + + self.modules = [ 'meta-tools', + 'meta-glucose', + 'meta-fructose' ] + + self.checkoutroot = os.path.join(self._base_dir, 'source') + self.tarballdir = self.checkoutroot + + for package, source in sysdeps.get_packages(): + if source and source not in self.skip: + self.skip.append(source) + + def _add_moduleset(self, moduleset, release_dir=None): + if release_dir: + path = os.path.join(self._set_dir, release_dir) + else: + path = self._set_dir + + self.moduleset.append(os.path.join(path, moduleset)) + + def setup_env(self): + self.prefix = os.path.join(self._base_dir, 'install') + + jhbuild.config.Config.setup_env(self) + + jhbuild.config.addpath('XDG_DATA_DIRS', '/usr/share') + jhbuild.config.addpath('XDG_DATA_DIRS', os.path.join(self.prefix, 'share')) + + if self.use_lib64: + path = 'lib64/gtk-2.0/' + else: + path = 'lib/gtk-2.0/' + jhbuild.config.addpath('GTK_PATH', os.path.join(self.prefix, path)) + jhbuild.config.addpath('GTK_DATA_PREFIX', self.prefix) + + os.environ['SUGAR_PREFIX'] = self.prefix + os.environ['SUGAR_PATH'] = os.path.join(self.prefix, 'share', 'sugar') + os.environ['SUGAR_LOGGER_LEVEL'] = 'debug' + + # Enable debug log of the Telepathy components + os.environ['GABBLE_DEBUG'] = 'all' + os.environ['SALUT_DEBUG'] = 'all' + os.environ['STREAM_ENGINE_DEBUG'] = 'all' + + # We need to add the gtk-2.0 directory explicitly to + # the Python path since '.pth' files (here pygtk.pth) + # only work properly in system directories + pythonversion = 'python' + str(sys.version_info[0]) + '.' + \ + str(sys.version_info[1]) + if self.use_lib64: + pythonpath = os.path.join(self.prefix, 'lib64', pythonversion, + 'site-packages', 'gtk-2.0') + else: + pythonpath = os.path.join(self.prefix, 'lib', pythonversion, + 'site-packages', 'gtk-2.0') + jhbuild.config.addpath('PYTHONPATH', pythonpath) + + python_lib = os.path.join(self.prefix, 'lib', 'python2.5', 'site-packages') + os.environ['PYTHON_LIB'] = python_lib + + if 'DBUS_SESSION_BUS_ADDRESS' in os.environ: + del os.environ['DBUS_SESSION_BUS_ADDRESS'] + + if not 'SUGAR_PROFILE' in os.environ: + os.environ['SUGAR_PROFILE'] = 'default' diff --git a/scripts/data/pylintrc b/scripts/data/pylintrc new file mode 100644 index 0000000..b161e38 --- /dev/null +++ b/scripts/data/pylintrc @@ -0,0 +1,311 @@ +# lint Python modules using external checkers. +# +# This is the main checker controling the other ones and the reports +# generation. It is itself both a raw checker and an astng checker in order +# to: +# * handle message activation / deactivation at the module level +# * handle some basic but necessary stats'data (number of classes, methods...) +# +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Profiled execution. +profile=no + +# Add <file or directory> to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore=CVS,_sugarext.so,_sugarbaseext.so,dispatch + +# Pickle collected data for later comparisons. +persistent=yes + +# Set the cache size for astng objects. +cache-size=500 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable only checker(s) with the given id(s). This option conflicts with the +# disable-checker option +#enable-checker= + +# Enable all checker(s) except those with the given id(s). This option +# conflicts with the enable-checker option +#disable-checker= + +# Enable all messages in the listed categories. +#enable-msg-cat= + +# Disable all messages in the listed categories. +disable-msg-cat=refactor,information + +# Enable the message(s) with the given id(s). +#enable-msg= + +# Disable the message(s) with the given id(s). +# FIXME +# W0105 reported on doc string for toplevel module variables +disable-msg=C0111,C0103,C0302,W0511,W0233,W0231,W0142,W0613,W0703,W0603,W0105 + + +[REPORTS] + +# set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells wether to display a full report or only the messages +reports=no + +# Python expression which should return a note less than 10 (10 is the highest +# note).You have access to the variables errors warning, statement which +# respectivly contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (R0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (R0004). +comment=no + +# Enable the report(s) with the given id(s). +#enable-report= + +# Disable the report(s) with the given id(s). +#disable-report= + + +# checks for : +# * doc strings +# * modules / classes / functions / methods / arguments / variables name +# * number of arguments, local variables, branchs, returns and statements in +# functions, methods +# * required module attributes +# * dangerous default values as arguments +# * redefinition of function / method / class +# * uses of the global statement +# +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z1-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + + +# try to find bugs in the code using type inference +# +[TYPECHECK] + +# Tells wether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamicaly set). +ignored-classes=SQLObject + +# When zope mode is activated, consider the acquired-members option to ignore +# access to some undefined attributes. +zope=no + +# List of members which are usually get through zope's acquisition mecanism and +# so shouldn't trigger E0201 when accessed (need zope=yes to be considered). +acquired-members=REQUEST,acl_users,aq_parent + + +# checks for +# * unused variables / imports +# * undefined variables +# * redefinition of variable from builtins or from an outer scope +# * use of variable before assigment +# +[VARIABLES] + +# Tells wether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching names used for dummy variables (i.e. not used). +dummy-variables-rgx=([^_]*_)|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +# checks for : +# * methods without self as first argument +# * overridden methods signature +# * access only to existant members via self +# * attributes not defined in the __init__ method +# * supported interfaces implementation +# * unreachable code +# +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +# checks for +# * external modules dependencies +# * relative / wildcard imports +# * cyclic imports +# * uses of deprecated modules +# +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report R0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report R0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report R0402 must +# not be disabled) +int-import-graph= + + +# checks for sign of poor/misdesign: +# * number of methods, attributes, local variables... +# * size, complexity of functions, methods +# +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +# checks for similarities and duplicated code. This computation may be +# memory / CPU intensive, so you should disable it if you experiments some +# problems. +# +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +# checks for: +# * warning notes in the code like FIXME, XXX +# * PEP 263: source code with non ascii character but no encoding declaration +# +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +# checks for : +# * unauthorized constructions +# * strict indentation +# * line length +# * use of <> instead of != +# +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' diff --git a/scripts/depscheck.py b/scripts/depscheck.py new file mode 100644 index 0000000..f318404 --- /dev/null +++ b/scripts/depscheck.py @@ -0,0 +1,37 @@ +import sys +from optparse import make_option + +from jhbuild.commands import Command, register_command + +import sysdeps + +class cmd_depscheck(Command): + + name = 'depscheck' + usage_args = '' + + def __init__(self): + Command.__init__(self, [ + make_option('-s', '--script', + action='store_true', dest='script', default=False, + help=_('script friendly output')), + ]) + + def run(self, config, options, args): + deps = sysdeps.get_packages() + if not options.script and not deps: + print 'Dependencies information is missing, skip sanity check.' + return + + missing_deps = [] + for package, source in deps: + if not sysdeps.check_package(package): + missing_deps.append(package) + + if missing_deps: + if not options.script: + print 'Missing packages:' + print ' '.join(missing_deps) + sys.exit(1) + +register_command(cmd_depscheck) diff --git a/scripts/main.py b/scripts/main.py new file mode 100644 index 0000000..272fe98 --- /dev/null +++ b/scripts/main.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +# jhbuild - a build script for GNOME 1.x and 2.x +# Copyright (C) 2001-2006 James Henstridge +# +# main.py: parses command line arguments and starts the build +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import sys, os +import optparse +import traceback + +import jhbuild.main +import jhbuild.commands +from jhbuild.errors import UsageError, FatalError + +import bundlemodule +import depscheck +import check +import report +import clear + +from config import Config + +def help_commands(option, opt_str, value, parser): + commands = [ + ('build', 'update and compile (the default)'), + ('build-base', 'build the base sugar dependencies'), + ('buildone', 'modules build a single module'), + ('update', 'update from version control'), + ('updateone', 'update a fixed set of modules'), + ('list', 'list what modules would be built'), + ('info', 'prints information about modules'), + ('tinderbox', 'build non-interactively with logging'), + ('gui', 'build targets from a gui app'), + ('run', 'run a command in the build environment'), + ('shell', 'start a shell in the build environment'), + ('depscheck', 'check that required dependencies exists'), + ('dot', 'output a dependency graph for processing with graphviz'), + ] + print 'sugar-jhbuild commands are:' + for (cmd, description) in commands: + print ' %-15s %s' % (cmd, description) + print + print 'For more information run "sugar-jhbuild <command> --help"' + parser.exit() + +def main(args): + parser = optparse.OptionParser( + usage='%prog [ -f config ] command [ options ... ]', + description='Build sugar and his dependencies.') + parser.disable_interspersed_args() + parser.add_option('--help-commands', action='callback', + callback=help_commands, + help='Information about available jhbuild commands') + parser.add_option('-f', '--file', action='store', metavar='CONFIG', + type='string', dest='configfile', + default=os.path.join(os.environ['HOME'], '.olpc.jhbuildrc'), + help='use a non default configuration file') + parser.add_option('-m', '--moduleset', action='store', metavar='URI', + type='string', dest='moduleset', default=None, + help='use a non default module set') + parser.add_option('--no-interact', action='store_true', + dest='nointeract', default=False, + help='do not prompt for input') + + options, args = parser.parse_args(args) + + try: + config = Config(options.configfile) + except FatalError, exc: + sys.stderr.write('sugar-jhbuild: %s\n' % (str(exc))) + sys.exit(1) + + if options.moduleset: config.moduleset = options.moduleset + if options.nointeract: config.interact = False + + if not args or args[0][0] == '-': + command = 'build' # default to cvs update + compile + else: + command = args[0] + args = args[1:] + + if command == 'build' and len(args) == 0: + print 'Checking dependencies...' + jhbuild.commands.run('depscheck', config, []) + elif command == 'run' and len(args) == 0: + args.append('sugar-emulator') + + try: + return jhbuild.commands.run(command, config, args) + except UsageError, exc: + sys.stderr.write('sugar-jhbuild %s: %s\n' % (command, str(exc))) + parser.print_usage() + sys.exit(1) + except FatalError, exc: + sys.stderr.write('sugar-jhbuild %s: %s\n' % (command, str(exc))) + sys.exit(1) + except KeyboardInterrupt: + print "Interrupted" + sys.exit(1) + except EOFError: + print "EOF" + sys.exit(1) diff --git a/scripts/mockbuild b/scripts/mockbuild deleted file mode 100755 index 8bebb38..0000000 --- a/scripts/mockbuild +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -SJH="SJH_DISTRIBUTION=olpc-3 HOME=/ /opt/sugar/sugar-jhbuild" -SNAPSHOT="sugar-snapshot-`date +%Y%m%d%H%M`.tar.bz2" - -mock --init -mock --install yum git - -mock --shell << EOF - cd /opt - git clone git://dev.laptop.org/sugar-jhbuild sugar - - $SJH depscheck -s >/tmp/deps -EOF - -mock --shell cat /tmp/deps | xargs mock --install - -mock --shell << EOF - $SJH --no-interact build meta-glucose meta-fructose - - rm -rf /opt/sugar/source - tar cvfjP $SNAPSHOT /opt/sugar -EOF - -mock --copyout $SNAPSHOT . diff --git a/scripts/report.py b/scripts/report.py new file mode 100644 index 0000000..41e9a00 --- /dev/null +++ b/scripts/report.py @@ -0,0 +1,291 @@ +import os +import sys +import urllib +import csv +import re +import smtplib +import subprocess +import htmllib +import formatter +from optparse import make_option +import StringIO + +from jhbuild.commands import Command, register_command +import jhbuild.moduleset + +def _get_ticket_uri(number): + return 'http://dev.laptop.org/ticket/' + number + +def _send_mail(sender, from_address, to_address, subject, text): + msg = 'From: %s<%s>\nTo: %s\nSubject: %s\n%s' % \ + (sender, from_address, to_address, subject, text) + + server = smtplib.SMTP('localhost') + server.sendmail(from_address, to_address, msg) + server.quit() + +class TextWriter(object): + def __init__(self, out): + self.out = out + + def write_headline(self, headline): + self.out.write('= ' + headline + ' =\n\n') + + def write_tickets(self, tickets, compact=False): + for ticket in tickets: + number = ticket['number'] + summary = ticket['summary'] + if compact: + self.out.write('* #%s %s\n' % (number, summary)) + else: + self.out.write(summary + '\n') + self.out.write(_get_ticket_uri(number) + '\n\n') + + if compact: + self.out.write('\n') + + def write_testcases(self, testcases): + for number, testcase in testcases: + self.out.write('#%s\n\n%s\n\n\n' % (number, testcase)) + +class ReleaseReport(object): + def __init__(self, config, release): + module, self._version = release.rsplit('-', 1) + + module_set = jhbuild.moduleset.load(config) + self._module = module_set.modules[module] + self._config = config + self._tickets = [] + self._testcases = [] + + def generate(self): + cwd = os.getcwd() + os.chdir(self._module.get_srcdir(self._config)) + + p = subprocess.Popen(['git', 'tag'], stdout=subprocess.PIPE) + tags = p.stdout.read().split('\n') + tags = [ tag for tag in tags if tag.startswith('v') ] + + release_tag = 'v' + self._version + try: + i = tags.index(release_tag) + except ValueError: + print 'The tag you provided does not exist.' + return + + if i > 0: + previous = tags[i - 1] + interval = previous + '..' + release_tag + else: + interval = release_tag + + p = subprocess.Popen(['git', 'log', interval, + '--pretty=format:%an|||%s'], + stdout=subprocess.PIPE) + commits = p.stdout.read().split('\n') + + tickets = [] + for row in commits: + author, subject = row.split('|||') + + match = re.search("\#([0-9]*)", subject) + if match: + tickets.append(match.group(1)) + + for n in tickets: + f = urllib.urlopen('http://dev.laptop.org/ticket/%s?format=csv' % n) + + reader = csv.DictReader(f) + row = reader.next() + + ticket = { 'number' : row['id'], + 'summary' : row['summary'] } + self._tickets.append(ticket) + + f.close() + + url = 'http://dev.laptop.org/ticket/%s?format=rss' % n + + import feedparser + parser = feedparser.parse(url) + + for entry in parser.entries: + out = StringIO.StringIO() + + writer = formatter.DumbWriter(out) + form = formatter.AbstractFormatter(writer) + + html_parser = htmllib.HTMLParser(form) + html_parser.feed(entry['summary_detail']['value']) + html_parser.close() + + comment = out.getvalue().strip() + + out.close() + + marker = '|testcase|' + i = comment.lower().find(marker) + if i >= 0: + i += len(marker) + self._testcases.append([n, comment[i:].strip()]) + + os.chdir(cwd) + + def write(self, writer): + writer.write_headline('Closed tickets') + writer.write_tickets(self._tickets, compact=True) + + if self._testcases: + writer.write_headline('Testcases') + writer.write_testcases(self._testcases) + + def save(self): + pass + +class ReviewsReport(object): + def __init__(self, config): + self._requested = {} + self._approved = {} + self._rejected = {} + self._state_path = os.path.join(config.get_user_path(), 'reviews') + + def generate(self): + f = urllib.urlopen('http://dev.laptop.org/query?' \ + 'format=csv&' \ + 'component=sugar&' \ + 'component=datastore&' \ + 'component=presence-service&' \ + 'component=journal-activity&' \ + 'col=id&col=summary&col=keywords&' \ + 'keywords=~r%2B&keywords=~r-&keywords=~r%3F') + + reader = csv.reader(f) + reader.next() + + for row in reader: + number = row[0] + keyword = row[2] + + ticket = { 'number' : row[0], + 'summary' : row[1] } + + if 'r?' in keyword: + ticket['review_state'] = 'r?' + self._requested[number] = ticket + if 'r+' in keyword: + ticket['review_state'] = 'r+' + self._approved[number] = ticket + if 'r-' in keyword: + ticket['review_state'] = 'r-' + self._rejected[number] = ticket + + f.close() + + def write(self, writer): + old_requested, old_approved, old_rejected = self._load() + + requested = self._diff_tickets(old_requested, self._requested) + approved = self._diff_tickets(old_approved, self._approved) + rejected = self._diff_tickets(old_rejected, self._rejected) + + if requested: + writer.write_headline('New requests') + writer.write_tickets(requested) + + if approved: + writer.write_headline('Approved requests') + writer.write_tickets(approved) + + if rejected: + writer.write_headline('Rejected requests') + writer.write_tickets(rejected) + + def save(self): + s = json.write([self._requested, self._approved, self._rejected]) + + f = open(os.path.join(self._state_path), 'w') + f.write(s) + f.close() + + def _load(self): + path = os.path.join(self._state_path) + if os.path.exists(path): + f = open(path, 'r') + result = json.read(f.read()) + f.close() + + return result + else: + return ({}, {}, {}) + + def _diff_tickets(self, old, new): + diff = [] + for number, ticket in new.items(): + if number in old: + old_ticket = old[number] + if old_ticket['review_state'] != ticket['review_state']: + diff.append(ticket) + else: + diff.append(ticket) + + return diff + +class cmd_report(Command): + + name = 'report' + usage_args = '' + + def __init__(self): + Command.__init__(self, [ + make_option('-t', '--type', action='store', + dest='type', default=None, + help='specify the report type'), + make_option('-s', '--sendto', action='append', + dest='sendto', default=None, + help='send report to the specified mail address') + ]) + + def run(self, config, options, args): + report_types = [ 'reviews', 'release' ] + + if options.type not in report_types: + print 'Available reports:\n' + '\n'.join(report_types) + return 1 + + if options.type == 'reviews': + report = ReviewsReport(config) + elif options.type == 'release': + if args: + report = ReleaseReport(config, args[0]) + else: + print 'Please provide a module name.' + return 1 + + report.generate() + + out = StringIO.StringIO() + report.write(TextWriter(out)) + text = out.getvalue() + out.close() + + if options.sendto: + if text: + print 'Sending to ' + ', '.join(options.sendto) + + for to_address in options.sendto: + _send_mail('Release Team', + 'mpgritti@gmail.com', to_address, + 'Reviews report', text) + else: + print 'Empty report, do not send' + else: + print text + + report.save() + +try: + import json + + register_command(cmd_report) +except ImportError: + print 'Disable report, json-py is not installed.' diff --git a/scripts/sysdeps.py b/scripts/sysdeps.py new file mode 100644 index 0000000..b398cff --- /dev/null +++ b/scripts/sysdeps.py @@ -0,0 +1,91 @@ +import os +import subprocess + +from xml.dom import minidom + +scripts_dir = os.path.dirname(__file__) +base_dir = os.path.dirname(scripts_dir) + +def get_distribution(): + if 'SJH_DISTRIBUTION' in os.environ: + return os.environ['SJH_DISTRIBUTION'].split('-') + + # Fedora + if os.path.exists('/etc/fedora-release'): + name = 'fedora' + + f = open('/etc/fedora-release') + full_name = f.read() + f.close() + + if 'Rawhide' in full_name: + version = 'rawhide' + else: + version = full_name.split(' ')[2] + + return name, version + + # Debian and Ubuntu + try: + out, err = subprocess.Popen(['lsb_release', '-is'], + stdout=subprocess.PIPE).communicate() + name = out.strip().lower() + + out, err = subprocess.Popen(['lsb_release', '-rs'], + stdout=subprocess.PIPE).communicate() + version = out.strip() + + if name == 'debian' and version == 'testing': + version = 'unstable' + + return name, version + except OSError: + pass + + return None, None + +def check_package(package): + name, version = get_distribution() + if name == 'fedora': + ret = subprocess.call(['rpm', '--quiet', '-q', package]) + return ret == 0 + elif name in ['ubuntu', 'debian']: + cmd = ["dpkg-query", "-f='${status}'", "-W", package] + out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate() + return out.find('install ok installed') != -1 + + return None + +def parse_dependencies(): + name, version = get_distribution() + if name is None or version is None: + return None + + filename = os.path.join(base_dir, 'config', 'sysdeps', + '%s-%s.xml' % (name, version)) + + if not os.path.exists(filename): + return None + + return minidom.parse(filename) + +def get_packages(): + document = parse_dependencies() + if document is None: + return [] + + packages = [] + root = document.childNodes[0] + + for node in root.childNodes: + if node.nodeType == node.ELEMENT_NODE: + if node.nodeName == 'package': + name = node.getAttribute('name') + if node.hasAttribute('source'): + source = node.getAttribute('source') + else: + source = None + + packages.append((name, source)) + + return packages |