meta-ros/scripts/mcf

979 lines
44 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env python3
# Copyright (c) 2008-2019 LG Electronics, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Requires Python 3.5 or later.
import argparse
import errno
import logging
import os
import subprocess
import sys
import re
from time import gmtime, strftime, sleep
import shutil
import glob
import importlib.util
import importlib.machinery
__version__ = "7.1.1"
logger = logging.getLogger(__name__)
CLEAN = False
TRACE = False
REMOTE = "origin"
SSTATE_MIRRORS = ''
LAYERS = {}
DISTRO = None
SUPPORTED_MACHINES = []
def echo_check_call(todo, verbosity=False):
if verbosity or TRACE:
cmd = 'set -x; ' + todo
else:
cmd = todo
logger.debug(cmd)
return str(subprocess.check_output(cmd, shell=True), encoding='utf-8', errors='strict')
def enable_trace():
global TRACE
TRACE = True
def enable_clean():
logger.warn('Running in clean non-interactive mode, all possible local changes and untracked files will be removed')
global CLEAN
CLEAN = True
def set_log_level(level):
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
f = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
s = logging.StreamHandler()
s.setLevel(level)
s.setFormatter(f)
logging.getLogger('').addHandler(s)
# Essentially, mcf parses options, creates mcf.status, and runs mcf.status.
def process_file(f, replacements):
(ifile, ofile) = f
with open(ifile, 'r') as f:
status = f.read()
for i, j in replacements:
status = status.replace(i, j)
odir = os.path.dirname(ofile)
if odir and not os.path.isdir(odir):
os.mkdir(odir)
with open(ofile, 'w') as f:
f.write(status)
def getopts(bblayers_only):
mcfcommand_option = '--command'
mcfcommand_dest = 'mcfcommand'
# be careful when changing this, jenkins-job.sh is doing
# grep "mcfcommand_choices = \['configure', 'update', "
# to detect if it needs to explicitly run --command update after default action
mcfcommand_choices = ['configure', 'update', 'update+configure']
mcfcommand_default = 'update+configure'
if bblayers_only:
mcfcommand = 'update'
mcfcommand_choices = [mcfcommand]
mcfcommand_default = mcfcommand
mcfcommand_help = argparse.SUPPRESS
configfile_help_append = '(default: none)'
arglist = [mcfcommand_option, mcfcommand ] + sys.argv[1:]
else:
# Just parse the --command argument here, so that we can select a parser
mcfcommand_parser = argparse.ArgumentParser(add_help=False)
mcfcommand_parser.add_argument(mcfcommand_option, dest=mcfcommand_dest, choices=mcfcommand_choices, default=mcfcommand_default)
mcfcommand_parser_result = mcfcommand_parser.parse_known_args()
mcfcommand = mcfcommand_parser_result[0].mcfcommand
mcfcommand_help = 'command to mcf; if update is given, none of the remaining options nor MACHINE can be specified (default: %(default)s)'
configfile_help_append = '(default: weboslayers.py in same directory as this script)'
# Put --command back in (as the first option) so that the main parser sees everything
arglist = [mcfcommand_option, mcfcommand ] + mcfcommand_parser_result[1]
parser = argparse.ArgumentParser()
general = parser.add_argument_group('General Options')
verbosity = general.add_mutually_exclusive_group()
verbosity.add_argument('-s', '--silent', action='count', help='work silently, repeat the option twice to hide also the warnings, tree times to hide the errors as well')
verbosity.add_argument('-v', '--verbose', action='count', help='work verbosely, repeat the option twice for more debug output')
general.add_argument('-c', '--clean', dest='clean', action='store_true', default=False, help='clean checkout - WARN: removes all local changes')
general.add_argument('-V', '--version', action='version', version='%(prog)s {0}'.format(__version__), help='print version and exit')
general.add_argument(mcfcommand_option, dest=mcfcommand_dest, choices=mcfcommand_choices, default=mcfcommand_default,
help='command to mcf; if update is given, none of the remaining options nor MACHINE can be specified (default: %(default)s)')
general.add_argument('-f', '--config-file', dest='configfile', help=('.mcf/.py file specifying the build configuration ' + configfile_help_append))
if mcfcommand in ('configure','update+configure'):
variations = parser.add_argument_group('Build Instructions')
variations.add_argument('-p', '--enable-parallel-make', dest='parallel_make', type=int, default=0,
help='maximum number of parallel tasks each submake of bitbake should spawn (default: 0 means 2x the number of processor cores)')
variations.add_argument('-b', '--enable-bb-number-threads', dest='bb_number_threads', type=int, default=0,
help='maximum number of bitbake tasks to spawn (default: 0 means 2x the number of processor cores))')
icecc = parser.add_argument_group('ICECC Configuration')
icecc_enable = icecc.add_mutually_exclusive_group()
# This can be changed to enabled by default when ES-1618 is fixed
icecc_enable.add_argument('--enable-icecc', dest='enable_icecc', action='store_true', default=False,
help='enable build to use ICECC, causes the shared state from the build artifacts not to be used (default: False)')
icecc_enable.add_argument('--disable-icecc', dest='enable_icecc', action='store_false', default=True,
help='disable build from using ICECC (default: True)')
icecc.add_argument('--enable-icecc-parallel-make', dest='icecc_parallel_make', type=int, default=0,
help='Number of parallel threads for ICECC build (default: 0 = 4x the number of processor cores))')
icecc_advanced = parser.add_argument_group('ICECC Advanced Configuration')
icecc_advanced.add_argument('--enable-icecc-user-package-blacklist', dest='icecc_user_package_blacklist', action='append',
help='Space separated list of components/recipes to be excluded from using ICECC (default: None)')
icecc_advanced.add_argument('--enable-icecc-user-class-blacklist', dest='icecc_user_class_blacklist', action='append',
help='Space separated list of components/recipes class to be excluded from using ICECC (default: None)')
icecc_advanced.add_argument('--enable-icecc-user-package-whitelist', dest='icecc_user_package_whitelist', action='append',
help='Space separated list of components/recipes to be forced to use ICECC (default: None)')
icecc_advanced.add_argument('--enable-icecc-location', dest='icecc_location', default='',
help='location of ICECC tool (default: None)')
icecc_advanced.add_argument('--enable-icecc-env-exec', dest='icecc_env_exec', default='',
help='location of ICECC environment script (default: None)')
partitions = parser.add_argument_group('Source Identification')
mirrors = parser.add_argument_group('Networking and Mirrors')
network = mirrors.add_mutually_exclusive_group()
network.add_argument('--disable-network', dest='network', action='store_false', default=True,
help='disable fetching through the network (default: False)')
network.add_argument('--enable-network', dest='network', action='store_true', default=True,
help='enable fetching through the network (default: True)')
mirrors.add_argument('--sstatemirror', dest='sstatemirror', action='append',
help='set sstatemirror to specified URL, repeat this option if you want multiple sstate mirrors (default: None)')
premirrorurl = mirrors.add_mutually_exclusive_group()
default_premirror = 'http://downloads.yoctoproject.org/mirror/sources'
premirrorurl.add_argument('--enable-default-premirror', dest='premirror', action='store_const', const=default_premirror, default="",
help='enable default premirror URL (default: False)')
premirrorurl.add_argument('--premirror', '--enable-premirror', dest='premirror', default='',
help='set premirror to specified URL (default: None)')
premirroronly = mirrors.add_mutually_exclusive_group()
premirroronly.add_argument('--disable-fetch-premirror-only', dest='fetchpremirroronly', action='store_false', default=False,
help='disable fetching through the network (default: False)')
premirroronly.add_argument('--enable-fetch-premirror-only', dest='fetchpremirroronly', action='store_true', default=False,
help='enable fetching through the network (default: True)')
tarballs = mirrors.add_mutually_exclusive_group()
tarballs.add_argument('--disable-generate-mirror-tarballs', dest='generatemirrortarballs', action='store_false', default=False,
help='disable tarball generation of fetched components (default: True)')
tarballs.add_argument('--enable-generate-mirror-tarballs', dest='generatemirrortarballs', action='store_true', default=False,
help='generate tarballs suitable for mirroring (default: False)')
buildhistory = parser.add_argument_group('Buildhistory')
buildhistory1 = buildhistory.add_mutually_exclusive_group()
buildhistory1.add_argument('--disable-buildhistory', dest='buildhistory', action='store_false', default=True,
help='disable buildhistory functionality (default: False)')
buildhistory1.add_argument('--enable-buildhistory', dest='buildhistory', action='store_true', default=True,
help='enable buildhistory functionality (default: True)')
buildhistory.add_argument('--enable-buildhistoryauthor', dest='buildhistoryauthor', default='', help='specify name and email used in buildhistory git commits (default: none, will use author from git global config)')
parser.add_argument('MACHINE', nargs='+')
options = parser.parse_args(arglist)
if mcfcommand in ('configure','update+configure') and options.sstatemirror:
process_sstatemirror_option(options)
return options
def process_sstatemirror_option(options):
"""
Sets global variable SSTATE_MIRRORS based on list of mirrors in options.sstatemirror
/PATH suffix is automatically added when generating SSTATE_MIRRORS value
verify that user didn't already include it and show error if he did
"""
sstate_mirrors = ''
for m in options.sstatemirror:
if not m:
continue
if m.endswith("/PATH"):
logger.error("sstatemirror entry '%s', already ends with '/PATH', remove that" % m)
sys.exit(1)
if m.endswith("/"):
logger.error("sstatemirror entry '%s', ends with '/', remove that" % m)
sys.exit(1)
if len(m) <= 7:
logger.error("sstatemirror entry '%s', is incorrect, we expect at least 7 characters for protocol" % m)
sys.exit(1)
sstate_mirrors += "file://.* %s/PATH \\n \\\n" % m
if sstate_mirrors:
global SSTATE_MIRRORS
SSTATE_MIRRORS = "SSTATE_MIRRORS ?= \" \\\n%s\"\n" % sstate_mirrors
def _icecc_installed():
try:
# Note that if package is not installed following call will throw an exception
iceinstallstatus,iceversion = subprocess.check_output("dpkg-query -W icecc 2>&1" ,
shell=True,
universal_newlines=True).split()
# We are expecting icecc for the name
if 'icecc' == iceinstallstatus:
if '1.0.1-1' == iceversion:
return True
else:
logger.warn("WARNING: Wrong icecc package version {} is installed, disabling build from using ICECC.\n".format(iceversion) + \
"Please check 'How To Install ICECC on Your Workstation (Client)'\n" + \
"http://wiki.lgsvl.com/pages/viewpage.action?pageId=96175316")
return False
else:
logger.warn('WARNING: ICECC package installation check failed, disabling build from using ICECC.')
return False
except:
logger.warn('WARNING: ICECC package installation check failed, disabling build from using ICECC.')
return False
def location_to_dirname(location):
str1 = location.split('/')
return os.path.splitext(str1[len(str1)-1])[0]
def read_mcfconfigfile(configfile, bblayers_only):
if not os.path.isfile(configfile):
raise Exception("Error: Configuration file %s does not exist!" % configfile)
importlib.machinery.SOURCE_SUFFIXES.append(".mcf")
spec = importlib.util.spec_from_file_location("mcfconfigfile", configfile)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules["mcfconfigfile"] = module
try:
from mcfconfigfile import Layers
except ImportError:
from mcfconfigfile import webos_layers
Layers = webos_layers
for p in Layers:
layer = {"name":p[0], "priority":p[1], "url":p[2], "fetch":p[3], "location":p[4]}
LAYERS[layer["name"]] = layer
parsefetch(layer)
if not layer["url"] and not layer["location"]:
raise Exception("Error: Layer '%s' does not have either URL or alternative working-dir defined in %s" % (layer["name"], configfile))
if not layer["location"]:
layer["location"] = location_to_dirname(layer["url"])
from mcfconfigfile import Distribution
global DISTRO
DISTRO = Distribution
from mcfconfigfile import Machines
global SUPPORTED_MACHINES
SUPPORTED_MACHINES = Machines
def parsefetch(layer):
branch = ''
commit = ''
tag = ''
for vgit in layer["fetch"].split(','):
if not vgit:
continue
str1, str2 = vgit.split('=')
if str1.lower() == 'commit':
if not commit:
commit = str2
elif str1.lower() == 'branch':
branch = str2
elif str1.lower() == 'tag':
if not tag:
tag = str2
if not branch:
branch = 'master'
layer["branch_new"] = branch
layer["commit_new"] = commit
layer["tag_new"] = tag
def wait_for_git_mirror(newcommitid):
repodir=os.getcwd()
cmd = 'git fetch %s %s >&2' % (REMOTE, newcommitid)
success = False
nr_of_retries = 30
for i in range(1, nr_of_retries+1):
logger.info('MCF-%s: trying to fetch revision %s in %s attempt %s from %s' % (__version__, newcommitid, repodir, i, nr_of_retries))
try:
if newcommitid.startswith('refs/changes/'):
echo_check_call(cmd)
elif not contains_ref(newcommitid):
echo_check_call('git remote update && git fetch %s --tags' % REMOTE)
success = True
break
except subprocess.CalledProcessError:
sleep(30)
if not success:
logger.error("MCF-%s Cannot checkout %s in %s" % (__version__, newcommitid, repodir))
sys.exit(1)
def downloadrepo(layer):
cmd = 'git clone %s %s' % (layer["url"], layer["location"])
echo_check_call(cmd)
olddir = os.getcwd()
os.chdir(layer["location"])
newbranch = layer["branch_new"]
if newbranch:
refbranchlist = echo_check_call("git branch")
refbranch = refbranchlist.splitlines()
foundbranch = False
for ibranch in refbranch:
if newbranch in ibranch:
foundbranch = True
if not foundbranch:
refbranchlist = echo_check_call("git branch -r")
refbranch = refbranchlist.splitlines()
for ibranch in refbranch:
if ibranch == " %s/%s" % (REMOTE, newbranch):
foundbranch = True
logger.info( " found %s " % ibranch )
cmd ='git checkout -B %s %s' % (newbranch,ibranch)
echo_check_call(cmd)
break
currentbranch = echo_check_call("git rev-parse --abbrev-ref HEAD").rstrip()
newcommitid = layer["commit_new"]
if newcommitid:
if newcommitid.startswith('refs/changes/'):
wait_for_git_mirror(newcommitid)
if newbranch and newbranch != currentbranch:
# older git doesn't allow to update reference on currently checked out branch
cmd ='git checkout -B %s FETCH_HEAD' % (newbranch)
elif newbranch:
# we're already on requested branch
cmd ='git reset --hard FETCH_HEAD'
else:
# we don't have any branch preference use detached
cmd ='git checkout FETCH_HEAD'
echo_check_call(cmd)
else:
if not contains_ref(newcommitid):
wait_for_git_mirror(newcommitid)
if newbranch and newbranch != currentbranch:
# older git doesn't allow to update reference on currently checked out branch
cmd ='git checkout -B %s %s' % (newbranch,newcommitid)
elif newbranch:
# we're already on requested branch
cmd ='git reset --hard %s' % newcommitid
else:
# we don't have any branch preference use detached
cmd ='git checkout %s' % newcommitid
echo_check_call(cmd)
newtag = layer["tag_new"]
if newtag:
if newbranch and newbranch != currentbranch:
# older git doesn't allow to update reference on currently checked out branch
cmd ='git checkout -B %s %s' % (newbranch,newtag)
elif newbranch:
# we're already on requested branch
cmd ='git reset --hard %s' % newtag
else:
cmd ='git checkout %s' % newtag
echo_check_call(cmd)
os.chdir(olddir)
def parselayerconffile(layer, layerconffile):
with open(layerconffile, 'r') as f:
lines = f.readlines()
for line in lines:
if re.search( 'BBFILE_COLLECTIONS.*=' , line):
(dummy, collectionname) = line.rsplit('=')
collectionname = collectionname.strip()
collectionname = collectionname.strip("\"")
layer["collection_name"] = collectionname
logger.debug("parselayerconffile(%s,%s) -> %s" % (layer["name"], layerconffile, layer["collection_name"]))
def traversedir(layer):
for path, dirs, files in os.walk(layer["location"]):
if os.path.basename(os.path.dirname(path)) == layer["name"]:
for filename in files:
# XXX Should check that it's under a "conf" subdirectory.
if filename == 'layer.conf':
layer["collection_path"] = os.path.relpath(os.path.dirname(path), os.path.dirname(layer["location"]))
logger.debug("traversedir(%s,%s) -> %s" % (layer["name"], layer["location"], layer["collection_path"]))
layerconffile = os.path.join(path, filename)
parselayerconffile(layer, layerconffile)
break
def parse_collections(srcdir):
for layer in sorted(LAYERS.values(), key=lambda l: l["priority"]):
if os.path.exists(layer["location"]):
traversedir(layer)
else:
raise Exception("Error: Directory '%s' does not exist, you probably need to call update" % layer["location"])
def write_bblayers_conf(sourcedir, append):
locations = ""
bblayers = ""
priorities = ""
for layer in sorted(LAYERS.values(), key=lambda l: l["priority"], reverse=True):
if layer["priority"] == -1:
# Layer is not metadata layer, skip it
continue
if os.path.isabs(layer["location"]):
topdir = layer["location"]
else:
topdir = "${TOPDIR}"
layer_name = layer["name"].replace('-','_').upper()
if "collection_path" not in layer:
logger.error("Layer %s doesn't exist or no conf/layer.conf file was found inside" % layer["name"])
continue
locations += "%s_LAYER ?= \"%s/%s\"\n" % (layer_name, topdir, layer["collection_path"])
bblayers += " ${%s_LAYER} \\\n" % layer_name
priorities += "BBFILE_PRIORITY_%s_forcevariable = \"%s\"\n" % (layer["collection_name"], layer["priority"])
if not append:
with open(os.path.join(sourcedir, "conf", "bblayers.conf"), 'w') as f:
# XXX Figure out a way to find openembedded-core/meta/conf/bblayers.conf.sample so that it can be used as a template
# instead of the hardcoding below.
f.write('# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf\n')
f.write('# changes incompatibly\n')
f.write('LCONF_VERSION = "7"\n')
f.write('\n')
f.write('TOPDIR = "' + sourcedir + '"\n')
f.write('\n')
f.write('BBPATH = "${TOPDIR}"\n')
with open(os.path.join(sourcedir, "conf", "bblayers.conf"), 'a') as f:
f.write('\n')
f.write(locations)
f.write('\n')
f.write('BBFILES ?= ""\n')
f.write('BBLAYERS ?= " \\\n')
f.write(bblayers)
f.write('"\n')
f.write(priorities)
try:
from mcfconfigfile import BblayersConfExtraLines
if BblayersConfExtraLines:
f.write('\n# Lines from the BblayersConfExtraLines setting:\n')
f.writelines('\n'.join(BblayersConfExtraLines) + '\n')
except ImportError:
pass
def update_layers(sourcedir):
logger.info('MCF-%s: Updating layers' % __version__)
layers_sanity = list()
update_location = list()
for layer in sorted(LAYERS.values(), key=lambda l: l["priority"]):
if layer["fetch"] and layer["location"] not in update_location:
update_location.append(layer["location"])
if not os.path.exists(os.path.abspath(layer["location"])):
# downloadrepo
downloadrepo(layer)
else:
# run sanity check on repo
if reposanitycheck(layer) != 0:
layers_sanity.append(layer["location"])
# update layers
updaterepo(layer)
if layers_sanity:
logger.info('Found local changes for repos(s) %s' % layers_sanity)
printupdatesummary()
def printupdatesummary ():
logger.info('Repo Update Summary')
logger.info('===================')
found = False
for layer in sorted(LAYERS.values(), key=lambda l: l["priority"]):
if "sanity_uncommitted_clean" in layer and layer["sanity_uncommitted_clean"]:
logger.info(' *) local uncommitted changes were removed because of --clean parameter')
found = True
if "sanity_uncommitted_changes" in layer and layer["sanity_uncommitted_changes"]:
logger.info(' *) local uncommitted changes, use \'git stash pop\' to retrieve')
found = True
if "sanity_dumped_changes" in layer and layer["sanity_dumped_changes"]:
logger.info(' *) local committed changes, patches are backed up in %s/' % layer["repo_patch_dir"])
found = True
if "sanity_untracked_changes" in layer and layer["sanity_untracked_changes"]:
logger.info(' *) local untracked changes')
found = True
if "branch_new" in layer and "branch_current" in layer and layer["branch_new"] != layer["branch_current"]:
logger.info(' *) switched branches from %s to %s' % (layer["branch_current"], layer["branch_new"]))
found = True
if not found:
logger.info('No local changes found')
def get_remote_branch(newbranch, second_call = False):
remotebranch = None
refbranchlist = echo_check_call("git branch -r")
refbranch = refbranchlist.splitlines()
for ibranch in refbranch:
if ibranch == " %s/%s" % (REMOTE, newbranch):
remotebranch = ibranch.strip()
break
if remotebranch or second_call:
return remotebranch
else:
# try it again after "git remote update"
echo_check_call("git remote update")
return get_remote_branch(newbranch, True)
def reposanitycheck(layer):
olddir = os.getcwd()
os.chdir(layer["location"])
layer["branch_current"] = echo_check_call("git rev-parse --abbrev-ref HEAD").rstrip()
res = False
if CLEAN:
if echo_check_call("git status --porcelain -s"):
layer["sanity_uncommitted_clean"] = True
logger.warn('Removing all local changes and untracked files in [%s]' % layer["location"])
# abort rebase if git pull --rebase from update_layers got stuck on some local commit
try:
echo_check_call("git rebase --abort 2>/dev/null")
except subprocess.CalledProcessError:
# we can ignore this one
pass
echo_check_call("git stash clear")
echo_check_call("git clean -fdx")
echo_check_call("git reset --hard")
else:
logger.info('Checking for local changes in [%s]' % layer["location"])
if echo_check_call("git status --porcelain --u=no -s"):
logger.warn('Found local uncommitted changes in [%s]' % layer["location"])
layer["sanity_uncommitted_changes"] = True
echo_check_call("git stash")
res = True
if echo_check_call("git status --porcelain -s | grep -v '^?? MCF-PATCHES_' || true"):
logger.warn('Found local untracked changes in [%s]' % layer["location"])
layer["sanity_untracked_changes"] = True
res = True
try:
remote = echo_check_call('git remote | grep "^%s$"' % REMOTE)
except subprocess.CalledProcessError:
remote = ''
if not remote:
logger.error("Checkout %s doesn't have the remote '%s'" % (layer["location"], REMOTE))
raise Exception("Checkout %s doesn't have the remote '%s'" % (layer["location"], REMOTE))
try:
urlcurrent = echo_check_call("git config remote.%s.url" % REMOTE)
except subprocess.CalledProcessError:
# git config returns 1 when the option isn't set
urlcurrent = ''
# there is extra newline at the end
urlcurrent = urlcurrent.strip()
logger.debug("reposanitycheck(%s) dir %s, branchinfo %s, branchinfonew %s, url %s, urlnew %s" % (layer["name"], layer["location"], layer["branch_current"], layer["branch_new"], layer["url"], urlcurrent))
if urlcurrent != layer["url"]:
logger.warn("Changing url for remote '%s' from '%s' to '%s'" % (REMOTE, urlcurrent, layer["url"]))
echo_check_call("git remote set-url %s %s" % (REMOTE, layer["url"]))
# Sync with new remote repo
try:
echo_check_call('git remote update')
except subprocess.CalledProcessError:
raise Exception('Failed to fetch %s repo' % LOCATIONS[layer])
newbranch = layer["branch_new"]
if newbranch:
refbranchlist = echo_check_call("git branch")
refbranch = refbranchlist.splitlines()
foundlocalbranch = False
needcheckout = True
for ibranch in refbranch:
if ibranch == " %s" % newbranch:
foundlocalbranch = True
break
if ibranch == "* %s" % newbranch:
foundlocalbranch = True
needcheckout = False
break
remotebranch = get_remote_branch(newbranch)
if foundlocalbranch and remotebranch:
if needcheckout:
echo_check_call('git checkout %s' % newbranch)
head = echo_check_call("git rev-parse --abbrev-ref HEAD").rstrip()
patchdir = './MCF-PATCHES_%s-%s' % (head.replace('/','_'), timestamp)
layer["repo_patch_dir"] = "%s/%s" % (layer["location"], patchdir)
cmd ='git format-patch %s..%s -o %s' % (remotebranch,newbranch,patchdir)
rawpatches = echo_check_call(cmd)
patches = rawpatches.splitlines()
num = len(patches)
# logger.info( ' info: number of patches: %s ' % num)
if num > 0:
layer["sanity_dumped_changes"] = True
res = True
else:
# remove empty dir if there weren't any patches created by format-patch
cmd ='rmdir --ignore-fail-on-non-empty %s' % patchdir
echo_check_call(cmd)
try:
trackingbranch = echo_check_call("git config --get branch.%s.merge" % newbranch)
except subprocess.CalledProcessError:
# git config returns 1 when the option isn't set
trackingbranch = ''
try:
trackingremote = echo_check_call("git config --get branch.%s.remote" % newbranch)
except subprocess.CalledProcessError:
# git config returns 1 when the option isn't set
trackingremote = ''
# there is extra newline at the end
trackingbranch = trackingbranch.strip()
trackingremote = trackingremote.strip()
if not trackingbranch or not trackingremote or trackingbranch.replace('refs/heads',trackingremote) != remotebranch:
logger.warn("checkout %s was tracking '%s/%s' changing it to track '%s'" % (layer["location"], trackingremote, trackingbranch, remotebranch))
# to ensure we are tracking remote
echo_check_call('git branch %s --set-upstream-to %s' % (newbranch, remotebranch))
elif not foundlocalbranch and remotebranch:
echo_check_call('git checkout -b %s %s' % (newbranch, remotebranch))
else:
# anything else is failure
raise Exception('Could not find local and remote branches for %s' % newbranch)
else:
raise Exception('Undefined branch name')
newdir = os.chdir(olddir)
return res
# Taken from bitbake/lib/bb/fetch2/git.py with modifications for mcf usage
def contains_ref(tag):
cmd = "git log --pretty=oneline -n 1 %s -- 2>/dev/null | wc -l" % (tag)
output = echo_check_call(cmd)
if len(output.split()) > 1:
raise Exception("Error: '%s' gave output with more then 1 line unexpectedly, output: '%s'" % (cmd, output))
return output.split()[0] != "0"
def updaterepo(layer):
olddir = os.getcwd()
os.chdir(layer["location"])
layer["commit_current"] = echo_check_call("git log --pretty=format:%h -1")
newcommitid = layer["commit_new"]
currentcommitid = layer["commit_current"]
newbranch = layer["branch_new"]
currentbranch = layer["branch_current"]
logger.debug("updaterepo(%s) dir %s, id %s, newid %s, branch %s, newbranch %s" % (layer["name"], layer["location"], currentcommitid, newcommitid, currentbranch, newbranch))
if newcommitid != currentcommitid:
logger.info('Updating [%s]' % layer["location"])
if newcommitid:
if newcommitid.startswith('refs/changes/'):
wait_for_git_mirror(newcommitid)
if newbranch and newbranch != currentbranch:
# older git doesn't allow to update reference on currently checked out branch
cmd ='git checkout -B %s FETCH_HEAD' % (newbranch)
elif newbranch:
# we're already on requested branch
cmd ='git reset --hard FETCH_HEAD'
else:
# we don't have any branch preference use detached
cmd ='git checkout FETCH_HEAD'
echo_check_call(cmd)
else:
if not contains_ref(newcommitid):
wait_for_git_mirror(newcommitid)
if newbranch and newbranch != currentbranch:
# older git doesn't allow to update reference on currently checked out branch
cmd ='git checkout -B %s %s' % (newbranch,newcommitid)
elif newbranch:
# we're already on requested branch
cmd ='git reset --hard %s' % newcommitid
else:
# we don't have any branch preference use detached
cmd ='git checkout %s' % newcommitid
echo_check_call(cmd)
else:
if CLEAN:
echo_check_call("git remote update")
echo_check_call('git reset --hard %s/%s' % (REMOTE, newbranch))
else:
# current branch always tracks a remote one
echo_check_call('git pull %s' % REMOTE)
logger.info('Done updating [%s]' % layer["location"])
else:
logger.info(('[%s] is up-to-date.' % layer["location"]))
newdir = os.chdir(olddir)
os.getcwd()
def set_verbosity(options):
if options.silent and options.silent == 1:
set_log_level('WARNING')
elif options.silent and options.silent == 2:
set_log_level('ERROR')
elif options.silent and options.silent >= 3:
set_log_level('CRITICAL')
elif options.verbose and options.verbose == 1:
set_log_level('DEBUG')
elif options.verbose and options.verbose >= 2:
set_log_level('DEBUG')
# but also run every system command with set -x
enable_trace()
else:
set_log_level('INFO')
def recover_current_mcf_state(srcdir, origoptions):
mcfstatusfile = os.path.join(srcdir, "mcf.status")
if not os.path.exists(mcfstatusfile):
raise Exception("mcf.status does not exist.")
commandlinereconstructed = list()
commandlinereconstructed.append('ignored-argv-0')
start = False
with open(mcfstatusfile, 'r') as f:
for line in f.readlines():
line = line.strip()
if not start:
start = line.startswith("exec")
continue
if start:
if line.startswith('--command'):
# skip --command configure
continue
elif line.startswith('--'):
line = line.rstrip('\\')
line = line.strip(' ')
line = line.replace('\"','')
line = line.replace('\'','')
commandlinereconstructed.append(line)
else:
lines = line.rstrip('\\')
lines = lines.lstrip()
lines = lines.rstrip()
lines = lines.split()
for lline in lines:
commandlinereconstructed.append(lline)
sys.argv = commandlinereconstructed
options = getopts(False)
# always use clean/verbose/silent flags from origoptions not mcf.status
options.clean = origoptions.clean
options.verbose = origoptions.verbose
options.silent = origoptions.silent
return options
def checkmirror(name, url):
if url.startswith('file://'):
pathstr = url[7:]
if not os.path.isdir(pathstr):
logger.warn("%s parameter '%s' points to non-existent directory" % (name, url))
elif not os.listdir(pathstr):
logger.warn("%s parameter '%s' points to empty directory, did you forgot to mount it?" % (name, url))
elif len(url) <= 7:
logger.error("%s parameter '%s' is incorrect, we expect at least 7 characters for protocol" % (name, url))
def sanitycheck(options):
try:
mirror = echo_check_call('git config -l | grep "^url\..*insteadof=github.com/"')
except subprocess.CalledProcessError:
# git config returns 1 when the option isn't set
mirror = ''
pass
if not mirror:
logger.warn('No mirror for github.com was detected, please define mirrors in ~/.gitconfig if some are available')
if options.sstatemirror:
for m in options.sstatemirror:
if not m:
continue
checkmirror('sstatemirror', m)
if options.premirror:
checkmirror('premirror', options.premirror)
def generate_bblayers(srcdir, append):
parse_collections(srcdir)
write_bblayers_conf(srcdir, append)
def configure_build(srcdir, options):
files = [
[os.path.join(srcdir, 'build-templates', 'mcf-status.in'), 'mcf.status' ],
[os.path.join(srcdir, 'build-templates', 'oe-init-build-env.in'), 'oe-init-build-env' ],
[os.path.join(srcdir, 'build-templates', 'Makefile.in'), 'Makefile' ],
[os.path.join(srcdir, 'build-templates', 'bblayers-conf.in'), 'conf/bblayers.conf'],
[os.path.join(srcdir, 'build-templates', 'local-conf.in'), 'conf/local.conf' ],
]
replacements = [
['@bb_number_threads@', str(options.bb_number_threads)],
['@parallel_make@', str(options.parallel_make)],
['@no_network@', '0' if options.network else '1'],
['@fetchpremirroronly@', '1' if options.fetchpremirroronly else '0'],
['@generatemirrortarballs@', '1' if options.generatemirrortarballs else '0'],
['@buildhistory_enabled@', '1' if options.buildhistory else '0'],
['@buildhistory_class@', 'buildhistory' if options.buildhistory else '' ],
['@buildhistory_author_assignment@', 'BUILDHISTORY_COMMIT_AUTHOR ?= "%s"' % options.buildhistoryauthor if options.buildhistoryauthor else ''],
['@premirror_assignment@', 'SOURCE_MIRROR_URL ?= "%s"' % options.premirror if options.premirror else ''],
['@premirror_inherit@', 'INHERIT += "own-mirrors"' if options.premirror else ''],
['@sstatemirror_assignment@', SSTATE_MIRRORS if options.sstatemirror else ''],
['@premirror@', options.premirror],
['@sstatemirror@', ' --sstatemirror='.join(options.sstatemirror) if options.sstatemirror else ''],
['@buildhistoryauthor@', options.buildhistoryauthor],
['@buildhistory@', '--%s-buildhistory' % ('enable' if options.buildhistory else 'disable')],
['@network@', '--%s-network' % ('enable' if options.network else 'disable')],
['@fetchpremirroronlyoption@', '--%s-fetch-premirror-only' % ('enable' if options.fetchpremirroronly else 'disable')],
['@generatemirrortarballsoption@', '--%s-generate-mirror-tarballs' % ('enable' if options.generatemirrortarballs else 'disable')],
['@machine@', options.MACHINE[0]],
['@machines@', ' '.join(options.MACHINE)],
['@distro@', DISTRO],
['@prog@', progname],
['@srcdir@', srcdir],
['@abs_srcdir@', abs_srcdir],
['@configfileoption@', ('--config-file=%s' % options.configfile) if options.configfile else '' ],
]
# if icecc is not installed, or version does not match requirements, then disabling icecc is the correct action.
icestate = _icecc_installed()
icecc_replacements = [
['@icecc_disable_enable@', '1' if not icestate or not options.enable_icecc else ''],
['@icecc_parallel_make@', '%s' % options.icecc_parallel_make],
['@alternative_icecc_installation@', ('ICECC_PATH ?= "%s"' % options.icecc_location) if options.icecc_location else ''],
['@icecc_user_package_blacklist@', ('ICECC_USER_PACKAGE_BL ?= "%s"' % ' '.join(options.icecc_user_package_blacklist)) if options.icecc_user_package_blacklist else ''],
['@icecc_user_class_blacklist@', ('ICECC_USER_CLASS_BL ?= "%s"' % ' '.join(options.icecc_user_class_blacklist)) if options.icecc_user_class_blacklist else ''],
['@icecc_user_package_whitelist@', ('ICECC_USER_PACKAGE_WL ?= "%s"' % ' '.join(options.icecc_user_package_whitelist)) if options.icecc_user_package_whitelist else ''],
['@icecc_environment_script@', 'ICECC_ENV_EXEC ?= "%s"' % options.icecc_env_exec if options.icecc_location else ''],
['@icecc_disable_enable_mcf@', '--%s-icecc' % ('disable' if not icestate or not options.enable_icecc else 'enable')],
['@alternative_icecc_installation_mcf@', options.icecc_location if options.icecc_location else ''],
['@icecc_environment_script_mcf@', options.icecc_env_exec if options.icecc_location else ''],
['@icecc_user_package_blacklist_mcf@', (' '.join(options.icecc_user_package_blacklist)) if options.icecc_user_package_blacklist else ''],
['@icecc_user_class_blacklist_mcf@', (' '.join(options.icecc_user_class_blacklist)) if options.icecc_user_class_blacklist else ''],
['@icecc_user_package_whitelist_mcf@', (' '.join(options.icecc_user_package_whitelist)) if options.icecc_user_package_whitelist else ''],
]
replacements = replacements + icecc_replacements
logger.info('MCF-%s: Configuring build directory BUILD' % __version__)
for f in files:
process_file(f, replacements)
generate_bblayers(srcdir, True)
logger.info('MCF-%s: Done configuring build directory BUILD' % __version__)
echo_check_call('/bin/chmod a+x mcf.status', options.verbose)
if __name__ == '__main__':
# NB. The exec done by mcf.status causes argv[0] to be an absolute pathname
progname = sys.argv[0]
# Use the same timestamp for everything created by this invocation of mcf
timestamp = strftime("%Y%m%d%H%M%S", gmtime())
# If the templates aren't present, then only we update the layers and generate conf/bblayers.conf using the configuration file
# (which must be specified with -f/--config-file).
srcdir = os.path.dirname(progname)
bblayers_only = not os.path.exists(os.path.join(srcdir, 'build-templates'))
options = getopts(bblayers_only)
if options.configfile:
srcdir = "."
configfile = options.configfile
else:
if bblayers_only:
logger.error("Configuration file must be specified")
sys.exit(1)
else:
configfile = os.path.join(srcdir, "weboslayers.py")
abs_srcdir = os.path.abspath(srcdir)
if options.mcfcommand == 'update' and not bblayers_only:
# recover current mcf state
options = recover_current_mcf_state(srcdir, options)
set_verbosity(options)
if options.clean:
enable_clean()
read_mcfconfigfile(configfile, bblayers_only)
if not bblayers_only:
for M in options.MACHINE:
if M not in SUPPORTED_MACHINES:
logger.error("MACHINE argument '%s' isn't supported (does not appear in Machines in weboslayers.py '%s')" % (M, SUPPORTED_MACHINES))
sys.exit(1)
if options.mcfcommand != 'configure':
update_layers(srcdir)
if bblayers_only:
logger.info('MCF-%s: Generating conf/bblayers.conf' % __version__)
if not os.path.exists("conf"):
logger.error("'conf' subdirectory does not exist")
sys.exit(1)
generate_bblayers(abs_srcdir, False)
logger.info('MCF-%s: Done generating conf/bblayers.conf' % __version__)
else:
configure_build(srcdir, options)
sanitycheck(options)
logger.info('Done.')