aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiana Picus <diana.picus@linaro.org>2016-10-13 16:53:18 +0300
committerDiana Picus <diana.picus@linaro.org>2016-12-15 16:52:36 +0200
commit3b2ef824adf41d86c5371b8c8db4f74bb7ac83c0 (patch)
tree816320231faf04f54bba8e4c81517e3d7aa9d9c6
parent250b7aa1f79ef4377c7ae3e55560849db5b5bcf9 (diff)
downloadlinaro-scripts-3b2ef824adf41d86c5371b8c8db4f74bb7ac83c0.tar.gz
[llvmprojs] Rewrite llvm-projs in python
This is the first step in moving all our scripts to python. For now we keep the current content of the repo, but we'll start adding a new directory hierarchy for the python stuff: * modules: should contain most of the python code, organized as a package that can be imported by the scripts * scripts: the scripts themselves, which should be parsing the command line and calling stuff from 'modules' to do the actual work; can be broken down into the same categories we had before (helpers, bisect etc), or we could just have one big pile * tests: should contain unittests (for the stuff in modules) and command line interface tests (for the scripts) The code is heavily using functionality from the tcwg-release-tools repo (which probably needs to be renamed / reorganized), so you should have that in your PYTHONPATH when trying to run any of the scripts. To run the tests, just invoke check.sh. One of the important changes is that we'll be using python's argparse module to parse command line flags, which means we'll have to stick to a more traditional interface for the scripts. In particular, we can't have short options like "+c" anymore. This isn't much of a problem, because we will keep the bash scripts as they are and just make them invoke a tool written in python (scripts/llvm.py) to do the work. The tool will have subcommands for any functionality that we want, for instance the new interface for adding/removing subprojects is: llvm.py projects [-a subproject subproject ... subproject] [-r subproject ... subproject] The -a and -r options (followed by any number of subprojects) can be used to selectively enable/disable things. You have to pass the full name of the subproject (e.g. llvmprojs.py -a clang lld -r libcxx) for it to work. This is invoked by the llvm-projs bash script, which has the old, more convenient interface. For now the bash scripts will live in the same directories as they used to, but after the transition is complete we will want to move them to the scripts directory. Note that we're also eliding any dashes in the names of the scripts, in keeping with Python best practices for module names (i.e. using valid Python identifiers as names). Change-Id: I9ec08632dbb17464673240d6f6881a90f45d5371
-rw-r--r--.gitignore1
-rwxr-xr-xcheck.sh7
-rwxr-xr-xhelpers/llvm-projs165
-rw-r--r--modules/__init__.py0
-rw-r--r--modules/llvm.py224
-rw-r--r--scripts/llvm.py106
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/cli/__init__.py0
-rw-r--r--tests/cli/testllvmprojects.py318
-rw-r--r--tests/unittests/__init__.py0
-rw-r--r--tests/unittests/testllvmsourceconfig.py572
-rw-r--r--tests/unittests/testllvmsubprojects.py64
12 files changed, 1339 insertions, 118 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0d20b64
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/check.sh b/check.sh
new file mode 100755
index 0000000..f5b65fa
--- /dev/null
+++ b/check.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+echo "Running unit tests"
+python -m unittest discover -s tests/unittests
+
+echo "Running cli tests"
+python -m unittest discover -s tests/cli
diff --git a/helpers/llvm-projs b/helpers/llvm-projs
index 3429b89..288fc8c 100755
--- a/helpers/llvm-projs
+++ b/helpers/llvm-projs
@@ -1,14 +1,10 @@
#!/usr/bin/env bash
-# This script keeps track of all projects that are linked to the llvm src
-# directory. It can detect, enable, disable and map to specific projects,
-# so different builds get to see the source dir as they saw originally.
+# Shorthand script for adding/removing llvm subprojects. It has a simpler
+# interface than llvm.py, but it calls it to do the actual work.
-. llvm-common
-
-safe_run verify_env
-
-prog=`basename $0`
+prog=$(basename $0)
+progdir=$(dirname $0)
syntax() {
echo "Syntax: $prog [clang|lldb|lld|rt|libs|all|none] {+/-}[c|x|r|k|d|l|a|u|t]"
echo " no args: List linked projects"
@@ -32,86 +28,6 @@ syntax() {
echo " Long options unlink everything before proceeding. Use the short options for fine-tuning"
}
-# Dirs and links into LLVM
-clang_dir=clang
-clang_link=tools/clang
-clang_extra_dir=clang-tools-extra
-clang_extra_link=tools/clang/tools/extra
-rt_dir=compiler-rt
-rt_link=projects/compiler-rt
-libcxx_dir=libcxx
-libcxx_link=projects/libcxx
-libcxxabi_dir=libcxxabi
-libcxxabi_link=projects/libcxxabi
-libunwind_dir=libunwind
-libunwind_link=projects/libunwind
-lld_dir=lld
-lld_link=tools/lld
-lldb_dir=lldb
-lldb_link=tools/lldb
-tests_dir=test-suite
-tests_link=projects/test-suite
-
-# Check if link exists
-has_link() {
- link=$1
- [ -d "$LLVM_SRC/$link" ]
-}
-
-# Initialise status
-init() {
- link=$1
- if has_link $link; then
- echo "ON";
- else
- echo "OFF"
- fi
-}
-
-# Link/Unlink upon need
-update() {
- dir=$1
- link=$2
- need=$3
- if [ "$need" = ON ]; then
- if ! has_link $link; then
- pushdq $LLVM_SRC
- branch=`get_branch`
- popdq
-
- safe_run add_worktree $LLVM_ROOT/repos/$dir $LLVM_SRC/$link $branch
- fi
- else
- safe_run remove_worktree $LLVM_ROOT/repos/$dir $LLVM_SRC/$link
- fi
-}
-
-# Enable/disable projects in the CMake cache
-update_build_dirs() {
- if [ -d $LLVM_BLD/../build ]; then
- safe_run cmake $* $LLVM_BLD/../build
- fi
-
- if [ -d $LLVM_BLD/../debug ]; then
- safe_run cmake $* $LLVM_BLD/../debug
- fi
-}
-
-# Lists linked projects
-list_all() {
- echo "Projects linked:"
- has_link $clang_link && echo " + Clang"
- has_link $clang_extra_link && echo " + Clang Tools Extra"
- has_link $rt_link && echo " + Compiler-RT"
- has_link $libcxx_link && echo " + LibC++"
- has_link $libcxxabi_link && echo " + LibC++abi"
- has_link $libunwind_link && echo " + LibUnwind"
- has_link $lld_link && echo " + LLD"
- has_link $lldb_link && echo " + LLDB"
- has_link $tests_link && echo " + Test-Suite"
- echo
-}
-
need_all() {
need=$1
clang=$need
@@ -125,24 +41,17 @@ need_all() {
tests=$need
}
+llvmtool=$progdir/../scripts/llvm.py
+
# No args, list
if [ "$1" = "" ]; then
echo "Use $prog -h for options"
echo
- list_all
+ python $llvmtool projects
exit
fi
-# Need/not need
-clang=`init $clang_link`
-clang_extra=`init $clang_extra_link`
-rt=`init $rt_link`
-libcxx=`init $libcxx_link`
-libcxxabi=`init $libcxxabi_link`
-libunwind=`init $libunwind_link`
-lld=`init $lld_link`
-lldb=`init $lldb_link`
-tests=`init $tests_link`
+need_all UNDEF
# See if the first option is one of the long options
opt=$1
@@ -188,7 +97,7 @@ case $opt in
shift
;;
list)
- list_all
+ python $llvmtool projects
exit
;;
-h)
@@ -257,23 +166,43 @@ if [ "$clang_extra" = ON -a "$clang" = OFF ]; then
exit
fi
+add=""
+remove=""
+
+update() {
+ project="$1"
+ flag="$2"
+
+ case $flag in
+ ON)
+ add="$add $project"
+ ;;
+ OFF)
+ remove="$remove $project"
+ ;;
+ UNDEF)
+ # Don't care
+ ;;
+ esac
+}
# Update links
-update $tests_dir $tests_link $tests
-update $lldb_dir $lldb_link $lldb
-update $lld_dir $lld_link $lld
-update $libunwind_dir $libunwind_link $libunwind
-update $libcxxabi_dir $libcxxabi_link $libcxxabi
-update $libcxx_dir $libcxx_link $libcxx
-update $rt_dir $rt_link $rt
-update $clang_dir $clang_link $clang
-update $clang_extra_dir $clang_extra_link $clang_extra
-update_build_dirs -DLLVM_TOOL_LLDB_BUILD=$lldb \
- -DLLVM_TOOL_LLD_BUILD=$lld \
- -DLLVM_TOOL_LIBUNWIND_BUILD=$libunwind \
- -DLLVM_TOOL_LIBCXXABI_BUILD=$libcxxabi \
- -DLLVM_TOOL_LIBCXX_BUILD=$libcxx \
- -DLLVM_TOOL_COMPILER_RT_BUILD=$rt \
- -DLLVM_TOOL_CLANG_BUILD=$clang \
- -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=$clang_extra
-list_all
+update "test-suite" $tests
+update "lldb" $lldb
+update "lld" $lld
+update "libunwind" $libunwind
+update "libcxxabi" $libcxxabi
+update "libcxx" $libcxx
+update "compiler-rt" $rt
+update "clang" $clang
+update "clang-tools-extra" $clang_extra
+
+if [ "$add" != "" ]; then
+ add="-a $add"
+fi
+
+if [ "$remove" != "" ]; then
+ remove="-r $remove"
+fi
+
+python $llvmtool projects $add $remove
diff --git a/modules/__init__.py b/modules/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/__init__.py
diff --git a/modules/llvm.py b/modules/llvm.py
new file mode 100644
index 0000000..8922c83
--- /dev/null
+++ b/modules/llvm.py
@@ -0,0 +1,224 @@
+import os
+
+from linaropy.git.worktree import Worktree
+
+
+class LLVMSubproject(object):
+ """
+ Class that keeps track of everything related to an LLVM subproject (clang,
+ lld, compiler-rt etc): repo URL, location preferred by CMake,
+ CMake variable for adding or removing it from the build etc.
+ """
+
+ def __init__(self, cmake_path, cmake_var):
+ """Create an LLVMSubproject with the provided info.
+
+ Parameters
+ ----------
+ cmake_path
+ Path relative to the LLVM source root where this subproject should
+ live so that CMake can automatically pick it up during the build.
+ cmake_var
+ The name of the CMake variable that can be used to enable/disable
+ building this subproject.
+ """
+ self.cmake_path = cmake_path
+ self.cmake_var = cmake_var
+
+ def get_cmake_path(self, llvm_source_directory):
+ """
+ Get the path where this subproject should live in the given LLVM tree so
+ that CMake can pick it up by default.
+ """
+ return os.path.join(llvm_source_directory, self.cmake_path)
+
+ @classmethod
+ def get_all_subprojects(cls):
+ """
+ Return a dictionary of all the LLVM subprojects.
+ At the moment, llvm itself is not part of the subprojects, because it
+ always needs to be there and everything is relative to it.
+ """
+ return {
+ "clang":
+ cls(os.path.join("tools", "clang"), "LLVM_TOOL_CLANG_BUILD"),
+ "compiler-rt":
+ cls(os.path.join("projects", "compiler-rt"),
+ "LLVM_TOOL_COMPILER_RT_BUILD"),
+ "libcxx":
+ cls(os.path.join("projects", "libcxx"),
+ "LLVM_TOOL_LIBCXX_BUILD"),
+ "libcxxabi":
+ cls(os.path.join("projects", "libcxxabi"),
+ "LLVM_TOOL_LIBCXXABI_BUILD"),
+ "libunwind":
+ cls(os.path.join("projects", "libunwind"),
+ "LLVM_TOOL_LIBUNWIND_BUILD"),
+ "lld":
+ cls(os.path.join("tools", "lld"), "LLVM_TOOL_LLD_BUILD"),
+ "lldb":
+ cls(os.path.join("tools", "lldb"), "LLVM_TOOL_LLDB_BUILD"),
+ "test-suite":
+ cls(os.path.join("projects", "test-suite"), None),
+ }
+
+
+class LLVMSourceConfig(object):
+ """Class for managing an LLVM source tree.
+
+ It keeps track of which subprojects are enabled in a given tree and provides
+ functionality for adding / removing them.
+ """
+
+ def __init__(self, proj, sourcePath,
+ subprojs=LLVMSubproject.get_all_subprojects()):
+ """ Create a source configuration.
+
+ Parameters
+ ----------
+ proj : Proj
+ Temporary project directory (used mostly for logging).
+ sourcePath
+ Must point to a valid LLVM source tree.
+ subprojs : dictionary
+ Dictionary containing a number of LLVMSubproject objects.
+ By default, this contains all the LLVM subprojects as returned by
+ LLVMSubproject.get_all_subprojects(), but any subset would work just
+ as well.
+ The keys will be used to identify the subprojects in any of this
+ class's methods. It is an error to invoke them with a subproj that
+ does not exist in this dictionary.
+ """
+ sourcePath = str(sourcePath)
+ if not os.path.isdir(sourcePath):
+ raise EnvironmentError("Invalid path to LLVM source tree")
+
+ self.proj = proj
+ self.llvmSourceTree = Worktree(proj, sourcePath)
+ self.subprojs = subprojs
+
+ def get_enabled_subprojects(self):
+ """Get a list of the subprojects enabled in this configuration."""
+ enabled = []
+ for subproj in self.subprojs:
+ if self.__is_enabled(subproj):
+ enabled.append(subproj)
+ return enabled
+
+ def update(self, add={}, remove=[]):
+ """Update the configuration by adding/removing subprojects.
+
+ Parameters
+ ----------
+ add : dictionary
+ A map of (subproject name, repo) to add to the config. The order in
+ which the subprojects are added is unspecified. Subprojects that are
+ already enabled in the config are ignored.
+ remove: list
+ A list of subproject names to remove from the config. The order in
+ which the subprojects are removed is unspecified. Duplicates and
+ subprojects that aren't enabled in the config are ignored. A
+ subproject may be removed even if it is in an invalid state.
+
+ For both add and remove, the subproject name must exist in the
+ dictionary that the config was initialized with and the repo must be a
+ valid GitRepo object.
+
+ TODO: If adding/removing a project fails, this function should try to
+ recover.
+ """
+
+ # Make the inputs friendly
+ if add is None:
+ add = {}
+
+ if remove is None:
+ remove = []
+
+ for subproj in add.keys():
+ if subproj in remove:
+ raise ValueError("Can't add and remove {} at the same time"
+ .format(subproj))
+
+ for (subproj, repo) in add.items():
+ self.__add_subproject(subproj, repo)
+
+ for subproj in remove:
+ self.__remove_subproject(subproj)
+
+ def __get_subproj_object(self, subprojName):
+ """Get the LLVMSubproject object corresponding to subprojName."""
+ if not subprojName in self.subprojs.keys():
+ raise ValueError("Unknown llvm subproject {0}".format(subprojName))
+ return self.subprojs[subprojName]
+
+ def __get_subproj_cmake_path(self, subprojName):
+ """Get the full path to subprojName in this source tree."""
+ subproj = self.__get_subproj_object(subprojName)
+ return subproj.get_cmake_path(self.llvmSourceTree.repodir)
+
+ def __is_enabled(self, subprojName):
+ """
+ Check if subproj is enabled in this configuration. A subproj is
+ considered to be enabled if it is a worktree on the correct branch.
+ """
+ try:
+ # If this succeeds, the subproject has already been added.
+ existing = Worktree(self.proj,
+ self.__get_subproj_cmake_path(subprojName))
+ except EnvironmentError:
+ # If it's not a worktree (for whatever reason), it's not enabled.
+ return False
+
+ # If it is a worktree, but on the wrong branch, it is not enabled.
+ return existing.getbranch() == self.llvmSourceTree.getbranch()
+
+ # TODO: add_subproject, remove_subproject and is_enabled should live in
+ # another object (AddRemoveStrategy?) that would know what we want to add
+ # (worktrees, links, whatever)
+ def __add_subproject(self, subprojName, subprojRepo):
+ """Add a given subproject to this configuration.
+
+ This will make sure the subproject's sources are visible in the proper
+ place in the LLVM source tree that the configuration was created with.
+
+ We currently achieve this by creating a worktree for the subproject, but
+ if more flexibility is needed we can add a config option. The branch
+ that the worktree will be created with is the same branch that the
+ existing LLVM source tree is on, and it will be tracking a branch
+ corresponding to the one that the LLVM branch was forked from.
+ """
+ path = self.__get_subproj_cmake_path(subprojName)
+
+ if self.__is_enabled(subprojName):
+ # Subproject has already been added, nothing to do.
+ return
+
+ if os.path.exists(path):
+ raise EnvironmentError(
+ "{} already exists but is not a valid subproject directory."
+ .format(path) +
+ "Please make sure it is a worktree on the same branch as LLVM.")
+
+ # Create a new worktree
+ branch = self.llvmSourceTree.getbranch()
+ if subprojRepo.branchexists(branch):
+ Worktree.create(self.proj, subprojRepo, path, branch)
+ else:
+ trackedBranch = "master" # TODO: track proper branch
+ Worktree.create(
+ self.proj,
+ subprojRepo,
+ path,
+ branch,
+ trackedBranch)
+
+ def __remove_subproject(self, subprojName):
+ """Remove a given subproject from this build configuration."""
+ path = self.__get_subproj_cmake_path(subprojName)
+
+ if not os.path.isdir(path):
+ return
+
+ worktree = Worktree(self.proj, path)
+ worktree.clean(False)
diff --git a/scripts/llvm.py b/scripts/llvm.py
new file mode 100644
index 0000000..762f9e7
--- /dev/null
+++ b/scripts/llvm.py
@@ -0,0 +1,106 @@
+"""This is the main tool for handling llvm builds, bisects etc."""
+
+import os
+from sys import exit
+
+from modules.llvm import LLVMSubproject, LLVMSourceConfig
+from linaropy.git.clone import Clone
+from linaropy.proj import Proj
+
+from argparse import Action, ArgumentParser, RawTextHelpFormatter
+
+
+def die(message, config_to_dump=None):
+ """Print an error message and exit."""
+ print message
+
+ if config_to_dump is not None:
+ dump_config(config_to_dump)
+
+ exit(1)
+
+# Figure out the path to the LLVM repos
+if "LLVM_ROOT" not in os.environ:
+ die("Please, define $LLVM_ROOT to point to the root\n"
+ "path where the worktree setup should be performed")
+llvm_repos_root = os.path.join(os.environ["LLVM_ROOT"], "repos")
+
+# Figure out the path to the current LLVM tree
+if "LLVM_SRC" not in os.environ:
+ die("Please, define $LLVM_SRC to point to the current LLVM\n"
+ "worktree directory, or run llvm-env to set it for you")
+llvm_worktree_root = os.environ["LLVM_SRC"]
+
+
+def dump_config(config):
+ """Dump the list of projects that are enabled in the given config."""
+
+ print "Projects linked:"
+ enabled = config.get_enabled_subprojects()
+ if not enabled:
+ print "none"
+ else:
+ for subproj in sorted(enabled):
+ print " + {}".format(subproj)
+
+
+def projects(args):
+ """Add/remove subprojects based on the values in args."""
+
+ proj = Proj()
+ config = LLVMSourceConfig(proj, llvm_worktree_root)
+
+ if not args.add and not args.remove:
+ # Nothing to change, just print the current configuration
+ dump_config(config)
+ exit(0)
+
+ to_add = {}
+ if args.add:
+ for subproj in args.add:
+ repo = Clone(proj, os.path.join(llvm_repos_root, subproj))
+ to_add[subproj] = repo
+
+ try:
+ config.update(to_add, args.remove)
+ except (EnvironmentError, ValueError) as exc:
+ die("Failed to update subprojects because:\n{}".format(str(exc)))
+
+ dump_config(config)
+
+##########################################################################
+# Command line parsing #
+##########################################################################
+
+# If we decide we want shorthands for the subprojects, we can append to this
+# list
+valid_subprojects = LLVMSubproject.get_all_subprojects().keys()
+
+options = ArgumentParser(formatter_class=RawTextHelpFormatter)
+subcommands = options.add_subparsers()
+
+# Subcommand for adding / removing subprojects
+projs = subcommands.add_parser("projects", help="Add/remove LLVM subprojects")
+projs.set_defaults(run_command=projects)
+
+# TODO: Overwriting previous values is not necessarily what users expect (so for
+# instance --add S1 S2 --remove S3 --add S4 would lead to adding only S4). We
+# can do better by using action='append', which would create a list (of lists?
+# or of lists and scalars?) that we can flatten to obtain all the values passed
+# by the user.
+projs.add_argument(
+ '-a', '--add',
+ nargs='+',
+ choices=valid_subprojects,
+ metavar='subproject',
+ help="Link given subprojects. Valid values are:\n\t{}\n".format(
+ "\n\t".join(valid_subprojects)))
+projs.add_argument(
+ '-r', '--remove',
+ nargs='+',
+ choices=valid_subprojects,
+ metavar='subproject',
+ help="Unlink given subprojects.")
+
+args = options.parse_args()
+args.run_command(args)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/cli/__init__.py b/tests/cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/cli/__init__.py
diff --git a/tests/cli/testllvmprojects.py b/tests/cli/testllvmprojects.py
new file mode 100644
index 0000000..c33ddb5
--- /dev/null
+++ b/tests/cli/testllvmprojects.py
@@ -0,0 +1,318 @@
+"""Command line interface tests for llvmprojs.py
+
+Note that although this uses the unittest framework, it does *not* contain unit
+tests.
+
+"""
+
+import shutil
+import os
+import subprocess
+import unittest
+
+from functools import partial
+from tempfile import mkdtemp
+from uuid import uuid4
+
+from linaropy.cd import cd
+
+
+# TODO: move this somewhere more public (maybe linaropy?)
+def debug(test):
+ """
+ Decorator that dumps the output of any subprocess.CalledProcessError
+ exception. Use this to decorate a test function when you can't tell what the
+ problem is.
+ """
+ def wrapper(*args, **kwargs):
+ # Catch any exceptions so we can dump all the output
+ try:
+ test(*args, **kwargs)
+ except subprocess.CalledProcessError as exc:
+ print "Error in {}:".format(test.__name__)
+ print "Command {} exited with error code {}:\n{}".format(
+ exc.cmd, exc.returncode, exc.output)
+ return wrapper
+
+
+class Testllvmprojs(unittest.TestCase):
+ script = os.path.join("scripts", "llvm.py")
+
+ @classmethod
+ def __create_dummy_commit(cls):
+ filename = "filethatshouldntexist"
+ cls.run_quietly(["touch", filename])
+ cls.run_quietly(["git", "add", filename])
+ cls.run_quietly(["git", "commit", "-m", "Dummy commit"])
+
+ @classmethod
+ def __create_dummy_repo(cls, repopath):
+ if not os.path.isdir(repopath):
+ os.makedirs(repopath)
+
+ with cd(repopath):
+ cls.run_quietly(["git", "init"])
+ cls.__create_dummy_commit()
+
+ @classmethod
+ def __add_worktree(cls, repopath, worktreepath, branch):
+ with cd(repopath):
+ cls.run_quietly(["git", "worktree", "add", worktreepath,
+ "-b", branch])
+
+ @classmethod
+ def __get_subproj_repo(cls, subproj):
+ return os.path.join(cls.repos, subproj)
+
+ @classmethod
+ def setUpClass(cls):
+ """Create the file structure and environment that llvmprojs expects"""
+ cls.llvm_root = mkdtemp()
+ cls.repos = os.path.join(cls.llvm_root, "repos")
+
+ cls.all_repos = ("llvm", "clang", "compiler-rt", "lld", "lldb",
+ "libcxx", "libcxxabi", "libunwind", "test-suite")
+
+ # Set up helper functions for running commands (this needs to be done
+ # before creating the repos)
+ # FIXME: In newer versions of Python (3.3+) we should be able to bind
+ # the stdout to DEVNULL, as below:
+ # cls.run_quietly = partial(subprocess.call, stdout=subprocess.DEVNULL)
+ # This doesn't work in Python 2, so we'll just have to ignore the output
+ # for now...
+ cls.run_with_output = partial(subprocess.check_output,
+ stderr=subprocess.STDOUT)
+ cls.run_quietly = cls.run_with_output
+
+ # Create dummy repos
+ for reponame in cls.all_repos:
+ cls.__create_dummy_repo(cls.__get_subproj_repo(reponame))
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(cls.llvm_root)
+
+ @classmethod
+ def setUp(cls):
+ cls.llvm_src = os.path.join(cls.llvm_root, "src" + str(uuid4()))
+
+ # Create LLVM worktree
+ cls.branch = "br" + str(uuid4())
+ cls.__add_worktree(cls.__get_subproj_repo("llvm"), cls.llvm_src,
+ cls.branch)
+
+ # Set up the environment variables
+ os.environ["LLVM_ROOT"] = cls.llvm_root
+ os.environ["LLVM_SRC"] = cls.llvm_src
+
+ @classmethod
+ def tearDown(cls):
+ # Clean up the directories where we might have added subprojects.
+ # This isn't 100% clean, because we don't clean up the repos between
+ # tests (so any branches will remain), but it's good enough for the
+ # current tests.
+ for subprojdir in (os.path.join(cls.llvm_src, "projects"),
+ os.path.join(cls.llvm_src, "tools")):
+ if os.path.isdir(subprojdir):
+ shutil.rmtree(subprojdir)
+ os.makedirs(subprojdir)
+
+ # Run prune on the original repos, to remove any dangling worktrees.
+ for reponame in cls.all_repos:
+ repopath = cls.__get_subproj_repo(reponame)
+ with cd(repopath):
+ cls.run_quietly(["git", "worktree", "prune"])
+
+ def test_dump_empty_config(self):
+ """
+ Test that we're correctly dumping an empty configuration (i.e. no
+ projects linked) when running llvmprojs without arguments.
+ """
+ output = self.run_with_output(["python", self.script, "projects"])
+ self.assertRegexpMatches(output, "Projects linked:.*\n.*none.*")
+
+ def test_add_remove_subprojects(self):
+ """
+ Test that we can add and remove one or several subprojects.
+ """
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "clang"])
+ self.assertRegexpMatches(output, "Projects linked:.*\n.*clang.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "libcxx", "lldb"])
+ self.assertRegexpMatches(
+ output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*libcxx.*\n" +
+ ".*lldb.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--remove", "libcxx"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*lldb.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--remove", "clang", "lldb"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*none.*")
+
+ def test_add_remove_invalid_subprojects(self):
+ """
+ Test that we error out nicely when trying to add/remove invalid
+ subprojects.
+ """
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ self.run_quietly(["python", self.script, "projects",
+ "--add", "inventedsubproject"])
+
+ self.assertRegexpMatches(
+ context.exception.output,
+ "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
+
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ self.run_quietly(["python", self.script, "projects",
+ "--remove", "inventedsubproject"])
+
+ self.assertRegexpMatches(
+ context.exception.output,
+ "(.*\n)*.*invalid choice:.*inventedsubproject(.*\n)*")
+
+ def test_duplicate_add_remove(self):
+ """
+ Test that we don't crash when trying to add / remove the same subproject
+ twice with the same command.
+ """
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "clang", "lld", "clang"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*lld.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--remove", "lld", "lld", "clang"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*none.*")
+
+ def test_redundant_add_remove(self):
+ """
+ Test that we can add a subproject that already exists (either because it
+ was added by our script or manually) or remove a subproject that doesn't
+ exist.
+ """
+ self.__add_worktree(self.__get_subproj_repo("clang"),
+ os.path.join(self.llvm_src, "tools", "clang"),
+ self.branch)
+ self.__add_worktree(
+ self.__get_subproj_repo("compiler-rt"),
+ os.path.join(self.llvm_src, "projects", "compiler-rt"),
+ self.branch)
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "clang"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "compiler-rt", "lld"])
+ self.assertRegexpMatches(
+ output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*compiler-rt.*\n" +
+ ".*lld.*")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--remove", "lldb", "libcxx", "lld"])
+ self.assertRegexpMatches(
+ output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*compiler-rt.*")
+
+ def test_simultaneous_add_remove(self):
+ """
+ Test that we error out when someone is trying to add and remove the same
+ project with the same command.
+ """
+ # Try the really basic case and make sure we're not touching anything
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ self.run_quietly(["python", self.script, "projects",
+ "--add", "clang", "--remove", "clang"])
+
+ self.assertRegexpMatches(
+ context.exception.output,
+ "(.*\n)*.*Can't add and remove clang at the same time(.*\n)*")
+
+ self.assertFalse(
+ os.path.exists(
+ os.path.join(self.llvm_src, "tools", "clang")))
+
+ # Try something a bit more complicated and make sure we're not touching
+ # anything
+ self.__add_worktree(
+ self.__get_subproj_repo("lld"),
+ os.path.join(self.llvm_src, "tools", "lld"),
+ self.branch)
+
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ self.run_quietly(["python", self.script, "projects",
+ "--add", "clang", "lld", "libcxx",
+ "--remove", "lld", "libcxx"])
+ self.assertRegexpMatches(
+ context.exception.output,
+ "(.*\n)*" +
+ ".*Can't add and remove (lld|libcxx) at the same time(.*\n)*")
+
+ # Make sure we didn't touch anything
+ self.assertFalse(
+ os.path.exists(
+ os.path.join(self.llvm_src, "tools", "clang")))
+ self.assertTrue(
+ os.path.exists(
+ os.path.join(self.llvm_src, "tools", "lld")))
+ self.assertFalse(
+ os.path.exists(
+ os.path.join(self.llvm_src, "projects", "libcxx")))
+
+ def test_multiple_adds_removes(self):
+ """
+ Test that we can have multiple --add and --remove options in the same
+ command and that only the last one of each kind matters.
+ """
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "libcxxabi",
+ "--remove", "lld", "lldb",
+ "--add", "clang", "libcxx",
+ "--remove", "libunwind"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*libcxx.*\n")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "libunwind", "libcxxabi",
+ "--remove", "clang", "libcxx",
+ "--add", "compiler-rt",
+ "--remove", "libcxxabi"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*compiler-rt.*\n" +
+ ".*libcxx.*\n")
+
+ output = self.run_with_output(["python", self.script, "projects",
+ "--add", "libcxx", "--remove", "lld",
+ "--add", "lld", "--remove", "libcxx"])
+ self.assertRegexpMatches(output,
+ "Projects linked:.*\n" +
+ ".*clang.*\n" +
+ ".*compiler-rt.*\n" +
+ ".*lld.*\n")
diff --git a/tests/unittests/__init__.py b/tests/unittests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/unittests/__init__.py
diff --git a/tests/unittests/testllvmsourceconfig.py b/tests/unittests/testllvmsourceconfig.py
new file mode 100644
index 0000000..1add13a
--- /dev/null
+++ b/tests/unittests/testllvmsourceconfig.py
@@ -0,0 +1,572 @@
+import os
+import unittest
+import uuid
+
+from sh import git
+
+from linaropy.cd import cd
+from linaropy.proj import Proj
+from linaropy.git.clone import Clone
+from linaropy.git.worktree import Worktree
+
+from modules.llvm import LLVMSourceConfig, LLVMSubproject
+
+
+class TestLLVMSourceConfig(unittest.TestCase):
+ testdirprefix = "SourceConfigUT"
+
+ def __create_dummy_commit(self):
+ filename = "file" + str(uuid.uuid4())
+ open(filename, "a").close()
+ git("add", filename)
+ git("commit", "-m", "Branches without commits confuse git")
+
+ def __create_dummy_repo(self, path):
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ with cd(path):
+ git("init")
+ self.__create_dummy_commit()
+
+ def __get_subproj_repo_path(self, subproj):
+ return os.path.join(self.originalLLVM.repodir, "..", subproj + "-repo")
+
+ def __get_subproj_repo(self, subproj):
+ return Clone(self.proj, self.__get_subproj_repo_path(subproj))
+
+ def setUp(self):
+ # We're going to create a hierarchy with [llvm|clang|whatever]-repo
+ # containing dummy repos, and llvm-copy containing a worktree of
+ # llvm-repo
+ self.proj = Proj(prefix=TestLLVMSourceConfig.testdirprefix)
+ path = os.path.join(self.proj.projdir, "llvm-repo")
+ self.__create_dummy_repo(path)
+ self.originalLLVM = Clone(self.proj, path)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ for subproj in subprojs.keys():
+ repo = self.__get_subproj_repo_path(subproj)
+ self.__create_dummy_repo(repo)
+
+ self.temporaryLLVMbranch = "a-branch"
+ self.temporaryLLVM = Worktree.create(
+ self.proj, self.originalLLVM, os.path.join(
+ self.proj.projdir, "llvm-copy"), self.temporaryLLVMbranch)
+
+ def tearDown(self):
+ self.proj.cleanup()
+
+ def test_detect_enabled_all(self):
+ subprojs = LLVMSubproject.get_all_subprojects()
+ sourcePath = self.temporaryLLVM.repodir
+
+ for subproj in subprojs:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertEqual(set(subprojs), set(enabled),
+ "Expected %s but detected only %s" %
+ (str(set(subprojs)), str(enabled)))
+
+ def test_detect_enabled_none(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ path = os.path.join(sourcePath, "unrelated")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "wrong", "place", "for", "lld")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "projects", "clang")
+ os.makedirs(path)
+
+ path = os.path.join(sourcePath, "tools", "libcxx")
+ os.makedirs(path)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_detect_enabled_some(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["lld", "libcxxabi", "clang"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ self.assertTrue("lld" in enabled, "Failed to detect lld")
+ self.assertTrue("clang" in enabled, "Failed to detect clang")
+ self.assertTrue("libcxxabi" in enabled,
+ "Failed to detect libcxxabi")
+
+ def test_detect_enabled_not_worktree(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ path = subprojs["compiler-rt"].get_cmake_path(sourcePath)
+ os.makedirs(path)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ # Check that if it's not a worktree, it's not enabled.
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_detect_enabled_wrong_branch(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ path = subprojs["compiler-rt"].get_cmake_path(sourcePath)
+ branch = "different-than-" + self.temporaryLLVMbranch
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("compiler-rt"),
+ path,
+ branch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ enabled = config.get_enabled_subprojects()
+
+ # Check that if it's a worktree on the wrong branch, it's not enabled.
+ self.assertEqual(
+ enabled,
+ [],
+ "Detected unexpected projects %s" % str(enabled)
+ )
+
+ def test_add_invalid_subproject(self):
+ config = LLVMSourceConfig(self.proj, self.temporaryLLVM.repodir)
+ subproj = "not-an-llvm-subproject"
+ subprojPath = self.originalLLVM.repodir # Dummy path
+
+ with self.assertRaises(ValueError) as context:
+ config.update({subproj : Clone(self.proj, subprojPath)})
+
+ self.assertRegexpMatches(str(context.exception),
+ "Unknown llvm subproject %s" % subproj)
+
+ def test_add_each_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ for subproj in subprojs:
+ expectedPath = subprojs[subproj].get_cmake_path(sourcePath)
+ config.update({subproj : self.__get_subproj_repo(subproj)})
+ self.assertTrue(os.path.isdir(expectedPath),
+ "Failed to add subproject %s" % subproj)
+
+ def test_add_all_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ to_add = {}
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs:
+ to_add[subproj] = self.__get_subproj_repo(subproj)
+
+ config.update(to_add)
+
+ for subproj in subprojs:
+ expectedPath = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(expectedPath),
+ "Failed to add subproject %s" % subproj)
+
+ def test_add_some_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ to_add = {}
+ to_add["clang"] = self.__get_subproj_repo("clang")
+ to_add["compiler-rt"] = self.__get_subproj_repo("compiler-rt")
+
+ config.update(to_add)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ self.assertTrue(
+ os.path.isdir(subprojs["clang"].get_cmake_path(sourcePath)),
+ "Failed to add subproject clang")
+ self.assertTrue(
+ os.path.isdir(subprojs["compiler-rt"].get_cmake_path(sourcePath)),
+ "Failed to add subproject compiler-rt")
+
+ def test_add_existing_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lldb"),
+ existingPath,
+ self.temporaryLLVMbranch)
+
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_add_subproject_existing_branch(self):
+ """
+ Test that we can add a subproject that already has the branch that LLVM
+ is on. This can happen for instance if we have added and then removed
+ the subproject and now we're trying to add it again.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ clangRepo = self.__get_subproj_repo("clang")
+ with cd(clangRepo.repodir):
+ # Make sure that the branch that LLVM is on already exists in the
+ # clang repo as well.
+ git("checkout", "-b", self.temporaryLLVMbranch)
+ self.__create_dummy_commit()
+ git("checkout", "master")
+
+ config.update( { "clang" : clangRepo })
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ path = subprojs["clang"].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Failed to add subproject")
+
+ def test_add_subproject_not_a_worktree(self):
+ """
+ Test that we can't update a config to include a subproject that exists
+ but is not a worktree.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+ branch = "different-than-" + self.temporaryLLVMbranch
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ os.makedirs(existingPath)
+
+ with self.assertRaises(EnvironmentError) as context:
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ self.assertRegexpMatches(str(context.exception),
+ "{} already exists but is not a valid subproject directory.*"
+ .format(existingPath))
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_add_subproject_wrong_branch(self):
+ """
+ Test that we can't update a config to include a subproject that exists
+ but is on the wrong branch.
+ """
+ sourcePath = self.temporaryLLVM.repodir
+ branch = "different-than-" + self.temporaryLLVMbranch
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ existingPath = subprojs["lldb"].get_cmake_path(sourcePath)
+ Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lldb"),
+ existingPath,
+ branch)
+
+ with self.assertRaises(EnvironmentError) as context:
+ config.update({ "lldb" : self.__get_subproj_repo("lldb")})
+
+ self.assertRegexpMatches(str(context.exception),
+ "{} already exists but is not a valid subproject directory.*"
+ .format(existingPath))
+
+ # If we got this far, we're probably ok, but let's be pedantic and check
+ # that the subproject is still there
+ self.assertTrue(os.path.isdir(existingPath),
+ "Existing subproject vanished")
+
+ def test_remove_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ lldPath = subprojs["lld"].get_cmake_path(sourcePath)
+ lldWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lld"),
+ lldPath,
+ self.temporaryLLVMbranch)
+
+ clangPath = subprojs["clang"].get_cmake_path(sourcePath)
+ clangWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("clang"),
+ clangPath,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["lld"])
+
+ self.assertFalse(os.path.isdir(lldPath), "Failed to remove subproject")
+ self.assertTrue(os.path.isdir(clangPath), "Removed sibling subproject")
+
+ def test_remove_some_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["compiler-rt", "lld"])
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_remove_all_subprojects(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=subprojs.keys())
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ def test_remove_each_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+ self.assertTrue(os.path.isdir(path), "Failed to create worktree")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ for subproj in subprojs.keys():
+ config.update(remove=[subproj])
+
+ for subproj in subprojs.keys():
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ def test_remove_duplicates(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["compiler-rt", "lld", "lld", "compiler-rt"])
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_remove_invalid_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ subproj = "not-an-llvm-subproject"
+
+ with self.assertRaises(ValueError) as context:
+ config.update(remove=[subproj])
+
+ self.assertRegexpMatches(str(context.exception),
+ "Unknown llvm subproject %s" % subproj)
+
+ def test_remove_inexistent_subproject(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ lldPath = subprojs["lld"].get_cmake_path(sourcePath)
+ lldWorktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo("lld"),
+ lldPath,
+ self.temporaryLLVMbranch)
+
+ clangPath = subprojs["clang"].get_cmake_path(sourcePath)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update(remove=["clang"])
+
+ self.assertFalse(
+ os.path.isdir(clangPath),
+ "Failed to remove subproject")
+ self.assertTrue(os.path.isdir(lldPath), "Removed sibling subproject")
+
+ def test_add_after_remove(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+ lldbRepo = self.__get_subproj_repo("lldb")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+
+ config.update({"lldb" : lldbRepo})
+ self.assertTrue(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to add lldb")
+
+ config.update(remove=["lldb"])
+ self.assertFalse(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to remove lldb")
+
+ config.update({"lldb" : lldbRepo })
+ self.assertTrue(
+ os.path.isdir(subprojs["lldb"].get_cmake_path(sourcePath)),
+ "Failed to add lldb")
+
+ def test_mixed_adds_removes(self):
+ sourcePath = self.temporaryLLVM.repodir
+
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ for subproj in ["clang", "compiler-rt", "lld", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ worktree = Worktree.create(
+ self.proj,
+ self.__get_subproj_repo(subproj),
+ path,
+ self.temporaryLLVMbranch)
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ config.update({
+ "libcxx" : self.__get_subproj_repo("libcxx"),
+ "libcxxabi" : self.__get_subproj_repo("libcxxabi")},
+ ["compiler-rt", "lld"])
+
+ for subproj in ["libcxx", "libcxxabi"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Failed to add subproject")
+
+ for subproj in ["compiler-rt", "lld"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Failed to remove subproject")
+
+ for subproj in ["clang", "lldb", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertTrue(os.path.isdir(path), "Removed sibling subproject")
+
+ def test_simultaneous_add_remove(self):
+ sourcePath = self.temporaryLLVM.repodir
+ subprojs = LLVMSubproject.get_all_subprojects()
+
+ clangRepo = self.__get_subproj_repo("clang")
+ lldRepo = self.__get_subproj_repo("lld")
+ libunwindRepo = self.__get_subproj_repo("libunwind")
+
+ config = LLVMSourceConfig(self.proj, sourcePath)
+ with self.assertRaises(ValueError) as context:
+ config.update(
+ { "clang" : clangRepo, "lld" : lldRepo, "libunwind" :
+ libunwindRepo}, ["libcxx", "lld", "libcxxabi"])
+
+ self.assertEqual(str(context.exception),
+ "Can't add and remove lld at the same time")
+
+ # Make sure we didn't add any of the others either
+ for subproj in ["clang", "libunwind"]:
+ path = subprojs[subproj].get_cmake_path(sourcePath)
+ self.assertFalse(
+ os.path.isdir(path),
+ "Incorrectly added subproject")
+
+ # TODO: test with a different dictionary than the default one (not
+ # necessarily containing subprojects - it can contain "potato", "banana" and
+ # "gazpacho" for all we care); in fact, it would probably be best to move the
+ # existing tests to that...
+
+ # TODO: test that CMake gets our layout
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unittests/testllvmsubprojects.py b/tests/unittests/testllvmsubprojects.py
new file mode 100644
index 0000000..c39e7ae
--- /dev/null
+++ b/tests/unittests/testllvmsubprojects.py
@@ -0,0 +1,64 @@
+import os
+import unittest
+
+from modules.llvm import LLVMSubproject
+
+
+class TestLLVMSubproject(unittest.TestCase):
+ """Test that we have the right info on LLVM subprojects."""
+
+ def test_build_paths(self):
+ subprojects = LLVMSubproject.get_all_subprojects()
+
+ self.assertEqual(subprojects["clang"].cmake_path,
+ os.path.join("tools", "clang"))
+ self.assertEqual(subprojects["compiler-rt"].cmake_path,
+ os.path.join("projects", "compiler-rt"))
+ self.assertEqual(subprojects["libcxx"].cmake_path,
+ os.path.join("projects", "libcxx"))
+ self.assertEqual(subprojects["libcxxabi"].cmake_path,
+ os.path.join("projects", "libcxxabi"))
+ self.assertEqual(subprojects["libunwind"].cmake_path,
+ os.path.join("projects", "libunwind"))
+ self.assertEqual(subprojects["lld"].cmake_path,
+ os.path.join("tools", "lld"))
+ self.assertEqual(subprojects["lldb"].cmake_path,
+ os.path.join("tools", "lldb"))
+ self.assertEqual(subprojects["test-suite"].cmake_path,
+ os.path.join("projects", "test-suite"))
+
+ def test_cmake_vars(self):
+ subprojects = LLVMSubproject.get_all_subprojects()
+
+ self.assertEqual(
+ subprojects["clang"].cmake_var,
+ "LLVM_TOOL_CLANG_BUILD")
+ self.assertEqual(subprojects["compiler-rt"].cmake_var,
+ "LLVM_TOOL_COMPILER_RT_BUILD")
+ self.assertEqual(
+ subprojects["libcxx"].cmake_var,
+ "LLVM_TOOL_LIBCXX_BUILD")
+ self.assertEqual(subprojects["libcxxabi"].cmake_var,
+ "LLVM_TOOL_LIBCXXABI_BUILD")
+ self.assertEqual(
+ subprojects["libunwind"].cmake_var,
+ "LLVM_TOOL_LIBUNWIND_BUILD")
+ self.assertEqual(subprojects["lld"].cmake_var, "LLVM_TOOL_LLD_BUILD")
+ self.assertEqual(subprojects["lldb"].cmake_var, "LLVM_TOOL_LLDB_BUILD")
+ self.assertEqual(subprojects["test-suite"].cmake_var, None)
+
+ def test_get_cmake_path(self):
+ subproject = LLVMSubproject(
+ os.path.join(
+ "path",
+ "to",
+ "subproj"),
+ None)
+ cmake_path = subproject.get_cmake_path(os.path.join("llvm", "sources"))
+
+ self.assertEqual(
+ str(cmake_path),
+ os.path.join("llvm", "sources", "path", "to", "subproj"))
+
+if __name__ == "__main__":
+ unittest.main()