Morpheus / Mbed OS mbed-Client-Morpheus-hg

Dependencies:   mbed-os

neo.py

Committer:
Christopher Haster
Date:
2016-03-30
Revision:
40:2446665dfdf8
Parent:
39:8d6f31570710
Child:
41:59e9d808ee05

File content as of revision 40:2446665dfdf8:

#!/usr/bin/env python

import argparse
import sys
import re
import subprocess
import os
import contextlib
import shutil
from collections import *
from itertools import *

# Subparser handling
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()

def subcommand(name, *args, **kwargs):
    def subcommand(command):
        subparser = subparsers.add_parser(name, **kwargs)
    
        for arg in args:
            if arg.endswith('?'):
                subparser.add_argument(arg.strip('?'), nargs='?')
            elif arg.endswith('*'):
                pass
            else:
                subparser.add_argument(arg)
    
        def thunk(parsed_args):
            ordered_args = [vars(parsed_args)[arg.strip('?*')]
                            if not arg.endswith('*') else remainder
                            for arg in args]
            return command(*ordered_args)
    
        subparser.set_defaults(command=thunk)
        return command
    return subcommand

# Process execution
class ProcessException(Exception):
    pass

def popen(command, stdin=None, **kwargs):
    # print for debugging
    print ' '.join(command)
    proc = subprocess.Popen(command, **kwargs)

    if proc.wait() != 0:
        raise ProcessException(proc.returncode)

def pquery(command, stdin=None, **kwargs):
    proc = subprocess.Popen(command, stdout=subprocess.PIPE, **kwargs)
    stdout, _ = proc.communicate(stdin)

    if proc.returncode != 0:
        raise ProcessException(proc.returncode)

    return stdout

# Directory navigation
@contextlib.contextmanager
def cd(newdir):
    prevdir = os.getcwd()
    os.chdir(newdir)
    try:
        yield
    finally:
        os.chdir(prevdir)

# Handling for multiple version controls
scms = OrderedDict()
def scm(name):
    def scm(cls):
        scms[name] = cls()
        return cls
    return scm

def staticclass(cls):
    for k, v in cls.__dict__.items():
        if hasattr(v, '__call__') and not k.startswith('__'):
            setattr(cls, k, staticmethod(v))

    return cls

@scm('hg')
@staticclass
class Hg(object):
    def clone(url, name=None, hash=None):
        popen(['hg', 'clone', url, name] + (['-u', hash] if hash else []))

    def add(file): popen(['hg', 'add', file])
    def remove(file):
        popen(['hg', 'rm', '-f', file])
        try:
            os.remove(file)
        except OSError:
            pass

    def commit(): popen(['hg', 'commit'])
    def push(): popen(['hg', 'push'])
    def pull(hash=None):
        popen(['hg', 'pull'])
        popen(['hg', 'update'] + (['-r', hash] if hash else []))

    def hash(): return pquery(['hg', 'id', '-i']).strip().strip('+')
    def dirty(): return pquery(['hg', 'status', '-q'])

@scm('git')
@staticclass
class Git(object):
    def clone(url, name=None, hash=None):
        popen(['git', 'clone', url, name])
        if hash:
            with cd(name):
                popen(['git', 'checkout', '-q', hash])

    def add(file): popen(['git', 'add', file])
    def remove(file): popen(['git', 'rm', '-f', file])

    def commit(): popen(['git', 'commit', '-a'])
    def push(): popen(['git', 'push', '--all'])
    def pull(hash=None):
        popen(['git', 'fetch', 'origin'])
        popen(['git', 'merge'] + ([hash] if hash else []))

    def hash(): return pquery(['git', 'rev-parse', '--short', 'HEAD']).strip()
    def dirty(): return pquery(['git', 'diff', '--name-only', 'HEAD'])

    def ignore(regex):
        with open('.git/info/exclude', 'a') as f:
            f.write(regex)


# Repository object
class Repo(object):
    @classmethod
    def fromurl(cls, url, path=None):
        repo = cls()

        m = re.match('^(.*/([+a-zA-Z0-9_-]+)/?)(?:#(.*))?$', url.strip())
        repo.name = os.path.basename(path or m.group(2))
        repo.path = os.path.abspath(
            path or os.path.join(os.getcwd(), repo.name))

        repo.repo = m.group(1)
        repo.hash = m.group(3)
        return repo

    @classmethod
    def fromlib(cls, lib=None):
        assert lib.endswith('.lib')
        with open(lib) as f:
            return cls.fromurl(f.read(), lib[:-4])

    @classmethod
    def fromrepo(cls, path=None):
        repo = cls()
        repo.path = os.path.abspath(path or os.getcwd())
        repo.name = os.path.basename(repo.path)

        repo.synch()
        return repo

    @property
    def lib(self):
        return self.path + '.lib'

    @property
    def url(self):
        if self.repo:
            return self.repo + ('#'+self.hash if self.hash else '')

    def synch(self):
        if os.path.isdir(self.path):
            try:
                self.scm  = self.getscm()
                self.hash = self.gethash()
                self.libs = list(self.getlibs())
            except ProcessException:
                pass

        if os.path.isfile(self.lib):
            try:
                self.repo = self.getrepo()
            except ProcessException:
                pass

    def getscm(self):
        for name, scm in scms.items():
            if os.path.isdir(os.path.join(self.path, '.'+name)):
                return scm

    def gethash(self):
        with cd(self.path):
            return self.scm.hash()

    def getlibs(self):
        for root, dirs, files in os.walk(self.path):
            dirs[:]  = [d for d in dirs  if not d.startswith('.')]
            files[:] = [f for f in files if not f.startswith('.')]

            for file in files:
                if file.endswith('.lib'):
                    yield Repo.fromlib(os.path.join(root, file))

    def getrepo(self):
        with open(self.lib) as f:
            return Repo.fromurl(f.read()).repo

    def write(self):
        print self.name, '->', self.url

        if os.path.isfile(self.lib):
            with open(self.lib) as f:
                if f.read().strip() == self.url.strip():
                    return

        with open(self.lib, 'w') as f:
            f.write(self.url + '\n')

