Merge branch 'buildman' of git://git.denx.de/u-boot-x86

This commit is contained in:
Tom Rini 2015-01-15 10:18:05 -05:00
commit d1c3310d40
9 changed files with 606 additions and 78 deletions

View File

@ -141,8 +141,8 @@ $ git clone git://git.denx.de/u-boot.git .
$ git checkout -b my-branch origin/master
$ # Add some commits to the branch, reading for testing
2. Create ~/.buildman to tell buildman where to find tool chains. As an
example:
2. Create ~/.buildman to tell buildman where to find tool chains (see 'The
.buildman file' later for details). As an example:
# Buildman settings file
@ -171,7 +171,16 @@ The toolchain-alias section indicates that the i386 toolchain should be used
to build x86 commits.
2. Check the available toolchains
3. Make sure you have the require Python pre-requisites
Buildman uses multiprocessing, Queue, shutil, StringIO, ConfigParser and
urllib2. These should normally be available, but if you get an error like
this then you will need to obtain those modules:
ImportError: No module named multiprocessing
4. Check the available toolchains
Run this check to make sure that you have a toolchain for every architecture.
@ -301,6 +310,47 @@ You can see that everything is covered, even some strange ones that won't
be used (c88 and c99). This is a feature.
5. Install new toolchains if needed
You can download toolchains and update the [toolchain] section of the
settings file to find them.
To make this easier, buildman can automatically download and install
toolchains from kernel.org. First list the available architectures:
$ ./tools/buildman/buildman sandbox --fetch-arch list
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.6.3/
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.6.2/
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.5.1/
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.2.4/
Available architectures: alpha am33_2.0 arm avr32 bfin cris crisv32 frv h8300
hppa hppa64 i386 ia64 m32r m68k mips mips64 or32 powerpc powerpc64 s390x sh4
sparc sparc64 tilegx x86_64 xtensa
Then pick one and download it:
$ ./tools/buildman/buildman sandbox --fetch-arch or32
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.6.3/
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.6.2/
Checking: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.5.1/
Downloading: https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.5.1//x86_64-gcc-4.5.1-nolibc_or32-linux.tar.xz
Unpacking to: /home/sjg/.buildman-toolchains
Testing
- looking in '/home/sjg/.buildman-toolchains/gcc-4.5.1-nolibc/or32-linux/.'
- looking in '/home/sjg/.buildman-toolchains/gcc-4.5.1-nolibc/or32-linux/bin'
- found '/home/sjg/.buildman-toolchains/gcc-4.5.1-nolibc/or32-linux/bin/or32-linux-gcc'
Tool chain test: OK
Buildman should now be set up to use your new toolchain.
At the time of writing, U-Boot has these architectures:
arc, arm, avr32, blackfin, m68k, microblaze, mips, nds32, nios2, openrisc
powerpc, sandbox, sh, sparc, x86
Of these, only arc, microblaze and nds32 are not available at kernel.org..
How to run it
=============
@ -310,8 +360,9 @@ branch with a valid upstream)
$ ./tools/buildman/buildman -b <branch> -n
If it can't detect the upstream branch, try checking out the branch, and
doing something like 'git branch --set-upstream <branch> upstream/master'
or something similar.
doing something like 'git branch --set-upstream-to upstream/master'
or something similar. Buildman will try to guess a suitable upstream branch
if it can't find one (you will see a message like" Guessing upstream as ...).
As an example:
@ -665,28 +716,62 @@ It is common when refactoring code for the rodata to decrease as the text size
increases, and vice versa.
Providing 'make' flags
======================
The .buildman file
==================
U-Boot's build system supports a few flags (such as BUILD_TAG) which affect
the build product. These flags can be specified in the buildman settings
file. They can also be useful when building U-Boot against other open source
software.
The .buildman file provides information about the available toolchains and
also allows build flags to be passed to 'make'. It consists of several
sections, with the section name in square brackets. Within each section are
a set of (tag, value) pairs.
[make-flags]
at91-boards=ENABLE_AT91_TEST=1
snapper9260=${at91-boards} BUILD_TAG=442
snapper9g45=${at91-boards} BUILD_TAG=443
'[toolchain]' section
This will use 'make ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
and 'make ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45. A special
variable ${target} is available to access the target name (snapper9260 and
snapper9g20 in this case). Variables are resolved recursively. Note that
variables can only contain the characters A-Z, a-z, 0-9, hyphen (-) and
underscore (_).
This lists the available toolchains. The tag here doesn't matter, but
make sure it is unique. The value is the path to the toolchain. Buildman
will look in that path for a file ending in 'gcc'. It will then execute
it to check that it is a C compiler, passing only the --version flag to
it. If the return code is 0, buildman assumes that it is a valid C
compiler. It uses the first part of the name as the architecture and
strips off the last part when setting the CROSS_COMPILE environment
variable (parts are delimited with a hyphen).
It is expected that any variables added are dealt with in U-Boot's
config.mk file and documented in the README.
For example powerpc-linux-gcc will be noted as a toolchain for 'powerpc'
and CROSS_COMPILE will be set to powerpc-linux- when using it.
'[toolchain-alias]' section
This converts toolchain architecture names to U-Boot names. For example,
if an x86 toolchains is called i386-linux-gcc it will not normally be
used for architecture 'x86'. Adding 'x86: i386 x86_64' to this section
will tell buildman that the i386 and x86_64 toolchains can be used for
the x86 architecture.
'[make-flags]' section
U-Boot's build system supports a few flags (such as BUILD_TAG) which
affect the build product. These flags can be specified in the buildman
settings file. They can also be useful when building U-Boot against other
open source software.
[make-flags]
at91-boards=ENABLE_AT91_TEST=1
snapper9260=${at91-boards} BUILD_TAG=442
snapper9g45=${at91-boards} BUILD_TAG=443
This will use 'make ENABLE_AT91_TEST=1 BUILD_TAG=442' for snapper9260
and 'make ENABLE_AT91_TEST=1 BUILD_TAG=443' for snapper9g45. A special
variable ${target} is available to access the target name (snapper9260
and snapper9g20 in this case). Variables are resolved recursively. Note
that variables can only contain the characters A-Z, a-z, 0-9, hyphen (-)
and underscore (_).
It is expected that any variables added are dealt with in U-Boot's
config.mk file and documented in the README.
Note that you can pass ad-hoc options to the build using environment
variables, for example:
SOME_OPTION=1234 ./tools/buildman/buildman my_board
Quick Sanity Check
@ -698,6 +783,17 @@ build the selected boards and display build status as it runs (i.e. -v is
enabled automatically). Use -e to see errors/warnings as well.
Building Ranges
===============
You can build a range of commits by specifying a range instead of a branch
when using the -b flag. For example:
upstream/master..us-buildman
will build commits in us-buildman that are not in upstream/master.
Other options
=============

