forked from mirrors/gecko-dev
		
	This is still referenced out of tree, but only to optionally (!) find it or to ignore it: http://mxr.mozilla.org/build/search?string=unsigned-unaligned&find=&findi=&filter=%5E%5B%5E%5C0%5D*%24&hitlimit=&tree=build MozReview-Commit-ID: Hq0TPVDzWOy --HG-- extra : rebase_source : 2e3dcfc516a6bcc5fd0517ca8bd98a4df7358251 extra : amend_source : f65baf2789517f5bd38384db89c6d2ea3d84676f extra : histedit_source : b901d25f4cbbf8bcecdf464a6042b5a44d51bea8
		
			
				
	
	
		
			347 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			347 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python
 | 
						|
#
 | 
						|
# This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
# 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/.
 | 
						|
#
 | 
						|
# When run directly, this script expects the following environment variables
 | 
						|
# to be set:
 | 
						|
# UPLOAD_HOST    : host to upload files to
 | 
						|
# UPLOAD_USER    : username on that host
 | 
						|
#  and one of the following:
 | 
						|
# UPLOAD_PATH    : path on that host to put the files in
 | 
						|
# UPLOAD_TO_TEMP : upload files to a new temporary directory
 | 
						|
#
 | 
						|
# If UPLOAD_HOST and UPLOAD_USER are not set, this script will simply write out
 | 
						|
# the properties file.
 | 
						|
#
 | 
						|
# If UPLOAD_HOST is "localhost", then files are simply copied to UPLOAD_PATH.
 | 
						|
# In this case, UPLOAD_TO_TEMP and POST_UPLOAD_CMD are not supported, and no
 | 
						|
# properties are written out.
 | 
						|
#
 | 
						|
# And will use the following optional environment variables if set:
 | 
						|
# UPLOAD_SSH_KEY : path to a ssh private key to use
 | 
						|
# UPLOAD_PORT    : port to use for ssh
 | 
						|
# POST_UPLOAD_CMD: a commandline to run on the remote host after uploading.
 | 
						|
#                  UPLOAD_PATH and the full paths of all files uploaded will
 | 
						|
#                  be appended to the commandline.
 | 
						|
#
 | 
						|
# All files to be uploaded should be passed as commandline arguments to this
 | 
						|
# script. The script takes one other parameter, --base-path, which you can use
 | 
						|
# to indicate that files should be uploaded including their paths relative
 | 
						|
# to the base path.
 | 
						|
 | 
						|
import sys, os
 | 
						|
import re
 | 
						|
import json
 | 
						|
import errno
 | 
						|
import hashlib
 | 
						|
import shutil
 | 
						|
from optparse import OptionParser
 | 
						|
from subprocess import (
 | 
						|
    check_call,
 | 
						|
    check_output,
 | 
						|
    STDOUT,
 | 
						|
    CalledProcessError,
 | 
						|
)
 | 
						|
import redo
 | 
						|
 | 
						|
def OptionalEnvironmentVariable(v):
 | 
						|
    """Return the value of the environment variable named v, or None
 | 
						|
    if it's unset (or empty)."""
 | 
						|
    if v in os.environ and os.environ[v] != "":
 | 
						|
        return os.environ[v]
 | 
						|
    return None
 | 
						|
 | 
						|
def FixupMsysPath(path):
 | 
						|
    """MSYS helpfully translates absolute pathnames in environment variables
 | 
						|
    and commandline arguments into Windows native paths. This sucks if you're
 | 
						|
    trying to pass an absolute path on a remote server. This function attempts
 | 
						|
    to un-mangle such paths."""
 | 
						|
    if 'OSTYPE' in os.environ and os.environ['OSTYPE'] == 'msys':
 | 
						|
        # sort of awful, find out where our shell is (should be in msys/bin)
 | 
						|
        # and strip the first part of that path out of the other path
 | 
						|
        if 'SHELL' in os.environ:
 | 
						|
            sh = os.environ['SHELL']
 | 
						|
            msys = sh[:sh.find('/bin')]
 | 
						|
            if path.startswith(msys):
 | 
						|
                path = path[len(msys):]
 | 
						|
    return path
 | 
						|
 | 
						|
