Clone of official tools

Revision:
35:da9c89f8be7d
Parent:
31:8ea194f6145b
--- a/test/examples/update.py	Mon Feb 13 09:29:13 2017 -0600
+++ b/test/examples/update.py	Wed Feb 15 13:53:18 2017 -0600
@@ -8,6 +8,8 @@
 import subprocess
 import shutil
 import stat
+import re
+from github import Github, GithubException
 
 ROOT = abspath(dirname(dirname(dirname(dirname(__file__)))))
 sys.path.insert(0, ROOT)
@@ -33,6 +35,26 @@
     
     return return_code
     
+def run_cmd_with_output(command, print_warning_on_fail=True):
+    """ Takes the command specified and runs it in a sub-process, obtaining the return code 
+        and the returned output.
+        
+    Args:
+    command - command to run, provided as a list of individual fields which are combined into a 
+              single command before passing to the sub-process call.
+    return_code - result of the command.
+    output - the output of the command
+
+    """
+    print('[Exec] %s' % ' '.join(command))
+    returncode = 0
+    output = None
+    try:
+        output = subprocess.check_output(command)
+    except subprocess.CalledProcessError as e:
+        print("The command '%s' failed with return code: %s" % (' '.join(command), e.returncode))
+        returncode = e.returncode
+    return returncode, output
 
 def rmtree_readonly(directory):
     """ Deletes a readonly directory tree.
@@ -63,7 +85,7 @@
     
     return examples
 
-def upgrade_single_example(example, tag, directory):
+def upgrade_single_example(example, tag, directory, ref):
     """ Updates the mbed-os.lib file in the example specified to correspond to the 
         version specified by the GitHub tag supplied. Also deals with 
         multiple sub-examples in the GitHub repo, updating them in the same way.
@@ -72,113 +94,157 @@
     example - json example object containing the GitHub repo to update.
     tag - GitHub tag corresponding to a version of mbed-os to upgrade to.
     directory - directory path for the example.
+    ref - SHA corresponding to the supplied tag
     returns - True if the upgrade was successful, False otherwise.
     
     """
-    print("Upgrading single example at path '%s'" % directory)
     cwd = os.getcwd()
     os.chdir(directory)
     
-    return_code = None
+    return_code = False
     
-    # Change directories to the mbed-os library
-    if not os.path.exists('mbed-os'):
-        print("'mbed-os' directory not found in the root of '%s'" % directory)
-        print("Ignoring and moving on to the next example")
-        os.chdir(cwd)
+    if os.path.isfile("mbed-os.lib"):
+        # Rename command will fail on some OS's if the target file already exist,
+        # so ensure if it does, it is deleted first.
+        if os.path.isfile("mbed-os.lib_bak"):
+            os.remove("mbed-os.lib_bak")
+        
+        os.rename("mbed-os.lib", "mbed-os.lib_bak")
+    else:
+        print("!! Error trying to backup mbed-os.lib prior to updating.")
         return False
     
-    os.chdir('mbed-os')
-    
-    # Setup and run the update command
-    update_cmd = ['mbed', 'update', tag]
-    return_code = run_cmd(update_cmd)
+    # mbed-os.lib file contains one line with the following format
+    # e.g. https://github.com/ARMmbed/mbed-os/#0789928ee7f2db08a419fa4a032fffd9bd477aa7
+    lib_re = re.compile('https://github.com/ARMmbed/mbed-os/#[A-Za-z0-9]+')
+    updated = False
+
+    # Scan through mbed-os.lib line by line
+    with open('mbed-os.lib_bak', 'r') as ip, open('mbed-os.lib', 'w') as op:
+        for line in ip:
+
+            opline = line
+            
+            regexp = lib_re.match(line)
+            if regexp:
+                opline = 'https://github.com/ARMmbed/mbed-os/#' + ref
+                updated = True
     
-    if return_code:
-        os.chdir(cwd)
-        return False
-    
-    os.chdir('../')
-    
-    # Setup and run the add command
-    add_cmd = ['git', 'add', 'mbed-os.lib']
-    return_code = run_cmd(add_cmd)
-    
+            op.write(opline)
+
+    if updated:
+        # Setup and run the git add command
+        cmd = ['git', 'add', 'mbed-os.lib']
+        return_code = run_cmd(cmd)
+
     os.chdir(cwd)
     return not return_code
 
-def upgrade_example(example, tag):
-    """ Clones the example specified from GitHub and updates the associated mbed-os.lib file
-        to correspond to the version specified by the GitHub tag supplied. Also deals with 
-        multiple sub-examples in the GitHub repo, updating them in the same way.
+def prepare_fork(arm_example):
+    """ Synchronises a cloned fork to ensure it is up to date with the original. 
         
     Args:
+    arm_example - Full GitHub repo path for original example 
+    ret - True if the fork was synchronised successfully, False otherwise
+    
+    """
+
+    print "In " + os.getcwd()
+
+    for cmd in [['git', 'remote', 'add', 'armmbed', arm_example],
+                ['git', 'fetch', 'armmbed'],
+                ['git', 'reset', '--hard', 'armmbed/master'],
+                ['git', 'push', '-f', 'origin']]:
+        if run_cmd(cmd):
+            print("preparation of the fork failed!")
+            return False
+    return True
+
+
+def upgrade_example(github, example, tag, user, ref):
+    """ Clone a fork of the example specified.
+        Ensures the fork is up to date with the original and then and updates the associated 
+        mbed-os.lib file on that fork to correspond to the version specified by the GitHub tag supplied. 
+        Also deals with multiple sub-examples in the GitHub repo, updating them in the same way.
+        The updates are pushed to the forked repo.
+        Finally a PR is raised against the original example repo for the changes.
+        
+    Args:
+    github - GitHub instance to allow internal git commands to be run
     example - json example object containing the GitHub repo to update.
     tag - GitHub tag corresponding to a version of mbed-os to upgrade to.
+    user - GitHub user name
+    ref - SHA corresponding to the tag
     
     """
