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
81 sconscript_files list(str): a list of paths to sconscript files
82 error str: optionally specify the error message
83
84 Returns:
85 int: count of the number of forbidden dependencies found
86 """
87 n_forbidden = 0
88 for sconscript_filename in sconscript_files:
89
90 # get the dependencies from this SConscript file
91 this_dependencies = get_dependencies(sconscript_filename)
92
93 # using the nice and cool and mathsy python set syntax: figure out if
94 # we depend on anything in the forbidden packages list
95 forbidden_packages_dependencies = forbidden.intersection(this_dependencies)
96
97 # if so then throw the error
98 if len(forbidden_packages_dependencies) != 0:
99 print(
100 f"The sconscript file {sconscript_filename} depends on",
101 forbidden_packages_dependencies,
102 error,
103 )
104 n_forbidden += 1
105 return n_forbidden
106
107
108if __name__ == "__main__":
109
110 # skip this for central releases because we have no git repository there
111 # (and it's too late to fix anyhow)
112 skip_test_if_central()
113
114 # grab all of the light release packages
115 # (defined by the .light file for the sparse checkout)
116 light_file = basf2.find_file(".light")
117 with open(light_file) as fi:
118 light_packages = [li.strip().replace("/", "") for li in fi if li.strip().endswith("/")]
119
120 # we also need all packages (to compare), for this use b2code-package-list.
121 # note that b2code-package-list has to be executed from the top basf2 directory,
122 # since it requires the presence of the .release file: we use cwd for this.
123 all_packages = subprocess.check_output(
124 ["b2code-package-list"], cwd=os.path.dirname(light_file),
125 )
126 all_packages = all_packages.decode("utf-8").strip().split(" ")
127
128 # the forbidden packages
129 non_light_packages = set(all_packages).difference(set(light_packages))
130
131 # sum up the return codes
132 return_code_sum = 0
133
134 # run the check over all packages - just a dumb nested loop
135 start = time()
136 for package in light_packages:
137 sconscript_files = get_sconscripts(package)
138 return_code_sum += check_dependencies(
139 non_light_packages, sconscript_files, "This breaks light releases! Sorry."
140 )
141
142 # test finished, now report
143 print("Test of light dependencies, the loop took:", time() - start, "seconds to run")
144 print("There were", return_code_sum, "forbidden dependencies")
145
146 sys.exit(return_code_sum)