def WindowsPathToMsysPath(path):
 | 
						|
    """Translate a Windows pathname to an MSYS pathname.
 | 
						|
    Necessary because we call out to ssh/scp, which are MSYS binaries
 | 
						|
    and expect MSYS paths."""
 | 
						|
    # If we're not on Windows, or if we already have an MSYS path (starting
 | 
						|
    # with '/' instead of 'c:' or something), then just return.
 | 
						|
    if sys.platform != 'win32' or path.startswith('/'):
 | 
						|
        return path
 | 
						|
    (drive, path) = os.path.splitdrive(os.path.abspath(path))
 | 
						|
    return "/" + drive[0] + path.replace('\\','/')
 | 
						|
 | 
						|
def AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key):
 | 
						|
    """Given optional port and ssh key values, append valid OpenSSH
 | 
						|
    commandline arguments to the list cmdline if the values are not None."""
 | 
						|
    if port is not None:
 | 
						|
        cmdline.append("-P%d" % port)
 | 
						|
    if ssh_key is not None:
 | 
						|
        # Don't interpret ~ paths - ssh can handle that on its own
 | 
						|
        if not ssh_key.startswith('~'):
 | 
						|
            ssh_key = WindowsPathToMsysPath(ssh_key)
 | 
						|
        cmdline.extend(["-o", "IdentityFile=%s" % ssh_key])
 | 
						|
 | 
						|
def DoSSHCommand(command, user, host, port=None, ssh_key=None):
 | 
						|
    """Execute command on user@host using ssh. Optionally use
 | 
						|
    port and ssh_key, if provided."""
 | 
						|
    cmdline = ["ssh"]
 | 
						|
    AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
 | 
						|
    cmdline.extend(["%s@%s" % (user, host), command])
 | 
						|
 | 
						|
    with redo.retrying(check_output, sleeptime=10) as f:
 | 
						|
        try:
 | 
						|
            output = f(cmdline, stderr=STDOUT).strip()
 | 
						|
        except CalledProcessError as e:
 | 
						|
            print "failed ssh command output:"
 | 
						|
            print '=' * 20
 | 
						|
            print e.output
 | 
						|
            print '=' * 20
 | 
						|
            raise
 | 
						|
        return output
 | 
						|
 | 
						|
    raise Exception("Command %s returned non-zero exit code" % cmdline)
 | 
						|
 | 
						|
def DoSCPFile(file, remote_path, user, host, port=None, ssh_key=None):
 | 
						|
    """Upload file to user@host:remote_path using scp. Optionally use
 | 
						|
    port and ssh_key, if provided."""
 | 
						|
    cmdline = ["scp"]
 | 
						|
    AppendOptionalArgsToSSHCommandline(cmdline, port, ssh_key)
 | 
						|
    cmdline.extend([WindowsPathToMsysPath(file),
 | 
						|
                    "%s@%s:%s" % (user, host, remote_path)])
 | 
						|
    with redo.retrying(check_call, sleeptime=10) as f:
 | 
						|
        f(cmdline)
 | 
						|
        return
 | 
						|
 | 
						|
    raise Exception("Command %s returned non-zero exit code" % cmdline)
 | 
						|
 | 
						|
def GetBaseRelativePath(path, local_file, base_path):
 | 
						|
    """Given a remote path to upload to, a full path to a local file, and an
 | 
						|
    optional full path that is a base path of the local file, construct the
 | 
						|
    full remote path to place the file in. If base_path is not None, include
 | 
						|
    the relative path from base_path to file."""
 | 
						|
    if base_path is None or not local_file.startswith(base_path):
 | 
						|
        # Hack to work around OSX uploading the i386 SDK from i386/dist. Both
 | 
						|
        # the i386 SDK and x86-64 SDK end up in the same directory this way.
 | 
						|
        if base_path.endswith('/x86_64/dist'):
 | 
						|
            return GetBaseRelativePath(path, local_file, base_path.replace('/x86_64/', '/i386/'))
 | 
						|
        return path
 | 
						|
    dir = os.path.dirname(local_file)
 | 
						|
    # strip base_path + extra slash and make it unixy
 | 
						|
    dir = dir[len(base_path)+1:].replace('\\','/')
 | 
						|
    return path + dir
 | 
						|
 | 
						|
