diff --git a/wa/commands/create.py b/wa/commands/create.py index f1b78ea5..c2475d98 100644 --- a/wa/commands/create.py +++ b/wa/commands/create.py @@ -1,7 +1,9 @@ import os import sys import stat +import shutil import string +import getpass from collections import OrderedDict from distutils.dir_util import copy_tree @@ -106,6 +108,44 @@ class CreateWorkloadSubcommand(SubCommand): self.logger.error('ERROR: {}'.format(e)) +class CreatePackageSubcommand(SubCommand): + + name = 'package' + description = '''Create a new empty Python package for WA extensions. On installation, + this package will "advertise" itself to WA so that Plugins within it will + be loaded by WA when it runs.''' + + def initialize(self, context): + self.parser.add_argument('name', metavar='NAME', + help='Name of the package to be created') + self.parser.add_argument('-p', '--path', metavar='PATH', default=None, + help='The location at which the new package will be created. If not specified, ' + + 'current working directory will be used.') + self.parser.add_argument('-f', '--force', action='store_true', + help='Create the new package even if a file or directory with the same name ' + 'already exists at the specified location.') + + def execute(self, state, args): # pylint: disable=R0201 + package_dir = args.path or os.path.abspath('.') + template_path = os.path.join(TEMPLATES_DIR, 'setup.template') + self.create_extensions_package(package_dir, args.name, template_path, args.force) + + def create_extensions_package(self, location, name, setup_template_path, overwrite=False): + package_path = os.path.join(location, name) + if os.path.exists(package_path): + if overwrite: + self.logger.info('overwriting existing "{}"'.format(package_path)) + shutil.rmtree(package_path) + else: + raise CommandError('Location "{}" already exists.'.format(package_path)) + actual_package_path = os.path.join(package_path, name) + os.makedirs(actual_package_path) + setup_text = render_template(setup_template_path, {'package_name': name, 'user': getpass.getuser()}) + with open(os.path.join(package_path, 'setup.py'), 'w') as wfh: + wfh.write(setup_text) + touch(os.path.join(actual_package_path, '__init__.py')) + + class CreateCommand(ComplexCommand): name = 'create' @@ -117,7 +157,7 @@ class CreateCommand(ComplexCommand): subcmd_classes = [ CreateWorkloadSubcommand, CreateAgendaSubcommand, - #CreatePackageSubcommand, + CreatePackageSubcommand, ] @@ -204,3 +244,7 @@ def render_template(name, params): def get_class_name(name, postfix=''): name = identifier(name) return ''.join(map(capitalize, name.split('_'))) + postfix + +def touch(path): + with open(path, 'w') as _: + pass diff --git a/wa/commands/templates/setup.template b/wa/commands/templates/setup.template new file mode 100644 index 00000000..d68801a2 --- /dev/null +++ b/wa/commands/templates/setup.template @@ -0,0 +1,102 @@ +import os +import sys +import warnings +from multiprocessing import Process + +try: + from setuptools.command.install import install as orig_install + from setuptools import setup +except ImportError: + from distutils.command.install import install as orig_install + from distutils.core import setup + +try: + import pwd +except ImportError: + pwd = None + +warnings.filterwarnings('ignore', "Unknown distribution option: 'install_requires'") + +try: + os.remove('MANIFEST') +except OSError: + pass + + +packages = [] +data_files = {} +source_dir = os.path.dirname(__file__) +for root, dirs, files in os.walk('$package_name'): + rel_dir = os.path.relpath(root, source_dir) + data = [] + if '__init__.py' in files: + for f in files: + if os.path.splitext(f)[1] not in ['.py', '.pyc', '.pyo']: + data.append(f) + package_name = rel_dir.replace(os.sep, '.') + package_dir = root + packages.append(package_name) + data_files[package_name] = data + else: + # use previous package name + filepaths = [os.path.join(root, f) for f in files] + data_files[package_name].extend([os.path.relpath(f, package_dir) for f in filepaths]) + +params = dict( + name='$package_name', + version='0.0.1', + packages=packages, + package_data=data_files, + url='N/A', + maintainer='$user', + maintainer_email='$user@example.com', + install_requires=[ + 'wa', + ], + # https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'License :: Other/Proprietary License', + 'Operating System :: Unix', + 'Programming Language :: Python :: 2.7', + ], +) + + +def update_wa_packages(): + sudo_user = os.getenv('SUDO_USER') + if sudo_user: + user_entry = pwd.getpwnam(sudo_user) + os.setgid(user_entry.pw_gid) + os.setuid(user_entry.pw_uid) + env_root = os.getenv('WA_USER_DIRECTORY', os.path.join(os.path.expanduser('~'), '.workload_automation')) + if not os.path.isdir(env_root): + os.makedirs(env_root) + wa_packages_file = os.path.join(env_root, 'packages') + if os.path.isfile(wa_packages_file): + with open(wa_packages_file, 'r') as wfh: + package_list = wfh.read().split() + if params['name'] not in package_list: + package_list.append(params['name']) + else: # no existing package file + package_list = [params['name']] + with open(wa_packages_file, 'w') as wfh: + wfh.write('\n'.join(package_list)) + + +class install(orig_install): + + def run(self): + orig_install.run(self) + # Must be done in a separate process because will drop privileges if + # sudo, and won't be able to reacquire them. + p = Process(target=update_wa_packages) + p.start() + p.join() + + +params['cmdclass'] = {'install': install} + + +setup(**params)