View File

@ -40,7 +40,16 @@ def GetItems(section):
try:
return settings.items(section)
except ConfigParser.NoSectionError as e:
print e
return []
except:
raise
def SetItem(section, tag, value):
"""Set an item and write it back to the settings file"""
global settings
global config_fname
settings.set(section, tag, value)
if config_fname is not None:
with open(config_fname, 'w') as fd:
settings.write(fd)

View File

@ -174,7 +174,8 @@ class Builder:
self.func_sizes = func_sizes
def __init__(self, toolchains, base_dir, git_dir, num_threads, num_jobs,
gnu_make='make', checkout=True, show_unknown=True, step=1):
gnu_make='make', checkout=True, show_unknown=True, step=1,
no_subdirs=False, full_path=False, verbose_build=False):
"""Create a new Builder object
Args:
@ -188,6 +189,11 @@ class Builder:
This is used for testing.
show_unknown: Show unknown boards (those not built) in summary
step: 1 to process every commit, n to process every nth commit
no_subdirs: Don't create subdirectories when building current
source for a single board
full_path: Return the full path in CROSS_COMPILE and don't set
PATH
verbose_build: Run build with V=1 and don't use 'make -s'
"""
self.toolchains = toolchains
self.base_dir = base_dir
@ -213,6 +219,9 @@ class Builder:
self._step = step
self.in_tree = False
self._error_lines = 0
self.no_subdirs = no_subdirs
self.full_path = full_path
self.verbose_build = verbose_build
self.col = terminal.Color()
@ -392,15 +401,17 @@ class Builder:
Args:
commit_upto: Commit number to use (0..self.count-1)
"""
commit_dir = None
if self.commits:
commit = self.commits[commit_upto]
subject = commit.subject.translate(trans_valid_chars)
commit_dir = ('%02d_of_%02d_g%s_%s' % (commit_upto + 1,
self.commit_count, commit.hash, subject[:20]))
else:
elif not self.no_subdirs:
commit_dir = 'current'
output_dir = os.path.join(self.base_dir, commit_dir)
return output_dir
if not commit_dir:
return self.base_dir
return os.path.join(self.base_dir, commit_dir)
def GetBuildDir(self, commit_upto, target):
"""Get the name of the build directory for a commit number
@ -1115,6 +1126,8 @@ class Builder:
create. Having left over directories is confusing when the user wants
to check the output manually.
"""
if not self.commits:
return
dir_list = []
for commit_upto in range(self.commit_count):
dir_list.append(self._GetOutputDir(commit_upto))

View File

@ -177,7 +177,7 @@ class BuilderThread(threading.Thread):
commit = 'current'
# Set up the environment and command line
env = self.toolchain.MakeEnvironment()
env = self.toolchain.MakeEnvironment(self.builder.full_path)
Mkdir(out_dir)
args = []
cwd = work_dir
@ -197,7 +197,8 @@ class BuilderThread(threading.Thread):
src_dir = os.getcwd()
else:
args.append('O=build')
args.append('-s')
if not self.builder.verbose_build:
args.append('-s')
if self.builder.num_jobs is not None:
args.extend(['-j', str(self.builder.num_jobs)])
config_args = ['%s_defconfig' % brd.target]
@ -284,7 +285,7 @@ class BuilderThread(threading.Thread):
print >>fd, 'path', result.toolchain.path
# Write out the image and function size information and an objdump
env = result.toolchain.MakeEnvironment()
env = result.toolchain.MakeEnvironment(self.builder.full_path)
lines = []
for fname in ['u-boot', 'spl/u-boot-spl']:
cmd = ['%snm' % self.toolchain.cross, '--size-sort', fname]

View File

@ -36,6 +36,10 @@ def ParseArgs():
parser.add_option('-F', '--force-build-failures', dest='force_build_failures',
action='store_true', default=False,
help='Force build of previously-failed build')
parser.add_option('--fetch-arch', type='string',
help="Fetch a toolchain for architecture FETCH_ARCH ('list' to list)."
' You can also fetch several toolchains separate by comma, or'
" 'all' to download all")
parser.add_option('-g', '--git', type='string',
help='Git repo containing branch to build', default='.')
parser.add_option('-G', '--config-file', type='string',
@ -55,11 +59,15 @@ def ParseArgs():
help='List available tool chains')
parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
default=False, help="Do a dry run (describe actions, but do nothing)")
parser.add_option('-N', '--no-subdirs', action='store_true', dest='no_subdirs',
default=False, help="Don't create subdirectories when building current source for a single board")
parser.add_option('-o', '--output-dir', type='string',
dest='output_dir', default='..',
help='Directory where all builds happen and buildman has its workspace (default is ../)')
parser.add_option('-Q', '--quick', action='store_true',
default=False, help='Do a rough build, with limited warning resolution')
parser.add_option('-p', '--full-path', action='store_true',
default=False, help="Use full toolchain path in CROSS_COMPILE")
parser.add_option('-s', '--summary', action='store_true',
default=False, help='Show a build summary')
parser.add_option('-S', '--show-sizes', action='store_true',
@ -74,6 +82,8 @@ def ParseArgs():
default=False, help='Show boards with unknown build result')
parser.add_option('-v', '--verbose', action='store_true',
default=False, help='Show build results while the build progresses')
parser.add_option('-V', '--verbose-build', action='store_true',
default=False, help='Run make with V=1, showing all output')
parser.add_option('-x', '--exclude', dest='exclude',
type='string', action='append',
help='Specify a list of boards to exclude, separated by comma')

View File

@ -118,21 +118,45 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
print
return 0
if options.fetch_arch:
if options.fetch_arch == 'list':
sorted_list = toolchains.ListArchs()
print 'Available architectures: %s\n' % ' '.join(sorted_list)
return 0
else:
fetch_arch = options.fetch_arch
if fetch_arch == 'all':
fetch_arch = ','.join(toolchains.ListArchs())
print 'Downloading toolchains: %s\n' % fetch_arch
for arch in fetch_arch.split(','):
ret = toolchains.FetchAndInstall(arch)
if ret:
return ret
return 0
# Work out how many commits to build. We want to build everything on the
# branch. We also build the upstream commit as a control so we can see
# problems introduced by the first commit on the branch.
col = terminal.Color()
count = options.count
has_range = options.branch and '..' in options.branch
if count == -1:
if not options.branch:
count = 1
else:
count = gitutil.CountCommitsInBranch(options.git_dir,
options.branch)
if has_range:
count, msg = gitutil.CountCommitsInRange(options.git_dir,
options.branch)
else:
count, msg = gitutil.CountCommitsInBranch(options.git_dir,
options.branch)
if count is None:
str = ("Branch '%s' not found or has no upstream" %
options.branch)
sys.exit(col.Color(col.RED, str))
sys.exit(col.Color(col.RED, msg))
elif count == 0:
sys.exit(col.Color(col.RED, "Range '%s' has no commits" %
options.branch))
if msg:
print col.Color(col.YELLOW, msg)
count += 1 # Build upstream commit also
if not count:
@ -172,8 +196,11 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
# to overwrite earlier ones by setting allow_overwrite=True
if options.branch:
if count == -1:
range_expr = gitutil.GetRangeInBranch(options.git_dir,
options.branch)
if has_range:
range_expr = options.branch
else:
range_expr = gitutil.GetRangeInBranch(options.git_dir,
options.branch)
upstream_commit = gitutil.GetUpstream(options.git_dir,
options.branch)
series = patchstream.GetMetaDataForList(upstream_commit,
@ -207,17 +234,22 @@ def DoBuildman(options, args, toolchains=None, make_func=None, boards=None,
if not gnu_make:
sys.exit('GNU Make not found')
# Create a new builder with the selected options
# Create a new builder with the selected options.
output_dir = options.output_dir
if options.branch:
dirname = options.branch.replace('/', '_')
else:
dirname = 'current'
output_dir = os.path.join(options.output_dir, dirname)
if clean_dir and os.path.exists(output_dir):
# As a special case allow the board directory to be placed in the
# output directory itself rather than any subdirectory.
if not options.no_subdirs:
output_dir = os.path.join(options.output_dir, dirname)
if (clean_dir and output_dir != options.output_dir and
os.path.exists(output_dir)):
shutil.rmtree(output_dir)
builder = Builder(toolchains, output_dir, options.git_dir,
options.threads, options.jobs, gnu_make=gnu_make, checkout=True,
show_unknown=options.show_unknown, step=options.step)
show_unknown=options.show_unknown, step=options.step,
no_subdirs=options.no_subdirs, full_path=options.full_path,
verbose_build=options.verbose_build)
builder.force_config_on_failure = not options.quick
if make_func:
builder.do_make = make_func

View File

@ -24,6 +24,16 @@ import commit
import terminal
import toolchain
settings_data = '''
# Buildman settings file
[toolchain]
main: /usr/sbin
[toolchain-alias]
x86: i386 x86_64
'''
errors = [
'''main.c: In function 'main_loop':
main.c:260:6: warning: unused variable 'joe' [-Wunused-variable]
@ -83,6 +93,8 @@ boards = [
['Active', 'sandbox', 'sandbox', '', 'Tester', 'Sandbox board', 'board4', ''],
]
BASE_DIR = 'base'
class Options:
"""Class that holds build options"""
pass
@ -111,8 +123,11 @@ class TestBuild(unittest.TestCase):
self.boards.AddBoard(board.Board(*brd))
self.boards.SelectBoards([])
# Add some test settings
bsettings.Setup(None)
bsettings.AddFile(settings_data)
# Set up the toolchains
bsettings.Setup()
self.toolchains = toolchain.Toolchains()
self.toolchains.Add('arm-linux-gcc', test=False)
self.toolchains.Add('sparc-linux-gcc', test=False)
@ -341,6 +356,64 @@ class TestBuild(unittest.TestCase):
self.assertEqual(self.boards.SelectBoards(['sandbox sandbox',
'sandbox']),
{'all': 1, 'sandbox': 1})
def CheckDirs(self, build, dirname):
self.assertEqual('base%s' % dirname, build._GetOutputDir(1))
self.assertEqual('base%s/fred' % dirname,
build.GetBuildDir(1, 'fred'))
self.assertEqual('base%s/fred/done' % dirname,
build.GetDoneFile(1, 'fred'))
self.assertEqual('base%s/fred/u-boot.sizes' % dirname,
build.GetFuncSizesFile(1, 'fred', 'u-boot'))
self.assertEqual('base%s/fred/u-boot.objdump' % dirname,
build.GetObjdumpFile(1, 'fred', 'u-boot'))
self.assertEqual('base%s/fred/err' % dirname,
build.GetErrFile(1, 'fred'))
def testOutputDir(self):
build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
checkout=False, show_unknown=False)
build.commits = self.commits
build.commit_count = len(self.commits)
subject = self.commits[1].subject.translate(builder.trans_valid_chars)
dirname ='/%02d_of_%02d_g%s_%s' % (2, build.commit_count, commits[1][0],
subject[:20])
self.CheckDirs(build, dirname)
def testOutputDirCurrent(self):
build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
checkout=False, show_unknown=False)
build.commits = None
build.commit_count = 0
self.CheckDirs(build, '/current')
def testOutputDirNoSubdirs(self):
build = builder.Builder(self.toolchains, BASE_DIR, None, 1, 2,
checkout=False, show_unknown=False,
no_subdirs=True)
build.commits = None
build.commit_count = 0
self.CheckDirs(build, '')
def testToolchainAliases(self):
self.assertTrue(self.toolchains.Select('arm') != None)
with self.assertRaises(ValueError):
self.toolchains.Select('no-arch')
with self.assertRaises(ValueError):
self.toolchains.Select('x86')
self.toolchains = toolchain.Toolchains()
self.toolchains.Add('x86_64-linux-gcc', test=False)
self.assertTrue(self.toolchains.Select('x86') != None)
self.toolchains = toolchain.Toolchains()
self.toolchains.Add('i386-linux-gcc', test=False)
self.assertTrue(self.toolchains.Select('x86') != None)
def testToolchainDownload(self):
"""Test that we can download toolchains"""
self.assertEqual('https://www.kernel.org/pub/tools/crosstool/files/bin/x86_64/4.6.3/x86_64-gcc-4.6.3-nolibc_arm-unknown-linux-gnueabi.tar.xz',
self.toolchains.LocateArchUrl('arm'))
if __name__ == "__main__":
unittest.main()

View File

@ -5,11 +5,42 @@
import re
import glob
from HTMLParser import HTMLParser
import os
import sys
import tempfile
import urllib2
import bsettings
import command
# Simple class to collect links from a page
class MyHTMLParser(HTMLParser):
def __init__(self, arch):
"""Create a new parser
After the parser runs, self.links will be set to a list of the links
to .xz archives found in the page, and self.arch_link will be set to
the one for the given architecture (or None if not found).
Args:
arch: Architecture to search for
"""
HTMLParser.__init__(self)
self.arch_link = None
self.links = []
self._match = '_%s-' % arch
def handle_starttag(self, tag, attrs):
if tag == 'a':
for tag, value in attrs:
if tag == 'href':
if value and value.endswith('.xz'):
self.links.append(value)
if self._match in value:
self.arch_link = value
class Toolchain:
"""A single toolchain
@ -20,7 +51,6 @@ class Toolchain:
arch: Architecture of toolchain as determined from the first
component of the filename. E.g. arm-linux-gcc becomes arm
"""
def __init__(self, fname, test, verbose=False):
"""Create a new toolchain object.
@ -30,11 +60,18 @@ class Toolchain:
"""
self.gcc = fname
self.path = os.path.dirname(fname)
self.cross = os.path.basename(fname)[:-3]
# Find the CROSS_COMPILE prefix to use for U-Boot. For example,
# 'arm-linux-gnueabihf-gcc' turns into 'arm-linux-gnueabihf-'.
basename = os.path.basename(fname)
pos = basename.rfind('-')
self.cross = basename[:pos + 1] if pos != -1 else ''
# The architecture is the first part of the name
pos = self.cross.find('-')
self.arch = self.cross[:pos] if pos != -1 else 'sandbox'
env = self.MakeEnvironment()
env = self.MakeEnvironment(False)
# As a basic sanity check, run the C compiler with --version
cmd = [fname, '--version']
@ -74,15 +111,23 @@ class Toolchain:
return prio
return prio
def MakeEnvironment(self):
def MakeEnvironment(self, full_path):
"""Returns an environment for using the toolchain.
Thie takes the current environment, adds CROSS_COMPILE and
augments PATH so that the toolchain will operate correctly.
Thie takes the current environment and adds CROSS_COMPILE so that
the tool chain will operate correctly.
Args:
full_path: Return the full path in CROSS_COMPILE and don't set
PATH
"""
env = dict(os.environ)
env['CROSS_COMPILE'] = self.cross
env['PATH'] += (':' + self.path)
if full_path:
env['CROSS_COMPILE'] = os.path.join(self.path, self.cross)
else:
env['CROSS_COMPILE'] = self.cross
env['PATH'] = self.path + ':' + env['PATH']
return env
@ -101,18 +146,29 @@ class Toolchains:
self.paths = []
self._make_flags = dict(bsettings.GetItems('make-flags'))
def GetSettings(self):
def GetPathList(self):
"""Get a list of available toolchain paths
Returns:
List of strings, each a path to a toolchain mentioned in the
[toolchain] section of the settings file.
"""
toolchains = bsettings.GetItems('toolchain')
if not toolchains:
print ("Warning: No tool chains - please add a [toolchain] section"
" to your buildman config file %s. See README for details" %
bsettings.config_fname)
paths = []
for name, value in toolchains:
if '*' in value:
self.paths += glob.glob(value)
paths += glob.glob(value)
else:
self.paths.append(value)
paths.append(value)
return paths
def GetSettings(self):
self.paths += self.GetPathList()
def Add(self, fname, test=True, verbose=False):
"""Add a toolchain to our list
@ -132,6 +188,24 @@ class Toolchains:
if add_it:
self.toolchains[toolchain.arch] = toolchain
def ScanPath(self, path, verbose):
"""Scan a path for a valid toolchain
Args:
path: Path to scan
verbose: True to print out progress information
Returns:
Filename of C compiler if found, else None
"""
for subdir in ['.', 'bin', 'usr/bin']:
dirname = os.path.join(path, subdir)
if verbose: print " - looking in '%s'" % dirname
for fname in glob.glob(dirname + '/*gcc'):
if verbose: print " - found '%s'" % fname
return fname
return None
def Scan(self, verbose):
"""Scan for available toolchains and select the best for each arch.
@ -145,12 +219,9 @@ class Toolchains:
if verbose: print 'Scanning for tool chains'
for path in self.paths:
if verbose: print " - scanning path '%s'" % path
for subdir in ['.', 'bin', 'usr/bin']:
dirname = os.path.join(path, subdir)
if verbose: print " - looking in '%s'" % dirname
for fname in glob.glob(dirname + '/*gcc'):
if verbose: print " - found '%s'" % fname
self.Add(fname, True, verbose)
fname = self.ScanPath(path, verbose)
if fname:
self.Add(fname, True, verbose)
def List(self):
"""List out the selected toolchains for each architecture"""
@ -170,9 +241,11 @@ class Toolchains:
returns:
toolchain object, or None if none found
"""
for name, value in bsettings.GetItems('toolchain-alias'):
if arch == name:
arch = value
for tag, value in bsettings.GetItems('toolchain-alias'):
if arch == tag:
for alias in value.split():
if alias in self.toolchains:
return self.toolchains[alias]
if not arch in self.toolchains:
raise ValueError, ("No tool chain found for arch '%s'" % arch)
@ -247,3 +320,160 @@ class Toolchains:
else:
i += 1
return args
def LocateArchUrl(self, fetch_arch):
"""Find a toolchain available online
Look in standard places for available toolchains. At present the
only standard place is at kernel.org.
Args:
arch: Architecture to look for, or 'list' for all
Returns:
If fetch_arch is 'list', a tuple:
Machine architecture (e.g. x86_64)
List of toolchains
else
URL containing this toolchain, if avaialble, else None
"""
arch = command.OutputOneLine('uname', '-m')
base = 'https://www.kernel.org/pub/tools/crosstool/files/bin'
versions = ['4.6.3', '4.6.2', '4.5.1', '4.2.4']
links = []
for version in versions:
url = '%s/%s/%s/' % (base, arch, version)
print 'Checking: %s' % url
response = urllib2.urlopen(url)
html = response.read()
parser = MyHTMLParser(fetch_arch)
parser.feed(html)
if fetch_arch == 'list':
links += parser.links
elif parser.arch_link:
return url + parser.arch_link
if fetch_arch == 'list':
return arch, links
return None
def Download(self, url):
"""Download a file to a temporary directory
Args:
url: URL to download
Returns:
Tuple:
Temporary directory name
Full path to the downloaded archive file in that directory,
or None if there was an error while downloading
"""
print "Downloading: %s" % url
leaf = url.split('/')[-1]
tmpdir = tempfile.mkdtemp('.buildman')
response = urllib2.urlopen(url)
fname = os.path.join(tmpdir, leaf)
fd = open(fname, 'wb')
meta = response.info()
size = int(meta.getheaders("Content-Length")[0])
done = 0
block_size = 1 << 16
status = ''
# Read the file in chunks and show progress as we go
while True:
buffer = response.read(block_size)
if not buffer:
print chr(8) * (len(status) + 1), '\r',
break
done += len(buffer)
fd.write(buffer)
status = r"%10d MiB [%3d%%]" % (done / 1024 / 1024,
done * 100 / size)
status = status + chr(8) * (len(status) + 1)
print status,
sys.stdout.flush()
fd.close()
if done != size:
print 'Error, failed to download'
os.remove(fname)
fname = None
return tmpdir, fname
def Unpack(self, fname, dest):
"""Unpack a tar file
Args:
fname: Filename to unpack
dest: Destination directory
Returns:
Directory name of the first entry in the archive, without the
trailing /
"""
stdout = command.Output('tar', 'xvfJ', fname, '-C', dest)
return stdout.splitlines()[0][:-1]
def TestSettingsHasPath(self, path):
"""Check if builmand will find this toolchain
Returns:
True if the path is in settings, False if not
"""
paths = self.GetPathList()
return path in paths
def ListArchs(self):
"""List architectures with available toolchains to download"""
host_arch, archives = self.LocateArchUrl('list')
re_arch = re.compile('[-a-z0-9.]*_([^-]*)-.*')
arch_set = set()
for archive in archives:
# Remove the host architecture from the start
arch = re_arch.match(archive[len(host_arch):])
if arch:
arch_set.add(arch.group(1))
return sorted(arch_set)
def FetchAndInstall(self, arch):
"""Fetch and install a new toolchain
arch:
Architecture to fetch, or 'list' to list
"""
# Fist get the URL for this architecture
url = self.LocateArchUrl(arch)
if not url:
print ("Cannot find toolchain for arch '%s' - use 'list' to list" %
arch)
return 2
home = os.environ['HOME']
dest = os.path.join(home, '.buildman-toolchains')
if not os.path.exists(dest):
os.mkdir(dest)
# Download the tar file for this toolchain and unpack it
tmpdir, tarfile = self.Download(url)
if not tarfile:
return 1
print 'Unpacking to: %s' % dest,
sys.stdout.flush()
path = self.Unpack(tarfile, dest)
os.remove(tarfile)
os.rmdir(tmpdir)
print
# Check that the toolchain works
print 'Testing'
dirpath = os.path.join(dest, path)
compiler_fname = self.ScanPath(dirpath, True)
if not compiler_fname:
print 'Could not locate C compiler - fetch failed.'
return 1
toolchain = Toolchain(compiler_fname, True, True)
# Make sure that it will be found by buildman
if not self.TestSettingsHasPath(dirpath):
print ("Adding 'download' to config file '%s'" %
bsettings.config_fname)
tools_dir = os.path.dirname(dirpath)
bsettings.SetItem('toolchain', 'download', '%s/*' % tools_dir)
return 0