def GetFileHashAndSize(filename):
 | 
						|
    sha512Hash = 'UNKNOWN'
 | 
						|
    size = 'UNKNOWN'
 | 
						|
 | 
						|
    try:
 | 
						|
        # open in binary mode to make sure we get consistent results
 | 
						|
        # across all platforms
 | 
						|
        with open(filename, "rb") as f:
 | 
						|
            shaObj = hashlib.sha512(f.read())
 | 
						|
            sha512Hash = shaObj.hexdigest()
 | 
						|
 | 
						|
        size = os.path.getsize(filename)
 | 
						|
    except:
 | 
						|
        raise Exception("Unable to get filesize/hash from file: %s" % filename)
 | 
						|
 | 
						|
    return (sha512Hash, size)
 | 
						|
 | 
						|
def GetMarProperties(filename):
 | 
						|
    if not os.path.exists(filename):
 | 
						|
        return {}
 | 
						|
    (mar_hash, mar_size) = GetFileHashAndSize(filename)
 | 
						|
    return {
 | 
						|
        'completeMarFilename': os.path.basename(filename),
 | 
						|
        'completeMarSize': mar_size,
 | 
						|
        'completeMarHash': mar_hash,
 | 
						|
    }
 | 
						|
 | 
						|
def GetUrlProperties(output, package):
 | 
						|
    # let's create a switch case using name-spaces/dict
 | 
						|
    # rather than a long if/else with duplicate code
 | 
						|
    property_conditions = [
 | 
						|
        # key: property name, value: condition
 | 
						|
        ('symbolsUrl', lambda m: m.endswith('crashreporter-symbols.zip') or
 | 
						|
                       m.endswith('crashreporter-symbols-full.zip')),
 | 
						|
        ('testsUrl', lambda m: m.endswith(('tests.tar.bz2', 'tests.zip'))),
 | 
						|
        ('robocopApkUrl', lambda m: m.endswith('apk') and 'robocop' in m),
 | 
						|
        ('jsshellUrl', lambda m: 'jsshell-' in m and m.endswith('.zip')),
 | 
						|
        ('completeMarUrl', lambda m: m.endswith('.complete.mar')),
 | 
						|
        ('partialMarUrl', lambda m: m.endswith('.mar') and '.partial.' in m),
 | 
						|
        ('codeCoverageURL', lambda m: m.endswith('code-coverage-gcno.zip')),
 | 
						|
        ('sdkUrl', lambda m: m.endswith(('sdk.tar.bz2', 'sdk.zip'))),
 | 
						|
        ('testPackagesUrl', lambda m: m.endswith('test_packages.json')),
 | 
						|
        ('packageUrl', lambda m: m.endswith(package)),
 | 
						|
    ]
 | 
						|
    url_re = re.compile(r'''^(https?://.*?\.(?:tar\.bz2|dmg|zip|apk|rpm|mar|tar\.gz|json))$''')
 | 
						|
    properties = {}
 | 
						|
 | 
						|
    try:
 | 
						|
        for line in output.splitlines():
 | 
						|
            m = url_re.match(line.strip())
 | 
						|
            if m:
 | 
						|
                m = m.group(1)
 | 
						|
                for prop, condition in property_conditions:
 | 
						|
                    if condition(m):
 | 
						|
                        properties.update({prop: m})
 | 
						|
                        break
 | 
						|
    except IOError as e:
 | 
						|
        if e.errno != errno.ENOENT:
 | 
						|
            raise
 | 
						|
        properties = {prop: 'UNKNOWN' for prop, condition in property_conditions}
 | 
						|
    return properties
 | 
						|
 | 
						|
