blob: b01db489da107bfd599742db55a4bb243c092558 [file] [log] [blame]
[email protected]cb155a82011-11-29 17:25:341#!/usr/bin/env python
[email protected]9aaa0a52012-01-31 18:42:412# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]957082a02010-03-18 21:55:243# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
[email protected]54ce7262010-04-17 00:02:556"""Utility for checking and processing licensing information in third_party
[email protected]957082a02010-03-18 21:55:247directories.
[email protected]54ce7262010-04-17 00:02:558
9Usage: licenses.py <command>
10
11Commands:
12 scan scan third_party directories, verifying that we have licensing info
13 credits generate about:credits on stdout
14
15(You can also import this as a module.)
[email protected]957082a02010-03-18 21:55:2416"""
17
[email protected]54ce7262010-04-17 00:02:5518import cgi
[email protected]957082a02010-03-18 21:55:2419import os
[email protected]54ce7262010-04-17 00:02:5520import sys
[email protected]957082a02010-03-18 21:55:2421
[email protected]e64b3d82010-03-19 00:46:1822# Paths from the root of the tree to directories to skip.
23PRUNE_PATHS = set([
[email protected]f52832292010-12-15 00:43:1624 # Same module occurs in both the top-level third_party and others.
[email protected]946cbf752011-01-16 06:07:2925 os.path.join('base','third_party','icu'),
[email protected]f52832292010-12-15 00:43:1626
[email protected]25395492010-04-15 15:29:4427 # Assume for now that breakpad has their licensing in order.
[email protected]946cbf752011-01-16 06:07:2928 os.path.join('breakpad'),
[email protected]25395492010-04-15 15:29:4429
[email protected]607855b2012-03-09 14:10:5930 # This is just a tiny vsprops file, presumably written by the google-url
[email protected]e64b3d82010-03-19 00:46:1831 # authors. Not third-party code.
[email protected]946cbf752011-01-16 06:07:2932 os.path.join('googleurl','third_party','icu'),
[email protected]e64b3d82010-03-19 00:46:1833
[email protected]25395492010-04-15 15:29:4434 # Assume for now that native client has their licensing in order.
[email protected]946cbf752011-01-16 06:07:2935 os.path.join('native_client'),
[email protected]25395492010-04-15 15:29:4436
[email protected]f52832292010-12-15 00:43:1637 # Same module occurs in chrome/ and in net/, so skip one of them.
[email protected]946cbf752011-01-16 06:07:2938 os.path.join('net','third_party','mozilla_security_manager'),
[email protected]f52832292010-12-15 00:43:1639
[email protected]946cbf752011-01-16 06:07:2940 # Same module occurs in base/, net/, and src/ so skip all but one of them.
41 os.path.join('third_party','nss'),
42 os.path.join('net','third_party','nss'),
[email protected]f52832292010-12-15 00:43:1643
[email protected]e64b3d82010-03-19 00:46:1844 # We don't bundle o3d samples into our resulting binaries.
[email protected]946cbf752011-01-16 06:07:2945 os.path.join('o3d','samples'),
[email protected]e64b3d82010-03-19 00:46:1846
[email protected]25395492010-04-15 15:29:4447 # Not in the public Chromium tree.
[email protected]946cbf752011-01-16 06:07:2948 os.path.join('third_party','adobe'),
[email protected]25395492010-04-15 15:29:4449
[email protected]e64b3d82010-03-19 00:46:1850 # Written as part of Chromium.
[email protected]946cbf752011-01-16 06:07:2951 os.path.join('third_party','fuzzymatch'),
[email protected]e64b3d82010-03-19 00:46:1852
[email protected]f52832292010-12-15 00:43:1653 # Same license as Chromium.
[email protected]946cbf752011-01-16 06:07:2954 os.path.join('third_party','lss'),
[email protected]f52832292010-12-15 00:43:1655
56 # Only binaries, used during development.
[email protected]946cbf752011-01-16 06:07:2957 os.path.join('third_party','valgrind'),
[email protected]f52832292010-12-15 00:43:1658
[email protected]e64b3d82010-03-19 00:46:1859 # Two directories that are the same as those in base/third_party.
[email protected]946cbf752011-01-16 06:07:2960 os.path.join('v8','src','third_party','dtoa'),
61 os.path.join('v8','src','third_party','valgrind'),
62
63 # Used for development and test, not in the shipping product.
[email protected]5f5ceac2012-03-07 21:12:3664 os.path.join('third_party','android_testrunner'),
[email protected]78de2e42011-05-11 19:50:2165 os.path.join('third_party','bidichecker'),
[email protected]946cbf752011-01-16 06:07:2966 os.path.join('third_party','cygwin'),
[email protected]b07806c12012-02-03 22:44:5967 os.path.join('third_party','gold'),
[email protected]946cbf752011-01-16 06:07:2968 os.path.join('third_party','lighttpd'),
[email protected]946cbf752011-01-16 06:07:2969 os.path.join('third_party','mingw-w64'),
70 os.path.join('third_party','pefile'),
71 os.path.join('third_party','python_26'),
72
[email protected]fe33b982011-02-05 04:10:0573 # Stuff pulled in from chrome-internal for official builds/tools.
74 os.path.join('third_party', 'clear_cache'),
75 os.path.join('third_party', 'gnu'),
76 os.path.join('third_party', 'googlemac'),
77 os.path.join('third_party', 'pcre'),
78 os.path.join('third_party', 'psutils'),
79 os.path.join('third_party', 'sawbuck'),
80
[email protected]946cbf752011-01-16 06:07:2981 # Redistribution does not require attribution in documentation.
82 os.path.join('third_party','directxsdk'),
83 os.path.join('third_party','platformsdk_win2008_6_1'),
84 os.path.join('third_party','platformsdk_win7'),
85
86 # Harfbuzz-ng is not currently shipping in any product:
87 os.path.join('third_party','harfbuzz-ng'),
[email protected]e64b3d82010-03-19 00:46:1888])
89
90# Directories we don't scan through.
91PRUNE_DIRS = ('.svn', '.git', # VCS metadata
92 'out', 'Debug', 'Release', # build files
93 'layout_tests') # lots of subdirs
[email protected]957082a02010-03-18 21:55:2494
[email protected]e32bfad2010-10-22 01:34:3295ADDITIONAL_PATHS = (
96 # The directory with the word list for Chinese and Japanese segmentation
97 # with different license terms than ICU.
[email protected]946cbf752011-01-16 06:07:2998 os.path.join('third_party','icu','source','data','brkitr'),
[email protected]9aaa0a52012-01-31 18:42:4199 # Fake directory so we can include the strongtalk license.
100 os.path.join('v8', 'strongtalk'),
[email protected]607855b2012-03-09 14:10:59101 # Fake directory so we can include the google-url license.
102 'googleurl',
[email protected]e32bfad2010-10-22 01:34:32103)
104
105
[email protected]e657bfc2010-03-22 23:56:19106# Directories where we check out directly from upstream, and therefore
107# can't provide a README.chromium. Please prefer a README.chromium
108# wherever possible.
109SPECIAL_CASES = {
[email protected]607855b2012-03-09 14:10:59110 'googleurl': {
111 "Name": "google-url",
112 "URL": "http://code.google.com/p/google-url/",
113 "License File": "LICENSE.txt",
114 },
[email protected]878cbc72010-05-03 14:22:28115 os.path.join('third_party', 'angle'): {
[email protected]dd4f0602010-04-29 21:50:58116 "Name": "Almost Native Graphics Layer Engine",
117 "URL": "http://code.google.com/p/angleproject/",
118 },
[email protected]07c75d52010-10-05 19:02:35119 os.path.join('third_party', 'lss'): {
120 "Name": "linux-syscall-support",
121 "URL": "http://code.google.com/p/lss/",
122 },
[email protected]878cbc72010-05-03 14:22:28123 os.path.join('third_party', 'ots'): {
[email protected]e657bfc2010-03-22 23:56:19124 "Name": "OTS (OpenType Sanitizer)",
125 "URL": "http://code.google.com/p/ots/",
[email protected]789894e2010-03-29 17:49:47126 },
[email protected]c4826852010-09-10 21:32:40127 os.path.join('third_party', 'ppapi'): {
128 "Name": "ppapi",
129 "URL": "http://code.google.com/p/ppapi/",
130 },
[email protected]878cbc72010-05-03 14:22:28131 os.path.join('third_party', 'WebKit'): {
[email protected]25395492010-04-15 15:29:44132 "Name": "WebKit",
133 "URL": "http://webkit.org/",
[email protected]54ce7262010-04-17 00:02:55134 # Absolute path here is resolved as relative to the source root.
135 "License File": "/webkit/LICENSE",
[email protected]25395492010-04-15 15:29:44136 },
[email protected]946cbf752011-01-16 06:07:29137 os.path.join('third_party', 'GTM'): {
138 "Name": "Google Toolbox for Mac",
139 "URL": "http://code.google.com/p/google-toolbox-for-mac/",
140 "License File": "COPYING",
141 },
[email protected]946cbf752011-01-16 06:07:29142 os.path.join('third_party', 'pdfsqueeze'): {
143 "Name": "pdfsqueeze",
144 "URL": "http://code.google.com/p/pdfsqueeze/",
[email protected]fe33b982011-02-05 04:10:05145 "License File": "COPYING",
[email protected]946cbf752011-01-16 06:07:29146 },
[email protected]38acf472012-03-19 13:15:36147 os.path.join('third_party', 'v8-i18n'): {
148 "Name": "Internationalization Library for v8",
149 "URL": "http://code.google.com/p/v8-i18n/",
150 },
[email protected]9aaa0a52012-01-31 18:42:41151 os.path.join('v8', 'strongtalk'): {
152 "Name": "Strongtalk",
153 "URL": "http://www.strongtalk.org/",
154 "License File": "/v8/LICENSE.strongtalk",
155 },
[email protected]e657bfc2010-03-22 23:56:19156}
157
[email protected]957082a02010-03-18 21:55:24158class LicenseError(Exception):
159 """We raise this exception when a directory's licensing info isn't
160 fully filled out."""
161 pass
162
[email protected]689d81c2012-02-03 20:03:38163def AbsolutePath(path, filename):
164 """Convert a path in README.chromium to be absolute based on the source
165 root."""
166 if filename.startswith('/'):
167 # Absolute-looking paths are relative to the source root
168 # (which is the directory we're run from).
169 absolute_path = os.path.join(os.getcwd(), filename[1:])
170 else:
171 absolute_path = os.path.join(path, filename)
172 if os.path.exists(absolute_path):
173 return absolute_path
174 return None
[email protected]957082a02010-03-18 21:55:24175
176def ParseDir(path):
177 """Examine a third_party/foo component and extract its metadata."""
178
[email protected]957082a02010-03-18 21:55:24179 # Parse metadata fields out of README.chromium.
[email protected]e64b3d82010-03-19 00:46:18180 # We examine "LICENSE" for the license file by default.
[email protected]957082a02010-03-18 21:55:24181 metadata = {
[email protected]4a7a3ac2010-03-18 22:36:41182 "License File": "LICENSE", # Relative path to license text.
183 "Name": None, # Short name (for header on about:credits).
184 "URL": None, # Project home page.
[email protected]957082a02010-03-18 21:55:24185 }
[email protected]e657bfc2010-03-22 23:56:19186
[email protected]689d81c2012-02-03 20:03:38187 # Relative path to a file containing some html we're required to place in
188 # about:credits.
189 optional_keys = ["Required Text"]
190
[email protected]e657bfc2010-03-22 23:56:19191 if path in SPECIAL_CASES:
192 metadata.update(SPECIAL_CASES[path])
193 else:
194 # Try to find README.chromium.
195 readme_path = os.path.join(path, 'README.chromium')
196 if not os.path.exists(readme_path):
197 raise LicenseError("missing README.chromium")
198
199 for line in open(readme_path):
200 line = line.strip()
201 if not line:
202 break
[email protected]689d81c2012-02-03 20:03:38203 for key in metadata.keys() + optional_keys:
[email protected]e657bfc2010-03-22 23:56:19204 field = key + ": "
205 if line.startswith(field):
206 metadata[key] = line[len(field):]
[email protected]957082a02010-03-18 21:55:24207
208 # Check that all expected metadata is present.
209 for key, value in metadata.iteritems():
210 if not value:
211 raise LicenseError("couldn't find '" + key + "' line "
[email protected]e657bfc2010-03-22 23:56:19212 "in README.chromium or licences.py "
213 "SPECIAL_CASES")
[email protected]957082a02010-03-18 21:55:24214
215 # Check that the license file exists.
[email protected]e64b3d82010-03-19 00:46:18216 for filename in (metadata["License File"], "COPYING"):
[email protected]689d81c2012-02-03 20:03:38217 license_path = AbsolutePath(path, filename)
218 if license_path is not None:
[email protected]54ce7262010-04-17 00:02:55219 metadata["License File"] = license_path
[email protected]e64b3d82010-03-19 00:46:18220 break
[email protected]e64b3d82010-03-19 00:46:18221
222 if not license_path:
223 raise LicenseError("License file not found. "
224 "Either add a file named LICENSE, "
225 "import upstream's COPYING if available, "
226 "or add a 'License File:' line to README.chromium "
227 "with the appropriate path.")
[email protected]957082a02010-03-18 21:55:24228
[email protected]689d81c2012-02-03 20:03:38229 if "Required Text" in metadata:
230 required_path = AbsolutePath(path, metadata["Required Text"])
231 if required_path is not None:
232 metadata["Required Text"] = required_path
233 else:
234 raise LicenseError("Required text file listed but not found.")
235
[email protected]957082a02010-03-18 21:55:24236 return metadata
237
238
[email protected]957082a02010-03-18 21:55:24239def FindThirdPartyDirs():
240 """Find all third_party directories underneath the current directory."""
[email protected]957082a02010-03-18 21:55:24241 third_party_dirs = []
242 for path, dirs, files in os.walk('.'):
243 path = path[len('./'):] # Pretty up the path.
244
[email protected]e64b3d82010-03-19 00:46:18245 if path in PRUNE_PATHS:
246 dirs[:] = []
247 continue
248
[email protected]957082a02010-03-18 21:55:24249 # Prune out directories we want to skip.
[email protected]e64b3d82010-03-19 00:46:18250 # (Note that we loop over PRUNE_DIRS so we're not iterating over a
251 # list that we're simultaneously mutating.)
252 for skip in PRUNE_DIRS:
[email protected]957082a02010-03-18 21:55:24253 if skip in dirs:
254 dirs.remove(skip)
255
256 if os.path.basename(path) == 'third_party':
[email protected]e64b3d82010-03-19 00:46:18257 # Add all subdirectories that are not marked for skipping.
258 for dir in dirs:
259 dirpath = os.path.join(path, dir)
260 if dirpath not in PRUNE_PATHS:
261 third_party_dirs.append(dirpath)
262
[email protected]957082a02010-03-18 21:55:24263 # Don't recurse into any subdirs from here.
264 dirs[:] = []
265 continue
266
[email protected]e32bfad2010-10-22 01:34:32267 for dir in ADDITIONAL_PATHS:
268 third_party_dirs.append(dir)
269
[email protected]957082a02010-03-18 21:55:24270 return third_party_dirs
271
[email protected]cb155a82011-11-29 17:25:34272
[email protected]54ce7262010-04-17 00:02:55273def ScanThirdPartyDirs():
274 """Scan a list of directories and report on any problems we find."""
275 third_party_dirs = FindThirdPartyDirs()
276
277 errors = []
278 for path in sorted(third_party_dirs):
279 try:
280 metadata = ParseDir(path)
281 except LicenseError, e:
282 errors.append((path, e.args[0]))
283 continue
284
285 for path, error in sorted(errors):
286 print path + ": " + error
287
288 return len(errors) == 0
289
[email protected]cb155a82011-11-29 17:25:34290
[email protected]54ce7262010-04-17 00:02:55291def GenerateCredits():
292 """Generate about:credits, dumping the result to stdout."""
293
294 def EvaluateTemplate(template, env, escape=True):
295 """Expand a template with variables like {{foo}} using a
296 dictionary of expansions."""
297 for key, val in env.items():
[email protected]689d81c2012-02-03 20:03:38298 if escape and not key.endswith("_unescaped"):
[email protected]54ce7262010-04-17 00:02:55299 val = cgi.escape(val)
300 template = template.replace('{{%s}}' % key, val)
301 return template
302
303 third_party_dirs = FindThirdPartyDirs()
304
305 entry_template = open('chrome/browser/resources/about_credits_entry.tmpl',
306 'rb').read()
307 entries = []
308 for path in sorted(third_party_dirs):
[email protected]7511d4c2010-04-23 17:18:14309 try:
310 metadata = ParseDir(path)
311 except LicenseError:
312 print >>sys.stderr, ("WARNING: licensing info for " + path +
313 " is incomplete, skipping.")
314 continue
[email protected]54ce7262010-04-17 00:02:55315 env = {
316 'name': metadata['Name'],
317 'url': metadata['URL'],
318 'license': open(metadata['License File'], 'rb').read(),
[email protected]689d81c2012-02-03 20:03:38319 'license_unescaped': '',
[email protected]54ce7262010-04-17 00:02:55320 }
[email protected]689d81c2012-02-03 20:03:38321 if 'Required Text' in metadata:
322 required_text = open(metadata['Required Text'], 'rb').read()
323 env["license_unescaped"] = required_text
[email protected]54ce7262010-04-17 00:02:55324 entries.append(EvaluateTemplate(entry_template, env))
325
326 file_template = open('chrome/browser/resources/about_credits.tmpl',
327 'rb').read()
[email protected]7511d4c2010-04-23 17:18:14328 print "<!-- Generated by licenses.py; do not edit. -->"
[email protected]54ce7262010-04-17 00:02:55329 print EvaluateTemplate(file_template, {'entries': '\n'.join(entries)},
330 escape=False)
[email protected]957082a02010-03-18 21:55:24331
[email protected]cb155a82011-11-29 17:25:34332
333def main():
[email protected]54ce7262010-04-17 00:02:55334 command = 'help'
335 if len(sys.argv) > 1:
336 command = sys.argv[1]
337
338 if command == 'scan':
339 if not ScanThirdPartyDirs():
[email protected]cb155a82011-11-29 17:25:34340 return 1
[email protected]54ce7262010-04-17 00:02:55341 elif command == 'credits':
342 if not GenerateCredits():
[email protected]cb155a82011-11-29 17:25:34343 return 1
[email protected]54ce7262010-04-17 00:02:55344 else:
345 print __doc__
[email protected]cb155a82011-11-29 17:25:34346 return 1
347
348
349if __name__ == '__main__':
350 sys.exit(main())