build/release/info.py
author Phil Ringnalda <philringnalda@gmail.com>
Wed, 28 Oct 2015 22:57:43 -0700
changeset 303546 b4c323832f317d650acd4bd7066d9432f3c1768c
parent 150821 d8f26f00c82f12df862edd0a36d7f47b5848362a
permissions -rw-r--r--
Back out 8 changesets (bug 1207355) for OS X 10.10 reftest failures in generated-content/ CLOSED TREE Backed out changeset aafd6db2fbb4 (bug 1207355) Backed out changeset 9dd950b837fb (bug 1207355) Backed out changeset e941e0e106a1 (bug 1207355) Backed out changeset ecebca101fcb (bug 1207355) Backed out changeset 08f2017137e1 (bug 1207355) Backed out changeset 3dc69e37c9b4 (bug 1207355) Backed out changeset bcdf51edb121 (bug 1207355) Backed out changeset 1d4c00dbf49a (bug 1207355)

from datetime import datetime
import os
from os import path
import re
import shutil
import sys
from urllib2 import urlopen

from release.paths import makeCandidatesDir

import logging
log = logging.getLogger(__name__)

# If version has two parts with no trailing specifiers like "rc", we
# consider it a "final" release for which we only create a _RELEASE tag.
FINAL_RELEASE_REGEX = "^\d+\.\d+$"


class ConfigError(Exception):
    pass


def getBuildID(platform, product, version, buildNumber, nightlyDir='nightly',
               server='stage.mozilla.org'):
    infoTxt = makeCandidatesDir(product, version, buildNumber, nightlyDir,
                                protocol='http', server=server) + \
        '%s_info.txt' % platform
    try:
        buildInfo = urlopen(infoTxt).read()
    except:
        log.error("Failed to retrieve %s" % infoTxt)
        raise

    for line in buildInfo.splitlines():
        key, value = line.rstrip().split('=', 1)
        if key == 'buildID':
            return value


def findOldBuildIDs(product, version, buildNumber, platforms,
                    nightlyDir='nightly', server='stage.mozilla.org'):
    ids = {}
    if buildNumber <= 1:
        return ids
    for n in range(1, buildNumber):
        for platform in platforms:
            if platform not in ids:
                ids[platform] = []
            try:
                id = getBuildID(platform, product, version, n, nightlyDir,
                                server)
                ids[platform].append(id)
            except Exception, e:
                log.error("Hit exception: %s" % e)
    return ids


def getReleaseConfigName(product, branch, version=None, staging=False):
    # XXX: Horrible hack for bug 842741. Because Thunderbird release
    # and esr both build out of esr17 repositories we'll bump the wrong
    # config for release without this.
    if product == 'thunderbird' and 'esr17' in branch and version and 'esr' not in version:
        cfg = 'release-thunderbird-comm-release.py'
    else:
        cfg = 'release-%s-%s.py' % (product, branch)
    if staging:
        cfg = 'staging_%s' % cfg
    return cfg


def readReleaseConfig(configfile, required=[]):
    return readConfig(configfile, keys=['releaseConfig'], required=required)


def readBranchConfig(dir, localconfig, branch, required=[]):
    shutil.copy(localconfig, path.join(dir, "localconfig.py"))
    oldcwd = os.getcwd()
    os.chdir(dir)
    sys.path.append(".")
    try:
        return readConfig("config.py", keys=['BRANCHES', branch],
                          required=required)
    finally:
        os.chdir(oldcwd)
        sys.path.remove(".")


def readConfig(configfile, keys=[], required=[]):
    c = {}
    execfile(configfile, c)
    for k in keys:
        c = c[k]
    items = c.keys()
    err = False
    for key in required:
        if key not in items:
            err = True
            log.error("Required item `%s' missing from %s" % (key, c))
    if err:
        raise ConfigError("Missing at least one item in config, see above")
    return c


def isFinalRelease(version):
    return bool(re.match(FINAL_RELEASE_REGEX, version))


def getBaseTag(product, version):
    product = product.upper()
    version = version.replace('.', '_')
    return '%s_%s' % (product, version)


def getTags(baseTag, buildNumber, buildTag=True):
    t = ['%s_RELEASE' % baseTag]
    if buildTag:
        t.append('%s_BUILD%d' % (baseTag, int(buildNumber)))
    return t


