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.
6 """Checks third-party licenses for the purposes of the Android WebView build.
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.
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.
27 REPOSITORY_ROOT
= os
.path
.abspath(os
.path
.join(
28 os
.path
.dirname(__file__
), '..', '..'))
30 sys
.path
.append(os
.path
.join(REPOSITORY_ROOT
, 'tools'))
35 def GetIncompatibleDirectories():
36 """Gets a list of third-party directories which use licenses incompatible
37 with Android. This is used by the snapshot tool.
39 A list of directories.
43 'Apache( Version)? 2(\.0)?',
44 '(New )?BSD( [23]-Clause)?( with advertising clause)?',
45 'L?GPL ?v?2(\.[01])?( or later)?',
47 'MPL 1\.1 ?/ ?GPL 2(\.0)? ?/ ?LGPL 2\.1',
49 'Microsoft Limited Public License',
50 'Microsoft Permissive License',
52 'SGI Free Software License B',
55 regex
= '^(%s)$' % '|'.join(whitelist
)
57 for directory
in _FindThirdPartyDirs():
58 if directory
in known_issues
.KNOWN_ISSUES
:
59 result
.append(directory
)
62 metadata
= licenses
.ParseDir(directory
, REPOSITORY_ROOT
,
63 require_license_file
=False)
64 except licenses
.LicenseError
as e
:
65 print 'Got LicenseError while scanning ' + directory
67 if metadata
.get('License Android Compatible', 'no').upper() == 'YES':
69 license
= re
.split(' [Ll]icenses?$', metadata
['License'])[0]
70 tokens
= [x
.strip() for x
in re
.split(' and |,', license
) if len(x
) > 0]
72 if not re
.match(regex
, token
, re
.IGNORECASE
):
73 result
.append(directory
)
77 class ScanResult(object):
78 Ok
, Warnings
, Errors
= range(3)
80 def _CheckLicenseHeaders(excluded_dirs_list
, whitelisted_files
):
81 """Checks that all files which are not in a listed third-party directory,
82 and which do not use the standard Chromium license, are whitelisted.
84 excluded_dirs_list: The list of directories to exclude from scanning.
85 whitelisted_files: The whitelist of files.
87 ScanResult.Ok if all files with non-standard license headers are whitelisted
88 and the whitelist contains no stale entries;
89 ScanResult.Warnings if there are stale entries;
90 ScanResult.Errors if new non-whitelisted entries found.
93 excluded_dirs_list
= [d
for d
in excluded_dirs_list
if not 'third_party' in d
]
94 # Using a commond pattern for third-partyies makes the ignore regexp shorter
95 excluded_dirs_list
.append('third_party')
97 excluded_dirs_list
.append('.git')
98 excluded_dirs_list
.append('.svn')
100 excluded_dirs_list
.append('out/Debug')
101 excluded_dirs_list
.append('out/Release')
102 # 'Copyright' appears in license agreements
103 excluded_dirs_list
.append('chrome/app/resources')
104 # This is a test output directory
105 excluded_dirs_list
.append('chrome/tools/test/reference_build')
106 # This is tests directory, doesn't exist in the snapshot
107 excluded_dirs_list
.append('content/test/data')
108 # This is a test output directory
109 excluded_dirs_list
.append('data/dom_perf')
110 # Histogram tools, doesn't exist in the snapshot
111 excluded_dirs_list
.append('tools/histograms')
112 # Arm sysroot tools, doesn't exist in the snapshot
113 excluded_dirs_list
.append('arm-sysroot')
114 # Data is not part of open source chromium, but are included on some bots.
115 excluded_dirs_list
.append('data')
117 args
= ['android_webview/tools/find_copyrights.pl',
119 ] + excluded_dirs_list
120 p
= subprocess
.Popen(args
=args
, cwd
=REPOSITORY_ROOT
, stdout
=subprocess
.PIPE
)
121 lines
= p
.communicate()[0].splitlines()
124 allowed_copyrights
= '^(?:\*No copyright\*' \
125 '|20[0-9][0-9](?:-20[0-9][0-9])? The Chromium Authors\. ' \
126 'All rights reserved.*)$'
127 allowed_copyrights_re
= re
.compile(allowed_copyrights
)
129 entries
= l
.split('\t')
130 if entries
[1] == "GENERATED FILE":
132 copyrights
= entries
[1].split(' / ')
134 if c
and not allowed_copyrights_re
.match(c
):
135 offending_files
.append(os
.path
.normpath(entries
[0]))
138 unknown
= set(offending_files
) - set(whitelisted_files
)
140 print 'The following files contain a third-party license but are not in ' \
141 'a listed third-party directory and are not whitelisted. You must ' \
142 'add the following files to the whitelist.\n%s' % \
143 '\n'.join(sorted(unknown
))
145 stale
= set(whitelisted_files
) - set(offending_files
)
147 print 'The following files are whitelisted unnecessarily. You must ' \
148 ' remove the following files from the whitelist.\n%s' % \
149 '\n'.join(sorted(stale
))
152 return ScanResult
.Errors
154 return ScanResult
.Warnings
160 """Reads a file from disk.
162 path: The path of the file to read, relative to the root of the repository.
164 The contents of the file as a string.
167 return open(os
.path
.join(REPOSITORY_ROOT
, path
), 'rb').read()
170 def _FindThirdPartyDirs():
171 """Gets the list of third-party directories.
173 The list of third-party directories.
176 # Please don't add here paths that have problems with license files,
177 # as they will end up included in Android WebView snapshot.
178 # Instead, add them into known_issues.py.
180 # Placeholder directory, no third-party code.
181 os
.path
.join('third_party', 'adobe'),
182 # Apache 2.0 license. See
183 # https://code.google.com/p/chromium/issues/detail?id=140478.
184 os
.path
.join('third_party', 'bidichecker'),
185 # Isn't checked out on clients
186 os
.path
.join('third_party', 'gles2_conform'),
187 # The llvm-build doesn't exist for non-clang builder
188 os
.path
.join('third_party', 'llvm-build'),
189 # Binaries doesn't apply to android
190 os
.path
.join('third_party', 'widevine'),
191 # third_party directories in this tree aren't actually third party, but
192 # provide a way to shadow experimental buildfiles into those directories.
193 os
.path
.join('tools', 'gn', 'secondary'),
194 # Not shipped, Chromium code
195 os
.path
.join('tools', 'swarm_client'),
197 third_party_dirs
= licenses
.FindThirdPartyDirs(prune_paths
, REPOSITORY_ROOT
)
198 return licenses
.FilterDirsWithFiles(third_party_dirs
, REPOSITORY_ROOT
)
202 """Checks that license meta-data is present for all third-party code and
203 that all non third-party code doesn't contain external copyrighted code.
205 ScanResult.Ok if everything is in order;
206 ScanResult.Warnings if there are non-fatal problems (e.g. stale whitelist
208 ScanResult.Errors otherwise.
211 third_party_dirs
= _FindThirdPartyDirs()
213 # First, check designated third-party directories using src/tools/licenses.py.
214 all_licenses_valid
= True
215 for path
in sorted(third_party_dirs
):
217 licenses
.ParseDir(path
, REPOSITORY_ROOT
)
218 except licenses
.LicenseError
, e
:
219 if not (path
in known_issues
.KNOWN_ISSUES
):
220 print 'Got LicenseError "%s" while scanning %s' % (e
, path
)
221 all_licenses_valid
= False
223 # Second, check for non-standard license text.
224 files_data
= _ReadFile(os
.path
.join('android_webview', 'tools',
225 'third_party_files_whitelist.txt'))
226 whitelisted_files
= []
227 for line
in files_data
.splitlines():
228 match
= re
.match(r
'([^#\s]+)', line
)
230 whitelisted_files
.append(match
.group(1))
231 licenses_check
= _CheckLicenseHeaders(third_party_dirs
, whitelisted_files
)
233 return licenses_check
if all_licenses_valid
else ScanResult
.Errors
236 def GenerateNoticeFile():
237 """Generates the contents of an Android NOTICE file for the third-party code.
238 This is used by the snapshot tool.
240 The contents of the NOTICE file.
243 third_party_dirs
= _FindThirdPartyDirs()
245 # Don't forget Chromium's LICENSE file
246 content
= [_ReadFile('LICENSE')]
248 # We provide attribution for all third-party directories.
249 # TODO(steveblock): Limit this to only code used by the WebView binary.
250 for directory
in sorted(third_party_dirs
):
251 metadata
= licenses
.ParseDir(directory
, REPOSITORY_ROOT
,
252 require_license_file
=False)
253 license_file
= metadata
['License File']
254 if license_file
and license_file
!= licenses
.NOT_SHIPPED
:
255 content
.append(_ReadFile(license_file
))
257 return '\n'.join(content
)
261 class FormatterWithNewLines(optparse
.IndentedHelpFormatter
):
262 def format_description(self
, description
):
263 paras
= description
.split('\n')
264 formatted_paras
= [textwrap
.fill(para
, self
.width
) for para
in paras
]
265 return '\n'.join(formatted_paras
) + '\n'
267 parser
= optparse
.OptionParser(formatter
=FormatterWithNewLines(),
268 usage
='%prog [options]')
269 parser
.description
= (__doc__
+
271 ' scan Check licenses.\n' \
272 ' notice Generate Android NOTICE file on stdout')
273 (options
, args
) = parser
.parse_args()
276 return ScanResult
.Errors
278 if args
[0] == 'scan':
279 scan_result
= _Scan()
280 if scan_result
== ScanResult
.Ok
:
283 elif args
[0] == 'notice':
284 print GenerateNoticeFile()
288 return ScanResult
.Errors
290 if __name__
== '__main__':