Permalink
Browse files

Bug 905556 - Bump python dependencies for latest mozbase versions and…

… sync setup_development.py script
  • Loading branch information...
1 parent a241775 commit 197ae350b77d6b74e926268b74394aec44656a63 @whimboo whimboo committed Aug 15, 2013
Showing with 251 additions and 9 deletions.
  1. +3 −3 mozmill/setup.py
  2. +2 −1 mutt/setup.py
  3. +246 −5 setup_development.py
View
@@ -9,9 +9,9 @@
PACKAGE_VERSION = "2.0rc4"
deps = ['jsbridge == 3.0rc4',
- 'ManifestDestiny == 0.5.6',
- 'mozinfo == 0.4',
- 'mozrunner == 5.18',
+ 'ManifestDestiny == 0.5.7',
+ 'mozinfo == 0.6',
+ 'mozrunner == 5.23',
]
# take description from README
View
@@ -9,7 +9,8 @@
PACKAGE_NAME = "mutt"
PACKAGE_VERSION = "0.1"
-deps = ['ManifestDestiny == 0.5.6', 'mozmill']
+deps = ['ManifestDestiny==0.5.7',
+ 'mozmill==2.0rc4']
desc = "Test Harness for Mozmill"
license = 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
View
@@ -4,14 +4,255 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, you can obtain one at http://mozilla.org/MPL/2.0/.
-PACKAGES = ['jsbridge', 'mozmill', 'mutt']
+"""
+Setup mozmill packages for development.
+
+Packages may be specified as command line arguments.
+If no arguments are given, install all packages.
+
+See https://wiki.mozilla.org/Auto-tools/Projects/Mozmill
+"""
import os
import subprocess
import sys
+from optparse import OptionParser
+from subprocess import PIPE
+try:
+ from subprocess import check_call as call
+except ImportError:
+ from subprocess import call
+
+
+# directory containing this file
+here = os.path.dirname(os.path.abspath(__file__))
+
+# all python packages
+mozmill_packages = [i for i in os.listdir(here)
+ if os.path.exists(os.path.join(here, i, 'setup.py'))]
+extra_packages = []
+
+
+def cycle_check(order, dependencies):
+ """ensure no cyclic dependencies"""
+ order_dict = dict([(j, i) for i, j in enumerate(order)])
+ for package, deps in dependencies.items():
+ index = order_dict[package]
+ for d in deps:
+ assert index > order_dict[d], "Cyclic dependencies detected"
+
+
+def info(directory):
+ "get the package setup.py information"
+
+ assert os.path.exists(os.path.join(directory, 'setup.py'))
+
+ # setup the egg info
+ try:
+ call([sys.executable, 'setup.py', 'egg_info'], cwd=directory, stdout=PIPE)
+ except subprocess.CalledProcessError:
+ print "Error running setup.py in %s" % directory
+ raise
+
+ # get the .egg-info directory
+ egg_info = [entry for entry in os.listdir(directory)
+ if entry.endswith('.egg-info')]
+ assert len(egg_info) == 1, 'Expected one .egg-info directory in %s, got: %s' % (directory, egg_info)
+ egg_info = os.path.join(directory, egg_info[0])
+ assert os.path.isdir(egg_info), "%s is not a directory" % egg_info
+
+ # read the package information
+ pkg_info = os.path.join(egg_info, 'PKG-INFO')
+ info_dict = {}
+ for line in file(pkg_info).readlines():
+ if not line or line[0].isspace():
+ continue # XXX neglects description
+ assert ':' in line
+ key, value = [i.strip() for i in line.split(':', 1)]
+ info_dict[key] = value
+
+ return info_dict
+
+
+def get_dependencies(directory):
+ "returns the package name and dependencies given a package directory"
+
+ # get the package metadata
+ info_dict = info(directory)
+
+ # get the .egg-info directory
+ egg_info = [entry for entry in os.listdir(directory)
+ if entry.endswith('.egg-info')][0]
+
+ # read the dependencies
+ requires = os.path.join(directory, egg_info, 'requires.txt')
+ if os.path.exists(requires):
+ dependencies = [line.strip()
+ for line in file(requires).readlines()
+ if line.strip()]
+ else:
+ dependencies = []
+
+ # return the information
+ return info_dict['Name'], dependencies
+
+
+def dependency_info(dep):
+ "return dictionary of dependency information from a dependency string"
+ retval = dict(Name=None, Type=None, Version=None)
+ for joiner in ('==', '<=', '>='):
+ if joiner in dep:
+ retval['Type'] = joiner
+ name, version = [i.strip() for i in dep.split(joiner, 1)]
+ retval['Name'] = name
+ retval['Version'] = version
+ break
+ else:
+ retval['Name'] = dep.strip()
+ return retval
+
+
+def unroll_dependencies(dependencies):
+ """
+ unroll a set of dependencies to a flat list
+
+ dependencies = {'packageA': set(['packageB', 'packageC', 'packageF']),
+ 'packageB': set(['packageC', 'packageD', 'packageE', 'packageG']),
+ 'packageC': set(['packageE']),
+ 'packageE': set(['packageF', 'packageG']),
+ 'packageF': set(['packageG']),
+ 'packageX': set(['packageA', 'packageG'])}
+ """
+
+ order = []
+
+ # flatten all
+ packages = set(dependencies.keys())
+ for deps in dependencies.values():
+ packages.update(deps)
+
+ while len(order) != len(packages):
+
+ for package in packages.difference(order):
+ if set(dependencies.get(package, set())).issubset(order):
+ order.append(package)
+ break
+ else:
+ raise AssertionError("Cyclic dependencies detected")
+
+ cycle_check(order, dependencies) # sanity check
+
+ return order
+
+
+def main(args=sys.argv[1:]):
+
+ # parse command line options
+ usage = '%prog [options] [package] [package] [...]'
+ parser = OptionParser(usage=usage, description=__doc__)
+ parser.add_option('-d', '--dependencies', dest='list_dependencies',
+ action='store_true', default=False,
+ help="list dependencies for the packages")
+ parser.add_option('--list', action='store_true', default=False,
+ help="list what will be installed")
+ parser.add_option('--extra', '--install-extra-packages', action='store_true', default=False,
+ help="installs extra supporting packages as well as core mozmill ones")
+ options, packages = parser.parse_args(args)
+
+ if not packages:
+ # install all packages
+ packages = sorted(mozmill_packages)
+
+ # ensure specified packages are in the list
+ assert set(packages).issubset(mozmill_packages), "Packages should be in %s (You gave: %s)" % (mozmill_packages, packages)
+
+ if options.list_dependencies:
+ # list the package dependencies
+ for package in packages:
+ print '%s: %s' % get_dependencies(os.path.join(here, package))
+ parser.exit()
+
+ # gather dependencies
+ # TODO: version conflict checking
+ deps = {}
+ alldeps = {}
+ mapping = {} # mapping from subdir name to package name
+ # core dependencies
+ for package in packages:
+ key, value = get_dependencies(os.path.join(here, package))
+ deps[key] = [dependency_info(dep)['Name'] for dep in value]
+ mapping[package] = key
+
+ # keep track of all dependencies for non-mozmill packages
+ for dep in value:
+ alldeps[dependency_info(dep)['Name']] = ''.join(dep.split())
+
+ # indirect dependencies
+ flag = True
+ while flag:
+ flag = False
+ for value in deps.values():
+ for dep in value:
+ if dep in mozmill_packages and dep not in deps:
+ key, value = get_dependencies(os.path.join(here, dep))
+ deps[key] = [sanitize_dependency(dep) for dep in value]
+
+ for dep in value:
+ alldeps[sanitize_dependency(dep)] = ''.join(dep.split())
+ mapping[package] = key
+ flag = True
+ break
+ if flag:
+ break
+
+ # get the remaining names for the mapping
+ for package in mozmill_packages:
+ if package in mapping:
+ continue
+ key, value = get_dependencies(os.path.join(here, package))
+ mapping[package] = key
+
+ # unroll dependencies
+ unrolled = unroll_dependencies(deps)
+
+ # make a reverse mapping: package name -> subdirectory
+ reverse_mapping = dict([(j,i) for i, j in mapping.items()])
+
+ # we only care about dependencies in mozmill
+ unrolled = [package for package in unrolled if package in reverse_mapping]
+
+ if options.list:
+ # list what will be installed
+ for package in unrolled:
+ print package
+ parser.exit()
+
+ # set up the packages for development
+ for package in unrolled:
+ call([sys.executable, 'setup.py', 'develop', '--no-deps'],
+ cwd=os.path.join(here, reverse_mapping[package]))
+
+ # add the directory of sys.executable to path to aid the correct
+ # `easy_install` getting called
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=893878
+ os.environ['PATH'] = '%s%s%s' % (os.path.dirname(os.path.abspath(sys.executable)),
+ os.path.pathsep,
+ os.environ.get('PATH', '').strip(os.path.pathsep))
+
+ # install non-mozmill dependencies
+ # these need to be installed separately and the --no-deps flag
+ # subsequently used due to a bug in setuptools; see
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=759836
+ pypi_deps = dict([(i, j) for i,j in alldeps.items()
+ if i not in unrolled])
+ for package, version in pypi_deps.items():
+ # easy_install should be available since we rely on setuptools
+ call(['easy_install', version])
-path = os.path.dirname(os.path.abspath(__file__))
+ # install extra non-mozmill packages if desired
+ if options.extra:
+ for package in extra_packages:
+ call(['easy_install', package])
-for package in PACKAGES:
- os.chdir(os.path.join(path, package))
- subprocess.call([sys.executable, "setup.py", "develop"])
+if __name__ == '__main__':
+ main()

0 comments on commit 197ae35

Please sign in to comment.