-    print("Updating example '%s'" % example['name'])
+    ret = False
+    print("\nUpdating example '%s'" % example['name'])
     cwd = os.getcwd()
-    
-    # Setup and run the import command
-    clone_cmd = ['git', 'clone', example['github']]
+
+    full_repo_name = 'ARMmbed/'+ example['name']
+    fork = "https://github.com/" + user + '/' + example['name'] 
+
+    # Check access to mbed-os repo
+    try:
+        repo = github.get_repo(full_repo_name, False)
+
+    except:
+        print("\t\t!! Repo does not exist - skipping\n")
+        return False
+
+
+    # Clone the forked example repo
+    clone_cmd = ['git', 'clone', fork]
     return_code = run_cmd(clone_cmd)
     
-    if return_code:
-        return False
-    
-    # Find all examples
-    example_directories = find_all_examples(example['name'])
-    
-    os.chdir(example['name'])
+    if not return_code:
     
-    # Setup and run the update command
-    import_cmd = ['mbed', 'update']
-    return_code = run_cmd(import_cmd)
-    if return_code:
-        os.chdir(cwd)
-        return False
-    
-    for example_directory in example_directories:
-        if not upgrade_single_example(example, tag, os.path.relpath(example_directory, example['name'])):
-            os.chdir(cwd)
-            return False
+        # Find all examples
+        example_directories = find_all_examples(example['name'])
+        
+        os.chdir(example['name'])
+        
+        # checkout and synchronise the release-candidate branch
+        prepare_fork(example['github'])
         
-    # Setup the default commit message
-    commit_message = 'Updating mbed-os to {{' + tag +'}}'
+        for example_directory in example_directories:
+            if not upgrade_single_example(example, tag, os.path.relpath(example_directory, example['name']), ref):
+                os.chdir(cwd)
+                return False
+
+        # Setup the default commit message
+        commit_message = 'Updating mbed-os to ' + tag 
+
+        # Setup and run the commit command
+        commit_cmd = ['git', 'commit', '-m', commit_message]
+        return_code = run_cmd(commit_cmd)
+        if not return_code:
+
+            # Setup and run the push command
+            push_cmd = ['git', 'push', 'origin']
+            return_code = run_cmd(push_cmd)
             
-    # Setup and run the commit command
-    commit_cmd = ['git', 'commit', '-m', commit_message]
-    return_code = run_cmd(commit_cmd)
-    if return_code:
-        if return_code == 1:
-            print("[WARNING] 'git commit' exited with a return code of 1. " + \
-            "This usually inidicates that no update was made. Still " + \
-            "attempting to create a tag.")
+            if not return_code:
+                body = "Please test/merge this PR and then tag Master with " + tag
+                # Raise a PR from release-candidate to master
+                user_fork = user + ':master' 
+                try:
+                    pr = repo.create_pull(title='Updating mbed-os to ' + tag, head=user_fork, base='master', body=body)
+                    ret = True
+                except GithubException as e:
+                    # Default to False
+                    print("Creation of Pull Request from release-candidate to master failed with the following error!")
+                    print e
+            else:
+                print("!!! Git push command failed.")
         else:
