|
18 | 18 | import subprocess |
19 | 19 |
|
20 | 20 |
|
| 21 | +PROJECT_ROOT = os.path.abspath( |
| 22 | + os.path.join(os.path.dirname(__file__), '..')) |
21 | 23 | LOCAL_REMOTE_ENV = 'GOOGLE_CLOUD_TESTING_REMOTE' |
22 | 24 | LOCAL_BRANCH_ENV = 'GOOGLE_CLOUD_TESTING_BRANCH' |
23 | 25 | IN_TRAVIS_ENV = 'TRAVIS' |
24 | 26 | TRAVIS_PR_ENV = 'TRAVIS_PULL_REQUEST' |
25 | 27 | TRAVIS_BRANCH_ENV = 'TRAVIS_BRANCH' |
| 28 | +PACKAGE_PREFIX = 'google-cloud-' |
26 | 29 |
|
27 | 30 |
|
28 | 31 | def in_travis(): |
@@ -159,3 +162,79 @@ def get_changed_packages(blob_name1, blob_name2, package_list): |
159 | 162 | result.add(file_root) |
160 | 163 |
|
161 | 164 | return sorted(result) |
| 165 | + |
| 166 | + |
| 167 | +def get_required_packages(file_contents): |
| 168 | + """Get required packages from a requirements.txt file. |
| 169 | +
|
| 170 | + .. note:: |
| 171 | +
|
| 172 | + This could be done in a bit more complete way via |
| 173 | + https://pypi.python.org/pypi/requirements-parser |
| 174 | +
|
| 175 | + :type file_contents: str |
| 176 | + :param file_contents: The contents of a requirements.txt file. |
| 177 | +
|
| 178 | + :rtype: list |
| 179 | + :returns: The list of required packages. |
| 180 | + """ |
| 181 | + requirements = file_contents.strip().split('\n') |
| 182 | + result = [] |
| 183 | + for required in requirements: |
| 184 | + parts = required.split() |
| 185 | + result.append(parts[0]) |
| 186 | + return result |
| 187 | + |
| 188 | + |
| 189 | +def get_dependency_graph(package_list): |
| 190 | + """Get a directed graph of package dependencies. |
| 191 | +
|
| 192 | + :type package_list: list |
| 193 | + :param package_list: The list of **all** valid packages. |
| 194 | +
|
| 195 | + :rtype: dict |
| 196 | + :returns: A dictionary where keys are packages and values are |
| 197 | + the set of packages that depend on the key. |
| 198 | + """ |
| 199 | + result = {package: set() for package in package_list} |
| 200 | + for package in package_list: |
| 201 | + reqs_file = os.path.join(PROJECT_ROOT, package, |
| 202 | + 'requirements.txt') |
| 203 | + with open(reqs_file, 'r') as file_obj: |
| 204 | + file_contents = file_obj.read() |
| 205 | + |
| 206 | + requirements = get_required_packages(file_contents) |
| 207 | + for requirement in requirements: |
| 208 | + if not requirement.startswith(PACKAGE_PREFIX): |
| 209 | + continue |
| 210 | + _, req_package = requirement.split(PACKAGE_PREFIX) |
| 211 | + req_package = req_package.replace('-', '_') |
| 212 | + result[req_package].add(package) |
| 213 | + |
| 214 | + return result |
| 215 | + |
| 216 | + |
| 217 | +def follow_dependencies(subset, package_list): |
| 218 | + """Get a directed graph of packages dependency. |
| 219 | +
|
| 220 | + :type subset: list |
| 221 | + :param subset: List of a subset of package names. |
| 222 | +
|
| 223 | + :type package_list: list |
| 224 | + :param package_list: The list of **all** valid packages. |
| 225 | +
|
| 226 | + :rtype: list |
| 227 | + :returns: An expanded list of packages containing everything |
| 228 | + in ``subset`` and any packages that depend on those. |
| 229 | + """ |
| 230 | + dependency_graph = get_dependency_graph(package_list) |
| 231 | + |
| 232 | + curr_pkgs = None |
| 233 | + updated_pkgs = set(subset) |
| 234 | + while curr_pkgs != updated_pkgs: |
| 235 | + curr_pkgs = updated_pkgs |
| 236 | + updated_pkgs = set(curr_pkgs) |
| 237 | + for package in curr_pkgs: |
| 238 | + updated_pkgs.update(dependency_graph[package]) |
| 239 | + |
| 240 | + return sorted(curr_pkgs) |
0 commit comments