blob: 1763b1c835be642366cba9585c350b9f1b29c2a1 [file] [log] [blame]
[email protected]957082a02010-03-18 21:55:241#!/usr/bin/python
2# Copyright (c) 2010 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
[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]e64b3d82010-03-19 00:46:1830 # This is just a tiny vsprops file, presumably written by the googleurl
31 # 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.
64 os.path.join('third_party','cygwin'),
65 os.path.join('third_party','lighttpd'),
[email protected]946cbf752011-01-16 06:07:2966 os.path.join('third_party','mingw-w64'),
67 os.path.join('third_party','pefile'),
68 os.path.join('third_party','python_26'),
69
[email protected]fe33b982011-02-05 04:10:0570 # Stuff pulled in from chrome-internal for official builds/tools.
71 os.path.join('third_party', 'clear_cache'),
72 os.path.join('third_party', 'gnu'),
73 os.path.join('third_party', 'googlemac'),
74 os.path.join('third_party', 'pcre'),
75 os.path.join('third_party', 'psutils'),
76 os.path.join('third_party', 'sawbuck'),
77
[email protected]946cbf752011-01-16 06:07:2978 # Redistribution does not require attribution in documentation.
79 os.path.join('third_party','directxsdk'),
80 os.path.join('third_party','platformsdk_win2008_6_1'),
81 os.path.join('third_party','platformsdk_win7'),
82
83 # Harfbuzz-ng is not currently shipping in any product:
84 os.path.join('third_party','harfbuzz-ng'),
[email protected]e64b3d82010-03-19 00:46:1885])
86
87# Directories we don't scan through.
88PRUNE_DIRS = ('.svn', '.git', # VCS metadata
89 'out', 'Debug', 'Release', # build files
90 'layout_tests') # lots of subdirs
[email protected]957082a02010-03-18 21:55:2491
[email protected]e32bfad2010-10-22 01:34:3292ADDITIONAL_PATHS = (
93 # The directory with the word list for Chinese and Japanese segmentation
94 # with different license terms than ICU.
[email protected]946cbf752011-01-16 06:07:2995 os.path.join('third_party','icu','source','data','brkitr'),
[email protected]e32bfad2010-10-22 01:34:3296)
97
98
[email protected]e657bfc2010-03-22 23:56:1999# Directories where we check out directly from upstream, and therefore
100# can't provide a README.chromium. Please prefer a README.chromium
101# wherever possible.
102SPECIAL_CASES = {
[email protected]878cbc72010-05-03 14:22:28103 os.path.join('third_party', 'angle'): {
[email protected]dd4f0602010-04-29 21:50:58104 "Name": "Almost Native Graphics Layer Engine",
105 "URL": "http://code.google.com/p/angleproject/",
106 },
[email protected]07c75d52010-10-05 19:02:35107 os.path.join('third_party', 'lss'): {
108 "Name": "linux-syscall-support",
109 "URL": "http://code.google.com/p/lss/",
110 },
[email protected]878cbc72010-05-03 14:22:28111 os.path.join('third_party', 'ots'): {
[email protected]e657bfc2010-03-22 23:56:19112 "Name": "OTS (OpenType Sanitizer)",
113 "URL": "http://code.google.com/p/ots/",
[email protected]789894e2010-03-29 17:49:47114 },
[email protected]c4826852010-09-10 21:32:40115 os.path.join('third_party', 'ppapi'): {
116 "Name": "ppapi",
117 "URL": "http://code.google.com/p/ppapi/",
118 },
[email protected]878cbc72010-05-03 14:22:28119 os.path.join('third_party', 'pywebsocket'): {
[email protected]789894e2010-03-29 17:49:47120 "Name": "pywebsocket",
121 "URL": "http://code.google.com/p/pywebsocket/",
122 },
[email protected]878cbc72010-05-03 14:22:28123 os.path.join('third_party', 'WebKit'): {
[email protected]25395492010-04-15 15:29:44124 "Name": "WebKit",
125 "URL": "http://webkit.org/",
[email protected]54ce7262010-04-17 00:02:55126 # Absolute path here is resolved as relative to the source root.
127 "License File": "/webkit/LICENSE",
[email protected]25395492010-04-15 15:29:44128 },
[email protected]946cbf752011-01-16 06:07:29129 os.path.join('third_party', 'GTM'): {
130 "Name": "Google Toolbox for Mac",
131 "URL": "http://code.google.com/p/google-toolbox-for-mac/",
132 "License File": "COPYING",
133 },
[email protected]946cbf752011-01-16 06:07:29134 os.path.join('third_party', 'pdfsqueeze'): {
135 "Name": "pdfsqueeze",
136 "URL": "http://code.google.com/p/pdfsqueeze/",
[email protected]fe33b982011-02-05 04:10:05137 "License File": "COPYING",
[email protected]946cbf752011-01-16 06:07:29138 },
[email protected]e657bfc2010-03-22 23:56:19139}
140
[email protected]957082a02010-03-18 21:55:24141class LicenseError(Exception):
142 """We raise this exception when a directory's licensing info isn't
143 fully filled out."""
144 pass
145
146
147def ParseDir(path):
148 """Examine a third_party/foo component and extract its metadata."""
149
[email protected]957082a02010-03-18 21:55:24150 # Parse metadata fields out of README.chromium.
[email protected]e64b3d82010-03-19 00:46:18151 # We examine "LICENSE" for the license file by default.
[email protected]957082a02010-03-18 21:55:24152 metadata = {
[email protected]4a7a3ac2010-03-18 22:36:41153 "License File": "LICENSE", # Relative path to license text.
154 "Name": None, # Short name (for header on about:credits).
155 "URL": None, # Project home page.
[email protected]957082a02010-03-18 21:55:24156 }
[email protected]e657bfc2010-03-22 23:56:19157
158 if path in SPECIAL_CASES:
159 metadata.update(SPECIAL_CASES[path])
160 else:
161 # Try to find README.chromium.
162 readme_path = os.path.join(path, 'README.chromium')
163 if not os.path.exists(readme_path):
164 raise LicenseError("missing README.chromium")
165
166 for line in open(readme_path):
167 line = line.strip()
168 if not line:
169 break
170 for key in metadata.keys():
171 field = key + ": "
172 if line.startswith(field):
173 metadata[key] = line[len(field):]
[email protected]957082a02010-03-18 21:55:24174
175 # Check that all expected metadata is present.
176 for key, value in metadata.iteritems():
177 if not value:
178 raise LicenseError("couldn't find '" + key + "' line "
[email protected]e657bfc2010-03-22 23:56:19179 "in README.chromium or licences.py "
180 "SPECIAL_CASES")
[email protected]957082a02010-03-18 21:55:24181
182 # Check that the license file exists.
[email protected]e64b3d82010-03-19 00:46:18183 for filename in (metadata["License File"], "COPYING"):
[email protected]54ce7262010-04-17 00:02:55184 if filename.startswith('/'):
185 # Absolute-looking paths are relative to the source root
186 # (which is the directory we're run from).
187 license_path = os.path.join(os.getcwd(), filename[1:])
188 else:
189 license_path = os.path.join(path, filename)
[email protected]e64b3d82010-03-19 00:46:18190 if os.path.exists(license_path):
[email protected]54ce7262010-04-17 00:02:55191 metadata["License File"] = license_path
[email protected]e64b3d82010-03-19 00:46:18192 break
193 license_path = None
194
195 if not license_path:
196 raise LicenseError("License file not found. "
197 "Either add a file named LICENSE, "
198 "import upstream's COPYING if available, "
199 "or add a 'License File:' line to README.chromium "
200 "with the appropriate path.")
[email protected]957082a02010-03-18 21:55:24201
202 return metadata
203
204
[email protected]957082a02010-03-18 21:55:24205def FindThirdPartyDirs():
206 """Find all third_party directories underneath the current directory."""
[email protected]957082a02010-03-18 21:55:24207 third_party_dirs = []
208 for path, dirs, files in os.walk('.'):
209 path = path[len('./'):] # Pretty up the path.
210
[email protected]e64b3d82010-03-19 00:46:18211 if path in PRUNE_PATHS:
212 dirs[:] = []
213 continue
214
[email protected]957082a02010-03-18 21:55:24215 # Prune out directories we want to skip.
[email protected]e64b3d82010-03-19 00:46:18216 # (Note that we loop over PRUNE_DIRS so we're not iterating over a
217 # list that we're simultaneously mutating.)
218 for skip in PRUNE_DIRS:
[email protected]957082a02010-03-18 21:55:24219 if skip in dirs:
220 dirs.remove(skip)
221
222 if os.path.basename(path) == 'third_party':
[email protected]e64b3d82010-03-19 00:46:18223 # Add all subdirectories that are not marked for skipping.
224 for dir in dirs:
225 dirpath = os.path.join(path, dir)
226 if dirpath not in PRUNE_PATHS:
227 third_party_dirs.append(dirpath)
228
[email protected]957082a02010-03-18 21:55:24229 # Don't recurse into any subdirs from here.
230 dirs[:] = []
231 continue
232
[email protected]e32bfad2010-10-22 01:34:32233 for dir in ADDITIONAL_PATHS:
234 third_party_dirs.append(dir)
235
[email protected]957082a02010-03-18 21:55:24236 return third_party_dirs
237
[email protected]54ce7262010-04-17 00:02:55238def ScanThirdPartyDirs():
239 """Scan a list of directories and report on any problems we find."""
240 third_party_dirs = FindThirdPartyDirs()
241
242 errors = []
243 for path in sorted(third_party_dirs):
244 try:
245 metadata = ParseDir(path)
246 except LicenseError, e:
247 errors.append((path, e.args[0]))
248 continue
249
250 for path, error in sorted(errors):
251 print path + ": " + error
252
253 return len(errors) == 0
254
255def GenerateCredits():
256 """Generate about:credits, dumping the result to stdout."""
257
258 def EvaluateTemplate(template, env, escape=True):
259 """Expand a template with variables like {{foo}} using a
260 dictionary of expansions."""
261 for key, val in env.items():
262 if escape:
263 val = cgi.escape(val)
264 template = template.replace('{{%s}}' % key, val)
265 return template
266
267 third_party_dirs = FindThirdPartyDirs()
268
269 entry_template = open('chrome/browser/resources/about_credits_entry.tmpl',
270 'rb').read()
271 entries = []
272 for path in sorted(third_party_dirs):
[email protected]7511d4c2010-04-23 17:18:14273 try:
274 metadata = ParseDir(path)
275 except LicenseError:
276 print >>sys.stderr, ("WARNING: licensing info for " + path +
277 " is incomplete, skipping.")
278 continue
[email protected]54ce7262010-04-17 00:02:55279 env = {
280 'name': metadata['Name'],
281 'url': metadata['URL'],
282 'license': open(metadata['License File'], 'rb').read(),
283 }
284 entries.append(EvaluateTemplate(entry_template, env))
285
286 file_template = open('chrome/browser/resources/about_credits.tmpl',
287 'rb').read()
[email protected]7511d4c2010-04-23 17:18:14288 print "<!-- Generated by licenses.py; do not edit. -->"
[email protected]54ce7262010-04-17 00:02:55289 print EvaluateTemplate(file_template, {'entries': '\n'.join(entries)},
290 escape=False)
[email protected]957082a02010-03-18 21:55:24291
292if __name__ == '__main__':
[email protected]54ce7262010-04-17 00:02:55293 command = 'help'
294 if len(sys.argv) > 1:
295 command = sys.argv[1]
296
297 if command == 'scan':
298 if not ScanThirdPartyDirs():
299 sys.exit(1)
300 elif command == 'credits':
301 if not GenerateCredits():
302 sys.exit(1)
303 else:
304 print __doc__
305 sys.exit(1)