# -*- python -*-
# -*- coding: utf-8 -*-
#
# This file is part of the easydev software
#
# Copyright (c) 2011-2013
#
# File author(s): Thomas Cokelaer <cokelaer@gmail.com>
#
# Distributed under the GPLv3 License.
# See accompanying file LICENSE.txt or copy at
# http://www.gnu.org/licenses/gpl-3.0.html
#
# Website: https://github.com/cokelaer/easydev
# Documentation: http://packages.python.org/easydev
#
##############################################################################
# $:Id $
"""Calling setup.py recursively and/or in multi python packages.
The commands are similar to those expected by setup.py. In addition,
there are a few commands dedicated to multisetup (see --help).
:Example:
.. doctest::
:options: +SKIP
>>> python multisetup install --quiet
>>> python multisetup install sdist --dist-dir ../dist
>>> python multisetup --keep-going install sdist --dist-dir ../dist
Based on OpenAlea.Misc http://openalea.gforge.inria.fr
"""
__license__ = "GPLv3"
__revision__ = "$Id$"
import sys
import os
from subprocess import PIPE, Popen
try:
from easydev.console import bold, red, green, \
color_terminal, nocolor, underline, purple
except ImportError:
pass
"""
args = sys.argv[1:]
if len(args) == 1 and args[0] in ['-h', '--help']:
Multisetup.help()
else:
if 'develop -u' in args:
dirs.reverse()
"""
[docs]class Multisetup(object):
"""The main base class to build Multisetup instances
In practice, you create a python script with this kind of code::
if __name__ == "__main__"
from easydev.multisetup import Multisetup
import sys
packages = ['pkg1', 'pkg2']
mysetup = Multisetup(commands=sys.argv[1:], packages=packages)
mysetup.run()
"""
def __init__(self, commands, packages=None, curdir='.', verbose=True):
""".. rubric:: Constructor
:param commands: list of user commands or command (see :meth:`parse_commands`)
accepted commands are --packages, --exclude-packages, -quiet, --keep-going
:param list packages: list of packages to process
:param str curdir: current directory default is .
:param bool verbose: verbose option
:type commands: a string or list of strings
The argument `commands` must be a list of strings combining arguments
from multisetup and setup.
:Examples:
.. doctest::
:options: +SKIP
>>> Multisetup("install --keep-going", ['pkg1', 'pkg2'], '.', verbose=True)
>>> Multisetup(["install","--keep-going"], ['pkg1', 'pkg2'], '.', verbose=True)
"""
if len(commands) == 1 and commands[0] in ['-h', '--help']:
Multisetup.help()
if 'develop -u' in " ".join(commands):
packages.reverse()
# default
self.curdir = os.path.abspath(curdir)
if isinstance(commands, list):
self.commands = list(commands)
elif isinstance(commands, str):
self.commands = list(commands.split(" "))
else:
raise TypeError("commands argument must be a list of arguments or a string")
self.packages = list(packages)
self.verbose = verbose
self.force = False
# parsing user arguments
self.parse_packages()
# self.parse_intern_commands()
self.parse_commands()
@classmethod
[docs] def help(cls):
"""help: to get more help and usage
"""
print("""
MultiSetup allows to build and install all the packages found in this
directory usinf the same commands and setuptools.
Examples:
---------
# Developer mode : Installation of the pks from svn
>>> python multisetup.py develop
# User mode: Installation of the packages on the system as root
>>> python multisetup.py install
# Administrator mode: Create distribution of the packages
>>> python multisetup.py nosetests -w test install bdist_egg -d ../dist sdist -d ../dist
Common commands:
multisetup.py sdist -d ./dist will create a source distribution underneath 'dist/'
multisetup.py install will install the package
Global options:
--quiet do not show setup outputs [default=False]
-k, --keep-going force the commands running[default=False]
-h, --help show detailed help message
--packages list of packages to run
[default: none]
--exclude-packages list of packages to not run
print "usage: multisetup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
""")
[docs] def parse_packages(self):
"""Search and remove package(S) from multisetup command (e.g., --package)
.. todo:: known issue: python multisetup.py --packages with two
packages will be confused by following commands. Must be put
at the end of the command
"""
if '--packages' in self.commands:
index = self.commands.index('--packages')
self.commands.remove('--packages')
self.packages = set()
found = True
while found is True:
try: #test is no more argument
self.commands[index]
except: # then breaks
break
# otherwise if next argument starts with -, break
if self.commands[index].startswith('-'):
break
# or carry on to gather package names
else:
self.packages.add(self.commands[index])
self.commands.remove(self.commands[index])
continue
#self.commands.pop(index)
if '--exclude-packages' in self.commands:
# keep track of --exclude-package index
index = self.commands.index('--exclude-packages')
# remove it from the commands
self.commands.remove('--exclude-packages')
# remove all packages provided afterwards until next arguments is found
found = True
while found is True:
# look for next argument/package that may be the end of the command
try:
package_to_remove = self.commands[index]
except:
break
# if this is a valid package name
if package_to_remove in self.packages:
# remove it from the package list
self.packages.remove(package_to_remove)
# and from the command line
self.commands.remove(package_to_remove)
# until we found another package
continue
# otherwise, it is an argument that
else:
#starts with a - sign
if package_to_remove.startswith('-'):
break
# or is invalid
raise ValueError('--exclude-packages error: package %s not found in package list' \
% self.commands[index])
#self.commands.pop(index)
[docs] def parse_commands(self):
"""Search and remove multisetup options
Get the user command line arguments (self.commands) that are dedicated
to multisetup such as --help, --quiet, --keep-going so that the
remaining commands are fully comptatible with setuptools.
"""
if '--quiet' in self.commands:
self.verbose = False
self.commands.remove('--quiet')
if '-k' in self.commands:
self.force = True
self.commands.remove('-k')
if '--keep-going' in self.commands:
self.force = True
self.commands.remove('--keep-going')
L = len(self.commands)
i = 0
while (i < L):
if self.commands[i].startswith('-'):
try:
self.commands[i-1] = self.commands[i-1] + ' ' + self.commands[i] + ' ' + self.commands[i+1]
self.commands.pop(i)
self.commands.pop(i)
except:
self.commands[i-1] = self.commands[i-1] + ' ' + self.commands[i]
self.commands.pop(i)
else:
i += 1
L = len(self.commands)
[docs] def run(self, color=True):
"""Executes 'python setup.py' with the user commands on all packages. """
if color:
try:
from easydev.console import bold, red, green, \
color_terminal, nocolor, underline, purple
except:
try:
sys.path.insert(0, os.path.join('deploy', 'src', 'deploy'))
from console import bold, red, green, \
color_terminal, nocolor, underline, purple
except:
pass
if not color_terminal():
# Windows' poor cmd box doesn't understand ANSI sequences
nocolor()
else:
bold = purple = red = green = underline = str
print(bold("Running multisetup version %s" % __revision__.split()[2]))
#project_dir = self.curdir.basename()
directories = [package for package in self.packages]
print('Will process the following directories: ',)
for directory in directories:
print(bold(directory)),
#print bold(directory.basename()),
print('')
try:
for directory in directories:
try:
os.chdir(directory)
print(underline('Entering %s package'
% os.path.basename(directory)))
# % directory.basename())
except OSError as err:
print(underline('Entering %s package'
% os.path.basename(directory)))
print(red("cannot find this directory (%s)"
% os.path.basename(directory)))
print(err)
print('Python exec : ' , sys.executable)
#print underline('Entering %s package' % directory.basename())
for cmd in self.commands:
setup_command = '%s setup.py %s ' % (sys.executable,cmd)
print("\tExecuting " + setup_command + '...processing',)
#Run setup.py with user commands
outputs = None
errors = None
if self.verbose:
process = Popen(setup_command,
shell=True)
status = process.wait()
else:
process = Popen(setup_command, stdout=PIPE, stderr=PIPE,
shell=True)
#status = process.wait()
outputs, errors = process.communicate()
if process.returncode == 0:
print(green('done'))
else:
if not self.verbose:
print(red('\tFailed. ( error code %s) ' %
(process.returncode)))
os.chdir(self.curdir)
if not self.force:
raise RuntimeError()
if 'pylint' in cmd:
if outputs is not None:
for x in outputs.split('\n'):
if x.startswith('Your code has been'):
print(purple('\t%s' % x))
if 'nosetests' in cmd:
if errors is not None:
for x in errors.split('\n'):
if x.startswith('TOTAL'):
res = x.replace('TOTAL', 'Total coverage')
res = " ".join (res.split())
print(purple('\t%s' % res))
if x.startswith('Ran'):
print(purple('\t%s' % x))
if x.startswith('FAILED'):
print(purple('\t%s' % x))
else:
print(purple('all right'))
os.chdir(self.curdir)
except RuntimeError:
sys.exit()
os.chdir(self.curdir)