-            os.chdir(cwd)
-            return False
-    
-    # Setup and run the tag command
-    tag_cmd = ['git', 'tag', '-a', tag, '-m', tag]
-    return_code = run_cmd(tag_cmd)
-    if return_code:
-        os.chdir(cwd)
-        return False
-    
-    # Setup and run the push command
-    push_cmd = ['git', 'push', 'origin', 'master']
-    return_code = run_cmd(push_cmd)
-    
-    if return_code:
-        os.chdir(cwd)
-        return False
-        
-    push_cmd = ['git', 'push', 'origin', tag]
-    return_code = run_cmd(push_cmd)
-    
+            print("!!! Git commit command failed.")
+    else:
+        print("!!! Could not clone user fork %s\n" % fork)
+
+
     os.chdir(cwd)
-    return not return_code
+    return ret
 
 def create_work_directory(path):
     """ Create a new directory specified in 'path', overwrite if the directory already 
@@ -220,11 +286,27 @@
     
 
 def main(arguments):
+    """ Will update any mbed-os.lib files found in the example list specified by the config file.
+        If no config file is specified the default 'examples.json' is used. 
+        The update is done by cloning a fork of each example (the fork must be present in the 
+        github account specified by the github user parameter). The fork is searched for any
+        mbed-os.lib files and each one found is updated with the SHA corresponding to the supplied
+        github tag. A pull request is then made from the fork to the original example repo.
+        
+    Args:
+    tag - tag to update the mbed-os.lib to. E.g. mbed-os-5.3.1
+    github_token - Pre-authorised token to allow github access
+    github_user - github username whose account contains the example forks
+    config_file - optional parameter to specify a list of examples
+
+    """
 
     parser = argparse.ArgumentParser(description=__doc__,
                                      formatter_class=argparse.RawDescriptionHelpFormatter)
     parser.add_argument('tag', help="mbed-os tag to which all examples will be updated")
     parser.add_argument('-c', '--config_file', help="Path to the configuration file (default is 'examples.json')", default='examples.json')
+    parser.add_argument('-T', '--github_token', help="GitHub token for secure access")
+    parser.add_argument('-U', '--github_user', help="GitHub user for forked repos")
     
     args = parser.parse_args(arguments)
 
@@ -238,37 +320,33 @@
         print("Failed to load config file '%s'" % args.config_file)
         sys.exit(1)
     
-    # Create work directories
+    # Create working directory
     create_work_directory('examples')
-    
+
+    github = Github(args.github_token)
+
+    # Get the github sha corresponding to the specified mbed-os tag
+    cmd = ['git', 'rev-list', '-1', args.tag]
+    return_code, ref = run_cmd_with_output(cmd) 
+
+    if return_code:
+        print("Could not obtain SHA for tag: %s\n" % args.tag)
+        sys.exit(1)
+
     # Loop through the examples
     failures = []
     successes = []
-    not_compiled = []
     results = {}
     os.chdir('examples')
-
-    results = test_compile(config, args.tag)
-    lib.print_compilation_summary(results)
     
     for example in config['examples']:
-        # Determine if this example should be updated
+        # Determine if this example should be updated and if so update any found 
+        # mbed-os.lib files.
 
-        # Attempt to update if:
-        #   group of examples passed compilation and
-        #   auto update is set to True
-        # Note: results fields are [compiled flag, pass flag, successes list, failures list]
-        if not results[example['name']][0]:
-            # Example was not compiled
-            not_compiled += [example['name']]
+        if upgrade_example(github, example, args.tag, args.github_user, ref):
+            successes += [example['name']]
         else:
-            if results[example['name']][1] and example['auto-update']: 
-                if upgrade_example(example, args.tag):
-                    successes += [example['name']]
-                else:
-                    failures += [example['name']]
-            else:    
-                failures += [example['name']]
+            failures += [example['name']]
     
     os.chdir('../')
     
@@ -276,7 +354,7 @@
     print(os.linesep + os.linesep +'Finished updating examples!' + os.linesep)
     
     if successes:
-        print('The following examples updated successfully:')
+        print('\nThe following examples updated successfully:')
         for success in successes:
             print('    - %s' % success)
     
@@ -285,11 +363,5 @@
         for fail in failures:
             print('    - %s' % fail)
 
-    if not_compiled:
-        print('The following examples were skipped:')
-        for example in not_compiled:
-            print('    - %s' % example)
-
-
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
\ No newline at end of file