blob: 8c1c14ada0f7ca980c2d91fac9280be53581fd58 [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]25395492010-04-15 15:29:4424 # Assume for now that breakpad has their licensing in order.
25 "breakpad",
26
[email protected]e64b3d82010-03-19 00:46:1827 # This is just a tiny vsprops file, presumably written by the googleurl
28 # authors. Not third-party code.
29 "googleurl/third_party/icu",
30
[email protected]25395492010-04-15 15:29:4431 # Assume for now that native client has their licensing in order.
32 "native_client",
33
[email protected]e64b3d82010-03-19 00:46:1834 # We don't bundle o3d samples into our resulting binaries.
35 "o3d/samples",
36
[email protected]25395492010-04-15 15:29:4437 # Not in the public Chromium tree.
38 "third_party/adobe",
39
[email protected]e64b3d82010-03-19 00:46:1840 # Written as part of Chromium.
41 "third_party/fuzzymatch",
42
43 # Two directories that are the same as those in base/third_party.
44 "v8/src/third_party/dtoa",
45 "v8/src/third_party/valgrind",
[email protected]6bb63732010-04-28 00:52:0946
47 # Same module occurs in base/ and in net/, so skip one of them.
48 "net/third_party/nss",
[email protected]3de2b0d2010-10-19 17:58:1049
50 # Same module occurs in chrome/ and in net/, so skip one of them.
51 "net/third_party/mozilla_security_manager",
52
53 # Same module occurs in both the top-level third_party and others.
54 "base/third_party/icu",
[email protected]e64b3d82010-03-19 00:46:1855])
56
57# Directories we don't scan through.
58PRUNE_DIRS = ('.svn', '.git', # VCS metadata
59 'out', 'Debug', 'Release', # build files
60 'layout_tests') # lots of subdirs
[email protected]957082a02010-03-18 21:55:2461
[email protected]e32bfad2010-10-22 01:34:3262ADDITIONAL_PATHS = (
63 # The directory with the word list for Chinese and Japanese segmentation
64 # with different license terms than ICU.
65 "third_party/icu/source/data/brkitr",
66)
67
68
[email protected]e657bfc2010-03-22 23:56:1969# Directories where we check out directly from upstream, and therefore
70# can't provide a README.chromium. Please prefer a README.chromium
71# wherever possible.
72SPECIAL_CASES = {
[email protected]878cbc72010-05-03 14:22:2873 os.path.join('third_party', 'angle'): {
[email protected]dd4f0602010-04-29 21:50:5874 "Name": "Almost Native Graphics Layer Engine",
75 "URL": "http://code.google.com/p/angleproject/",
76 },
[email protected]07c75d52010-10-05 19:02:3577 os.path.join('third_party', 'lss'): {
78 "Name": "linux-syscall-support",
79 "URL": "http://code.google.com/p/lss/",
80 },
[email protected]878cbc72010-05-03 14:22:2881 os.path.join('third_party', 'ots'): {
[email protected]e657bfc2010-03-22 23:56:1982 "Name": "OTS (OpenType Sanitizer)",
83 "URL": "http://code.google.com/p/ots/",
[email protected]789894e2010-03-29 17:49:4784 },
[email protected]c4826852010-09-10 21:32:4085 os.path.join('third_party', 'ppapi'): {
86 "Name": "ppapi",
87 "URL": "http://code.google.com/p/ppapi/",
88 },
[email protected]878cbc72010-05-03 14:22:2889 os.path.join('third_party', 'pywebsocket'): {
[email protected]789894e2010-03-29 17:49:4790 "Name": "pywebsocket",
91 "URL": "http://code.google.com/p/pywebsocket/",
92 },
[email protected]878cbc72010-05-03 14:22:2893 os.path.join('third_party', 'WebKit'): {
[email protected]25395492010-04-15 15:29:4494 "Name": "WebKit",
95 "URL": "http://webkit.org/",
[email protected]54ce7262010-04-17 00:02:5596 # Absolute path here is resolved as relative to the source root.
97 "License File": "/webkit/LICENSE",
[email protected]25395492010-04-15 15:29:4498 },
[email protected]e657bfc2010-03-22 23:56:1999}
100
[email protected]957082a02010-03-18 21:55:24101class LicenseError(Exception):
102 """We raise this exception when a directory's licensing info isn't
103 fully filled out."""
104 pass
105
106
107def ParseDir(path):
108 """Examine a third_party/foo component and extract its metadata."""
109
[email protected]957082a02010-03-18 21:55:24110 # Parse metadata fields out of README.chromium.
[email protected]e64b3d82010-03-19 00:46:18111 # We examine "LICENSE" for the license file by default.
[email protected]957082a02010-03-18 21:55:24112 metadata = {
[email protected]4a7a3ac2010-03-18 22:36:41113 "License File": "LICENSE", # Relative path to license text.
114 "Name": None, # Short name (for header on about:credits).
115 "URL": None, # Project home page.
[email protected]957082a02010-03-18 21:55:24116 }
[email protected]e657bfc2010-03-22 23:56:19117
118 if path in SPECIAL_CASES:
119 metadata.update(SPECIAL_CASES[path])
120 else:
121 # Try to find README.chromium.
122 readme_path = os.path.join(path, 'README.chromium')
123 if not os.path.exists(readme_path):
124 raise LicenseError("missing README.chromium")
125
126 for line in open(readme_path):
127 line = line.strip()
128 if not line:
129 break
130 for key in metadata.keys():
131 field = key + ": "
132 if line.startswith(field):
133 metadata[key] = line[len(field):]
[email protected]957082a02010-03-18 21:55:24134
135 # Check that all expected metadata is present.
136 for key, value in metadata.iteritems():
137 if not value:
138 raise LicenseError("couldn't find '" + key + "' line "
[email protected]e657bfc2010-03-22 23:56:19139 "in README.chromium or licences.py "
140 "SPECIAL_CASES")
[email protected]957082a02010-03-18 21:55:24141
142 # Check that the license file exists.
[email protected]e64b3d82010-03-19 00:46:18143 for filename in (metadata["License File"], "COPYING"):
[email protected]54ce7262010-04-17 00:02:55144 if filename.startswith('/'):
145 # Absolute-looking paths are relative to the source root
146 # (which is the directory we're run from).
147 license_path = os.path.join(os.getcwd(), filename[1:])
148 else:
149 license_path = os.path.join(path, filename)
[email protected]e64b3d82010-03-19 00:46:18150 if os.path.exists(license_path):
[email protected]54ce7262010-04-17 00:02:55151 metadata["License File"] = license_path
[email protected]e64b3d82010-03-19 00:46:18152 break
153 license_path = None
154
155 if not license_path:
156 raise LicenseError("License file not found. "
157 "Either add a file named LICENSE, "
158 "import upstream's COPYING if available, "
159 "or add a 'License File:' line to README.chromium "
160 "with the appropriate path.")
[email protected]957082a02010-03-18 21:55:24161
162 return metadata
163
164
[email protected]957082a02010-03-18 21:55:24165def FindThirdPartyDirs():
166 """Find all third_party directories underneath the current directory."""
[email protected]957082a02010-03-18 21:55:24167 third_party_dirs = []
168 for path, dirs, files in os.walk('.'):
169 path = path[len('./'):] # Pretty up the path.
170
[email protected]e64b3d82010-03-19 00:46:18171 if path in PRUNE_PATHS:
172 dirs[:] = []
173 continue
174
[email protected]957082a02010-03-18 21:55:24175 # Prune out directories we want to skip.
[email protected]e64b3d82010-03-19 00:46:18176 # (Note that we loop over PRUNE_DIRS so we're not iterating over a
177 # list that we're simultaneously mutating.)
178 for skip in PRUNE_DIRS:
[email protected]957082a02010-03-18 21:55:24179 if skip in dirs:
180 dirs.remove(skip)
181
182 if os.path.basename(path) == 'third_party':
[email protected]e64b3d82010-03-19 00:46:18183 # Add all subdirectories that are not marked for skipping.
184 for dir in dirs:
185 dirpath = os.path.join(path, dir)
186 if dirpath not in PRUNE_PATHS:
187 third_party_dirs.append(dirpath)
188
[email protected]957082a02010-03-18 21:55:24189 # Don't recurse into any subdirs from here.
190 dirs[:] = []
191 continue
192
[email protected]e32bfad2010-10-22 01:34:32193 for dir in ADDITIONAL_PATHS:
194 third_party_dirs.append(dir)
195
[email protected]957082a02010-03-18 21:55:24196 return third_party_dirs
197
[email protected]54ce7262010-04-17 00:02:55198def ScanThirdPartyDirs():
199 """Scan a list of directories and report on any problems we find."""
200 third_party_dirs = FindThirdPartyDirs()
201
202 errors = []
203 for path in sorted(third_party_dirs):
204 try:
205 metadata = ParseDir(path)
206 except LicenseError, e:
207 errors.append((path, e.args[0]))
208 continue
209
210 for path, error in sorted(errors):
211 print path + ": " + error
212
213 return len(errors) == 0
214
215def GenerateCredits():
216 """Generate about:credits, dumping the result to stdout."""
217
218 def EvaluateTemplate(template, env, escape=True):
219 """Expand a template with variables like {{foo}} using a
220 dictionary of expansions."""
221 for key, val in env.items():
222 if escape:
223 val = cgi.escape(val)
224 template = template.replace('{{%s}}' % key, val)
225 return template
226
227 third_party_dirs = FindThirdPartyDirs()
228
229 entry_template = open('chrome/browser/resources/about_credits_entry.tmpl',
230 'rb').read()
231 entries = []
232 for path in sorted(third_party_dirs):
[email protected]7511d4c2010-04-23 17:18:14233 try:
234 metadata = ParseDir(path)
235 except LicenseError:
236 print >>sys.stderr, ("WARNING: licensing info for " + path +
237 " is incomplete, skipping.")
238 continue
[email protected]54ce7262010-04-17 00:02:55239 env = {
240 'name': metadata['Name'],
241 'url': metadata['URL'],
242 'license': open(metadata['License File'], 'rb').read(),
243 }
244 entries.append(EvaluateTemplate(entry_template, env))
245
246 file_template = open('chrome/browser/resources/about_credits.tmpl',
247 'rb').read()
[email protected]7511d4c2010-04-23 17:18:14248 print "<!-- Generated by licenses.py; do not edit. -->"
[email protected]54ce7262010-04-17 00:02:55249 print EvaluateTemplate(file_template, {'entries': '\n'.join(entries)},
250 escape=False)
[email protected]957082a02010-03-18 21:55:24251
252if __name__ == '__main__':
[email protected]54ce7262010-04-17 00:02:55253 command = 'help'
254 if len(sys.argv) > 1:
255 command = sys.argv[1]
256
257 if command == 'scan':
258 if not ScanThirdPartyDirs():
259 sys.exit(1)
260 elif command == 'credits':
261 if not GenerateCredits():
262 sys.exit(1)
263 else:
264 print __doc__
265 sys.exit(1)