blob: 4887f248557b3a4fb87f26518269f0bcdf03ffd1 [file] [log] [blame]
Joshua Habermane59d2c82021-04-05 10:47:53 -07001#!/usr/bin/python
2#
Joshua Haberman823eb092021-04-05 12:26:41 -07003# Copyright (c) 2009-2021, Google LLC
Joshua Habermane59d2c82021-04-05 10:47:53 -07004# 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 Haberman9abff2b2018-11-03 19:37:45 -070027
Adam Cozzette71895392022-09-29 20:46:08 +000028"""Shared code for validating staleness_test() rules.
Joshua Haberman5f74d432021-04-05 10:54:04 -070029
Adam Cozzette71895392022-09-29 20:46:08 +000030This code is used by test scripts generated from staleness_test() rules.
Joshua Haberman5f74d432021-04-05 10:54:04 -070031"""
32
Josh Haberman9abff2b2018-11-03 19:37:45 -070033from __future__ import absolute_import
34from __future__ import print_function
35
Adam Cozzettef50e8b22022-12-27 18:18:55 -080036import difflib
Joshua Habermane3f41de2020-10-17 13:15:58 -070037import sys
Josh Haberman9abff2b2018-11-03 19:37:45 -070038import os
39from shutil import copyfile
40
41
42class _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
50class 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
66def _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 Habermane3f41de2020-10-17 13:15:58 -070078 has_bazel_genfiles = os.path.exists("bazel-bin")
Josh Haberman9abff2b2018-11-03 19:37:45 -070079
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 Habermane3f41de2020-10-17 13:15:58 -070084 generated = os.path.join("bazel-bin", generated)
Josh Haberman9abff2b2018-11-03 19:37:45 -070085
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 Habermane3f41de2020-10-17 13:15:58 -070092 sys.exit(1)
Josh Haberman9abff2b2018-11-03 19:37:45 -070093 ret.append(_FilePair(target, generated))
94
95 return ret
96
97
98def _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 Habermane3f41de2020-10-17 13:15:58 -0700119 with open(pair.generated) as g, open(pair.target) as t:
120 if g.read() != t.read():
121 stale_files.append(pair)
Josh Haberman9abff2b2018-11-03 19:37:45 -0700122
123 return missing_files, stale_files
124
125
126def _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
142def 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
155def 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 Cozzettef50e8b22022-12-27 18:18:55 -0800175 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 Haberman9abff2b2018-11-03 19:37:45 -0700179
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