[zorg] r249757 - llvmlab bisect tool

Vassil Vassilev via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 16 03:10:15 PDT 2016


On 08/10/15 23:52, Chris Matthews via llvm-commits wrote:
> Author: cmatthews
> Date: Thu Oct  8 16:52:50 2015
> New Revision: 249757
>
> URL: http://llvm.org/viewvc/llvm-project?rev=249757&view=rev
> Log:
> llvmlab bisect tool
>
> Added:
>      zorg/trunk/llvmbisect/
>      zorg/trunk/llvmbisect/bin/
>      zorg/trunk/llvmbisect/bin/llvmlab   (with props)
>      zorg/trunk/llvmbisect/docs/
>      zorg/trunk/llvmbisect/docs/Makefile
>      zorg/trunk/llvmbisect/docs/builders.rst
>      zorg/trunk/llvmbisect/docs/conf.py
>      zorg/trunk/llvmbisect/docs/index.rst
>      zorg/trunk/llvmbisect/docs/llvmlab_bisect.rst
>      zorg/trunk/llvmbisect/llvmlab/
>      zorg/trunk/llvmbisect/llvmlab/__init__.py
>      zorg/trunk/llvmbisect/llvmlab/algorithm.py
>      zorg/trunk/llvmbisect/llvmlab/ci.py
>      zorg/trunk/llvmbisect/llvmlab/clang_link   (with props)
>      zorg/trunk/llvmbisect/llvmlab/gcs.py
>      zorg/trunk/llvmbisect/llvmlab/llvmlab.py
>      zorg/trunk/llvmbisect/llvmlab/scripts.py
>      zorg/trunk/llvmbisect/llvmlab/shell.py
>      zorg/trunk/llvmbisect/llvmlab/test_llvmlab.py
>      zorg/trunk/llvmbisect/llvmlab/util.py
>      zorg/trunk/llvmbisect/setup.py
>
> Added: zorg/trunk/llvmbisect/bin/llvmlab
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/bin/llvmlab?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/bin/llvmlab (added)
> +++ zorg/trunk/llvmbisect/bin/llvmlab Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,27 @@
> +#!/usr/bin/env python
> +
> +import sys
> +import errno
> +
> +from llvmlab.ci import action_fetch, action_ls, action_bisect, action_exec
> +from llvmlab.ci import action_test
> +from llvmlab import scripts
> +
> +
> +tool = scripts.Tool(locals())
> +main = tool.main
> +
> +if __name__ == '__main__':
> +    rc = None
> +    # Execute the main function in a try block to catch EPIPE exceptions.
> +    try:
> +        rc = main(sys.argv)
> +
> +        # Force a flush on the output pipe to ensure EPIPE shows up here (prior
> +        # to sys.stdout shutdown).
> +        sys.stdout.flush()
> +        sys.stderr.flush()
> +    except IOError as e:
> +        if e.errno != errno.EPIPE:
> +            raise
> +    sys.exit(0)
>
> Propchange: zorg/trunk/llvmbisect/bin/llvmlab
> ------------------------------------------------------------------------------
>      svn:executable = *
>
> Added: zorg/trunk/llvmbisect/docs/Makefile
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/docs/Makefile?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/docs/Makefile (added)
> +++ zorg/trunk/llvmbisect/docs/Makefile Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,177 @@
> +# Makefile for Sphinx documentation
> +#
> +
> +# You can set these variables from the command line.
> +SPHINXOPTS    =
> +SPHINXBUILD   = sphinx-build
> +PAPER         =
> +BUILDDIR      = _build
> +
> +# User-friendly check for sphinx-build
> +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
> +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
> +endif
> +
> +# Internal variables.
> +PAPEROPT_a4     = -D latex_paper_size=a4
> +PAPEROPT_letter = -D latex_paper_size=letter
> +ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
> +# the i18n builder cannot share the environment and doctrees with the others
> +I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
> +
> +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
> +
> +help:
> +	@echo "Please use \`make <target>' where <target> is one of"
> +	@echo "  html       to make standalone HTML files"
> +	@echo "  dirhtml    to make HTML files named index.html in directories"
> +	@echo "  singlehtml to make a single large HTML file"
> +	@echo "  pickle     to make pickle files"
> +	@echo "  json       to make JSON files"
> +	@echo "  htmlhelp   to make HTML files and a HTML help project"
> +	@echo "  qthelp     to make HTML files and a qthelp project"
> +	@echo "  devhelp    to make HTML files and a Devhelp project"
> +	@echo "  epub       to make an epub"
> +	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
> +	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
> +	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
> +	@echo "  text       to make text files"
> +	@echo "  man        to make manual pages"
> +	@echo "  texinfo    to make Texinfo files"
> +	@echo "  info       to make Texinfo files and run them through makeinfo"
> +	@echo "  gettext    to make PO message catalogs"
> +	@echo "  changes    to make an overview of all changed/added/deprecated items"
> +	@echo "  xml        to make Docutils-native XML files"
> +	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
> +	@echo "  linkcheck  to check all external links for integrity"
> +	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
> +
> +clean:
> +	rm -rf $(BUILDDIR)/*
> +
> +html:
> +	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
> +	@echo
> +	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
> +
> +dirhtml:
> +	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
> +	@echo
> +	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
> +
> +singlehtml:
> +	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
> +	@echo
> +	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
> +
> +pickle:
> +	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
> +	@echo
> +	@echo "Build finished; now you can process the pickle files."
> +
> +json:
> +	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
> +	@echo
> +	@echo "Build finished; now you can process the JSON files."
> +
> +htmlhelp:
> +	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
> +	@echo
> +	@echo "Build finished; now you can run HTML Help Workshop with the" \
> +	      ".hhp project file in $(BUILDDIR)/htmlhelp."
> +
> +qthelp:
> +	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
> +	@echo
> +	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
> +	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
> +	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/LLVMLabTools.qhcp"
> +	@echo "To view the help file:"
> +	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/LLVMLabTools.qhc"
> +
> +devhelp:
> +	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
> +	@echo
> +	@echo "Build finished."
> +	@echo "To view the help file:"
> +	@echo "# mkdir -p $$HOME/.local/share/devhelp/LLVMLabTools"
> +	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/LLVMLabTools"
> +	@echo "# devhelp"
> +
> +epub:
> +	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
> +	@echo
> +	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
> +
> +latex:
> +	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
> +	@echo
> +	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
> +	@echo "Run \`make' in that directory to run these through (pdf)latex" \
> +	      "(use \`make latexpdf' here to do that automatically)."
> +
> +latexpdf:
> +	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
> +	@echo "Running LaTeX files through pdflatex..."
> +	$(MAKE) -C $(BUILDDIR)/latex all-pdf
> +	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
> +
> +latexpdfja:
> +	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
> +	@echo "Running LaTeX files through platex and dvipdfmx..."
> +	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
> +	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
> +
> +text:
> +	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
> +	@echo
> +	@echo "Build finished. The text files are in $(BUILDDIR)/text."
> +
> +man:
> +	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
> +	@echo
> +	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
> +
> +texinfo:
> +	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
> +	@echo
> +	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
> +	@echo "Run \`make' in that directory to run these through makeinfo" \
> +	      "(use \`make info' here to do that automatically)."
> +
> +info:
> +	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
> +	@echo "Running Texinfo files through makeinfo..."
> +	make -C $(BUILDDIR)/texinfo info
> +	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
> +
> +gettext:
> +	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
> +	@echo
> +	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
> +
> +changes:
> +	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
> +	@echo
> +	@echo "The overview file is in $(BUILDDIR)/changes."
> +
> +linkcheck:
> +	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
> +	@echo
> +	@echo "Link check complete; look for any errors in the above output " \
> +	      "or in $(BUILDDIR)/linkcheck/output.txt."
> +
> +doctest:
> +	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
> +	@echo "Testing of doctests in the sources finished, look at the " \
> +	      "results in $(BUILDDIR)/doctest/output.txt."
> +
> +xml:
> +	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
> +	@echo
> +	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
> +
> +pseudoxml:
> +	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
> +	@echo
> +	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
>
> Added: zorg/trunk/llvmbisect/docs/builders.rst
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/docs/builders.rst?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/docs/builders.rst (added)
> +++ zorg/trunk/llvmbisect/docs/builders.rst Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,19 @@
> +.. _builders:
> +
> +Adding your builder to llvmlab bisect
> +=====================================
> +
> +llvmlab bisect compilers are stored on Google Cloud Storage.  There is a common
> +bucket called llvm-build-artifacts, within that there is a directory for each
> +build.  Builds can be uploaded in two ways, with authorized credentials with
> +the gsutil tool, or if the builder is in lab.llvm.org, from the labmaster2
> +stageing server.
> +
> +On the labmaster2 staging server any builds uploaded to:
> +``/Library/WebServer/Documents/artifacts/<buildername>/`` will be uploaded via
> +a cron job.  Your builders public key will need to be added to that machine.
> +Rsync or scp can be used to upload the files.
> +
> +llvmbisect uses some regexes in llvmlab.py to parse the comiler information.
> +The tar file you upload will need to match those regexes. For example:
> +``clang-r249497-t13154-b13154.tar.gz``
>
> Added: zorg/trunk/llvmbisect/docs/conf.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/docs/conf.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/docs/conf.py (added)
> +++ zorg/trunk/llvmbisect/docs/conf.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,258 @@
> +# -*- coding: utf-8 -*-
> +#
> +# LLVM Lab Tools documentation build configuration file, created by
> +# sphinx-quickstart on Tue Oct  6 18:29:53 2015.
> +#
> +# This file is execfile()d with the current directory set to its
> +# containing dir.
> +#
> +# Note that not all possible configuration values are present in this
> +# autogenerated file.
> +#
> +# All configuration values have a default; values that are commented out
> +# serve to show the default.
> +
> +import sys
> +import os
> +
> +# If extensions (or modules to document with autodoc) are in another directory,
> +# add these directories to sys.path here. If the directory is relative to the
> +# documentation root, use os.path.abspath to make it absolute, like shown here.
> +#sys.path.insert(0, os.path.abspath('.'))
> +
> +# -- General configuration ------------------------------------------------
> +
> +# If your documentation needs a minimal Sphinx version, state it here.
> +#needs_sphinx = '1.0'
> +
> +# Add any Sphinx extension module names here, as strings. They can be
> +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
> +# ones.
> +extensions = []
> +
> +# Add any paths that contain templates here, relative to this directory.
> +templates_path = ['_templates']
> +
> +# The suffix of source filenames.
> +source_suffix = '.rst'
> +
> +# The encoding of source files.
> +#source_encoding = 'utf-8-sig'
> +
> +# The master toctree document.
> +master_doc = 'index'
> +
> +# General information about the project.
> +project = u'LLVM Lab Tools'
> +copyright = u'2015, Daniel Dunbar, Chris Matthews'
> +
> +# The version info for the project you're documenting, acts as replacement for
> +# |version| and |release|, also used in various other places throughout the
> +# built documents.
> +#
> +# The short X.Y version.
> +version = '1.0'
> +# The full version, including alpha/beta/rc tags.
> +release = '1.0'
> +
> +# The language for content autogenerated by Sphinx. Refer to documentation
> +# for a list of supported languages.
> +#language = None
> +
> +# There are two options for replacing |today|: either, you set today to some
> +# non-false value, then it is used:
> +#today = ''
> +# Else, today_fmt is used as the format for a strftime call.
> +#today_fmt = '%B %d, %Y'
> +
> +# List of patterns, relative to source directory, that match files and
> +# directories to ignore when looking for source files.
> +exclude_patterns = ['_build']
> +
> +# The reST default role (used for this markup: `text`) to use for all
> +# documents.
> +#default_role = None
> +
> +# If true, '()' will be appended to :func: etc. cross-reference text.
> +#add_function_parentheses = True
> +
> +# If true, the current module name will be prepended to all description
> +# unit titles (such as .. function::).
> +#add_module_names = True
> +
> +# If true, sectionauthor and moduleauthor directives will be shown in the
> +# output. They are ignored by default.
> +#show_authors = False
> +
> +# The name of the Pygments (syntax highlighting) style to use.
> +pygments_style = 'sphinx'
> +
> +# A list of ignored prefixes for module index sorting.
> +#modindex_common_prefix = []
> +
> +# If true, keep warnings as "system message" paragraphs in the built documents.
> +#keep_warnings = False
> +
> +
> +# -- Options for HTML output ----------------------------------------------
> +
> +# The theme to use for HTML and HTML Help pages.  See the documentation for
> +# a list of builtin themes.
> +html_theme = 'default'
> +
> +# Theme options are theme-specific and customize the look and feel of a theme
> +# further.  For a list of options available for each theme, see the
> +# documentation.
> +#html_theme_options = {}
> +
> +# Add any paths that contain custom themes here, relative to this directory.
> +#html_theme_path = []
> +
> +# The name for this set of Sphinx documents.  If None, it defaults to
> +# "<project> v<release> documentation".
> +#html_title = None
> +
> +# A shorter title for the navigation bar.  Default is the same as html_title.
> +#html_short_title = None
> +
> +# The name of an image file (relative to this directory) to place at the top
> +# of the sidebar.
> +#html_logo = None
> +
> +# The name of an image file (within the static path) to use as favicon of the
> +# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
> +# pixels large.
> +#html_favicon = None
> +
> +# Add any paths that contain custom static files (such as style sheets) here,
> +# relative to this directory. They are copied after the builtin static files,
> +# so a file named "default.css" will overwrite the builtin "default.css".
> +html_static_path = ['_static']
> +
> +# Add any extra paths that contain custom files (such as robots.txt or
> +# .htaccess) here, relative to this directory. These files are copied
> +# directly to the root of the documentation.
> +#html_extra_path = []
> +
> +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
> +# using the given strftime format.
> +#html_last_updated_fmt = '%b %d, %Y'
> +
> +# If true, SmartyPants will be used to convert quotes and dashes to
> +# typographically correct entities.
> +#html_use_smartypants = True
> +
> +# Custom sidebar templates, maps document names to template names.
> +#html_sidebars = {}
> +
> +# Additional templates that should be rendered to pages, maps page names to
> +# template names.
> +#html_additional_pages = {}
> +
> +# If false, no module index is generated.
> +#html_domain_indices = True
> +
> +# If false, no index is generated.
> +#html_use_index = True
> +
> +# If true, the index is split into individual pages for each letter.
> +#html_split_index = False
> +
> +# If true, links to the reST sources are added to the pages.
> +#html_show_sourcelink = True
> +
> +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
> +#html_show_sphinx = True
> +
> +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
> +#html_show_copyright = True
> +
> +# If true, an OpenSearch description file will be output, and all pages will
> +# contain a <link> tag referring to it.  The value of this option must be the
> +# base URL from which the finished HTML is served.
> +#html_use_opensearch = ''
> +
> +# This is the file name suffix for HTML files (e.g. ".xhtml").
> +#html_file_suffix = None
> +
> +# Output file base name for HTML help builder.
> +htmlhelp_basename = 'LLVMLabToolsdoc'
> +
> +
> +# -- Options for LaTeX output ---------------------------------------------
> +
> +latex_elements = {
> +# The paper size ('letterpaper' or 'a4paper').
> +#'papersize': 'letterpaper',
> +
> +# The font size ('10pt', '11pt' or '12pt').
> +#'pointsize': '10pt',
> +
> +# Additional stuff for the LaTeX preamble.
> +#'preamble': '',
> +}
> +
> +# Grouping the document tree into LaTeX files. List of tuples
> +# (source start file, target name, title,
> +#  author, documentclass [howto, manual, or own class]).
> +latex_documents = [
> +  ('index', 'LLVMLabTools.tex', u'LLVM Lab Tools Documentation',
> +   u'Daniel Dunbar, Chris Matthews', 'manual'),
> +]
> +
> +# The name of an image file (relative to this directory) to place at the top of
> +# the title page.
> +#latex_logo = None
> +
> +# For "manual" documents, if this is true, then toplevel headings are parts,
> +# not chapters.
> +#latex_use_parts = False
> +
> +# If true, show page references after internal links.
> +#latex_show_pagerefs = False
> +
> +# If true, show URL addresses after external links.
> +#latex_show_urls = False
> +
> +# Documents to append as an appendix to all manuals.
> +#latex_appendices = []
> +
> +# If false, no module index is generated.
> +#latex_domain_indices = True
> +
> +
> +# -- Options for manual page output ---------------------------------------
> +
> +# One entry per manual page. List of tuples
> +# (source start file, name, description, authors, manual section).
> +man_pages = [
> +    ('index', 'llvmlabtools', u'LLVM Lab Tools Documentation',
> +     [u'Daniel Dunbar, Chris Matthews'], 1)
> +]
> +
> +# If true, show URL addresses after external links.
> +#man_show_urls = False
> +
> +
> +# -- Options for Texinfo output -------------------------------------------
> +
> +# Grouping the document tree into Texinfo files. List of tuples
> +# (source start file, target name, title, author,
> +#  dir menu entry, description, category)
> +texinfo_documents = [
> +  ('index', 'LLVMLabTools', u'LLVM Lab Tools Documentation',
> +   u'Daniel Dunbar, Chris Matthews', 'LLVMLabTools', 'One line description of project.',
> +   'Miscellaneous'),
> +]
> +
> +# Documents to append as an appendix to all manuals.
> +#texinfo_appendices = []
> +
> +# If false, no module index is generated.
> +#texinfo_domain_indices = True
> +
> +# How to display URL addresses: 'footnote', 'no', or 'inline'.
> +#texinfo_show_urls = 'footnote'
> +
> +# If true, do not generate a @detailmenu in the "Top" node's menu.
> +#texinfo_no_detailmenu = False
>
> Added: zorg/trunk/llvmbisect/docs/index.rst
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/docs/index.rst?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/docs/index.rst (added)
> +++ zorg/trunk/llvmbisect/docs/index.rst Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,23 @@
> +.. LLVM Lab Tools documentation master file, created by
> +   sphinx-quickstart on Tue Oct  6 18:29:53 2015.
> +   You can adapt this file completely to your liking, but it should at least
> +   contain the root `toctree` directive.
> +
> +Welcome to LLVM Lab Tools's documentation!
> +==========================================
> +
> +Contents:
> +
> +.. toctree::
> +   :maxdepth: 2
> +
> +   llvmlab_bisect
> +   builders
> +
> +
> +Indices and tables
> +==================
> +
> +* :ref:`genindex`
> +* :ref:`modindex`
> +* :ref:`search`
>
> Added: zorg/trunk/llvmbisect/docs/llvmlab_bisect.rst
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/docs/llvmlab_bisect.rst?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/docs/llvmlab_bisect.rst (added)
> +++ zorg/trunk/llvmbisect/docs/llvmlab_bisect.rst Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,569 @@
> +.. _llvmlab-bisect:
> +
> +Automatic compiler bisecting with llvmlab bisect
> +==================================================
> +
> +``llvmlab bisect`` is a tool for automatically identifying
> +when a regression was introduced in any build of Clang, or LLVM
> +produced by one of our Buildbots or Jenkins jobs.
> +
> +The basics of the tool are very simple, you must provided it with a "test case"
> +which will reproduce the problem you are seeing. Once you have done that, the
> +tool will automatically download compiler packages from the cloud
> +(typically produced by our continuous integration system) and will check whether
> +the problem reproduces with that compiler or not. The tool will then attempt to
> +narrow down on the first compiler which broke the test, and will report the last
> +compiler which worked and the first compiler that failed.
> +
> +Getting the tool
> +~~~~~~~~~~~~~~~~
> +
> +The tool is in our tools repo::
> +    $ svn checkout https://llvm.org/svn/llvm-project/zorg
Did you mean https://llvm.org/svn/llvm-project/zorg/trunk zorg ? 
Otherwise the next command doesn't work.

Also I ended up working around the 'sudo' requirement by:

   svn co https://llvm.org/svn/llvm-project/zorg/trunk/ zorg
   cd zorg/llvmbisect
LOCAL_PYTHON_INSTALL_PATH=$(pwd)/local_python_packages/lib/python2.7/site-packages/
   mkdir -p $LOCAL_PYTHON_INSTALL_PATH
   export PYTHONPATH=$LOCAL_PYTHON_INSTALL_PATH:$PYTHONPATH
   python setup.py install --prefix=$(pwd)/local_python_packages
   export PATH=$(pwd)/bin:$PATH

Do you think it is worth mentioning in the docs?

--Vassil
> +    $ cd zorg/llvmbisect
> +    $ sudo python setup.py install
> +    $ llvmlab ls
> +
> +
> +The Bisection Process
> +~~~~~~~~~~~~~~~~~~~~~
> +
> +There are several parts of the bisection process you should understand to use
> +``llvmlab bisect`` effectively:
> +
> + * How the tool gets compiler builds.
> +
> + * How tests (bisection predicates) are run.
> +
> + * How the bisect process sandboxes tests.
> +
> + * Test filters.
> +
> +Compiler Packages
> ++++++++++++++++++
> +
> +Bisection uses packages produced by the continuous integration
> +system. Currently, it will only consider packages which are produced by one
> +particular "builder". The default is to use the ``clang-stage1-configure-RA_build``
> +line of packages, because that is produced by our mini farm and has a very high
> +granularity and a long history.
> +
> +You can tell the tool to use a particular line of builds using the ``-b`` or
> +``--build`` command line option.
> +
> +You can see a list of the kinds of builds which are published using::
> +
> +  $ ./llvmlab ls
> +  clang-stage1-configure-RA_build
> +  clang-stage2-foo
> +
> +Each of these corresponds to a particular buildbot/Jenkins builder
> +which is constantly building new revisions and uploading them to
> +the cloud.
> +
> +The important thing to understand is that the particular compiler package in use
> +may impact your test. For example, ``clang-stage1-configure-RA_build`` builds
> +are x86_64 compilers built on Yosemite in release asserts mode.  Generally, you
> +should make sure your test explicitly sets anything which could impact the test
> +(like the architecture).
> +
> +The other way this impacts your tests is that some packages are layed out
> +differently than others. Most compiler packages are layed out in a "Unix" style,
> +with ``bin`` and ``lib`` subdirectories. One easy way to see the package layout
> +is to use ``llvmlab fetch`` to grab a build from the particular builder you
> +are using and poke at it. For example::
> +
> +  $ llvmlab fetch clang-i386-darwin9-snt
> +  downloaded root: clang-r128299-b6960.tgz
> +  extracted path :
> +  $ ls clang-r128299-b6960
> +  bin	docs	lib	share
> +
> +See ``llvmlab fetch --help`` for more information on the ``fetch`` tool.
> +
> +The main exception to remember is that Apple style builds generally will have
> +"root" style layouts, where the package is meant to be installed directly into
> +``/``, and will be layed out with ``usr/bin`` and ``Developer/usr/bin``
> +subdirectories.
> +
> +
> +The Build Cache
> ++++++++++++++++
> +
> +``llvmlab bisect`` can be configured to cache downloaded archives. This is
> +useful for users who frequently bisect things and want the command to run as
> +fast as possible. Note that the tool doesn't try and do anything smart about
> +minimizing the amount of disk space the cache uses, so use this at your own
> +risk.
> +
> +To enable the cache::
> +  $ mkdir -p ~/.llvmlab
> +  $ echo "[ci]" > ~/.llvmlab/config
> +  $ echo "cache_builds = True" > ~/.llvmlab/config
> +
> +
> +Bisection Predicates
> +++++++++++++++++++++
> +
> +Like most bisection tools, ``llvmlab bisect`` needs to have a way to test
> +whether a particular build "passes" or "fails". ``llvmlab bisect`` uses a
> +format which allows writing most bisection commands on a single command line
> +without having to write extra shell scripts.
> +
> +``llvmlab ci exec`` is an invaluable tool for checking bisection
> +predicates. It accepts the exact same syntax as llvmlab bisect, but prints a
> +bit more information by default and only runs a single command. This is useful
> +for vetting bisection predicates before running a full bisection process.
> +
> +Predicates are written as commands which are expected to exit successfully
> +(i.e., return 0 as the exit code) when the test succeeds
> +[#predicate_tense]_. The command will be run once for each downloaded package to
> +determine if the test passes or fails on that particular build.
> +
> +``llvmlab bisect`` treats all non-optional command line arguments as the
> +command to be run. Each argument will be rewritten to possibly substitute
> +variables, and then the entire command line will be run (i.e., ``exec()``'d) to
> +determine whether the test passes or fails.
> +
> +.. _string: http://docs.python.org/library/stdtypes.html#string-formatting
> +
> +Bisection downloads each package into a separate directory inside a sandbox and
> +provides a mechanism for substituting the path to package into the command to be
> +run. Variables are substituted using the Python syntax with string_ formatting
> +named keys. For the most part, the syntax is like ``printf`` but variable names
> +are written in parentheses before the format specifier [#sh_parens]_.
> +
> +The most important variable is "path", which will be set to the path to the
> +downloaded package. For example::
> +
> +  %(path)s/bin/clang
> +
> +would typically be expanded to something like::
> +
> +  .../<sandbox>/clang-r128289-b6957/bin/clang
> +
> +before the command is run. You can use the ``-v`` (``--verbose``) command line
> +option to have ``llvmlab bisect`` print the command lines it is running after
> +substitution.
> +
> +The tool provides a few other variables but "path" is the only one needed for
> +all but the rarest bisections. You can see the others in ``llvmlab bisect
> +--help``.
> +
> +The tool optimizes for the situation where downloaded packages include command
> +line executable which are going to be used in the tests, by automatically
> +extending the PATH and DYLD_LIBRARY_PATH variables to point into the downloaded
> +build directory whenever it sees that the downloaded package has ``bin`` or
> +``lib`` directories (the tool will also look for ``/Developer/usr/...``
> +directories). This environment extensions mean that it is usually possible to
> +write simple test commands without requiring any substitutions.
> +
> +For some bisection scenarios, it is easier to write a test script than to try
> +and come up with a single predicate command. For these scenarioes, ``llvmlab
> +bisect`` also makes all of the substitution variables available in the command's
> +environment. Each variable is injected into the environment as
> +``TEST_<variable>``. As an example, the following script could be used as a test
> +predicate which just checks that the compile succeeds::
> +
> +  #!/bin/sh
> +
> +  $TEST_PATH/bin/clang -c t.c
> +
> +Even though llvmlab bisect itself will only run one individual command per
> +build, you can write arbitrarily complicated test predicates by either (a)
> +writing external test scripts, or (b) writing shell "one-liners" and using
> +``/bin/sh -c`` to execute them. For example, the following bisect will test that
> +a particular source file both compiles and executes successfully::
> +
> +  llvmlab bisect /bin/sh -c '%(path)s/bin/clang t.c && ./a.out'
> +
> +llvmlab bisect also supports a shortcut for this particular pattern. Separate
> +test commands can be separated on the command by a literal "----" command line
> +argument. Each command will be substituted as usual, but will they will be run
> +separately in order and if any command fails the entire test will fail.
> +
> +.. [#predicate_tense] Note that ``llvmlab bisect`` always looks for the latest
> +                      build where a predicate *passes*. This means that it
> +                      generally expects the predicate to fail on any recent
> +                      build. If you are used to using tools like ``delta`` you
> +                      may be used to the predicate having the opposite tense --
> +                      however, for regression analysis usually one is
> +                      investigating a failure, and so one expects the test to
> +                      currently fail.
> +
> +.. [#sh_parens] Most shells will assign a syntax to (foo) so you generally have
> +                to quote arguments which require substitution. One day I'll
> +                think of a clever way I like to commands even easier to
> +                write. Until then, quote away!
> +
> +
> +The Bisection Sandbox
> ++++++++++++++++++++++
> +
> +``llvmlab bisect`` tries to be very lightweight and not modify your working
> +directory or leave stray files around unless asked to. For that reason, it
> +downloads all of the packages and runs all of the tests inside a sandbox. By
> +default, the tool uses a sandbox inside ``/tmp`` and will destroy the sandbox
> +when it is done running tests.
> +
> +The tool also tries to be quiet and minimize command output, so the output of
> +each individual test run is also stored inside the sandbox. Unfortunately, this
> +means when the sandbox is destroyed you will no longer have access to the log
> +files if you think the predicate was not working correctly.
> +
> +For long running or complicated bisects, it is recommended to use the ``-s`` or
> +``--sandbox`` to tell the tool where to put the sandbox. If this option is used,
> +the sandbox will not be destroyed and you can investigate the log files for each
> +predicate run and the downloaded packages at your leisure.
> +
> +Predicates commands themselves are **NOT** run inside the sandbox, they are
> +always run in the current working directory. This is useful for referring to
> +test input files, but may be a problem if you wish to store the outputs of each
> +individual test run (for example, to analyze later). For that case, one method
> +is to store the test outputs inside the download package directories. The
> +following example will store each generated executable inside the build
> +directory for testing later::
> +
> +  llvmlab bisect /bin/sh -c '%(path)s/bin/clang t.c -o %(path)s/foo && %(path)s/foo'
> +
> +
> +Environment Extensions
> +++++++++++++++++++++++
> +
> +``llvmlab bisect`` tries to optimize for the common case where build product
> +have executables or libraries to test, by automatically extending the ``PATH``
> +and ``DYLD_LIBRARY_PATH`` variables when it recognizes that the build package
> +has ``bin`` or ``lib`` subdirectories.
> +
> +For almost all common bisection tasks, this makes it possible to run the tool
> +without having to explicitly specify the substitution variables.
> +
> +For example::
> +
> +  llvmlab bisect '%(path)s/bin/clang' -c t.c
> +
> +could just be written as::
> +
> +  llvmlab bisect clang -c t.c
> +
> +because the ``clang`` binary in the downloaded package will be found first in
> +the environment lookup.
> +
> +
> +Test Filters
> +++++++++++++
> +
> +For more advanced uses, llvmlab bisect has a syntax for specifying "filters"
> +on individual commands. The syntax for filters is that they should be specified
> +at the start of the command using arguments like "%%<filter expression>%%".
> +
> +The filters are used as a way to specify additional parameters which only apply
> +to particular test commands. The expressions themselves are just Python
> +expressions which should evaluate to a boolean result, which becomes the result
> +of the test.
> +
> +The Python expressions are evaluate in an environment which contains the
> +following predefined variables:
> +
> +``result``
> +
> +  The current boolean result of the test predicate (that is, true if the test is
> +  "passing"). This may have been modified by preceeding filters.
> +
> +``user_time``, ``sys_time``, ``wall_time``
> +
> +  The user, system, and wall time the command took to execute, respectively.
> +
> +These variables can be used to easily construct predicates which fail based on
> +more complex criterion. For example, here is a filter to look for the latest
> +build where the compiler succeeds in less than .5 seconds::
> +
> +  llvmlab bisect "%% result and user_time < .5 %%" clang -c t.c
> +
> +
> +Using ``llvmlab bisect``
> +~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +``llvmlab bisect`` is very flexible but takes some getting used to. The
> +following section has example bisection commands for many common scenarios.
> +
> +Compiler Crashes
> +++++++++++++++++
> +
> +This is the simplest case, a bisection for a compiler crash or assertion failure
> +usually looks like::
> +
> +  $ llvmlab bisect '%(path)s'/bin/clang -c t.c ... compiler flags ...
> +
> +because when the compiler crashes it will have a non-zero exit code. *For
> +bisecting assertion failures, you should make sure the build being tested has
> +assertions compiled in!*
> +
> +Suppose you are investigating a crash which has been fixed, and you want to know
> +where. Just use the LLVM ``not`` tool to reverse the test:
> +
> +  $ llvmlab bisect not '%(path)s'/bin/clang -c t.c ... compiler flags ...
> +
> +By looking for the latest build where ``not clang ...`` *passes* we are
> +effectively looking for the latest broken build. The next build will generally
> +be the one which fixed the problem.
> +
> +
> +Miscompiles
> ++++++++++++
> +
> +Miscompiles usually involve compiling and running the output.
> +
> +The simplest scenario is when the program crashes when run. In that case the
> +simplest method is to use the ``/bin/sh -c "... arguments ..."`` trick to
> +combine the compile and execute steps into one command line::
> +
> +  $ llvmlab bisect /bin/sh -c '%(path)s/bin/clang t.c && ./a.out'
> +
> +Note that because we are already quoting the shell command, we can just move the
> +quotes around the entire line and not worry about quoting individual arguments
> +(unless they have spaces!).
> +
> +A more complex scenario is when the program runs but has bad output. Usually
> +this just means you need to grep the output for correct output. For example, to
> +bisect a program which is supposed to print "OK" (but isn't currently) we could
> +use::
> +
> +  $ llvmlab bisect /bin/sh -c '%(path)s/bin/clang t.c && ./a.out | grep "OK"'
> +
> +Beware the pitfalls of exit codes and pipes, and use temporary files if you
> +aren't sure of what you are doing!
> +
> +
> +Overlapped Failures
> ++++++++++++++++++++
> +
> +If you are used to using a test case reduction tool like ``delta`` or
> +``bugpoint``, you are probably familiar with the problem of running the tool for
> +hours, only to find that it found a very nice test case for a different problem
> +than what you were looking for.
> +
> +The same problem happens when bisecting a program which was previously broken
> +for a different reason. If you run the tool but the results don't seem to make
> +sense, I recommend saving the sandbox (e.g., ``llvmlab bisect -s /tmp/foo
> +...``) and investigating the log files to make sure bisection looked for the
> +problem you are interested in. If it didn't, usually you should make your
> +predicate more precise, for example by using ``grep`` to search the output for a
> +more precise failure message (like an assertion failure string).
> +
> +
> +Infinite Loops
> +++++++++++++++
> +
> +On occasion, you will want to bisect something that infinite loops or takes
> +much longer than usual. This is a problem because you usually don't want to wait
> +for a long time (or infinity) for the predicate to complete.
> +
> +One simple trick which can work is to use the ``ulimit`` command to set a time
> +limit. The following command will look for the latest build where the compiler
> +runs in less than 10 seconds on the given input::
> +
> +  $ llvmlab bisect /bin/sh -c 'ulimit -t 10; %(path)s/bin/clang -c t.c'
> +
> +
> +Performance Regressions
> ++++++++++++++++++++++++
> +
> +Bisecting performance regressions is done most easily using the filter
> +expressions. Usually you would start by determining what an approximate upper
> +bound on the expected time of the command is. Then, use a ``max_time`` filter
> +with that time to cause any test running longer than that to fail.
> +
> +For example, the following example shows a real bisection of a performance
> +regression on the ``telecom-gsm`` benchmark::
> +
> +  llvmlab bisect \
> +    '%(path)s/bin/clang' -o telecomm-gsm.exe -w -arch x86_64 -O3 \
> +        ~/llvm-test-suite/MultiSource/Benchmarks/MiBench/telecomm-gsm/*.c \
> +        -lm -DSTUPID_COMPILER -DNeedFunctionPrototypes=1 -DSASR \
> +    ---- \
> +    "%% user_time < 0.25 %%" ./telecomm-gsm.exe -fps -c \
> +        ~/llvm-test-suite/MultiSource/Benchmarks/MiBench/telecomm-gsm/large.au
> +
> +
> +Nightly Test Failures
> ++++++++++++++++++++++
> +
> +If you are bisecting a nightly test failure, it commonly helps to leverage the
> +existing nightly test Makefiles rather than try to write your own step to build
> +or test an executable against the expected output. In particular, the Makefiles
> +generate report files which say whether the test passed or failed.
> +
> +For example, if you are using LNT to run your nightly tests, then the top line
> +the ``test.log`` file shows the exact command used to run the tests. You can
> +always rerun this command in any subdirectory. For example, here is an example
> +from an i386 Clang run::
> +
> +  2010-10-12 08:54:39: running: "make" "-k" "-j" "1" "report" "report.simple.csv" \
> +      "TARGET_LLVMGCC=/Users/ddunbar/llvm.ref/2010-10-12_00-01.install/bin/clang" \
> +      "CC_UNDER_TEST_TARGET_IS_I386=1" "ENABLE_HASHED_PROGRAM_OUTPUT=1" "TARGET_CXX=None" \
> +      "LLI_OPTFLAGS=-O0" "TARGET_CC=None" \
> +      "TARGET_LLVMGXX=/Users/ddunbar/llvm.ref/2010-10-12_00-01.install/bin/clang++" \
> +      "TEST=simple" "CC_UNDER_TEST_IS_CLANG=1" "TARGET_LLCFLAGS=" "TARGET_FLAGS=-g -arch i386" \
> +      "USE_REFERENCE_OUTPUT=1" "OPTFLAGS=-O0" "SMALL_PROBLEM_SIZE=1" "LLC_OPTFLAGS=-O0" \
> +      "ENABLE_OPTIMIZED=1" "ARCH=x86" "DISABLE_CBE=1" "DISABLE_JIT=1"
> +
> +Suppose we wanted to bisect a test failure on something complicated, like
> +``254.gap``. The "easiest" thing to do is:
> +
> + #. Replace the compiler paths with "%(path)s" so that we use the right compiler to test.
> +
> + #. Change into the test directory, in this case ``External/SPEC/CINT2000/254.gap``.
> +
> + #. Each test produces a ``<test name>.simple.execute.report.txt`` text file which will have a line that looks like::
> +
> +      TEST-FAIL: exec /Users/ddunbar/nt/clang.i386.O0.g/test-2011-03-25_06-35-35/External/SPEC/CINT2000/254.gap/254.gap
> +
> +    because the tests are make driven, we can tell make to only build this
> +    file. In SingleSource directories, this would make sure we don't run any
> +    tests we don't need to.
> +
> +    In this case, replace the "report" and "report.simple.csv" make targest on
> +    the command line with "Output/254.gap.simple.exec.txt".
> +
> + #. Make sure your test predicate removes the Output directory and any ``report...`` files (if
> +    you forget this, you won't end up rebuilding the test with the right compiler).
> +
> + #. Add a grep for "TEST-PASS" of the report file.
> +
> +An example of what the final bisect command might look like::
> +
> +  $ llvmlab bisect /bin/sh -c \
> +      'rm -rf report.* Output && \
> +       "make" "-k" "-j" "1" "Output/254.gap.simple.exec.txt" \
> +           "TARGET_LLVMGCC=%(path)s/bin/clang" \
> +           "CC_UNDER_TEST_TARGET_IS_I386=1" "ENABLE_HASHED_PROGRAM_OUTPUT=1" "TARGET_CXX=None" \
> +           "LLI_OPTFLAGS=-O0" "TARGET_CC=None" \
> +           "TARGET_LLVMGXX=%(path)s/bin/clang++" \
> +           "TEST=simple" "CC_UNDER_TEST_IS_CLANG=1" "TARGET_LLCFLAGS=" "TARGET_FLAGS=-g -arch i386" \
> +           "USE_REFERENCE_OUTPUT=1" "OPTFLAGS=-O0" "SMALL_PROBLEM_SIZE=1" "LLC_OPTFLAGS=-O0" \
> +           "ENABLE_OPTIMIZED=1" "ARCH=x86" "DISABLE_CBE=1" "DISABLE_JIT=1" && \
> +       grep "TEST-PASS" "Output/254.gap.simple.exec.txt"'
> +
> +
> +Nightly Test Performance Regressions
> +++++++++++++++++++++++++++++++++++++
> +
> +This is similar to the problem of bisecting nightly test above, but made more
> +complicated because the test predicate needs to do a comparison on the
> +performance result.
> +
> +One way to do this is to extract a script which reproduces the performance
> +regression, and use a filter expression as described previously. However, this
> +requires extracting the exact commands which are run by the ``test-suite``
> +Makefiles.
> +
> +A simpler way is to use the ``test-suite/tools/get-report-time`` script in
> +conjunction with a standard Unix command line tool like ``expr`` to do the
> +performance comparison.
> +
> +The basic process is similar to the one above, the differences are that instead
> +of just using ``grep`` to check the output, we use the ``get-report-time`` tool
> +and a quick script using ``bc`` to compare the result. Here is an example::
> +
> +  $ llvmlab bisect -s sandbox /bin/sh -c \
> +      'set -ex; \
> +       rm -rf Output && \
> +       "make" "-k" "-j" "1" "Output/security-rijndael.simple.compile.report.txt" \
> +           "TARGET_LLVMGCC=%(path)s/bin/clang" "ENABLE_HASHED_PROGRAM_OUTPUT=1" "TARGET_CXX=None" \
> +           "LLI_OPTFLAGS=-O0" "TARGET_CC=None" \
> +           "TARGET_LLVMGXX=%(path)s/bin/clang++" \
> +           "TEST=simple" "CC_UNDER_TEST_IS_CLANG=1" "ENABLE_PARALLEL_REPORT=1" "TARGET_FLAGS=-g" \
> +           "USE_REFERENCE_OUTPUT=1" "CC_UNDER_TEST_TARGET_IS_X86_64=1" "OPTFLAGS=-O0" \
> +           "LLC_OPTFLAGS=-O0" "ENABLE_OPTIMIZED=1" "ARCH=x86_64" "DISABLE_CBE=1" "DISABLE_JIT=1" && \
> +       ./check-value.sh'
> +
> +Where ``check-value.sh`` looks like this::
> +
> +      #!/bin/sh -x
> +
> +      cmd1=`/Volumes/Data/sources/llvm/projects/test-suite/tools/get-report-time \
> +      		Output/security-rijndael.simple.compile.report.txt`
> +      cmd2=`echo "$cmd1 < 0.42" | bc -l`
> +
> +      if [ $cmd2 == '1' ]; then
> +        exit 0
> +      fi
> +
> +      exit 1
> +
> +Another trick this particular example uses is using the bash ``set -x`` command
> +to log the commands which get run. In this case, this allows us to inspect the
> +log files in the ``sandbox`` directory and see what the time used in the
> +``expr`` comparison was. This is handy in case we aren't exactly sure if the
> +comparison time we used is correct.
> +
> +
> +Tests With Interactive Steps
> +++++++++++++++++++++++++++++
> +
> +Sometimes test predicates require some steps that must be performed
> +interactively or are too hard to automate in a test script.
> +
> +In such cases its still possible to use llvmlab bisect by writing the test
> +script in such a way that it will wait for the user to inform it whether the
> +test passed or failed. For example, here is a real test script that was used to
> +bisect where I was running a GUI app to check for distorted colors as part of
> +the test step.
> +
> +After each step, the GUI app would be launched, I would check the colors, and
> +then type in "yes" or "no" based on whether the app worked or not. Note that
> +because llvmlab bisect hides the test output by default, the prompt itself
> +doesn't show up, but the command still can read stdin.
> +
> +Here is the test script::
> +
> +  #!/bin/sh
> +
> +  git reset --hard
> +
> +  CC=clang
> +  COMPILE HERE
> +  sudo ditto built_files/ /
> +
> +  open /Applications/GUIApp
> +
> +  while true; do
> +      read -p "OK?" is_ok
> +      if [ "$is_ok" == "yes" ]; then
> +          echo "OK!"
> +          exit 0
> +      elif [ "$is_ok" == "no" ]; then
> +          echo "FAILED!"
> +          exit 1
> +      else
> +          echo "Answer yes or no you!";
> +      fi
> +  done
> +
> +And here is log showing the transcript of the bisect::
> +
> +  bash-3.2# ~admin/zorg/utils/llvmlab bisect --max-rev 131837 ./test.sh
> +  no
> +  FAIL: clang-r131837-b8165
> +  no
> +  FAIL: clang-r131835-b8164
> +  no
> +  FAIL: clang-r131832-b8162
> +  no
> +  FAIL: clang-r131828-b8158
> +  yes
> +  PASS: clang-r131795-b8146
> +  no
> +  FAIL: clang-r131809-b8151
> +  no
> +  FAIL: clang-r131806-b8149
> +  no
> +  FAIL: clang-r131801-b8147
> +  clang-r131795-b8146: first working build
> +  clang-r131801-b8147: next failing build
> +
> +Note that it is very easy to make a mistake and type the wrong answer when
> +following this process, in which case the bisect will come up with the wrong
> +answer. It's always worth sanity checking the results (e.g., using ``llvmlab
> +ci exec``) after the bisect is complete.
>
> Added: zorg/trunk/llvmbisect/llvmlab/__init__.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/__init__.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/__init__.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/__init__.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1 @@
> +""""""
>
> Added: zorg/trunk/llvmbisect/llvmlab/algorithm.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/algorithm.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/algorithm.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/algorithm.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,85 @@
> +"""Handy algorithms."""
> +
> +
> +def bisect(predicate, list):
> +    """
> +    bisect(predicate, list) -> item or None
> +
> +    Given a test predicate and a list of items, search return the first item in
> +    the list for which the predicate succeeds, or None if no such item is
> +    found.
> +
> +    The list is assumed to be ordered such that (predicate(i) for i in list) is
> +    monotonic. If this condition is not met, the returned item is guaranteed to
> +    satisfy the predicate and the item preceeding it is guaranteed to fail the
> +    predicate, but that is all. Additionally, if the last item does not pass
> +    the predicate, such an item might not be found.
> +
> +    This function is optimized for the case where the searched for item is near
> +    the beginning of the list.
> +    """
> +
> +    if not list:
> +        return None
> +
> +    lo = 0
> +    hi = len(list)-1
> +
> +    # Check first item immediately.
> +    if predicate(list[lo]):
> +        return list[lo]
> +
> +    # Invariants:
> +    #  not predicate(list[lo])
> +    #  predicate(list[hi])
> +
> +    # Binary search region.
> +    while lo + 1 != hi:
> +        mid = (lo + hi) // 2
> +        if predicate(list[mid]):
> +            hi = mid
> +        else:
> +            lo = mid
> +
> +    return list[hi]
> +
> +
> +def gallop(predicate, list):
> +    """
> +    gallop(predicate, list) -> list or None
> +
> +    Given a test predicate and a list of items, reduce the search space
> +    assuming the searched for item is near the beginning of the list.
> +
> +    The list is assumed to be ordered such that (predicate(i) for i in list) is
> +    monotonic. If this condition is not met, the returned item is guaranteed to
> +    satisfy the predicate and the item preceeding it is guaranteed to fail the
> +    predicate, but that is all. Additionally, if the last item does not pass
> +    the predicate, such an item might not be found.
> +    """
> +
> +    if not list:
> +        return None
> +
> +    # Check first item immediately.
> +    if predicate(list[0]):
> +        return list[0:1]
> +
> +    # Invariants:
> +    #  not predicate(list[lo])
> +
> +    # Gallop to find initial search range, under the assumption that we are
> +    # most likely looking for something at the head of this list.
> +    lo = 0
> +    hi = 1
> +    while hi < len(list):
> +        if predicate(list[hi]):
> +            break
> +        lo, hi = hi, hi + (hi - lo)*2
> +
> +    # If we galloped past the end, limit the hi range.
> +    if hi >= len(list):
> +        hi = len(list) - 1
> +        if hi == lo or not predicate(list[hi]):
> +            return None
> +    return list[lo:hi+1]
>
> Added: zorg/trunk/llvmbisect/llvmlab/ci.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/ci.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/ci.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/ci.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,650 @@
> +"""
> +Tools for working with llvmlab CI infrastructure.
> +"""
> +
> +import errno
> +import os
> +import resource
> +import shutil
> +import subprocess
> +import sys
> +import tempfile
> +import time
> +
> +
> +from . import shell
> +from . import algorithm
> +from . import llvmlab
> +from . import util
> +from util import warning, fatal, note
> +from . import scripts
> +from . import util
> +
> +from optparse import OptionParser
> +
> +
> +class Command(object):
> +    class Filter(object):
> +        def __init__(self):
> +            pass
> +
> +        def evaluate(self, command):
> +            raise RuntimeError("Abstract method.")
> +
> +    class NotFilter(Filter):
> +        def evaluate(self, command):
> +            warning("'negate' filter is deprecated, use 'not result' "
> +                    "filter expression")
> +            command.result = not command.result
> +
> +    class MaxTimeFilter(Filter):
> +        def __init__(self, value):
> +            try:
> +                self.value = float(value)
> +            except:
> +                fatal("invalid argument: %r" % time)
> +            warning("'max_time' filter is deprecated, use "
> +                    "'user_time < %.4f' filter expression" % self.value)
> +
> +        def evaluate(self, command):
> +            if command.metrics["user_time"] >= self.value:
> +                command.result = False
> +
> +    available_filters = {"negate": NotFilter(),  # note this is an instance.
> +                         "max_time": MaxTimeFilter}
> +
> +    def __init__(self, command, stdout_path, stderr_path, env):
> +        self.command = command
> +        self.stdout_path = stdout_path
> +        self.stderr_path = stderr_path
> +        self.env = env
> +
> +        # Test data.
> +        self.metrics = {}
> +        self.result = None
> +
> +    def execute(self, verbose=False):
> +        if verbose:
> +            note('executing: %s' % ' '.join("'%s'" % arg
> +                                            for arg in self.command))
> +
> +        start_rusage = resource.getrusage(resource.RUSAGE_CHILDREN)
> +        start_time = time.time()
> +
> +        p = subprocess.Popen(self.command,
> +                             stdout=open(self.stdout_path, 'w'),
> +                             stderr=open(self.stderr_path, 'w'),
> +                             env=self.env)
> +        self.result = p.wait() == 0
> +
> +        end_time = time.time()
> +        end_rusage = resource.getrusage(resource.RUSAGE_CHILDREN)
> +        self.metrics["user_time"] = end_rusage.ru_utime - start_rusage.ru_utime
> +        self.metrics["sys_time"] = end_rusage.ru_stime - start_rusage.ru_stime
> +        self.metrics["wall_time"] = end_time - start_time
> +
> +        if verbose:
> +            note("command executed in -- "
> +                 "user: %.4fs, wall: %.4fs, sys: %.4fs" % (
> +                    self.metrics["user_time"], self.metrics["wall_time"],
> +                    self.metrics["sys_time"]))
> +
> +    def evaluate_filter_spec(self, spec):
> +        # Run the filter in an environment with the builtin filters and the
> +        # metrics.
> +        env = {"result": self.result}
> +        env.update(self.available_filters)
> +        env.update(self.metrics)
> +        result = eval(spec, {}, env)
> +
> +        # If the result is a filter object, evaluate it.
> +        if isinstance(result, Command.Filter):
> +            result.evaluate(self)
> +            return
> +
> +        # Otherwise, treat the result as a boolean predicate.
> +        self.result = bool(result)
> +
> +
> +def execute_sandboxed_test(sandbox, builder, build, args,
> +                           verbose=False, very_verbose=False,
> +                           add_path_variables=True,
> +                           show_command_output=False,
> +                           reuse_sandbox=False):
> +
> +    def split_command_filters(command):
> +        for i, arg in enumerate(command):
> +            if arg[:2] != "%%" or arg[-2:] != "%%":
> +                break
> +        else:
> +            fatal("invalid command: %s, only contains filter "
> +                  "specifications" % ("".join('"%s"' % a for a in command)))
> +
> +        return ([a[2:-2] for a in command[:i]],
> +                command[i:])
> +
> +    path = build.tobasename(include_suffix=False)
> +    fullpath = build.tobasename()
> +
> +    if verbose:
> +        note('testing %r' % path)
> +
> +    # Create the sandbox directory, if it doesn't exist.
> +    is_temp = False
> +    if sandbox is None:
> +        sandbox = tempfile.mkdtemp()
> +        is_temp = True
> +    else:
> +        # Make absolute.
> +        sandbox = os.path.abspath(sandbox)
> +        if not os.path.exists(sandbox):
> +            os.mkdir(sandbox)
> +
> +    # Compute paths and make sure sandbox is clean.
> +    root_path = os.path.join(sandbox, fullpath)
> +    builddir_path = os.path.join(sandbox, path)
> +    need_build = True
> +    if reuse_sandbox and (os.path.exists(root_path) and
> +                          os.path.exists(builddir_path)):
> +        need_build = False
> +    else:
> +        for p in (root_path, builddir_path):
> +            if os.path.exists(p):
> +                fatal('sandbox is not clean, %r exists' % p)
> +
> +    # Fetch and extract the build.
> +    if need_build:
> +        start_time = time.time()
> +        llvmlab.fetch_build_to_path(builder, build, root_path, builddir_path)
> +        if very_verbose:
> +            note("extracted build in %.2fs" % (time.time() - start_time,))
> +
> +    # Attempt to find clang/clang++ in the downloaded build.
> +    def find_binary(name):
> +        x = subprocess.check_output(['find', builddir_path, '-name', name])\
> +                    .strip().split("\n")[0]
> +        if x == '':
> +            x = None
> +        return x
> +
> +    clang_path = find_binary('clang')
> +    clangpp_path = find_binary('clang++')
> +    liblto_path = find_binary('libLTO.dylib')
> +    if liblto_path is not None:
> +        liblto_dir = os.path.dirname(liblto_path)
> +    else:
> +        liblto_dir = None
> +
> +    # Construct the interpolation variables.
> +    options = {'sandbox': sandbox,
> +               'path': builddir_path,
> +               'revision': build.revision,
> +               'build': build.build,
> +               'clang': clang_path,
> +               'clang++': clangpp_path,
> +               'libltodir': liblto_dir}
> +
> +    # Inject environment variables.
> +    env = os.environ.copy()
> +    for key, value in options.items():
> +        env['TEST_%s' % key.upper()] = str(value)
> +
> +    # Extend the environment to include the path to the extracted build.
> +    #
> +    # FIXME: Ideally, we would be able to read some kind of configuration
> +    # notermation about a builder so that we could just set this up, it doesn't
> +    # necessarily here as hard-coded notermation.
> +    if add_path_variables:
> +        path_extensions = []
> +        dyld_library_path_extensions = []
> +        toolchains_dir = os.path.join(builddir_path,
> +                                      ('Applications/Xcode.app/Contents/'
> +                                       'Developer/Toolchains'))
> +        toolchain_paths = []
> +        if os.path.exists(toolchains_dir):
> +            toolchain_paths = [os.path.join(toolchains_dir, name, 'usr')
> +                               for name in os.listdir(toolchains_dir)]
> +        for package_root in ['', 'Developer/usr/'] + toolchain_paths:
> +            p = os.path.join(builddir_path, package_root, 'bin')
> +            if os.path.exists(p):
> +                path_extensions.append(p)
> +            p = os.path.join(builddir_path, package_root, 'lib')
> +            if os.path.exists(p):
> +                dyld_library_path_extensions.append(p)
> +        if path_extensions:
> +            env['PATH'] = os.pathsep.join(
> +                path_extensions + [os.environ.get('PATH', '')])
> +        if dyld_library_path_extensions:
> +            env['DYLD_LIBRARY_PATH'] = os.pathsep.join(
> +                dyld_library_path_extensions + [
> +                    os.environ.get('DYLD_LIBRARY_PATH', '')])
> +
> +    # Split the arguments into distinct commands.
> +    #
> +    # Extended command syntax allows running multiple commands by separating
> +    # them with '----'.
> +    test_commands = util.list_split(args, "----")
> +
> +    # Split command specs into filters and commands.
> +    test_commands = [split_command_filters(spec) for spec in test_commands]
> +
> +    # Execute the test.
> +    command_objects = []
> +    interpolated_variables = False
> +    for i, (filters, command) in enumerate(test_commands):
> +        # Interpolate arguments.
> +        old_command = command
> +        command = [a % options for a in command]
> +        if old_command != command:
> +            interpolated_variables = True
> +
> +        # Create the command object...
> +        stdout_log_path = os.path.join(sandbox, '%s.%d.stdout' % (path, i))
> +        stderr_log_path = os.path.join(sandbox, '%s.%d.stderr' % (path, i))
> +        cmd_object = Command(command, stdout_log_path, stderr_log_path, env)
> +        command_objects.append(cmd_object)
> +
> +        # Execute the command.
> +        try:
> +            cmd_object.execute(verbose=verbose)
> +        except OSError, e:
> +            # Python's exceptions are horribly to read, and this one is
> +            # incredibly common when people don't use the right syntax (or
> +            # misspell something) when writing a predicate. Detect this and
> +            # notify the user.
> +            if e.errno == errno.ENOENT:
> +                fatal("invalid command, executable doesn't exist: %r" % (
> +                        cmd_object.command[0],))
> +            elif e.errno == errno.ENOEXEC:
> +                fatal("invalid command, executable has a bad format. Did you "
> +                      "forget to put a #! at the top of a script?: %r"
> +                      % (cmd_object.command[0],))
> +            else:
> +                # Otherwise raise the error again.
> +                raise e
> +
> +        # Evaluate the filters.
> +        for filter in filters:
> +            cmd_object.evaluate_filter_spec(filter)
> +
> +        if show_command_output:
> +            for p, type in ((stdout_log_path, "stdout"),
> +                            (stderr_log_path, "stderr")):
> +                if not os.path.exists(p):
> +                    continue
> +
> +                f = open(p)
> +                data = f.read()
> +                f.close()
> +                if data:
> +                    print ("-- command %s (note: suppressed by default, "
> +                           "see sandbox dir for log files) --" % (type))
> +                    print "--\n%s--\n" % data
> +
> +        test_result = cmd_object.result
> +        if not test_result:
> +            break
> +    if not interpolated_variables:
> +        warning('no substitutions found. Fetched root ignored?')
> +
> +    # Remove the temporary directory.
> +    if is_temp:
> +        if shell.execute(['rm', '-rf', sandbox]) != 0:
> +            note('unable to remove sandbox dir %r' % path)
> +
> +    return test_result, command_objects
> +
> +
> +def get_best_match(builds, name, key=lambda x: x):
> +    builds = list(builds)
> +    builds.sort(key=key)
> +
> +    if name is None and builds:
> +        return builds[-1]
> +
> +    to_find = llvmlab.Build.frombasename(name, None)
> +
> +    best = None
> +    for item in builds:
> +        build = key(item)
> +        # Check for a prefix match.
> +        path = build.tobasename()
> +        if path.startswith(name):
> +            return item
> +
> +        # Check for a revision match.
> +        if build.revision == to_find.revision and build.revision is not None:
> +            return item
> +
> +        # Otherwise, stop when we aren't getting closer.
> +        if build > to_find:
> +            break
> +        best = item
> +
> +    return best
> +
> +
> +def action_fetch(name, args):
> +    """fetch a build from the server"""
> +
> +    parser = OptionParser("""\
> +usage: %%prog %(name)s [options] builder [build-name]
> +
> +Fetch the build from the named builder which matchs build-name. If no match is
> +found, get the first build before the given name. If no build name is given,
> +the most recent build is fetched.
> +
> +The available builders can be listed using:
> +
> +  %%prog ci ls""" % locals())
> +    parser.add_option("-f", "--force", dest="force",
> +                      help=("always download and extract, overwriting any"
> +                            "existing files"),
> +                      action="store_true", default=False)
> +    parser.add_option("", "--update-link", dest="update_link", metavar="PATH",
> +                      help=("update a symbolic link at PATH to point to the "
> +                            "fetched build (on success)"),
> +                      action="store", default=None)
> +    parser.add_option("-d", "--dry-run", dest='dry_run',
> +                      help=("Perform all operations except the actual "
> +                            "downloading and extracting of any files"),
> +                      action="store_true", default=False)
> +
> +    (opts, args) = parser.parse_args(args)
> +
> +    if len(args) == 0:
> +        parser.error("please specify a builder name")
> +    elif len(args) == 1:
> +        builder, = args
> +        build_name = None
> +    elif len(args) == 2:
> +        builder, build_name = args
> +    else:
> +        parser.error("invalid number of arguments")
> +
> +    builds = list(llvmlab.fetch_builds(builder))
> +    if not builds:
> +        parser.error("no builds for builder: %r" % builder)
> +
> +    build = get_best_match(builds, build_name)
> +    if not build:
> +        parser.error("no match for build %r" % build_name)
> +
> +    path = build.tobasename()
> +    if build_name is not None and not path.startswith(build_name):
> +        note('no exact match, fetching %r' % path)
> +
> +    # Get the paths to extract to.
> +    root_path = path
> +    builddir_path = build.tobasename(include_suffix=False)
> +
> +    if not opts.dry_run:
> +        # Check that the download and extract paths are clean.
> +        for p in (root_path, builddir_path):
> +            if os.path.exists(p):
> +                # If we are using --force, then clean the path.
> +                if opts.force:
> +                    shutil.rmtree(p, ignore_errors=True)
> +                    continue
> +                fatal('current directory is not clean, %r exists' % p)
> +        llvmlab.fetch_build_to_path(builder, build, root_path, builddir_path)
> +
> +    print 'downloaded root: %s' % root_path
> +    print 'extracted path : %s' % builddir_path
> +
> +    # Update the symbolic link, if requested.
> +    if not opts.dry_run and opts.update_link:
> +        # Remove the existing path.
> +        try:
> +            os.unlink(opts.update_link)
> +        except OSError as e:
> +            if e.errno != errno.ENOENT:
> +                fatal('unable to update symbolic link at %r, cannot unlink' % (
> +                        opts.update_link))
> +
> +        # Create the symbolic link.
> +        os.symlink(os.path.abspath(builddir_path), opts.update_link)
> +        print 'updated link at: %s' % opts.update_link
> +    return os.path.abspath(builddir_path)
> +
> +
> +def action_ls(name, args):
> +    """list available build names or builds"""
> +
> +    parser = OptionParser("""\
> +usage: %%prog %s [build-name]
> +
> +With no arguments, list the available build names on 'llvmlab'. With a build
> +name, list the available builds for that builder.\
> +""" % name)
> +
> +    (opts, args) = parser.parse_args(args)
> +
> +    if not len(args):
> +        available_buildnames = llvmlab.fetch_builders()
> +        available_buildnames.sort()
> +        for item in available_buildnames:
> +            print item
> +        return available_buildnames
> +
> +    for name in args:
> +        if len(args) > 1:
> +            if name is not args[0]:
> +                print
> +            print '%s:' % name
> +        available_builds = list(llvmlab.fetch_builds(name))
> +        available_builds.sort()
> +        available_builds.reverse()
> +        for build in available_builds:
> +            print build.tobasename(include_suffix=False)
> +        min_rev = min([x.revision for x in available_builds])
> +        max_rev = max([x.revision for x in available_builds])
> +        note("Summary: found {} builds: r{}-r{}".format(len(available_builds),
> +                                                        min_rev, max_rev))
> +        return available_builds
> +
> +DEFAULT_BUILDER = "clang-stage1-configure-RA_build"
> +
> +
> +def action_bisect(name, args):
> +    """find first failing build using binary search"""
> +
> +    parser = OptionParser("""\
> +usage: %%prog %(name)s [options] ... test command args ...
> +
> +Look for the first published build where a test failed, using the builds on
> +llvmlab. The command arguments are executed once per build tested, but each
> +argument is first subject to string interpolation. The syntax is
> +"%%(VARIABLE)FORMAT" where FORMAT is a standard printf format, and VARIABLE is
> +one of:
> +
> +  'sandbox'   - the path to the sandbox directory.
> +  'path'      - the path to the build under test.
> +  'revision'  - the revision number of the build.
> +  'build'     - the build number of the build under test.
> +  'clang'     - the path to the clang binary of the build if it exists.
> +  'clang++'   - the path to the clang++ binary of the build if it exists.
> +  'libltodir' - the path to the directory containing libLTO.dylib, if it
> +   exists.
> +
> +Each test is run in a sandbox directory. By default, sandbox directories are
> +temporary directories which are created and destroyed for each test (see
> +--sandbox).
> +
> +For use in auxiliary test scripts, each test is also run with each variable
> +available in the environment as TEST_<variable name> (variables are converted
> +to uppercase). For example, a test script could use "TEST_PATH" to find the
> +path to the build under test.
> +
> +The stdout and stderr of the command are logged to files inside the sandbox
> +directory. Use an explicit sandbox directory if you would like to look at
> +them.
> +
> +It is possible to run multiple distinct commands for each test by separating
> +them in the command line arguments by '----'. The failure of any command causes
> +the entire test to fail.\
> +""" % locals())
> +
> +    parser.add_option("-b", "--build", dest="build_name", metavar="STR",
> +                      help="name of build to fetch",
> +                      action="store", default=DEFAULT_BUILDER)
> +    parser.add_option("-s", "--sandbox", dest="sandbox",
> +                      help="directory to use as a sandbox",
> +                      action="store", default=None)
> +    parser.add_option("-v", "--verbose", dest="verbose",
> +                      help="output more test notermation",
> +                      action="store_true", default=False)
> +    parser.add_option("-V", "--very-verbose", dest="very_verbose",
> +                      help="output even more test notermation",
> +                      action="store_true", default=False)
> +    parser.add_option("", "--show-output", dest="show_command_output",
> +                      help="display command output",
> +                      action="store_true", default=False)
> +    parser.add_option("", "--single-step", dest="single_step",
> +                      help="single step instead of binary stepping",
> +                      action="store_true", default=False)
> +    parser.add_option("", "--min-rev", dest="min_rev",
> +                      help="minimum revision to test",
> +                      type="int", action="store", default=None)
> +    parser.add_option("", "--max-rev", dest="max_rev",
> +                      help="maximum revision to test",
> +                      type="int", action="store", default=None)
> +
> +    parser.disable_interspersed_args()
> +
> +    (opts, args) = parser.parse_args(args)
> +
> +    if opts.build_name is None:
> +        parser.error("no build name given (see --build)")
> +
> +    # Very verbose implies verbose.
> +    opts.verbose |= opts.very_verbose
> +
> +    start_time = time.time()
> +    available_builds = list(llvmlab.fetch_builds(opts.build_name))
> +    available_builds.sort()
> +    available_builds.reverse()
> +    if opts.very_verbose:
> +        note("fetched builds in %.2fs" % (time.time() - start_time,))
> +
> +    if opts.min_rev is not None:
> +        available_builds = [b for b in available_builds
> +                            if b.revision >= opts.min_rev]
> +    if opts.max_rev is not None:
> +        available_builds = [b for b in available_builds
> +                            if b.revision <= opts.max_rev]
> +
> +    def predicate(item):
> +        # Run the sandboxed test.
> +        test_result, _ = execute_sandboxed_test(
> +            opts.sandbox, opts.build_name, item, args, verbose=opts.verbose,
> +            very_verbose=opts.very_verbose,
> +            show_command_output=opts.show_command_output or opts.very_verbose)
> +
> +        # Print status.
> +        print '%s: %s' % (('FAIL', 'PASS')[test_result],
> +                          item.tobasename(include_suffix=False))
> +
> +        return test_result
> +
> +    if opts.single_step:
> +        for item in available_builds:
> +            if predicate(item):
> +                break
> +        else:
> +            item = None
> +    else:
> +        if opts.min_rev is None or opts.max_rev is None:
> +            # Gallop to find initial search range, under the assumption that we
> +            # are most likely looking for something at the head of this list.
> +            search_space = algorithm.gallop(predicate, available_builds)
> +        else:
> +            # If both min and max revisions are specified,
> +            # don't gallop - bisect the given range.
> +            search_space = available_builds
> +        item = algorithm.bisect(predicate, search_space)
> +
> +    if item is None:
> +        fatal('unable to find any passing build!')
> +
> +    print '%s: first working build' % item.tobasename(include_suffix=False)
> +    index = available_builds.index(item)
> +    if index == 0:
> +        print 'no failing builds!?'
> +    else:
> +        print '%s: next failing build' % available_builds[index-1].tobasename(
> +            include_suffix=False)
> +
> +
> +def action_exec(name, args):
> +    """execute a command against a published root"""
> +
> +    parser = OptionParser("""\
> +usage: %%prog %(name)s [options] ... test command args ...
> +
> +Executes the given command against the latest published build. The syntax for
> +commands (and exit code) is exactly the same as for the 'bisect' tool, so this
> +command is useful for testing bisect test commands.
> +
> +See 'bisect' for more notermation on the exact test syntax.\
> +""" % locals())
> +
> +    parser.add_option("-b", "--build", dest="build_name", metavar="STR",
> +                      help="name of build to fetch",
> +                      action="store", default=DEFAULT_BUILDER)
> +    parser.add_option("-s", "--sandbox", dest="sandbox",
> +                      help="directory to use as a sandbox",
> +                      action="store", default=None)
> +    parser.add_option("", "--min-rev", dest="min_rev",
> +                      help="minimum revision to test",
> +                      type="int", action="store", default=None)
> +    parser.add_option("", "--max-rev", dest="max_rev",
> +                      help="maximum revision to test",
> +                      type="int", action="store", default=None)
> +    parser.add_option("", "--near", dest="near_build",
> +                      help="use a build near NAME",
> +                      type="str", action="store", metavar="NAME", default=None)
> +
> +    parser.disable_interspersed_args()
> +
> +    (opts, args) = parser.parse_args(args)
> +
> +    if opts.build_name is None:
> +        parser.error("no build name given (see --build)")
> +
> +    available_builds = list(llvmlab.fetch_builds(opts.build_name))
> +    available_builds.sort()
> +    available_builds.reverse()
> +
> +    if opts.min_rev is not None:
> +        available_builds = [b for b in available_builds
> +                            if b.revision >= opts.min_rev]
> +    if opts.max_rev is not None:
> +        available_builds = [b for b in available_builds
> +                            if b.revision <= opts.max_rev]
> +
> +    if len(available_builds) == 0:
> +        fatal("No builds available for builder name: %s" % opts.build_name)
> +
> +    # Find the best match, if requested.
> +    if opts.near_build:
> +        build = get_best_match(available_builds, opts.near_build)
> +        if not build:
> +            parser.error("no match for build %r" % opts.near_build)
> +    else:
> +        # Otherwise, take the latest build.
> +        build = available_builds[0]
> +
> +    test_result, _ = execute_sandboxed_test(
> +        opts.sandbox, opts.build_name, build, args, verbose=True,
> +        show_command_output=True)
> +
> +    print '%s: %s' % (('FAIL', 'PASS')[test_result],
> +                      build.tobasename(include_suffix=False))
> +
> +    raise SystemExit(test_result != True)
> +
> +
> +def action_test(name, args):
> +    from . import test_llvmlab
> +    test_llvmlab.run_tests()
>
> Added: zorg/trunk/llvmbisect/llvmlab/clang_link
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/clang_link?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/clang_link (added)
> +++ zorg/trunk/llvmbisect/llvmlab/clang_link Thu Oct  8 16:52:50 2015
> @@ -0,0 +1 @@
> +link /Users/cmatthews/src/zorg/llvmbisect/llvmlab/clang-r219899-t2014-10-15_20-42-53-b808
> \ No newline at end of file
>
> Propchange: zorg/trunk/llvmbisect/llvmlab/clang_link
> ------------------------------------------------------------------------------
>      svn:special = *
>
> Added: zorg/trunk/llvmbisect/llvmlab/gcs.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/gcs.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/gcs.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/gcs.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,53 @@
> +"""Integration with Google Cloud Storage.
> +
> +"""
> +import os
> +import requests
> +
> +# Root URL to use for our queries.
> +GCS = "https://www.googleapis.com/storage/v1/"
> +
> +DEFAULT_BUCKET = "llvm-build-artifacts"
> +
> +BUCKET = os.getenv("BUCKET", DEFAULT_BUCKET)
> +
> +
> +def fetch_builders():
> +    """Each build kind is stored as a folder in the GCS bucket.
> +    List all the folders in the bucket, which is our list of possible
> +    compilers.
> +    """
> +    params = {'delimiter': "/", 'fields': "prefixes"}
> +    r = requests.get(GCS + "b/" + BUCKET + "/o", params=params)
> +    r.raise_for_status()
> +    reply_data = r.json()
> +    folders = reply_data['prefixes']
> +    no_slashes = [x.replace("/", "") for x in folders]
> +    return no_slashes
> +
> +
> +def fetch_builds(project):
> +    """Given a builder name, get the list of all the files stored for that
> +    builder.
> +    """
> +    assert project is not None
> +    params = {'delimiter': "/",
> +              "fields": "kind,items(name, mediaLink)",
> +              'prefix': project + "/"}
> +    r = requests.get(GCS + "b/" + BUCKET + "/o", params=params)
> +    r.raise_for_status()
> +    reply_data = r.json()
> +    return reply_data
> +
> +#  Dunno what this could be moved up to?
> +CHUNK_SIZE = 5124288
> +
> +
> +def get_compiler(url, filename):
> +    """Get the compiler at the url, and save to filename."""
> +    r = requests.get(url)
> +    r.raise_for_status()
> +    with open(filename, 'wb') as fd:
> +        for chunk in r.iter_content(CHUNK_SIZE):
> +            fd.write(chunk)
> +    return filename
>
> Added: zorg/trunk/llvmbisect/llvmlab/llvmlab.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/llvmlab.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/llvmlab.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/llvmlab.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,272 @@
> +"""Utilities for accessing stuff from llvmlab."""
> +
> +import json
> +import os
> +import re
> +import shutil
> +import time
> +
> +from . import shell
> +from . import util
> +from . import gcs
> +
> +from util import fatal
> +
> +
> +class BuilderMap(object):
> +    # Expire the buildermap after 24 hours.
> +    expiration_time = 24 * 60 * 60
> +
> +    @classmethod
> +    def frompath(klass, path):
> +        with open(path) as f:
> +            data = json.load(f)
> +        return klass(data['builders'], data['timestamp'])
> +
> +    def __init__(self, builders, timestamp):
> +        self.builders = builders
> +        self.timestamp = timestamp
> +
> +    def topath(self, path):
> +        with open(path, 'w') as f:
> +            data = {'builders': self.builders,
> +                    'timestamp': self.timestamp}
> +            json.dump(data, f, indent=2)
> +
> +    def is_expired(self):
> +        return time.time() > self.timestamp + self.expiration_time
> +
> +BUILD_NAME_REGEX = re.compile(
> +    r"((apple-)?clang)-([0-9]+)(\.([0-9]+))?(\.([0-9]+))?"
> +    r"-([A-Z][A-Za-z]+)(\.(.*))?")
> +
> +
> +class Build(object):
> +    @staticmethod
> +    def frombasename(str, url=None):
> +
> +        str = os.path.basename(str)
> +        revision = timestamp = build = None
> +
> +        # Check if this is a BNI style build.
> +        m = BUILD_NAME_REGEX.match(str)
> +        if m:
> +            name, _, major_str, _, minor_str, _, micro_str, build, \
> +                _, suffix = m.groups()
> +            revision = [int(major_str)]
> +            if minor_str:
> +                revision.append(int(minor_str))
> +                if micro_str:
> +                    revision.append(int(micro_str))
> +            return Build(name, tuple(revision), None, build, suffix)
> +
> +        if '.' in str:
> +            str, suffix = str.split('.', 1)
> +        else:
> +            suffix = None
> +
> +        m = re.match(r'(.*)-b([0-9]+)', str)
> +        if m:
> +            str, build = m.groups()
> +            build = int(build)
> +
> +        m = re.match(r'(.*)-t([0-9-]{8,10}_[0-9-]{6,8})', str)
> +        if m:
> +            str, timestamp = m.groups()
> +
> +        m = re.match(r'(.*)-r([0-9]+)', str)
> +        if m:
> +            str, revision = m.groups()
> +            revision = int(revision)
> +
> +        return Build(str, revision, timestamp, build, suffix, url)
> +
> +    @staticmethod
> +    def fromdata(data):
> +        return Build(data['name'], data['revision'], data['timestamp'],
> +                     data['build'], data['suffix'])
> +
> +    def todata(self):
> +        return {'name': self.name,
> +                'revision': self.revision,
> +                'timestamp': self.timestamp,
> +                'build': self.build,
> +                'suffix': self.suffix}
> +
> +    def __init__(self, name, revision, timestamp, build, suffix, url):
> +        self.name = name
> +        self.revision = revision
> +        self.timestamp = timestamp
> +        self.build = build
> +        self.suffix = suffix
> +        self.url = url
> +
> +    def tobasename(self, include_suffix=True):
> +        basename = self.name
> +        if self.revision is not None:
> +            if isinstance(self.revision, (tuple, list)):
> +                basename += '-' + '.'.join(str(r) for r in self.revision)
> +            else:
> +                assert isinstance(self.revision, int)
> +                basename += '-r%d' % self.revision
> +        if self.timestamp is not None:
> +            basename += '-t%s' % self.timestamp
> +        if self.build is not None:
> +            if isinstance(self.build, str):
> +                basename += '-' + self.build
> +            else:
> +                basename += '-b%d' % self.build
> +        if include_suffix and self.suffix is not None:
> +            basename += '.%s' % self.suffix
> +        return basename
> +
> +    def __repr__(self):
> +        return "%s%r" % (self.__class__.__name__,
> +                         (self.name, self.revision, self.timestamp, self.build,
> +                          self.suffix))
> +
> +    def __cmp__(self, other):
> +        return cmp((self.revision, self.timestamp,
> +                    self.build, self.suffix, self.name),
> +                   ((other.revision, other.timestamp,
> +                    other.build, other.suffix, other.name)))
> +
> +
> +def load_builder_map(reload=False):
> +    """
> +    load_builder_map() -> BuilderMap
> +
> +    Load a map of builder names to the server url that holds those artifacts.
> +    """
> +
> +    prefs = util.get_prefs()
> +
> +    # Load load the builder map if present (and not reloading)
> +    data_path = os.path.join(prefs.path, "ci")
> +    buildermap_path = os.path.join(data_path, "build_map.json")
> +    if not reload and os.path.exists(buildermap_path):
> +        buildermap = BuilderMap.frompath(buildermap_path)
> +
> +        # If the buildermap is not out-of-date, return it.
> +        if not buildermap.is_expired():
> +            return buildermap
> +
> +    # Otherwise, we didn't have a buildermap or it is out of date, compute it.
> +    builders = {}
> +    for build in gcs.fetch_builders():
> +        builders[build] = build
> +
> +    # Create the buildermap and save it.
> +    buildermap = BuilderMap(builders, time.time())
> +    if not os.path.exists(data_path):
> +        shell.mkdir_p(data_path)
> +    buildermap.topath(buildermap_path)
> +
> +    return buildermap
> +
> +
> +def fetch_builders():
> +    """
> +    fetch_builders() -> [builder-name, ...]
> +
> +    Get a list of available builders.
> +    """
> +
> +    # Handle only_use_cache setting.
> +    prefs = util.get_prefs()
> +    if prefs.getboolean("ci", "only_use_cache"):
> +        cache_path = os.path.join(prefs.path, "ci", "build_cache")
> +        return sorted(os.listdir(cache_path))
> +
> +    # Otherwise, fetch the builder map.
> +    return sorted(load_builder_map().builders.keys())
> +
> +
> +def fetch_builds(name):
> +    """
> +    fetch_builds(name) -> [(path, revision, build), ...]
> +
> +    Get a list of available builds for the named builder.
> +    """
> +    # Handle only_use_cache setting.
> +    prefs = util.get_prefs()
> +    if prefs.getboolean("ci", "only_use_cache"):
> +        cache_path = os.path.join(prefs.path, "ci", "build_cache")
> +        cache_build_path = os.path.join(cache_path, name)
> +        items = os.listdir(cache_build_path)
> +        assert False, "Unimplemented?" + str(items)
> +    # Otherwise, load the builder map.
> +    buildermap = load_builder_map()
> +
> +    # If the builder isn't in the builder map, do a forced load of the builder
> +    # map.
> +    if name not in buildermap.builders:
> +        buildermap = load_builder_map(reload=True)
> +
> +    # If the builder doesn't exist, report an error.
> +    builder_artifacts = buildermap.builders.get(name)
> +    if builder_artifacts is None:
> +        fatal("unknown builder name: %r" % (name,))
> +
> +    # Otherwise, load the builder list.
> +    server_builds = gcs.fetch_builds(builder_artifacts)
> +    builds = []
> +    for path in server_builds['items']:
> +        build = Build.frombasename(path['name'], path['mediaLink'])
> +
> +        # Ignore any links which don't at least have a revision component.
> +        if build.revision is not None:
> +            builds.append(build)
> +
> +    # If there were no builds, report an error.
> +    if not builds:
> +        fatal("builder %r may be misconfigured (no items)" % (name,))
> +
> +    # Sort the builds, to make sure we return them ordered properly.
> +    builds.sort()
> +
> +    return builds
> +
> +
> +def fetch_build_to_path(builder, build, root_path, builddir_path):
> +    path = build.tobasename()
> +
> +    # Check whether we are using a build cache and get the cached build path if
> +    # so.
> +    prefs = util.get_prefs()
> +    cache_build_path = None
> +    if prefs.getboolean("ci", "cache_builds"):
> +        cache_path = os.path.join(prefs.path, "ci", "build_cache")
> +        cache_build_path = os.path.join(cache_path, builder, path)
> +
> +    # Copy the build from the cache or download it.
> +    if cache_build_path and os.path.exists(cache_build_path):
> +        shutil.copy(cache_build_path, root_path)
> +    else:
> +        # Load the builder map.
> +        buildermap = load_builder_map()
> +
> +        # If the builder isn't in the builder map, do a forced reload of the
> +        # builder map.
> +        if builder not in buildermap.builders:
> +            buildermap = load_builder_map(reload=True)
> +
> +        # If the builder doesn't exist, report an error.
> +        builder_artifacts = buildermap.builders.get(builder)
> +        if builder_artifacts is None:
> +            fatal("unknown builder name: %r" % (builder,))
> +
> +        # Otherwise create the build url.
> +        gcs.get_compiler(build.url, root_path)
> +
> +        # Copy the build into the cache, if enabled.
> +        if cache_build_path is not None:
> +            shell.mkdir_p(os.path.dirname(cache_build_path))
> +            shutil.copy(root_path, cache_build_path)
> +
> +    # Create the directory for the build.
> +    os.mkdir(builddir_path)
> +
> +    # Extract the build.
> +    if shell.execute(['tar', '-xf', root_path, '-C', builddir_path]):
> +        fatal('unable to extract %r to %r' % (root_path, builddir_path))
>
> Added: zorg/trunk/llvmbisect/llvmlab/scripts.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/scripts.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/scripts.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/scripts.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,60 @@
> +"""
> +Utilities for building the llvmlab multi-tool.
> +"""
> +
> +import os
> +import sys
> +
> +
> +class Tool(object):
> +    """
> +    This object defines a generic command line tool instance, which dynamically
> +    builds its commands from a module dictionary.
> +
> +    Example usage::
> +
> +      import scripts
> +
> +      def action_foo(name, args):
> +          "the foo command"
> +
> +          ...
> +
> +      tool = scripts.Tool(locals())
> +      if __name__ == '__main__':
> +        tool.main(sys.argv)
> +
> +    Any function beginning with "action_" is considered a tool command. It's
> +    name is defined by the function name suffix. Underscores in the function
> +    name are converted to '-' in the command line syntax. Actions ending ith
> +    "-debug" are not listed in the help.
> +    """
> +
> +    def __init__(self, locals):
> +        # Create the list of commands.
> +        self.commands = dict((name[7:].replace('_', '-'), f)
> +                             for name, f in locals.items()
> +                             if name.startswith('action_'))
> +
> +    def usage(self, name):
> +        print >>sys.stderr, "Usage: %s command [options]" % (
> +            os.path.basename(name))
> +        print >>sys.stderr
> +        print >>sys.stderr, "Available commands:"
> +        cmds_width = max(map(len, self.commands))
> +        for name, func in sorted(self.commands.items()):
> +            if name.endswith("-debug"):
> +                continue
> +
> +            print >>sys.stderr, "  %-*s - %s" % (cmds_width, name,
> +                                                 func.__doc__)
> +        sys.exit(1)
> +
> +    def main(self, args):
> +        if len(args) < 2 or args[1] not in self.commands:
> +            if len(args) >= 2:
> +                print >>sys.stderr, "error: invalid command %r\n" % args[1]
> +            self.usage(args[0])
> +
> +        cmd = args[1]
> +        return self.commands[cmd]('%s %s' % (args[0], cmd), args[2:])
>
> Added: zorg/trunk/llvmbisect/llvmlab/shell.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/shell.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/shell.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/shell.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,44 @@
> +"""
> +shell like utilities
> +"""
> +
> +import os
> +
> +
> +def execute(args):
> +    import subprocess
> +    """execute(command) - Run the given command (or argv list) in a shell and
> +    return the exit code."""
> +    return subprocess.Popen(args).wait()
> +
> +
> +def capture(args, include_stderr=False):
> +    import subprocess
> +    """capture(command) - Run the given command (or argv list) in a shell and
> +    return the standard output."""
> +    stderr = subprocess.PIPE
> +    if include_stderr:
> +        stderr = subprocess.STDOUT
> +    p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=stderr)
> +    out, _ = p.communicate()
> +    return p.wait(), out
> +
> +
> +def mkdir_p(path):
> +    """mkdir_p(path) - Make the "path" directory, if it does not exist; this
> +    will also make directories for any missing parent directories."""
> +    import errno
> +
> +    if not path or os.path.exists(path):
> +        return
> +
> +    parent = os.path.dirname(path)
> +    if parent != path:
> +        mkdir_p(parent)
> +
> +    try:
> +        os.mkdir(path)
> +    except OSError as e:
> +        # Ignore EEXIST, which may occur during a race condition.
> +        if e.errno != errno.EEXIST:
> +            raise
>
> Added: zorg/trunk/llvmbisect/llvmlab/test_llvmlab.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/test_llvmlab.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/test_llvmlab.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/test_llvmlab.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,50 @@
> +# RUN: pythong test_llvmlab.py
> +import unittest
> +
> +import os
> +import shutil
> +import tempfile
> +from . import ci
> +
> +class TestLLVMLabCI(unittest.TestCase):
> +
> +    def setUp(self):
> +        self.workdir = tempfile.mkdtemp()
> +        print self.workdir
> +        os.chdir(self.workdir)
> +
> +    def tearDown(self):
> +        shutil.rmtree(self.workdir)
> +
> +    def test_bisect(self):
> +        ci.action_bisect("llvmlab", ["--min-rev", "219719",
> +                              "--max-rev", "219899",
> +                              "bash", "-c",
> +                              "%(path)s/bin/clang -v | grep b700"])
> +
> +    def test_ls(self):
> +        """Check that you can """
> +        builds = ci.action_ls("llvmlab", [])
> +        self.assertIn("clang-stage1-configure-RA_build", builds)
> +        compilers = ci.action_ls("llvmlab",
> +                                 ["clang-stage1-configure-RA_build"])
> +        compiler_revs = [x.revision for x in compilers]
> +        self.assertIn(219899, compiler_revs)
> +
> +    def test_fetch_noargs(self):
> +        """ """
> +        path = ci.action_fetch("llvmlab", ["clang-stage1-configure-RA_build"])
> +        self.assertTrue(os.path.isdir(path), "Fetch did not get a compiler?")
> +
> +    def test_fetch_arg(self):
> +        """ """
> +        path = ci.action_fetch("llvmlab",
> +                               ["--update-link", "clang_link",
> +                                "clang-stage1-configure-RA_build",
> +                                "clang-r219899-t2014-10-15_20-42-53-b808"])
> +        self.assertTrue(os.path.isdir(path), "Fetch did not get a compiler?")
> +
> +
> +def run_tests():
> +    suite = unittest.TestLoader().loadTestsFromTestCase(TestLLVMLabCI)
> +    unittest.TextTestRunner(verbosity=2).run(suite)
>
> Added: zorg/trunk/llvmbisect/llvmlab/util.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/llvmlab/util.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/llvmlab/util.py (added)
> +++ zorg/trunk/llvmbisect/llvmlab/util.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,286 @@
> +import ConfigParser
> +import datetime
> +import inspect
> +import os
> +import sys
> +import traceback
> +
> +__all__ = []
> +
> +def _write_message(kind, message):
> +    # Get the file/line where this message was generated.
> +    f = inspect.currentframe()
> +    # Step out of _write_message, and then out of wrapper.
> +    f = f.f_back.f_back
> +    file,line,_,_,_ = inspect.getframeinfo(f)
> +    location = '%s:%d' % (os.path.basename(file), line)
> +
> +    print >>sys.stderr, '%s: %s: %s' % (location, kind, message)
> +
> +note = lambda message: _write_message('note', message)
> +warning = lambda message: _write_message('warning', message)
> +error = lambda message: _write_message('error', message)
> +fatal = lambda message: (_write_message('fatal error', message), sys.exit(1))
> +
> +
> +def sorted(l, **kwargs):
> +    l = list(l)
> +    l.sort(**kwargs)
> +    return l
> +
> +def list_split(list, item):
> +    parts = []
> +    while item in list:
> +        index = list.index(item)
> +        parts.append(list[:index])
> +        list = list[index+1:]
> +    parts.append(list)
> +    return parts
> +
> +def pairs(l):
> +    return zip(l, l[1:])
> +
> +###
> +
> +class EnumVal(object):
> +    def __init__(self, enum, name, value):
> +        self.enum = enum
> +        self.name = name
> +        self.value = value
> +
> +    def __repr__(self):
> +        return '%s.%s' % (self.enum._name, self.name)
> +
> +class Enum(object):
> +    def __init__(self, name, **kwargs):
> +        self._name = name
> +        self.__items = dict((name, EnumVal(self, name, value))
> +                            for name,value in kwargs.items())
> +        self.__reverse_map = dict((e.value,e.name)
> +                                  for e in self.__items.values())
> +        self.__dict__.update(self.__items)
> +
> +    def get_value(self, name):
> +        return self.__items.get(name)
> +
> +    def get_name(self, value):
> +        return self.__reverse_map.get(value)
> +
> +    def get_by_value(self, value):
> +        return self.__items.get(self.__reverse_map.get(value))
> +
> +    def contains(self, item):
> +        if not isinstance(item, EnumVal):
> +            return False
> +        return item.enum == self
> +
> +class multidict:
> +    def __init__(self, elts=()):
> +        self.data = {}
> +        for key,value in elts:
> +            self[key] = value
> +
> +    def __contains__(self, item):
> +        return item in self.data
> +    def __getitem__(self, item):
> +        return self.data[item]
> +    def __setitem__(self, key, value):
> +        if key in self.data:
> +            self.data[key].append(value)
> +        else:
> +            self.data[key] = [value]
> +    def items(self):
> +        return self.data.items()
> +    def values(self):
> +        return self.data.values()
> +    def keys(self):
> +        return self.data.keys()
> +    def __len__(self):
> +        return len(self.data)
> +    def get(self, key, default=None):
> +        return self.data.get(key, default)
> +    def todict(self):
> +        return self.data.copy()
> +
> +###
> +
> +class Preferences(object):
> +    def __init__(self, path):
> +        self.path = path
> +        self.config_path = os.path.join(path, "config")
> +        self.options = ConfigParser.RawConfigParser()
> +
> +        # Load the config file, if present.
> +        if os.path.exists(self.config_path):
> +            self.options.read(self.config_path)
> +
> +    def save(self):
> +        file = open(self.config_path, "w")
> +        try:
> +            self.options.write(file)
> +        finally:
> +            file.close()
> +
> +    def get(self, section, option, default = None):
> +        if self.options.has_option(section, option):
> +            return self.options.get(section, option)
> +        else:
> +            return default
> +
> +    def getboolean(self, section, option, default = None):
> +        if self.options.has_option(section, option):
> +            return self.options.getboolean(section, option)
> +        else:
> +            return default
> +
> +    def setboolean(self, section, option, value):
> +        return self.options.set(section, option, str(value))
> +
> +_prefs = None
> +def get_prefs():
> +    global _prefs
> +    if _prefs is None:
> +        _prefs = Preferences(os.path.expanduser("~/.llvmlab"))
> +
> +        # Allow dynamic override of only_use_cache option.
> +        if os.environ.get("LLVMLAB_ONLY_USE_CACHE"):
> +            _prefs.setboolean("ci", "only_use_cache", True)
> +
> +    return _prefs
> +
> +###
> +
> +import threading
> +import Queue
> +
> +def detect_num_cpus():
> +    """
> +    Detects the number of CPUs on a system. Cribbed from pp.
> +    """
> +    # Linux, Unix and MacOS:
> +    if hasattr(os, "sysconf"):
> +        if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
> +            # Linux & Unix:
> +            ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
> +            if isinstance(ncpus, int) and ncpus > 0:
> +                return ncpus
> +        else: # OSX:
> +            return int(os.popen2("sysctl -n hw.ncpu")[1].read())
> +    # Windows:
> +    if os.environ.has_key("NUMBER_OF_PROCESSORS"):
> +        ncpus = int(os.environ["NUMBER_OF_PROCESSORS"])
> +        if ncpus > 0:
> +            return ncpus
> +    return 1 # Default
> +
> +def execute_task_on_threads(fn, iterable, num_threads = None):
> +    """execute_task_on_threads(fn, iterable) -> iterable
> +
> +    Given a task function to run on an iterable list of work items, execute the
> +    task on each item in the list using some number of threads, and yield the
> +    results of the task function.
> +
> +    If a task function throws an exception, the exception will be
> +    printed but not returned to the caller. Clients which wish to
> +    control exceptions should handle them inside the task function.
> +    """
> +    def push_work():
> +        for item in iterable:
> +            work_queue.put(item)
> +
> +        # Push sentinels to cause workers to terminate.
> +        for i in range(num_threads):
> +            work_queue.put(_sentinel)
> +    def do_work():
> +        while True:
> +            # Read a work item.
> +            item = work_queue.get()
> +
> +            # If we hit a sentinel, propogate it to the output queue and
> +            # terminate.
> +            if item is _sentinel:
> +                output_queue.put(_sentinel)
> +                break
> +
> +            # Otherwise, execute the task and push to the output queue.
> +            try:
> +                output = (None, fn(item))
> +            except Exception, e:
> +                output = ('error', sys.exc_info())
> +
> +            output_queue.put(output)
> +
> +    # Compute the number of threads to use.
> +    if num_threads is None:
> +        num_threads = detect_num_cpus()
> +
> +    # Create two queues, one for feeding items to the works and another for
> +    # consuming the output.
> +    work_queue = Queue.Queue()
> +    output_queue = Queue.Queue()
> +
> +    # Create our unique sentinel object.
> +    _sentinel = []
> +
> +    # Create and run thread to push items onto the work queue.
> +    threading.Thread(target=push_work).start()
> +
> +    # Create and run the worker threads.
> +    for i in range(num_threads):
> +        t = threading.Thread(target=do_work)
> +        t.daemon = True
> +        t.start()
> +
> +    # Read items from the output queue until all threads are finished.
> +    finished = 0
> +    while finished != num_threads:
> +        item = output_queue.get()
> +
> +        # Check for termination marker.
> +        if item is _sentinel:
> +            finished += 1
> +            continue
> +
> +        # Check for exceptions.
> +        if item[0] == 'error':
> +            _,(t,v,tb) = item
> +            traceback.print_exception(t, v, tb)
> +            continue
> +
> +        assert item[0] is None
> +        yield item[1]
> +
> +def timestamp():
> +    return datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
> +
> +###
> +
> +import collections
> +
> +class orderedset(object):
> +    def __init__(self, items=None):
> +        self.base = collections.OrderedDict()
> +        if items is not None:
> +            self.update(items)
> +
> +    def update(self, items):
> +        for item in items:
> +            self.add(item)
> +
> +    def add(self, item):
> +        self.base[item] = None
> +
> +    def remove(self, item):
> +        del self.base[item]
> +
> +    def __nonzero__(self):
> +        return bool(self.base)
> +
> +    def __len__(self):
> +        return len(self.base)
> +
> +    def __iter__(self):
> +        return iter(self.base)
> +
> +    def __contains__(self, item):
> +        return item in self.base
>
> Added: zorg/trunk/llvmbisect/setup.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/llvmbisect/setup.py?rev=249757&view=auto
> ==============================================================================
> --- zorg/trunk/llvmbisect/setup.py (added)
> +++ zorg/trunk/llvmbisect/setup.py Thu Oct  8 16:52:50 2015
> @@ -0,0 +1,41 @@
> +import os
> +
> +from setuptools import setup, find_packages
> +
> +# setuptools expects to be invoked from within the directory of setup.py, but it
> +# is nice to allow:
> +#   python path/to/setup.py install
> +# to work (for scripts, etc.)
> +os.chdir(os.path.dirname(os.path.abspath(__file__)))
> +
> +setup(
> +    name = "llvmbisect",
> +    version = "1.0",
> +
> +    author = "Daniel Dunbar and Chris Matthews",
> +    author_email = "chris.matthews at apple.com",
> +    url = 'http://lab.llvm.org',
> +    license = 'BSD',
> +
> +    description = "Compiler bisection service.",
> +    keywords = 'testing compiler performance development llvm',
> +
> +    classifiers=[
> +        'Development Status :: 4 - Beta',
> +        'Environment :: Console',
> +        'Intended Audience :: Developers',
> +        ('License :: OSI Approved :: '
> +         'University of Illinois/NCSA Open Source License'),
> +        'Natural Language :: English',
> +        'Operating System :: OS Independent',
> +        'Progamming Language :: Python',
> +        'Topic :: Software Development :: Quality Assurance',
> +        'Topic :: Software Development :: Testing',
> +        ],
> +
> +    packages = find_packages(),
> +
> +    scripts = ['bin/llvmlab'],
> +
> +    install_requires=['requests'],
> +)
>
>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits




More information about the llvm-commits mailing list