def UploadFiles(user, host, path, files, verbose=False, port=None, ssh_key=None, base_path=None, upload_to_temp_dir=False, post_upload_command=None, package=None):
 | 
						|
    """Upload each file in the list files to user@host:path. Optionally pass
 | 
						|
    port and ssh_key to the ssh commands. If base_path is not None, upload
 | 
						|
    files including their path relative to base_path. If upload_to_temp_dir is
 | 
						|
    True files will be uploaded to a temporary directory on the remote server.
 | 
						|
    Generally, you should have a post upload command specified in these cases
 | 
						|
    that can move them around to their correct location(s).
 | 
						|
    If post_upload_command is not None, execute that command on the remote host
 | 
						|
    after uploading all files, passing it the upload path, and the full paths to
 | 
						|
    all files uploaded.
 | 
						|
    If verbose is True, print status updates while working."""
 | 
						|
    if not host or not user:
 | 
						|
        return {}
 | 
						|
    if (not path and not upload_to_temp_dir) or (path and upload_to_temp_dir):
 | 
						|
        print "One (and only one of UPLOAD_PATH or UPLOAD_TO_TEMP must be " + \
 | 
						|
                "defined."
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if upload_to_temp_dir:
 | 
						|
        path = DoSSHCommand("mktemp -d", user, host, port=port, ssh_key=ssh_key)
 | 
						|
    if not path.endswith("/"):
 | 
						|
        path += "/"
 | 
						|
    if base_path is not None:
 | 
						|
        base_path = os.path.abspath(base_path)
 | 
						|
    remote_files = []
 | 
						|
    properties = {}
 | 
						|
    try:
 | 
						|
        for file in files:
 | 
						|
            file = os.path.abspath(file)
 | 
						|
            if not os.path.isfile(file):
 | 
						|
                raise IOError("File not found: %s" % file)
 | 
						|
            # first ensure that path exists remotely
 | 
						|
            remote_path = GetBaseRelativePath(path, file, base_path)
 | 
						|
            DoSSHCommand("mkdir -p " + remote_path, user, host, port=port, ssh_key=ssh_key)
 | 
						|
            if verbose:
 | 
						|
                print "Uploading " + file
 | 
						|
            DoSCPFile(file, remote_path, user, host, port=port, ssh_key=ssh_key)
 | 
						|
            remote_files.append(remote_path + '/' + os.path.basename(file))
 | 
						|
        if post_upload_command is not None:
 | 
						|
            if verbose:
 | 
						|
                print "Running post-upload command: " + post_upload_command
 | 
						|
            file_list = '"' + '" "'.join(remote_files) + '"'
 | 
						|
            output = DoSSHCommand('%s "%s" %s' % (post_upload_command, path, file_list), user, host, port=port, ssh_key=ssh_key)
 | 
						|
            # We print since mozharness may parse URLs from the output stream.
 | 
						|
            print output
 | 
						|
            properties = GetUrlProperties(output, package)
 | 
						|
    finally:
 | 
						|
        if upload_to_temp_dir:
 | 
						|
            DoSSHCommand("rm -rf %s" % path, user, host, port=port,
 | 
						|
                         ssh_key=ssh_key)
 | 
						|
    if verbose:
 | 
						|
        print "Upload complete"
 | 
						|
    return properties
 | 
						|
 | 
						|
def CopyFilesLocally(path, files, verbose=False, base_path=None, package=None):
 | 
						|
    """Copy each file in the list of files to `path`.  The `base_path` argument is treated
 | 
						|
    as it is by UploadFiles."""
 | 
						|
    if not path.endswith("/"):
 | 
						|
        path += "/"
 | 
						|
    if base_path is not None:
 | 
						|
        base_path = os.path.abspath(base_path)
 | 
						|
    for file in files:
 | 
						|
        file = os.path.abspath(file)
 | 
						|
        if not os.path.isfile(file):
 | 
						|
            raise IOError("File not found: %s" % file)
 | 
						|
        # first ensure that path exists remotely
 | 
						|
        target_path = GetBaseRelativePath(path, file, base_path)
 | 
						|
        if not os.path.exists(target_path):
 | 
						|
            os.makedirs(target_path)
 | 
						|
        if verbose:
 | 
						|
            print "Copying " + file + " to " + target_path
 | 
						|
        shutil.copy(file, target_path)
 | 
						|
 | 
						|
