OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Checks third-party licenses for the purposes of the Android WebView build. | |
7 | |
8 The Android tree includes a snapshot of Chromium in order to power the system | |
9 WebView. This tool checks that all code uses open-source licenses compatible | |
10 with Android, and that we meet the requirements of those licenses. It can also | |
11 be used to generate an Android NOTICE file for the third-party code. | |
12 | |
13 It makes use of src/tools/licenses.py and the README.chromium files on which | |
14 it depends. It also makes use of a data file, third_party_files_whitelist.txt, | |
15 which whitelists indicidual files which contain third-party code but which | |
16 aren't in a third-party directory with a README.chromium file. | |
17 """ | |
18 | |
19 import optparse | |
20 import os | |
21 import re | |
22 import subprocess | |
23 import sys | |
24 import textwrap | |
25 | |
26 | |
27 REPOSITORY_ROOT = os.path.abspath(os.path.join( | |
28 os.path.dirname(__file__), '..', '..')) | |
29 | |
30 sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools')) | |
31 import licenses | |
32 | |
33 | |
34 def _CheckLicenseHeaders(directory_list, whitelisted_files): | |
35 """Checks that all files which are not in a listed third-party directory, | |
36 and which do not use the standard Chromium license, are listed. | |
Nico
2012/08/09 15:02:29
s/are listed/are whitelisted/?
Steve Block
2012/08/09 17:24:59
Done.
| |
37 Args: | |
38 directory_list: The list of directories. | |
39 whitelisted_files: The list of files. | |
40 Returns: | |
41 True if all files with non-standard license headers are listed and the | |
42 file list contains no stale entries, otherwise false. | |
43 """ | |
44 | |
45 # Matches one of ... | |
46 # - '[Cc]opyright', but not when followed by | |
47 # ' (c) 20[0-9][0-9] The Chromium Authors.', with optional date range | |
48 # - '([Cc]) (19|20)[0-9][0-9]', but not when preceeded by the word copyright, | |
49 # as this is handled above | |
50 regex = '[Cc]opyright(?!( \(c\))? 20[0-9][0-9](-20[01][0189])? ' \ | |
Nico
2012/08/09 15:02:29
Why not 20[0-9][0-9] for the second year in a rang
Steve Block
2012/08/09 17:24:59
Done.
| |
51 'The Chromium Authors\. All rights reserved\.)' \ | |
52 '|' \ | |
53 '(?<!(pyright |opyright))\([Cc]\) (19|20)[0-9][0-9]' | |
54 | |
55 args = ['grep', | |
56 '-rPlI', | |
57 '--exclude-dir', 'third_party', | |
58 '--exclude-dir', 'out', | |
59 '--exclude-dir', '.git', | |
60 regex, | |
61 '.'] | |
Nico
2012/08/09 15:02:29
Do you set / check the cwd anywhere?
Steve Block
2012/08/09 17:24:59
I set the cwd on the line below. But it turns out
| |
62 p = subprocess.Popen(args=args, cwd=REPOSITORY_ROOT, stdout=subprocess.PIPE) | |
63 files = p.communicate()[0].splitlines() | |
64 | |
65 # Exclude files under listed directories and some known offendors. | |
Nico
2012/08/09 15:02:29
"offenders" I think
Steve Block
2012/08/09 17:24:59
Done.
| |
66 offending_files = [] | |
67 for x in files: | |
68 x = os.path.normpath(x) | |
69 is_in_listed_directory = False | |
70 for y in directory_list: | |
71 if x.startswith(y): | |
72 is_in_listed_directory = True | |
73 break | |
74 if (not is_in_listed_directory | |
75 # Ignore these tools. | |
76 and not x.startswith('android_webview/tools/') | |
77 # This is a build intermediate directory. | |
78 and not x.startswith('chrome/app/theme/google_chrome/') | |
79 # This is a test output directory. | |
80 and not x.startswith('data/page_cycler/') | |
81 # 'Copyright' appears in strings. | |
82 and not x.startswith('chrome/app/resources/')): | |
Nico
2012/08/09 15:02:29
nit: Could you instead say
directory_list = dir
Steve Block
2012/08/09 17:24:59
Done.
| |
83 offending_files += [x] | |
Nico
2012/08/09 15:02:29
.append
Steve Block
2012/08/09 17:24:59
Done.
| |
84 | |
85 result = True | |
Nico
2012/08/09 15:02:29
s/result/all_files_valid/
Steve Block
2012/08/09 17:24:59
Done.
| |
86 unknown = set(offending_files) - set(whitelisted_files) | |
87 if unknown: | |
88 print 'The following files contain a third-party license but are not in ' \ | |
89 'a listed third-party directory and are not whitelisted. You must ' \ | |
90 'add the following files to the whitelist.\n%s' % \ | |
91 '\n'.join(sorted(unknown)) | |
92 result = False | |
93 | |
94 stale = set(whitelisted_files) - set(offending_files) | |
95 if stale: | |
96 print 'The following files are whitelisted unnecessarily. You must ' \ | |
97 ' remove the following files from the whitelist list.\n%s' % \ | |
Nico
2012/08/09 15:02:29
s/whitelist list/whitelist/
Steve Block
2012/08/09 17:24:59
Done.
| |
98 '\n'.join(sorted(stale)) | |
99 result = False | |
100 | |
101 return result | |
102 | |
103 | |
104 def _ReadFile(path): | |
105 """Reads a file from disk. | |
106 Args: | |
107 path: The path of the file to read, relative to the root of the repository. | |
108 Returns: | |
109 The contents of the file as a string. | |
110 """ | |
111 | |
112 with file(os.path.join(REPOSITORY_ROOT, path), 'r') as f: | |
113 lines = f.read() | |
Nico
2012/08/09 15:02:29
fyi: if you write
return open(...path...).read(
Steve Block
2012/08/09 17:24:59
Done.
| |
114 return lines | |
115 | |
116 | |
117 def _Scan(): | |
118 """Checks that license meta-data is present for all third-party code. | |
119 Returns: | |
120 Whether the check succeeded. | |
121 """ | |
122 | |
123 third_party_dirs = _FindThirdPartyDirs() | |
124 | |
125 # First, check designated third-party directories using src/tools/licenses.py. | |
126 result = True | |
Nico
2012/08/09 15:02:29
s/result/all_licenses_valid/
Steve Block
2012/08/09 17:24:59
Done.
| |
127 for path in sorted(third_party_dirs): | |
128 try: | |
129 licenses.ParseDir(path) | |
130 except licenses.LicenseError, e: | |
131 print 'Got LicenseError "%s" while scanning %s' % (e, path) | |
132 result = False | |
133 | |
134 # Second, check for non-standard license text. | |
135 files_data = _ReadFile(os.path.join('android_webview', 'tools', | |
136 'third_party_files_whitelist.txt')) | |
137 whitelisted_files = [] | |
138 for line in files_data.splitlines(): | |
139 match = re.match(r'([^#\s]*)', line) | |
140 if match and not len(match.group(1)) == 0: | |
Nico
2012/08/09 15:02:29
if you say + instead of *, you don't need the seco
Steve Block
2012/08/09 17:24:59
Done.
| |
141 whitelisted_files += [match.group(1)] | |
Nico
2012/08/09 15:02:29
append
Steve Block
2012/08/09 17:24:59
Done.
| |
142 return _CheckLicenseHeaders(third_party_dirs, whitelisted_files) and result | |
143 | |
144 | |
145 def _FindThirdPartyDirs(): | |
Nico
2012/08/09 15:02:29
Move above _Scan
Steve Block
2012/08/09 17:24:59
Done.
| |
146 """Gets the list of third-party directories. | |
147 Returns: | |
148 The list of third-party directories. | |
149 """ | |
150 | |
151 prune_paths = [ | |
152 # Placeholder directory, no third-party code. | |
153 os.path.join('third_party', 'adobe'), | |
154 # Apache 2.0 license. See | |
155 # https://code.google.com/p/chromium/issues/detail?id=140478. | |
156 os.path.join('third_party', 'bidichecker'), | |
157 ] | |
158 return licenses.FindThirdPartyDirs(prune_paths) | |
159 | |
160 | |
161 def _GenerateNoticeFile(print_warnings): | |
162 """Generates the contents of an Android NOTICE file for the third-party code. | |
163 Args: | |
164 print_warnings: Whether to print warnings. | |
165 Returns: | |
166 The contents of the NOTICE file. | |
167 """ | |
168 | |
169 third_party_dirs = _FindThirdPartyDirs() | |
170 | |
171 # Don't forget Chromium's LICENSE file | |
172 content = [_ReadFile('LICENSE')] | |
173 | |
174 # We provide attribution for all third-party directories. | |
175 # TODO(steveblock): Limit this to only code used by the WebView binary. | |
176 for directory in third_party_dirs: | |
177 content += [_ReadFile(licenses.ParseDir(directory)['License File'])] | |
178 | |
179 return '\n'.join(content) | |
180 | |
181 | |
182 def main(): | |
183 class IndentedHelpFormatterWithNL(optparse.IndentedHelpFormatter): | |
184 def format_description(self, description): | |
185 if not description: return "" | |
186 desc_width = self.width - self.current_indent | |
187 indent = " "*self.current_indent | |
188 bits = description.split('\n') | |
189 formatted_bits = [ | |
190 textwrap.fill(bit, | |
191 desc_width, | |
192 initial_indent=indent, | |
193 subsequent_indent=indent) | |
194 for bit in bits] | |
195 result = '\n'.join(formatted_bits) + '\n' | |
196 return result | |
Nico
2012/08/09 15:02:29
Do you need this? If so, adjust coding style (it k
Steve Block
2012/08/09 17:24:59
I found the snippet on a forum. I meant to clean i
| |
197 | |
198 parser = optparse.OptionParser(formatter=IndentedHelpFormatterWithNL(), | |
199 usage='%prog [options]') | |
200 parser.description = (__doc__ + | |
201 '\nCommands:\n' \ | |
202 ' scan Check licenses.\n' \ | |
203 ' notice Generate Android NOTICE file on stdout') | |
204 (options, args) = parser.parse_args() | |
205 if len(args) != 1: | |
206 parser.print_help() | |
207 return 1 | |
208 | |
209 if args[0] == 'scan': | |
210 if _Scan(): | |
211 print 'OK!' | |
212 return 0 | |
213 else: | |
214 return 1 | |
215 elif args[0] == 'notice': | |
216 print _GenerateNoticeFile(print_warnings=False) | |
217 return 0 | |
218 | |
219 parser.print_help() | |
220 return 1 | |
221 | |
222 if __name__ == '__main__': | |
223 sys.exit(main()) | |
OLD | NEW |