Joshua Haberman | e59d2c8 | 2021-04-05 10:47:53 -0700 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # |
Joshua Haberman | 823eb09 | 2021-04-05 12:26:41 -0700 | [diff] [blame] | 3 | # Copyright (c) 2009-2021, Google LLC |
Joshua Haberman | e59d2c8 | 2021-04-05 10:47:53 -0700 | [diff] [blame] | 4 | # All rights reserved. |
| 5 | # |
| 6 | # Redistribution and use in source and binary forms, with or without |
| 7 | # modification, are permitted provided that the following conditions are met: |
| 8 | # * Redistributions of source code must retain the above copyright |
| 9 | # notice, this list of conditions and the following disclaimer. |
| 10 | # * Redistributions in binary form must reproduce the above copyright |
| 11 | # notice, this list of conditions and the following disclaimer in the |
| 12 | # documentation and/or other materials provided with the distribution. |
| 13 | # * Neither the name of Google LLC nor the |
| 14 | # names of its contributors may be used to endorse or promote products |
| 15 | # derived from this software without specific prior written permission. |
| 16 | # |
| 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| 18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 20 | # DISCLAIMED. IN NO EVENT SHALL Google LLC BE LIABLE FOR ANY |
| 21 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 22 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 23 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 24 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 27 | |
Adam Cozzette | 7189539 | 2022-09-29 20:46:08 +0000 | [diff] [blame] | 28 | """Shared code for validating staleness_test() rules. |
Joshua Haberman | 5f74d43 | 2021-04-05 10:54:04 -0700 | [diff] [blame] | 29 | |
Adam Cozzette | 7189539 | 2022-09-29 20:46:08 +0000 | [diff] [blame] | 30 | This code is used by test scripts generated from staleness_test() rules. |
Joshua Haberman | 5f74d43 | 2021-04-05 10:54:04 -0700 | [diff] [blame] | 31 | """ |
| 32 | |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 33 | from __future__ import absolute_import |
| 34 | from __future__ import print_function |
| 35 | |
Adam Cozzette | f50e8b2 | 2022-12-27 18:18:55 -0800 | [diff] [blame] | 36 | import difflib |
Joshua Haberman | e3f41de | 2020-10-17 13:15:58 -0700 | [diff] [blame] | 37 | import sys |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 38 | import os |
| 39 | from shutil import copyfile |
| 40 | |
| 41 | |
| 42 | class _FilePair(object): |
| 43 | """Represents a single (target, generated) file pair.""" |
| 44 | |
| 45 | def __init__(self, target, generated): |
| 46 | self.target = target |
| 47 | self.generated = generated |
| 48 | |
| 49 | |
| 50 | class Config(object): |
| 51 | """Represents the configuration for a single staleness test target.""" |
| 52 | |
| 53 | def __init__(self, file_list): |
| 54 | # Duplicate to avoid modifying our arguments. |
| 55 | file_list = list(file_list) |
| 56 | |
| 57 | # The file list contains a few other bits of information at the end. |
| 58 | # This is packed by the code in build_defs.bzl. |
| 59 | self.target_name = file_list.pop() |
| 60 | self.package_name = file_list.pop() |
| 61 | self.pattern = file_list.pop() |
| 62 | |
| 63 | self.file_list = file_list |
| 64 | |
| 65 | |
| 66 | def _GetFilePairs(config): |
| 67 | """Generates the list of file pairs. |
| 68 | |
| 69 | Args: |
| 70 | config: a Config object representing this target's config. |
| 71 | |
| 72 | Returns: |
| 73 | A list of _FilePair objects. |
| 74 | """ |
| 75 | |
| 76 | ret = [] |
| 77 | |
Joshua Haberman | e3f41de | 2020-10-17 13:15:58 -0700 | [diff] [blame] | 78 | has_bazel_genfiles = os.path.exists("bazel-bin") |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 79 | |
| 80 | for filename in config.file_list: |
| 81 | target = os.path.join(config.package_name, filename) |
| 82 | generated = os.path.join(config.package_name, config.pattern % filename) |
| 83 | if has_bazel_genfiles: |
Joshua Haberman | e3f41de | 2020-10-17 13:15:58 -0700 | [diff] [blame] | 84 | generated = os.path.join("bazel-bin", generated) |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 85 | |
| 86 | # Generated files should always exist. Blaze should guarantee this before |
| 87 | # we are run. |
| 88 | if not os.path.isfile(generated): |
| 89 | print("Generated file '%s' does not exist." % generated) |
| 90 | print("Please run this command to generate it:") |
| 91 | print(" bazel build %s:%s" % (config.package_name, config.target_name)) |
Joshua Haberman | e3f41de | 2020-10-17 13:15:58 -0700 | [diff] [blame] | 92 | sys.exit(1) |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 93 | ret.append(_FilePair(target, generated)) |
| 94 | |
| 95 | return ret |
| 96 | |
| 97 | |
| 98 | def _GetMissingAndStaleFiles(file_pairs): |
| 99 | """Generates lists of missing and stale files. |
| 100 | |
| 101 | Args: |
| 102 | file_pairs: a list of _FilePair objects. |
| 103 | |
| 104 | Returns: |
| 105 | missing_files: a list of _FilePair objects representing missing files. |
| 106 | These target files do not exist at all. |
| 107 | stale_files: a list of _FilePair objects representing stale files. |
| 108 | These target files exist but have stale contents. |
| 109 | """ |
| 110 | |
| 111 | missing_files = [] |
| 112 | stale_files = [] |
| 113 | |
| 114 | for pair in file_pairs: |
| 115 | if not os.path.isfile(pair.target): |
| 116 | missing_files.append(pair) |
| 117 | continue |
| 118 | |
Joshua Haberman | e3f41de | 2020-10-17 13:15:58 -0700 | [diff] [blame] | 119 | with open(pair.generated) as g, open(pair.target) as t: |
| 120 | if g.read() != t.read(): |
| 121 | stale_files.append(pair) |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 122 | |
| 123 | return missing_files, stale_files |
| 124 | |
| 125 | |
| 126 | def _CopyFiles(file_pairs): |
| 127 | """Copies all generated files to the corresponding target file. |
| 128 | |
| 129 | The target files must be writable already. |
| 130 | |
| 131 | Args: |
| 132 | file_pairs: a list of _FilePair objects that we want to copy. |
| 133 | """ |
| 134 | |
| 135 | for pair in file_pairs: |
| 136 | target_dir = os.path.dirname(pair.target) |
| 137 | if not os.path.isdir(target_dir): |
| 138 | os.makedirs(target_dir) |
| 139 | copyfile(pair.generated, pair.target) |
| 140 | |
| 141 | |
| 142 | def FixFiles(config): |
| 143 | """Implements the --fix option: overwrites missing or out-of-date files. |
| 144 | |
| 145 | Args: |
| 146 | config: the Config object for this test. |
| 147 | """ |
| 148 | |
| 149 | file_pairs = _GetFilePairs(config) |
| 150 | missing_files, stale_files = _GetMissingAndStaleFiles(file_pairs) |
| 151 | |
| 152 | _CopyFiles(stale_files + missing_files) |
| 153 | |
| 154 | |
| 155 | def CheckFilesMatch(config): |
| 156 | """Checks whether each target file matches the corresponding generated file. |
| 157 | |
| 158 | Args: |
| 159 | config: the Config object for this test. |
| 160 | |
| 161 | Returns: |
| 162 | None if everything matches, otherwise a string error message. |
| 163 | """ |
| 164 | |
| 165 | diff_errors = [] |
| 166 | |
| 167 | file_pairs = _GetFilePairs(config) |
| 168 | missing_files, stale_files = _GetMissingAndStaleFiles(file_pairs) |
| 169 | |
| 170 | for pair in missing_files: |
| 171 | diff_errors.append("File %s does not exist" % pair.target) |
| 172 | continue |
| 173 | |
| 174 | for pair in stale_files: |
Adam Cozzette | f50e8b2 | 2022-12-27 18:18:55 -0800 | [diff] [blame] | 175 | with open(pair.generated) as g, open(pair.target) as t: |
| 176 | diff = ''.join(difflib.unified_diff(g.read().splitlines(keepends=True), |
| 177 | t.read().splitlines(keepends=True))) |
| 178 | diff_errors.append("File %s is out of date:\n%s" % (pair.target, diff)) |
Josh Haberman | 9abff2b | 2018-11-03 19:37:45 -0700 | [diff] [blame] | 179 | |
| 180 | if diff_errors: |
| 181 | error_msg = "Files out of date!\n\n" |
| 182 | error_msg += "To fix run THIS command:\n" |
| 183 | error_msg += " bazel-bin/%s/%s --fix\n\n" % (config.package_name, |
| 184 | config.target_name) |
| 185 | error_msg += "Errors:\n" |
| 186 | error_msg += " " + "\n ".join(diff_errors) |
| 187 | return error_msg |
| 188 | else: |
| 189 | return None |