def WriteProperties(files, properties_file, url_properties, package):
 | 
						|
    properties = url_properties
 | 
						|
    for file in files:
 | 
						|
        if file.endswith('.complete.mar'):
 | 
						|
            properties.update(GetMarProperties(file))
 | 
						|
    with open(properties_file, 'w') as outfile:
 | 
						|
        properties['packageFilename'] = package
 | 
						|
        properties['uploadFiles'] = [os.path.abspath(f) for f in files]
 | 
						|
        json.dump(properties, outfile, indent=4)
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    host = OptionalEnvironmentVariable('UPLOAD_HOST')
 | 
						|
    user = OptionalEnvironmentVariable('UPLOAD_USER')
 | 
						|
    path = OptionalEnvironmentVariable('UPLOAD_PATH')
 | 
						|
    upload_to_temp_dir = OptionalEnvironmentVariable('UPLOAD_TO_TEMP')
 | 
						|
    port = OptionalEnvironmentVariable('UPLOAD_PORT')
 | 
						|
    if port is not None:
 | 
						|
        port = int(port)
 | 
						|
    key = OptionalEnvironmentVariable('UPLOAD_SSH_KEY')
 | 
						|
    post_upload_command = OptionalEnvironmentVariable('POST_UPLOAD_CMD')
 | 
						|
 | 
						|
    if sys.platform == 'win32':
 | 
						|
        if path is not None:
 | 
						|
            path = FixupMsysPath(path)
 | 
						|
        if post_upload_command is not None:
 | 
						|
            post_upload_command = FixupMsysPath(post_upload_command)
 | 
						|
 | 
						|
    parser = OptionParser(usage="usage: %prog [options] <files>")
 | 
						|
    parser.add_option("-b", "--base-path",
 | 
						|
                      action="store",
 | 
						|
                      help="Preserve file paths relative to this path when uploading. If unset, all files will be uploaded directly to UPLOAD_PATH.")
 | 
						|
    parser.add_option("--properties-file",
 | 
						|
                      action="store",
 | 
						|
                      help="Path to the properties file to store the upload properties.")
 | 
						|
    parser.add_option("--package",
 | 
						|
                      action="store",
 | 
						|
                      help="Name of the main package.")
 | 
						|
    (options, args) = parser.parse_args()
 | 
						|
    if len(args) < 1:
 | 
						|
        print "You must specify at least one file to upload"
 | 
						|
        sys.exit(1)
 | 
						|
    if not options.properties_file:
 | 
						|
        print "You must specify a --properties-file"
 | 
						|
        sys.exit(1)
 | 
						|
 | 
						|
    if host == "localhost":
 | 
						|
        if upload_to_temp_dir:
 | 
						|
            print "Cannot use UPLOAD_TO_TEMP with UPLOAD_HOST=localhost"
 | 
						|
            sys.exit(1)
 | 
						|
        if post_upload_command:
 | 
						|
            # POST_UPLOAD_COMMAND is difficult to extract from the mozharness
 | 
						|
            # scripts, so just ignore it until it's no longer used anywhere
 | 
						|
            print "Ignoring POST_UPLOAD_COMMAND with UPLOAD_HOST=localhost"
 | 
						|
 | 
						|
    try:
 | 
						|
        if host == "localhost":
 | 
						|
            CopyFilesLocally(path, args, base_path=options.base_path,
 | 
						|
                             package=options.package,
 | 
						|
                             verbose=True)
 | 
						|
        else:
 | 
						|
 | 
						|
            url_properties = UploadFiles(user, host, path, args,
 | 
						|
                                         base_path=options.base_path, port=port, ssh_key=key,
 | 
						|
                                         upload_to_temp_dir=upload_to_temp_dir,
 | 
						|
                                         post_upload_command=post_upload_command,
 | 
						|
                                         package=options.package, verbose=True)
 | 
						|
 | 
						|
            WriteProperties(args, options.properties_file, url_properties, options.package)
 | 
						|
    except IOError, (strerror):
 | 
						|
        print strerror
 | 
						|
        sys.exit(1)
 |