Belle II Software development
test_light_dependencies.py
1#!/usr/bin/env python3
2
3
10
11import os
12import glob
13import re
14import sys
15import subprocess
16import basf2
17from time import time
18from b2test_utils import skip_test_if_central
19
20"""
21Check that no light-release-breaking dependencies have been added.
22
23If you are failing this test you have managed to break light builds, please
24check with the light release manager for more information.
25"""
26
27
28def get_sconscripts(package):
29 """glob to get the path to all sconscript files
30
31 Parameters:
32 package (str): the name of the package directory
33
34 Returns:
35 list(str): a list of all SConscript files
36 """
37 path_to_package = basf2.find_file(package)
38 return glob.glob(f"{path_to_package}/**/SConscript", recursive=True)
39
40
41def get_dependencies(sconscript_filename):
42 """grab all of the LIBS in the sconscript file
43
44 Parameters:
45 sconscript_filename (str): the path to the sconscript file
46
47 Returns:
48 set(str): a set of packages in the env['LIBS'] in this file
49 """
50 # get the " env['LIBS'] " line in the file
51 # (if no LIBS are in the SConscript file then ignore)
52 with open(sconscript_filename) as fi:
53 file_data = fi.read()
54 dependencies = re.search(
55 r"env\['LIBS'\] *= *\[\n*((?:.*?|\n)*?)\n*\]", file_data
56 )
57 if dependencies:
58 dependencies = dependencies.group(1)
59 else:
60 dependencies = ""
61
62 # clean up the list returned from the regex: only want the names of the
63 # basf2 libs (i.e. strings of the form: packagename_module)
64 dependencies = [
65 lb.replace("'", "").replace('"', "").strip()
66 for lb in re.split(",|\n", dependencies)
67 if not lb.count("#") and not lb == ''
68 ]
69 # now just trim only the packagename's from the library names
70 # and make a python set of them
71 package_dependencies = {lb.split("_")[0] for lb in dependencies}
72
73 return package_dependencies
74
75
76def check_dependencies(forbidden, sconscript_files, error=""):
77 """Check that there are no dependencies that are forbidden.
78
79 Parameters:
80 forbidden set(str): a set of packages we don't want to depend on sconscript_files list(str): a list of paths to sconscript files
81 error str: optionally specify the error message
82
83 Returns:
84 int: count of the number of forbidden dependencies found
85 """
86 n_forbidden = 0
87 for sconscript_filename in sconscript_files:
88
89 # get the dependencies from this SConscript file
90 this_dependencies = get_dependencies(sconscript_filename)
91
92 # using the nice and cool and mathsy python set syntax: figure out if
93 # we depend on anything in the forbidden packages list
94 forbidden_packages_dependencies = forbidden.intersection(this_dependencies)
95
96 # if so then throw the error
97 if len(forbidden_packages_dependencies) != 0:
98 print(
99 f"The sconscript file {sconscript_filename} depends on",
100 forbidden_packages_dependencies,
101 error,
102 )
103 n_forbidden += 1
104 return n_forbidden
105
106
107if __name__ == "__main__":
108
109 # skip this for central releases because we have no git repository there
110 # (and it's too late to fix anyhow)
111 skip_test_if_central()
112
113 # grab all of the light release packages
114 # (defined by the .light file for the sparse checkout)
115 light_file = basf2.find_file(".light")
116 with open(light_file) as fi:
117 light_packages = [li.strip().replace("/", "") for li in fi if li.strip().endswith("/")]
118
119 # we also need all packages (to compare), for this use b2code-package-list.
120 # note that b2code-package-list has to be executed from the top basf2 directory,
121 # since it requires the presence of the .release file: we use cwd for this.
122 all_packages = subprocess.check_output(
123 ["b2code-package-list"], cwd=os.path.dirname(light_file),
124 )
125 all_packages = all_packages.decode("utf-8").strip().split(" ")
126
127 # the forbidden packages
128 non_light_packages = set(all_packages).difference(set(light_packages))
129
130 # sum up the return codes
131 return_code_sum = 0
132
133 # run the check over all packages - just a dumb nested loop
134 start = time()
135 for package in light_packages:
136 sconscript_files = get_sconscripts(package)
137 return_code_sum += check_dependencies(
138 non_light_packages, sconscript_files, "This breaks light releases! Sorry."
139 )
140
141 # test finished, now report
142 print("Test of light dependencies, the loop took:", time() - start, "seconds to run")
143 print("There were", return_code_sum, "forbidden dependencies")
144
145 sys.exit(return_code_sum)
146