# Clone command
@subcommand('import', 'url', 'name?',
    help='recursively import a repository')
def import_(url, path=None):
    repo = Repo.fromurl(url, path)

    for scm in scms.values():
        try:
            scm.clone(repo.repo, repo.path, repo.hash)
            break
        except ProcessException:
            pass

    repo.synch()

    with cd(repo.path):
        for lib in repo.libs:
            import_(lib.url, lib.path)
            #repo.scm.ignore(lib.path)

        if (not os.path.isfile('mbed_settings.py') and 
            os.path.isfile('mbed-os/tools/settings.py')):
            shutil.copy('mbed-os/tools/default_settings.py', 'mbed_settings.py')

# Deploy command
@subcommand('deploy',
    help='recursively import libraries in current directory')
def deploy():
    repo = Repo.fromrepo()
    for lib in repo.libs:
        import_(lib.url, lib.path)

# Install/uninstall command
@subcommand('add', 'url', 'name?',
    help='add a library to the current repository')
def add(url, path=None):
    repo = Repo.fromrepo()

    lib = Repo.fromurl(url, path)
    import_(lib.url, lib.path)
    lib.synch()

    lib.write()
    repo.scm.add(lib.lib)
    #repo.scm.ignore(lib.path)

@subcommand('remove', 'path',
    help='remove a library from the current repository folder')
def remove(path):
    repo = Repo.fromrepo()
    lib = Repo.fromrepo(path)

    repo.scm.remove(lib.lib)
    shutil.rmtree(lib.path)

# Publish command
@subcommand('publish',
    help='recursively publish changes to remote repositories')
def publish(always=True):
    repo = Repo.fromrepo()
    for lib in repo.libs:
        with cd(lib.path):
            publish(False)
    synch()

    dirty = repo.scm.dirty()

    if dirty:
        print 'Uncommitted changes in %s (%s)' % (repo.name, repo.path)
        raw_input('Press enter to commit and push ')
        repo.scm.commit()

    if dirty or always:
        try:
            repo.scm.push()
        except ProcessException as e:
            sys.exit(e[0])

# Update command
@subcommand('update', 'ref?',
    help='recursively updates libraries and current repository')
def update(ref=None):
    repo = Repo.fromrepo()
    repo.scm.pull(ref)

    for lib in repo.libs:
        if (not os.path.isfile(lib.lib) or
            lib.repo != Repo.fromrepo(lib.path).repo):
            with cd(lib.path):
                if lib.cwd.dirty():
                    sys.stderr.write('Uncommitted changes in %s (%s)\n'
                        % (lib.name, lib.path))
                    sys.exit(1)

            shutil.rmtree(lib.path)

    repo.synch()

    for lib in repo.libs:
        if os.path.isdir(lib.path):
            with cd(lib.path):
                update(lib.hash)
        else:
            import_(lib.url, lib.path)

# Synch command
@subcommand('synch',
    help='synchronize lib files')
def synch():
    repo = Repo.fromrepo()
    for lib in repo.libs:
        lib.synch()
        lib.save()

# Compile command
@subcommand('compile', 'args*',
    help='compile project using workspace_tools')
def compile(args):
    if not os.path.isdir('mbed-os'):
        sys.stderr.write('Warning! mbed-os not found?\n')
        sys.exit(-1)

    repo = Repo.fromrepo()

    macros = []
    if os.path.isfile('MACROS.txt'):
        with open('MACROS.txt') as f:
            macros = f.read().splitlines()

    env = os.environ.copy()
    env['PYTHONPATH'] = '.'
    popen(['python', 'mbed-os/tools/make.py']
        + list(chain.from_iterable(izip(repeat('-D'), macros)))
        + ['--source=%s' % repo.path,
           '--build=%s' % os.path.join(repo.path, '.build')]
        + args,
        env=env)

# Export command
@subcommand('export', 'args*',
    help='generate project files')
def export(args):
    if not os.path.isdir('mbed-os'):
        sys.stderr.write('Warning! mbed-os not found?\n')
        sys.exit(-1)

    repo = Repo.fromrepo()

    env = os.environ.copy()
    env['PYTHONPATH'] = '.'
    popen(['python', 'mbed-os/tools/project.py',
           '--source=%s' % repo.path]
        + args,
        env=env)

# Parse/run command
args, remainder = parser.parse_known_args()
status = args.command(args)
sys.exit(status or 0)