def getRuntimeTag(tag):
    return "%s_RUNTIME" % tag


def getReleaseTag(tag):
    return "%s_RELEASE" % tag


def generateRelbranchName(version, prefix='GECKO'):
    return '%s%s_%s_RELBRANCH' % (
        prefix, version.replace('.', ''),
        datetime.now().strftime('%Y%m%d%H'))


def getReleaseName(product, version, buildNumber):
    return '%s-%s-build%s' % (product.title(), version, str(buildNumber))


def getRepoMatchingBranch(branch, sourceRepositories):
    for sr in sourceRepositories.values():
        if branch in sr['path']:
            return sr
    return None


def fileInfo(filepath, product):
    """Extract information about a release file.  Returns a dictionary with the
    following keys set:
    'product', 'version', 'locale', 'platform', 'contents', 'format',
    'pathstyle'

    'contents' is one of 'complete', 'installer'
    'format' is one of 'mar' or 'exe'
    'pathstyle' is either 'short' or 'long', and refers to if files are all in
        one directory, with the locale as part of the filename ('short' paths,
        firefox 3.0 style filenames), or if the locale names are part of the
        directory structure, but not the file name itself ('long' paths,
        firefox 3.5+ style filenames)
    """
    try:
        # Mozilla 1.9.0 style (aka 'short') paths
        # e.g. firefox-3.0.12.en-US.win32.complete.mar
        filename = os.path.basename(filepath)
        m = re.match("^(%s)-([0-9.]+)\.([-a-zA-Z]+)\.(win32)\.(complete|installer)\.(mar|exe)$" % product, filename)
        if not m:
            raise ValueError("Could not parse: %s" % filename)
        return {'product': m.group(1),
                'version': m.group(2),
                'locale': m.group(3),
                'platform': m.group(4),
                'contents': m.group(5),
                'format': m.group(6),
                'pathstyle': 'short',
                'leading_path': '',
                }
    except:
        # Mozilla 1.9.1 and on style (aka 'long') paths
        # e.g. update/win32/en-US/firefox-3.5.1.complete.mar
        #      win32/en-US/Firefox Setup 3.5.1.exe
        ret = {'pathstyle': 'long'}
        if filepath.endswith('.mar'):
            ret['format'] = 'mar'
            m = re.search("update/(win32|linux-i686|linux-x86_64|mac|mac64)/([-a-zA-Z]+)/(%s)-(\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?)\.(complete)\.mar" % product, filepath)
            if not m:
                raise ValueError("Could not parse: %s" % filepath)
            ret['platform'] = m.group(1)
            ret['locale'] = m.group(2)
            ret['product'] = m.group(3)
            ret['version'] = m.group(4)
            ret['contents'] = m.group(5)
            ret['leading_path'] = ''
        elif filepath.endswith('.exe'):
            ret['format'] = 'exe'
            ret['contents'] = 'installer'
            # EUballot builds use a different enough style of path than others
            # that we can't catch them in the same regexp
            if filepath.find('win32-EUballot') != -1:
                ret['platform'] = 'win32'
                m = re.search("(win32-EUballot/)([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+\d+)?(?:\ \w+\ \d+)?)\.exe" % product, filepath)
                if not m:
                    raise ValueError("Could not parse: %s" % filepath)
                ret['leading_path'] = m.group(1)
                ret['locale'] = m.group(2)
                ret['product'] = m.group(3).lower()
                ret['version'] = m.group(4)
            else:
                m = re.search("(partner-repacks/[-a-zA-Z0-9_]+/|)(win32|mac|linux-i686)/([-a-zA-Z]+)/((?i)%s) Setup (\d+\.\d+(?:\.\d+)?(?:\w+(?:\d+)?)?(?:\ \w+\ \d+)?)\.exe" % product, filepath)
                if not m:
                    raise ValueError("Could not parse: %s" % filepath)
                ret['leading_path'] = m.group(1)
                ret['platform'] = m.group(2)
                ret['locale'] = m.group(3)
                ret['product'] = m.group(4).lower()
                ret['version'] = m.group(5)
        else:
            raise ValueError("Unknown filetype for %s" % filepath)

        return ret