View File

@ -61,6 +61,52 @@ def CountCommitsToBranch():
patch_count = int(stdout)
return patch_count
def NameRevision(commit_hash):
"""Gets the revision name for a commit
Args:
commit_hash: Commit hash to look up
Return:
Name of revision, if any, else None
"""
pipe = ['git', 'name-rev', commit_hash]
stdout = command.RunPipe([pipe], capture=True, oneline=True).stdout
# We expect a commit, a space, then a revision name
name = stdout.split(' ')[1].strip()
return name
def GuessUpstream(git_dir, branch):
"""Tries to guess the upstream for a branch
This lists out top commits on a branch and tries to find a suitable
upstream. It does this by looking for the first commit where
'git name-rev' returns a plain branch name, with no ! or ^ modifiers.
Args:
git_dir: Git directory containing repo
branch: Name of branch
Returns:
Tuple:
Name of upstream branch (e.g. 'upstream/master') or None if none
Warning/error message, or None if none
"""
pipe = [LogCmd(branch, git_dir=git_dir, oneline=True, count=100)]
result = command.RunPipe(pipe, capture=True, capture_stderr=True,
raise_on_error=False)
if result.return_code:
return None, "Branch '%s' not found" % branch
for line in result.stdout.splitlines()[1:]:
commit_hash = line.split(' ')[0]
name = NameRevision(commit_hash)
if '~' not in name and '^' not in name:
if name.startswith('remotes/'):
name = name[8:]
return name, "Guessing upstream as '%s'" % name
return None, "Cannot find a suitable upstream for branch '%s'" % branch
def GetUpstream(git_dir, branch):
"""Returns the name of the upstream for a branch
@ -69,7 +115,9 @@ def GetUpstream(git_dir, branch):
branch: Name of branch
Returns:
Name of upstream branch (e.g. 'upstream/master') or None if none
Tuple:
Name of upstream branch (e.g. 'upstream/master') or None if none
Warning/error message, or None if none
"""
try:
remote = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
@ -77,13 +125,14 @@ def GetUpstream(git_dir, branch):
merge = command.OutputOneLine('git', '--git-dir', git_dir, 'config',
'branch.%s.merge' % branch)
except:
return None
upstream, msg = GuessUpstream(git_dir, branch)
return upstream, msg
if remote == '.':
return merge
elif remote and merge:
leaf = merge.split('/')[-1]
return '%s/%s' % (remote, leaf)
return '%s/%s' % (remote, leaf), None
else:
raise ValueError, ("Cannot determine upstream branch for branch "
"'%s' remote='%s', merge='%s'" % (branch, remote, merge))
@ -99,10 +148,29 @@ def GetRangeInBranch(git_dir, branch, include_upstream=False):
Expression in the form 'upstream..branch' which can be used to
access the commits. If the branch does not exist, returns None.
"""
upstream = GetUpstream(git_dir, branch)
upstream, msg = GetUpstream(git_dir, branch)
if not upstream:
return None
return '%s%s..%s' % (upstream, '~' if include_upstream else '', branch)
return None, msg
rstr = '%s%s..%s' % (upstream, '~' if include_upstream else '', branch)
return rstr, msg
def CountCommitsInRange(git_dir, range_expr):
"""Returns the number of commits in the given range.
Args:
git_dir: Directory containing git repo
range_expr: Range to check
Return:
Number of patches that exist in the supplied rangem or None if none
were found
"""
pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True)]
result = command.RunPipe(pipe, capture=True, capture_stderr=True,
raise_on_error=False)
if result.return_code:
return None, "Range '%s' not found or is invalid" % range_expr
patch_count = len(result.stdout.splitlines())
return patch_count, None
def CountCommitsInBranch(git_dir, branch, include_upstream=False):
"""Returns the number of commits in the given branch.
@ -114,14 +182,10 @@ def CountCommitsInBranch(git_dir, branch, include_upstream=False):
Number of patches that exist on top of the branch, or None if the
branch does not exist.
"""
range_expr = GetRangeInBranch(git_dir, branch, include_upstream)
range_expr, msg = GetRangeInBranch(git_dir, branch, include_upstream)
if not range_expr:
return None
pipe = [LogCmd(range_expr, git_dir=git_dir, oneline=True),
['wc', '-l']]
result = command.RunPipe(pipe, capture=True, oneline=True)
patch_count = int(result.stdout)
return patch_count
return None, msg
return CountCommitsInRange(git_dir, range_expr)
def CountCommits(commit_range):
"""Returns the number of commits in the given range.