1
2"""
3    Copyright Kristjan Kongas 2020
4
5    Boost Software License - Version 1.0 - August 17th, 2003
6
7    Permission is hereby granted, free of charge, to any person or organization
8    obtaining a copy of the software and accompanying documentation covered by
9    this license (the "Software") to use, reproduce, display, distribute,
10    execute, and transmit the Software, and to prepare derivative works of the
11    Software, and to permit third-parties to whom the Software is furnished to
12    do so, all subject to the following:
13
14    The copyright notices in the Software and this entire statement, including
15    the above license grant, this restriction and the following disclaimer,
16    must be included in all copies of the Software, in whole or in part, and
17    all derivative works of the Software, unless such copies or derivative
18    works are solely in the form of machine-executable object code generated by
19    a source language processor.
20
21    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23    FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
24    SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
25    FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
26    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27    DEALINGS IN THE SOFTWARE.
28"""
29
30from pathlib import Path
31import os, shutil, subprocess, sys, argparse, json, functools, random
32print = functools.partial(print, flush=True)
33
34description = """Batch-test combinations of compilers and cmake settings. CMake root directory needs to contain a file called `.release_tests.json`. Example contents:
35{
36  "compiler_prefix": "/usr/bin/",
37  "compilers": [
38    {"cc": "gcc", "cxx": "g++", "standards": [11, 14, 17, 20]},
39    {"cc": "clang", "cxx": "clang++", "standards": [11, 14, 17, 20]},
40    {"generator": "Unix Makefiles", "standards": [11, 14, 17, 20]}
41  ],
42  "cmake_build_types": ["Debug", "", "Release"]
43}
44
45"""
46
47def main():
48    parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawTextHelpFormatter)
49    parser.add_argument("cmake_root")
50    args = parser.parse_args()
51    cmake_root = Path(args.cmake_root).absolute()
52
53    # A separate build directory is created to ensure clearing cache doesn't unintentionally remove other files
54    build_dir = "build_" + str(random.randint(0, 100 * 1000 * 1000))
55    os.mkdir(build_dir)
56    os.chdir(build_dir)
57
58    json_path = cmake_root / ".release_tests.json"
59    with open(json_path, "r") as json_file:
60        setup = json.load(json_file)
61        try:
62            batch_test(cmake_root, setup.get("compiler_prefix"), setup["compilers"], setup["cmake_build_types"])
63        except KeyboardInterrupt:
64            print("Tests interrupted")
65
66    os.chdir("..")
67    shutil.rmtree(build_dir)
68
69
70def batch_test(cmake_root, prefix, compilers, cmake_build_types):
71    for comp in compilers:
72        assert ("cc" in comp and "cxx" in comp) or "generator" in comp
73
74        print()
75        print()
76        if "cc" in comp and "cxx" in comp:
77            print("Testing {} and {}".format(comp["cc"], comp["cxx"]))
78        if "generator" in comp:
79            print("Testing {}".format(comp["generator"]))
80        print()
81        for standard in comp["standards"]:
82            for build_type in cmake_build_types:
83                print_build = "Default" if build_type == "" else build_type
84                print("Testing " + print_build + " mode, C++" + str(standard))
85
86                clear_cmake_cache()
87
88                # Build and test
89                cmd = [
90                    "cmake", cmake_root,
91                    "-D", "CMAKE_CXX_STANDARD=" + str(standard),
92                    "-D", "CMAKE_BUILD_TYPE=" + build_type
93                ]
94
95                env = dict(os.environ)
96                if "cc" in comp and "cxx" in comp:
97                    env.update({"CC": str(Path(prefix) / comp["cc"]), "CXX": str(Path(prefix) / comp["cxx"])})
98                if "generator" in comp:
99                    env.update({"CMAKE_GENERATOR": comp["generator"]})
100
101                run(cmd, env=env)
102                run(["cmake", "--build", "."])
103                run([sys.executable, "tests/run_standard_tests.py"])
104
105    print()
106    print("++++++++++        SUCCESS        ++++++++++")
107    print()
108
109
110def run(cmd, env=None):
111    result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
112    if result.returncode != 0:
113        print("STDOUT:")
114        print(str(result.stdout, "utf-8"))
115        print()
116        print("STDERR:")
117        print(str(result.stderr, "utf-8"))
118        print()
119        print()
120        print("----------        FAILURE        ----------")
121        print()
122        sys.exit(1)
123
124
125def clear_cmake_cache():
126    for dir_path, _, files in os.walk("."):
127        if dir_path[-len("CMakeLists"):] == "CMakeLists":
128            shutil.rmtree(dir_path)
129        for file in files:
130            if file == "CMakeCache.txt":
131                os.remove(Path(dir_path) / file)
132
133
134if __name__ == "__main__":
135    main()
136