1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// See README in this directory for information on how this code is organized. 6 7import 'dart:async'; 8import 'dart:collection'; 9import 'dart:convert'; 10import 'dart:io' as system; 11import 'dart:math' as math; 12 13import 'package:args/args.dart'; 14import 'package:crypto/crypto.dart' as crypto; 15import 'package:licenses/patterns.dart'; 16import 'package:meta/meta.dart'; 17import 'package:path/path.dart' as path; 18 19import 'filesystem.dart' as fs; 20import 'licenses.dart'; 21 22 23// REPOSITORY OBJECTS 24 25abstract class _RepositoryEntry implements Comparable<_RepositoryEntry> { 26 _RepositoryEntry(this.parent, this.io); 27 final _RepositoryDirectory parent; 28 final fs.IoNode io; 29 String get name => io.name; 30 String get libraryName; 31 32 @override 33 int compareTo(_RepositoryEntry other) => toString().compareTo(other.toString()); 34 35 @override 36 String toString() => io.fullName; 37} 38 39abstract class _RepositoryFile extends _RepositoryEntry { 40 _RepositoryFile(_RepositoryDirectory parent, fs.File io) : super(parent, io); 41 42 Iterable<License> get licenses; 43 44 @override 45 String get libraryName => parent.libraryName; 46 47 @override 48 fs.File get io => super.io; 49} 50 51abstract class _RepositoryLicensedFile extends _RepositoryFile { 52 _RepositoryLicensedFile(_RepositoryDirectory parent, fs.File io) : super(parent, io); 53 54 // file names that we are confident won't be included in the final build product 55 static final RegExp _readmeNamePattern = RegExp(r'\b_*(?:readme|contributing|patents)_*\b', caseSensitive: false); 56 static final RegExp _buildTimePattern = RegExp(r'^(?!.*gen$)(?:CMakeLists\.txt|(?:pkgdata)?Makefile(?:\.inc)?(?:\.am|\.in|)|configure(?:\.ac|\.in)?|config\.(?:sub|guess)|.+\.m4|install-sh|.+\.sh|.+\.bat|.+\.pyc?|.+\.pl|icu-configure|.+\.gypi?|.*\.gni?|.+\.mk|.+\.cmake|.+\.gradle|.+\.yaml|pubspec\.lock|\.packages|vms_make\.com|pom\.xml|\.project|source\.properties)$', caseSensitive: false); 57 static final RegExp _docsPattern = RegExp(r'^(?:INSTALL|NEWS|OWNERS|AUTHORS|ChangeLog(?:\.rst|\.[0-9]+)?|.+\.txt|.+\.md|.+\.log|.+\.css|.+\.1|doxygen\.config|Doxyfile|.+\.spec(?:\.in)?)$', caseSensitive: false); 58 static final RegExp _devPattern = RegExp(r'^(?:codereview\.settings|.+\.~|.+\.~[0-9]+~|\.clang-format|\.gitattributes|\.landmines|\.DS_Store|\.travis\.yml|\.cirrus\.yml)$', caseSensitive: false); 59 static final RegExp _testsPattern = RegExp(r'^(?:tj(?:bench|example)test\.(?:java\.)?in|example\.c)$', caseSensitive: false); 60 61 bool get isIncludedInBuildProducts { 62 return !io.name.contains(_readmeNamePattern) 63 && !io.name.contains(_buildTimePattern) 64 && !io.name.contains(_docsPattern) 65 && !io.name.contains(_devPattern) 66 && !io.name.contains(_testsPattern) 67 && !isShellScript; 68 } 69 70 bool get isShellScript => false; 71} 72 73class _RepositorySourceFile extends _RepositoryLicensedFile { 74 _RepositorySourceFile(_RepositoryDirectory parent, fs.TextFile io) : super(parent, io); 75 76 @override 77 fs.TextFile get io => super.io; 78 79 static final RegExp _hashBangPattern = RegExp(r'^#! *(?:/bin/sh|/bin/bash|/usr/bin/env +(?:python|bash))\b'); 80 81 @override 82 bool get isShellScript { 83 return io.readString().startsWith(_hashBangPattern); 84 } 85 86 List<License> _licenses; 87 88 @override 89 Iterable<License> get licenses { 90 if (_licenses != null) 91 return _licenses; 92 String contents; 93 try { 94 contents = io.readString(); 95 } on FormatException { 96 print('non-UTF8 data in $io'); 97 system.exit(2); 98 } 99 _licenses = determineLicensesFor(contents, name, parent, origin: '$this'); 100 if (_licenses == null || _licenses.isEmpty) { 101 _licenses = parent.nearestLicensesFor(name); 102 if (_licenses == null || _licenses.isEmpty) 103 throw 'file has no detectable license and no in-scope default license file'; 104 } 105 _licenses.sort(); 106 for (License license in licenses) 107 license.markUsed(io.fullName, libraryName); 108 assert(_licenses != null && _licenses.isNotEmpty); 109 return _licenses; 110 } 111} 112 113class _RepositoryBinaryFile extends _RepositoryLicensedFile { 114 _RepositoryBinaryFile(_RepositoryDirectory parent, fs.File io) : super(parent, io); 115 116 List<License> _licenses; 117 118 @override 119 List<License> get licenses { 120 if (_licenses == null) { 121 _licenses = parent.nearestLicensesFor(name); 122 if (_licenses == null || _licenses.isEmpty) 123 throw 'no license file found in scope for ${io.fullName}'; 124 for (License license in licenses) 125 license.markUsed(io.fullName, libraryName); 126 } 127 return _licenses; 128 } 129} 130 131 132// LICENSES 133 134abstract class _RepositoryLicenseFile extends _RepositoryFile { 135 _RepositoryLicenseFile(_RepositoryDirectory parent, fs.File io) : super(parent, io); 136 137 List<License> licensesFor(String name); 138 License licenseOfType(LicenseType type); 139 License licenseWithName(String name); 140 141 License get defaultLicense; 142} 143 144abstract class _RepositorySingleLicenseFile extends _RepositoryLicenseFile { 145 _RepositorySingleLicenseFile(_RepositoryDirectory parent, fs.TextFile io, this.license) 146 : super(parent, io); 147 148 final License license; 149 150 @override 151 List<License> licensesFor(String name) { 152 if (license != null) 153 return <License>[license]; 154 return null; 155 } 156 157 @override 158 License licenseWithName(String name) { 159 if (this.name == name) 160 return license; 161 return null; 162 } 163 164 @override 165 License get defaultLicense => license; 166 167 @override 168 Iterable<License> get licenses sync* { yield license; } 169} 170 171class _RepositoryGeneralSingleLicenseFile extends _RepositorySingleLicenseFile { 172 _RepositoryGeneralSingleLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 173 : super(parent, io, License.fromBodyAndName(io.readString(), io.name, origin: io.fullName)); 174 175 _RepositoryGeneralSingleLicenseFile.fromLicense(_RepositoryDirectory parent, fs.TextFile io, License license) 176 : super(parent, io, license); 177 178 @override 179 License licenseOfType(LicenseType type) { 180 if (type == license.type) 181 return license; 182 return null; 183 } 184} 185 186class _RepositoryApache4DNoticeFile extends _RepositorySingleLicenseFile { 187 _RepositoryApache4DNoticeFile(_RepositoryDirectory parent, fs.TextFile io) 188 : super(parent, io, _parseLicense(io)); 189 190 @override 191 License licenseOfType(LicenseType type) => null; 192 193 static final RegExp _pattern = RegExp( 194 r'^(// ------------------------------------------------------------------\n' 195 r'// NOTICE file corresponding to the section 4d of The Apache License,\n' 196 r'// Version 2\.0, in this case for (?:.+)\n' 197 r'// ------------------------------------------------------------------\n)' 198 r'((?:.|\n)+)$', 199 multiLine: false, 200 caseSensitive: false 201 ); 202 203 static bool consider(fs.TextFile io) { 204 return io.readString().contains(_pattern); 205 } 206 207 static License _parseLicense(fs.TextFile io) { 208 final Match match = _pattern.allMatches(io.readString()).single; 209 assert(match.groupCount == 2); 210 return License.unique(match.group(2), LicenseType.apacheNotice, origin: io.fullName); 211 } 212} 213 214class _RepositoryLicenseRedirectFile extends _RepositorySingleLicenseFile { 215 _RepositoryLicenseRedirectFile(_RepositoryDirectory parent, fs.TextFile io, License license) 216 : super(parent, io, license); 217 218 @override 219 License licenseOfType(LicenseType type) { 220 if (type == license.type) 221 return license; 222 return null; 223 } 224 225 static _RepositoryLicenseRedirectFile maybeCreateFrom(_RepositoryDirectory parent, fs.TextFile io) { 226 final String contents = io.readString(); 227 final License license = interpretAsRedirectLicense(contents, parent, origin: io.fullName); 228 if (license != null) 229 return _RepositoryLicenseRedirectFile(parent, io, license); 230 return null; 231 } 232} 233 234class _RepositoryLicenseFileWithLeader extends _RepositorySingleLicenseFile { 235 _RepositoryLicenseFileWithLeader(_RepositoryDirectory parent, fs.TextFile io, RegExp leader) 236 : super(parent, io, _parseLicense(io, leader)); 237 238 @override 239 License licenseOfType(LicenseType type) => null; 240 241 static License _parseLicense(fs.TextFile io, RegExp leader) { 242 final String body = io.readString(); 243 final Match match = leader.firstMatch(body); 244 if (match == null) 245 throw 'failed to strip leader from $io\nleader: /$leader/\nbody:\n---\n$body\n---'; 246 return License.fromBodyAndName(body.substring(match.end), io.name, origin: io.fullName); 247 } 248} 249 250class _RepositoryReadmeIjgFile extends _RepositorySingleLicenseFile { 251 _RepositoryReadmeIjgFile(_RepositoryDirectory parent, fs.TextFile io) 252 : super(parent, io, _parseLicense(io)); 253 254 static final RegExp _pattern = RegExp( 255 r'Permission is hereby granted to use, copy, modify, and distribute this\n' 256 r'software \(or portions thereof\) for any purpose, without fee, subject to these\n' 257 r'conditions:\n' 258 r'\(1\) If any part of the source code for this software is distributed, then this\n' 259 r'README file must be included, with this copyright and no-warranty notice\n' 260 r'unaltered; and any additions, deletions, or changes to the original files\n' 261 r'must be clearly indicated in accompanying documentation\.\n' 262 r'\(2\) If only executable code is distributed, then the accompanying\n' 263 r'documentation must state that "this software is based in part on the work of\n' 264 r'the Independent JPEG Group"\.\n' 265 r'\(3\) Permission for use of this software is granted only if the user accepts\n' 266 r'full responsibility for any undesirable consequences; the authors accept\n' 267 r'NO LIABILITY for damages of any kind\.\n', 268 caseSensitive: false 269 ); 270 271 static License _parseLicense(fs.TextFile io) { 272 final String body = io.readString(); 273 if (!body.contains(_pattern)) 274 throw 'unexpected contents in IJG README'; 275 return License.message(body, LicenseType.ijg, origin: io.fullName); 276 } 277 278 @override 279 License licenseWithName(String name) { 280 if (this.name == name) 281 return license; 282 return null; 283 } 284 285 @override 286 License licenseOfType(LicenseType type) { 287 return null; 288 } 289} 290 291class _RepositoryDartLicenseFile extends _RepositorySingleLicenseFile { 292 _RepositoryDartLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 293 : super(parent, io, _parseLicense(io)); 294 295 static final RegExp _pattern = RegExp( 296 r'^This license applies to all parts of Dart that are not externally\n' 297 r'maintained libraries\. The external maintained libraries used by\n' 298 r'Dart are:\n' 299 r'\n' 300 r'(?:.+\n)+' 301 r'\n' 302 r'The libraries may have their own licenses; we recommend you read them,\n' 303 r'as their terms may differ from the terms below\.\n' 304 r'\n' 305 r'(Copyright (?:.|\n)+)$', 306 caseSensitive: false 307 ); 308 309 static License _parseLicense(fs.TextFile io) { 310 final Match match = _pattern.firstMatch(io.readString()); 311 if (match == null || match.groupCount != 1) 312 throw 'unexpected Dart license file contents'; 313 return License.template(match.group(1), LicenseType.bsd, origin: io.fullName); 314 } 315 316 @override 317 License licenseOfType(LicenseType type) { 318 return null; 319 } 320} 321 322class _RepositoryLibPngLicenseFile extends _RepositorySingleLicenseFile { 323 _RepositoryLibPngLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 324 : super(parent, io, License.blank(io.readString(), LicenseType.libpng, origin: io.fullName)) { 325 _verifyLicense(io); 326 } 327 328 static void _verifyLicense(fs.TextFile io) { 329 final String contents = io.readString(); 330 if (!contents.contains('COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:') || 331 !contents.contains('png') || 332 !contents.contains('END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE.')) 333 throw 'unexpected libpng license file contents:\n----8<----\n$contents\n----<8----'; 334 } 335 336 @override 337 License licenseOfType(LicenseType type) { 338 if (type == LicenseType.libpng) 339 return license; 340 return null; 341 } 342} 343 344class _RepositoryBlankLicenseFile extends _RepositorySingleLicenseFile { 345 _RepositoryBlankLicenseFile(_RepositoryDirectory parent, fs.TextFile io, String sanityCheck) 346 : super(parent, io, License.blank(io.readString(), LicenseType.unknown)) { 347 _verifyLicense(io, sanityCheck); 348 } 349 350 static void _verifyLicense(fs.TextFile io, String sanityCheck) { 351 final String contents = io.readString(); 352 if (!contents.contains(sanityCheck)) 353 throw 'unexpected file contents; wanted "$sanityCheck", but got:\n----8<----\n$contents\n----<8----'; 354 } 355 356 @override 357 License licenseOfType(LicenseType type) => null; 358} 359 360class _RepositoryCatapultApiClientLicenseFile extends _RepositorySingleLicenseFile { 361 _RepositoryCatapultApiClientLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 362 : super(parent, io, _parseLicense(io)); 363 364 static final RegExp _pattern = RegExp( 365 r' *Licensed under the Apache License, Version 2\.0 \(the "License"\);\n' 366 r' *you may not use this file except in compliance with the License\.\n' 367 r' *You may obtain a copy of the License at\n' 368 r' *\n' 369 r' *(http://www\.apache\.org/licenses/LICENSE-2\.0)\n' 370 r' *\n' 371 r' *Unless required by applicable law or agreed to in writing, software\n' 372 r' *distributed under the License is distributed on an "AS IS" BASIS,\n' 373 r' *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n' 374 r' *See the License for the specific language governing permissions and\n' 375 r' *limitations under the License\.\n', 376 multiLine: true, 377 caseSensitive: false, 378 ); 379 380 static License _parseLicense(fs.TextFile io) { 381 final Match match = _pattern.firstMatch(io.readString()); 382 if (match == null || match.groupCount != 1) 383 throw 'unexpected apiclient license file contents'; 384 return License.fromUrl(match.group(1), origin: io.fullName); 385 } 386 387 @override 388 License licenseOfType(LicenseType type) { 389 return null; 390 } 391} 392 393class _RepositoryCatapultCoverageLicenseFile extends _RepositorySingleLicenseFile { 394 _RepositoryCatapultCoverageLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 395 : super(parent, io, _parseLicense(io)); 396 397 static final RegExp _pattern = RegExp( 398 r' *Except where noted otherwise, this software is licensed under the Apache\n' 399 r' *License, Version 2.0 \(the "License"\); you may not use this work except in\n' 400 r' *compliance with the License\. You may obtain a copy of the License at\n' 401 r' *\n' 402 r' *(http://www\.apache\.org/licenses/LICENSE-2\.0)\n' 403 r' *\n' 404 r' *Unless required by applicable law or agreed to in writing, software\n' 405 r' *distributed under the License is distributed on an "AS IS" BASIS,\n' 406 r' *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n' 407 r' *See the License for the specific language governing permissions and\n' 408 r' *limitations under the License\.\n', 409 multiLine: true, 410 caseSensitive: false, 411 ); 412 413 static License _parseLicense(fs.TextFile io) { 414 final Match match = _pattern.firstMatch(io.readString()); 415 if (match == null || match.groupCount != 1) 416 throw 'unexpected coverage license file contents'; 417 return License.fromUrl(match.group(1), origin: io.fullName); 418 } 419 420 @override 421 License licenseOfType(LicenseType type) { 422 return null; 423 } 424} 425 426class _RepositoryLibJpegTurboLicense extends _RepositoryLicenseFile { 427 _RepositoryLibJpegTurboLicense(_RepositoryDirectory parent, fs.TextFile io) 428 : super(parent, io) { 429 _parseLicense(io); 430 } 431 432 static final RegExp _pattern = RegExp( 433 r'libjpeg-turbo is covered by three compatible BSD-style open source licenses:\n' 434 r'\n' 435 r'- The IJG \(Independent JPEG Group\) License, which is listed in\n' 436 r' \[README\.ijg\]\(README\.ijg\)\n' 437 r'\n' 438 r' This license applies to the libjpeg API library and associated programs\n' 439 r' \(any code inherited from libjpeg, and any modifications to that code\.\)\n' 440 r'\n' 441 r'- The Modified \(3-clause\) BSD License, which is listed in\n' 442 r' \[turbojpeg\.c\]\(turbojpeg\.c\)\n' 443 r'\n' 444 r' This license covers the TurboJPEG API library and associated programs\.\n' 445 r'\n' 446 r'- The zlib License, which is listed in \[simd/jsimdext\.inc\]\(simd/jsimdext\.inc\)\n' 447 r'\n' 448 r' This license is a subset of the other two, and it covers the libjpeg-turbo\n' 449 r' SIMD extensions\.\n' 450 ); 451 452 static void _parseLicense(fs.TextFile io) { 453 final String body = io.readString(); 454 if (!body.contains(_pattern)) 455 throw 'unexpected contents in libjpeg-turbo LICENSE'; 456 } 457 458 List<License> _licenses; 459 460 @override 461 List<License> get licenses { 462 if (_licenses == null) { 463 final _RepositoryReadmeIjgFile readme = parent.getChildByName('README.ijg'); 464 final _RepositorySourceFile main = parent.getChildByName('turbojpeg.c'); 465 final _RepositoryDirectory simd = parent.getChildByName('simd'); 466 final _RepositorySourceFile zlib = simd.getChildByName('jsimdext.inc'); 467 _licenses = <License>[]; 468 _licenses.add(readme.license); 469 _licenses.add(main.licenses.single); 470 _licenses.add(zlib.licenses.single); 471 } 472 return _licenses; 473 } 474 475 @override 476 License licenseWithName(String name) { 477 return null; 478 } 479 480 @override 481 List<License> licensesFor(String name) { 482 return licenses; 483 } 484 485 @override 486 License licenseOfType(LicenseType type) { 487 return null; 488 } 489 490 @override 491 License get defaultLicense => null; 492} 493 494class _RepositoryFreetypeLicenseFile extends _RepositoryLicenseFile { 495 _RepositoryFreetypeLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 496 : _target = _parseLicense(io), super(parent, io); 497 498 static final RegExp _pattern = RegExp( 499 r'The FreeType 2 font engine is copyrighted work and cannot be used\n' 500 r'legally without a software license\. In order to make this project\n' 501 r'usable to a vast majority of developers, we distribute it under two\n' 502 r'mutually exclusive open-source licenses\.\n' 503 r'\n' 504 r'This means that \*you\* must choose \*one\* of the two licenses described\n' 505 r'below, then obey all its terms and conditions when using FreeType 2 in\n' 506 r'any of your projects or products.\n' 507 r'\n' 508 r" - The FreeType License, found in the file `(FTL\.TXT)', which is similar\n" 509 r' to the original BSD license \*with\* an advertising clause that forces\n' 510 r" you to explicitly cite the FreeType project in your product's\n" 511 r' documentation\. All details are in the license file\. This license\n' 512 r" is suited to products which don't use the GNU General Public\n" 513 r' License\.\n' 514 r'\n' 515 r' Note that this license is compatible to the GNU General Public\n' 516 r' License version 3, but not version 2\.\n' 517 r'\n' 518 r" - The GNU General Public License version 2, found in `GPLv2\.TXT' \(any\n" 519 r' later version can be used also\), for programs which already use the\n' 520 r' GPL\. Note that the FTL is incompatible with GPLv2 due to its\n' 521 r' advertisement clause\.\n' 522 r'\n' 523 r'The contributed BDF and PCF drivers come with a license similar to that\n' 524 r'of the X Window System\. It is compatible to the above two licenses \(see\n' 525 r'file src/bdf/README and src/pcf/README\)\. The same holds for the files\n' 526 r"`fthash\.c' and `fthash\.h'; their code was part of the BDF driver in\n" 527 r'earlier FreeType versions\.\n' 528 r'\n' 529 r'The gzip module uses the zlib license \(see src/gzip/zlib\.h\) which too is\n' 530 r'compatible to the above two licenses\.\n' 531 r'\n' 532 r'The MD5 checksum support \(only used for debugging in development builds\)\n' 533 r'is in the public domain\.\n' 534 r'\n*' 535 r'--- end of LICENSE\.TXT ---\n*$' 536 ); 537 538 static String _parseLicense(fs.TextFile io) { 539 final Match match = _pattern.firstMatch(io.readString()); 540 if (match == null || match.groupCount != 1) 541 throw 'unexpected Freetype license file contents'; 542 return match.group(1); 543 } 544 545 final String _target; 546 List<License> _targetLicense; 547 548 void _warmCache() { 549 _targetLicense ??= <License>[parent.nearestLicenseWithName(_target)]; 550 } 551 552 @override 553 List<License> licensesFor(String name) { 554 _warmCache(); 555 return _targetLicense; 556 } 557 558 @override 559 License licenseOfType(LicenseType type) => null; 560 561 @override 562 License licenseWithName(String name) => null; 563 564 @override 565 License get defaultLicense { 566 _warmCache(); 567 return _targetLicense.single; 568 } 569 570 @override 571 Iterable<License> get licenses sync* { } 572} 573 574class _RepositoryIcuLicenseFile extends _RepositoryLicenseFile { 575 _RepositoryIcuLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 576 : _licenses = _parseLicense(io), 577 super(parent, io); 578 579 @override 580 fs.TextFile get io => super.io; 581 582 final List<License> _licenses; 583 584 static final RegExp _pattern = RegExp( 585 r'^COPYRIGHT AND PERMISSION NOTICE \(ICU 58 and later\)\n+' 586 r'( *Copyright (?:.|\n)+?)\n+' // 1 587 r'Third-Party Software Licenses\n+' 588 r' *This section contains third-party software notices and/or additional\n' 589 r' *terms for licensed third-party software components included within ICU\n' 590 r' *libraries\.\n+' 591 r' *1\. ICU License - ICU 1.8.1 to ICU 57.1[ \n]+?' 592 r' *COPYRIGHT AND PERMISSION NOTICE\n+' 593 r'(Copyright (?:.|\n)+?)\n+' //2 594 r' *2\. Chinese/Japanese Word Break Dictionary Data \(cjdict\.txt\)\n+' 595 r' # The Google Chrome software developed by Google is licensed under\n?' 596 r' # the BSD license\. Other software included in this distribution is\n?' 597 r' # provided under other licenses, as set forth below\.\n' 598 r' #\n' 599 r'( # The BSD License\n' 600 r' # http://opensource\.org/licenses/bsd-license\.php\n' 601 r' # +Copyright(?:.|\n)+?)\n' // 3 602 r' #\n' 603 r' #\n' 604 r' # The word list in cjdict.txt are generated by combining three word lists\n?' 605 r' # listed below with further processing for compound word breaking\. The\n?' 606 r' # frequency is generated with an iterative training against Google web\n?' 607 r' # corpora\.\n' 608 r' #\n' 609 r' # \* Libtabe \(Chinese\)\n' 610 r' # - https://sourceforge\.net/project/\?group_id=1519\n' 611 r' # - Its license terms and conditions are shown below\.\n' 612 r' #\n' 613 r' # \* IPADIC \(Japanese\)\n' 614 r' # - http://chasen\.aist-nara\.ac\.jp/chasen/distribution\.html\n' 615 r' # - Its license terms and conditions are shown below\.\n' 616 r' #\n' 617 r' # ---------COPYING\.libtabe ---- BEGIN--------------------\n' 618 r' #\n' 619 r' # +/\*\n' 620 r'( # +\* Copyright (?:.|\n)+?)\n' // 4 621 r' # +\*/\n' 622 r' #\n' 623 r' # +/\*\n' 624 r'( # +\* Copyright (?:.|\n)+?)\n' // 5 625 r' # +\*/\n' 626 r' #\n' 627 r'( # +Copyright (?:.|\n)+?)\n' // 6 628 r' #\n' 629 r' # +---------------COPYING\.libtabe-----END--------------------------------\n' 630 r' #\n' 631 r' #\n' 632 r' # +---------------COPYING\.ipadic-----BEGIN-------------------------------\n' 633 r' #\n' 634 r'( # +Copyright (?:.|\n)+?)\n' // 7 635 r' #\n' 636 r' # +---------------COPYING\.ipadic-----END----------------------------------\n' 637 r'\n' 638 r' *3\. Lao Word Break Dictionary Data \(laodict\.txt\)\n' 639 r'\n' 640 r'( # +Copyright(?:.|\n)+?)\n' // 8 641 r'\n' 642 r' *4\. Burmese Word Break Dictionary Data \(burmesedict\.txt\)\n' 643 r'\n' 644 r'( # +Copyright(?:.|\n)+?)\n' // 9 645 r'\n' 646 r' *5\. Time Zone Database\n' 647 r'((?:.|\n)+)\n' // 10 648 r'\n' 649 r' *6\. Google double-conversion\n' 650 r'\n' 651 r'(Copyright(?:.|\n)+)\n$', // 11 652 multiLine: true, 653 caseSensitive: false 654 ); 655 656 static final RegExp _unexpectedHash = RegExp(r'^.+ #', multiLine: true); 657 static final RegExp _newlineHash = RegExp(r' # ?'); 658 659 static String _dewrap(String s) { 660 if (!s.startsWith(' # ')) 661 return s; 662 if (s.contains(_unexpectedHash)) 663 throw 'ICU license file contained unexpected hash sequence'; 664 if (s.contains('\x2028')) 665 throw 'ICU license file contained unexpected line separator'; 666 return s.replaceAll(_newlineHash, '\x2028').replaceAll('\n', '').replaceAll('\x2028', '\n'); 667 } 668 669 static List<License> _parseLicense(fs.TextFile io) { 670 final Match match = _pattern.firstMatch(io.readString()); 671 if (match == null) 672 throw 'could not parse ICU license file'; 673 assert(match.groupCount == 11); 674 if (match.group(10).contains(copyrightMentionPattern) || match.group(11).contains('7.')) 675 throw 'unexpected copyright in ICU license file'; 676 final List<License> result = <License>[ 677 License.fromBodyAndType(_dewrap(match.group(1)), LicenseType.unknown, origin: io.fullName), 678 License.fromBodyAndType(_dewrap(match.group(2)), LicenseType.icu, origin: io.fullName), 679 License.fromBodyAndType(_dewrap(match.group(3)), LicenseType.bsd, origin: io.fullName), 680 License.fromBodyAndType(_dewrap(match.group(4)), LicenseType.bsd, origin: io.fullName), 681 License.fromBodyAndType(_dewrap(match.group(5)), LicenseType.bsd, origin: io.fullName), 682 License.fromBodyAndType(_dewrap(match.group(6)), LicenseType.unknown, origin: io.fullName), 683 License.fromBodyAndType(_dewrap(match.group(7)), LicenseType.unknown, origin: io.fullName), 684 License.fromBodyAndType(_dewrap(match.group(8)), LicenseType.bsd, origin: io.fullName), 685 License.fromBodyAndType(_dewrap(match.group(9)), LicenseType.bsd, origin: io.fullName), 686 License.fromBodyAndType(_dewrap(match.group(11)), LicenseType.bsd, origin: io.fullName), 687 ]; 688 return result; 689 } 690 691 @override 692 List<License> licensesFor(String name) { 693 return _licenses; 694 } 695 696 @override 697 License licenseOfType(LicenseType type) { 698 if (type == LicenseType.icu) 699 return _licenses[0]; 700 throw 'tried to use ICU license file to find a license by type but type wasn\'t ICU'; 701 } 702 703 @override 704 License licenseWithName(String name) { 705 throw 'tried to use ICU license file to find a license by name'; 706 } 707 708 @override 709 License get defaultLicense => _licenses[0]; 710 711 @override 712 Iterable<License> get licenses => _licenses; 713} 714 715Iterable<List<int>> splitIntList(List<int> data, int boundary) sync* { 716 int index = 0; 717 List<int> getOne() { 718 final int start = index; 719 int end = index; 720 while ((end < data.length) && (data[end] != boundary)) 721 end += 1; 722 end += 1; 723 index = end; 724 return data.sublist(start, end).toList(); 725 } 726 while (index < data.length) 727 yield getOne(); 728} 729 730class _RepositoryMultiLicenseNoticesForFilesFile extends _RepositoryLicenseFile { 731 _RepositoryMultiLicenseNoticesForFilesFile(_RepositoryDirectory parent, fs.File io) 732 : _licenses = _parseLicense(io), 733 super(parent, io); 734 735 final Map<String, License> _licenses; 736 737 static Map<String, License> _parseLicense(fs.File io) { 738 final Map<String, License> result = <String, License>{}; 739 // Files of this type should begin with: 740 // "Notices for files contained in the" 741 // ...then have a second line which is 60 "=" characters 742 final List<List<int>> contents = splitIntList(io.readBytes(), 0x0A).toList(); 743 if (!ascii.decode(contents[0]).startsWith('Notices for files contained in') || 744 ascii.decode(contents[1]) != '============================================================\n') 745 throw 'unrecognised syntax: ${io.fullName}'; 746 int index = 2; 747 while (index < contents.length) { 748 if (ascii.decode(contents[index]) != 'Notices for file(s):\n') 749 throw 'unrecognised syntax on line ${index + 1}: ${io.fullName}'; 750 index += 1; 751 final List<String> names = <String>[]; 752 do { 753 names.add(ascii.decode(contents[index])); 754 index += 1; 755 } while (ascii.decode(contents[index]) != '------------------------------------------------------------\n'); 756 index += 1; 757 final List<List<int>> body = <List<int>>[]; 758 do { 759 body.add(contents[index]); 760 index += 1; 761 } while (index < contents.length && 762 ascii.decode(contents[index], allowInvalid: true) != '============================================================\n'); 763 index += 1; 764 final List<int> bodyBytes = body.expand((List<int> line) => line).toList(); 765 String bodyText; 766 try { 767 bodyText = utf8.decode(bodyBytes); 768 } on FormatException { 769 bodyText = latin1.decode(bodyBytes); 770 } 771 final License license = License.unique(bodyText, LicenseType.unknown, origin: io.fullName); 772 for (String name in names) { 773 if (result[name] != null) 774 throw 'conflicting license information for $name in ${io.fullName}'; 775 result[name] = license; 776 } 777 } 778 return result; 779 } 780 781 @override 782 List<License> licensesFor(String name) { 783 final License license = _licenses[name]; 784 if (license != null) 785 return <License>[license]; 786 return null; 787 } 788 789 @override 790 License licenseOfType(LicenseType type) { 791 throw 'tried to use multi-license license file to find a license by type'; 792 } 793 794 @override 795 License licenseWithName(String name) { 796 throw 'tried to use multi-license license file to find a license by name'; 797 } 798 799 @override 800 License get defaultLicense { 801 assert(false); 802 throw '$this ($runtimeType) does not have a concept of a "default" license'; 803 } 804 805 @override 806 Iterable<License> get licenses => _licenses.values; 807} 808 809class _RepositoryCxxStlDualLicenseFile extends _RepositoryLicenseFile { 810 _RepositoryCxxStlDualLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 811 : _licenses = _parseLicenses(io), super(parent, io); 812 813 static final RegExp _pattern = RegExp( 814 r'==============================================================================\n' 815 r'.+ License.*\n' 816 r'==============================================================================\n' 817 r'\n' 818 r'The .+ library is dual licensed under both the University of Illinois\n' 819 r'"BSD-Like" license and the MIT license\. +As a user of this code you may choose\n' 820 r'to use it under either license\. +As a contributor, you agree to allow your code\n' 821 r'to be used under both\.\n' 822 r'\n' 823 r'Full text of the relevant licenses is included below\.\n' 824 r'\n' 825 r'==============================================================================\n' 826 r'((?:.|\n)+)\n' 827 r'==============================================================================\n' 828 r'((?:.|\n)+)' 829 r'$' 830 ); 831 832 static List<License> _parseLicenses(fs.TextFile io) { 833 final Match match = _pattern.firstMatch(io.readString()); 834 if (match == null || match.groupCount != 2) 835 throw 'unexpected dual license file contents'; 836 return <License>[ 837 License.fromBodyAndType(match.group(1), LicenseType.bsd), 838 License.fromBodyAndType(match.group(2), LicenseType.mit), 839 ]; 840 } 841 842 List<License> _licenses; 843 844 @override 845 List<License> licensesFor(String name) { 846 return _licenses; 847 } 848 849 @override 850 License licenseOfType(LicenseType type) { 851 throw 'tried to look up a dual-license license by type ("$type")'; 852 } 853 854 @override 855 License licenseWithName(String name) { 856 throw 'tried to look up a dual-license license by name ("$name")'; 857 } 858 859 @override 860 License get defaultLicense => _licenses[0]; 861 862 @override 863 Iterable<License> get licenses => _licenses; 864} 865 866 867// DIRECTORIES 868 869class _RepositoryDirectory extends _RepositoryEntry implements LicenseSource { 870 _RepositoryDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io) { 871 crawl(); 872 } 873 874 @override 875 fs.Directory get io => super.io; 876 877 final List<_RepositoryDirectory> _subdirectories = <_RepositoryDirectory>[]; 878 final List<_RepositoryLicensedFile> _files = <_RepositoryLicensedFile>[]; 879 final List<_RepositoryLicenseFile> _licenses = <_RepositoryLicenseFile>[]; 880 881 List<_RepositoryDirectory> get subdirectories => _subdirectories; 882 883 final Map<String, _RepositoryEntry> _childrenByName = <String, _RepositoryEntry>{}; 884 885 // the bit at the beginning excludes files like "license.py". 886 static final RegExp _licenseNamePattern = RegExp(r'^(?!.*\.py$)(?!.*(?:no|update)-copyright)(?!.*mh-bsd-gcc).*\b_*(?:license(?!\.html)|copying|copyright|notice|l?gpl|bsd|mpl?|ftl\.txt)_*\b', caseSensitive: false); 887 888 void crawl() { 889 for (fs.IoNode entry in io.walk) { 890 if (shouldRecurse(entry)) { 891 assert(!_childrenByName.containsKey(entry.name)); 892 if (entry is fs.Directory) { 893 final _RepositoryDirectory child = createSubdirectory(entry); 894 _subdirectories.add(child); 895 _childrenByName[child.name] = child; 896 } else if (entry is fs.File) { 897 try { 898 final _RepositoryFile child = createFile(entry); 899 assert(child != null); 900 if (child is _RepositoryLicensedFile) { 901 _files.add(child); 902 } else { 903 assert(child is _RepositoryLicenseFile); 904 _licenses.add(child); 905 } 906 _childrenByName[child.name] = child; 907 } catch (e) { 908 system.stderr.writeln('failed to handle $entry: $e'); 909 rethrow; 910 } 911 } else { 912 assert(entry is fs.Link); 913 } 914 } 915 } 916 917 for (_RepositoryDirectory child in virtualSubdirectories) { 918 _subdirectories.add(child); 919 _childrenByName[child.name] = child; 920 } 921 } 922 923 // Override this to add additional child directories that do not represent a 924 // direct child of this directory's filesystem node. 925 List<_RepositoryDirectory> get virtualSubdirectories => <_RepositoryDirectory>[]; 926 927 bool shouldRecurse(fs.IoNode entry) { 928 return !entry.fullName.endsWith('third_party/gn') && 929 entry.name != '.cipd' && 930 entry.name != '.git' && 931 entry.name != '.github' && 932 entry.name != '.gitignore' && 933 entry.name != '.vscode' && 934 entry.name != 'test' && 935 entry.name != 'test.disabled' && 936 entry.name != 'test_support' && 937 entry.name != 'tests' && 938 entry.name != 'javatests' && 939 entry.name != 'testing'; 940 } 941 942 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 943 if (entry.name == 'third_party') 944 return _RepositoryGenericThirdPartyDirectory(this, entry); 945 return _RepositoryDirectory(this, entry); 946 } 947 948 _RepositoryFile createFile(fs.IoNode entry) { 949 if (entry is fs.TextFile) { 950 if (_RepositoryApache4DNoticeFile.consider(entry)) { 951 return _RepositoryApache4DNoticeFile(this, entry); 952 } else { 953 _RepositoryFile result; 954 if (entry.name == 'NOTICE') 955 result = _RepositoryLicenseRedirectFile.maybeCreateFrom(this, entry); 956 if (result != null) { 957 return result; 958 } else if (entry.name.contains(_licenseNamePattern)) { 959 return _RepositoryGeneralSingleLicenseFile(this, entry); 960 } else if (entry.name == 'README.ijg') { 961 return _RepositoryReadmeIjgFile(this, entry); 962 } else { 963 return _RepositorySourceFile(this, entry); 964 } 965 } 966 } else if (entry.name == 'NOTICE.txt') { 967 return _RepositoryMultiLicenseNoticesForFilesFile(this, entry); 968 } else { 969 return _RepositoryBinaryFile(this, entry); 970 } 971 } 972 973 int get count => _files.length + _subdirectories.fold<int>(0, (int count, _RepositoryDirectory child) => count + child.count); 974 975 @override 976 List<License> nearestLicensesFor(String name) { 977 if (_licenses.isEmpty) { 978 if (_canGoUp(null)) 979 return parent.nearestLicensesFor('${io.name}/$name'); 980 return null; 981 } 982 if (_licenses.length == 1) 983 return _licenses.single.licensesFor(name); 984 final List<License> licenses = _licenses.expand((_RepositoryLicenseFile license) sync* { 985 final List<License> licenses = license.licensesFor(name); 986 if (licenses != null) 987 yield* licenses; 988 }).toList(); 989 if (licenses.isEmpty) 990 return null; 991 if (licenses.length > 1) { 992 //print('unexpectedly found multiple matching licenses for: $name'); 993 return licenses; // TODO(ianh): disambiguate them, in case we have e.g. a dual GPL/BSD situation 994 } 995 return licenses; 996 } 997 998 @override 999 License nearestLicenseOfType(LicenseType type) { 1000 License result = _nearestAncestorLicenseWithType(type); 1001 if (result == null) { 1002 for (_RepositoryDirectory directory in _subdirectories) { 1003 result = directory._localLicenseWithType(type); 1004 if (result != null) 1005 break; 1006 } 1007 } 1008 result ??= _fullWalkUpForLicenseWithType(type); 1009 return result; 1010 } 1011 1012 /// Searches the current and all parent directories (up to the license root) 1013 /// for a license of the specified type. 1014 License _nearestAncestorLicenseWithType(LicenseType type) { 1015 final License result = _localLicenseWithType(type); 1016 if (result != null) 1017 return result; 1018 if (_canGoUp(null)) 1019 return parent._nearestAncestorLicenseWithType(type); 1020 return null; 1021 } 1022 1023 /// Searches all subdirectories below the current license root for a license 1024 /// of the specified type. 1025 License _fullWalkUpForLicenseWithType(LicenseType type) { 1026 return _canGoUp(null) 1027 ? parent._fullWalkUpForLicenseWithType(type) 1028 : _fullWalkDownForLicenseWithType(type); 1029 } 1030 1031 /// Searches the current directory and all subdirectories for a license of 1032 /// the specified type. 1033 License _fullWalkDownForLicenseWithType(LicenseType type) { 1034 License result = _localLicenseWithType(type); 1035 if (result == null) { 1036 for (_RepositoryDirectory directory in _subdirectories) { 1037 result = directory._fullWalkDownForLicenseWithType(type); 1038 if (result != null) 1039 break; 1040 } 1041 } 1042 return result; 1043 } 1044 1045 /// Searches the current directory for licenses of the specified type. 1046 License _localLicenseWithType(LicenseType type) { 1047 final List<License> licenses = _licenses.expand((_RepositoryLicenseFile license) sync* { 1048 final License result = license.licenseOfType(type); 1049 if (result != null) 1050 yield result; 1051 }).toList(); 1052 if (licenses.length > 1) { 1053 print('unexpectedly found multiple matching licenses in $name of type $type'); 1054 return null; 1055 } 1056 if (licenses.isNotEmpty) 1057 return licenses.single; 1058 return null; 1059 } 1060 1061 @override 1062 License nearestLicenseWithName(String name, { String authors }) { 1063 License result = _nearestAncestorLicenseWithName(name, authors: authors); 1064 if (result == null) { 1065 for (_RepositoryDirectory directory in _subdirectories) { 1066 result = directory._localLicenseWithName(name, authors: authors); 1067 if (result != null) 1068 break; 1069 } 1070 } 1071 result ??= _fullWalkUpForLicenseWithName(name, authors: authors); 1072 result ??= _fullWalkUpForLicenseWithName(name, authors: authors, ignoreCase: true); 1073 if (authors != null && result == null) { 1074 // if (result == null) 1075 // print('could not find $name for authors "$authors", now looking for any $name in $this'); 1076 result = nearestLicenseWithName(name); 1077 // if (result == null) 1078 // print('completely failed to find $name for authors "$authors"'); 1079 // else 1080 // print('ended up finding a $name for "${result.authors}" instead'); 1081 } 1082 return result; 1083 } 1084 1085 bool _canGoUp(String authors) { 1086 return parent != null && (authors != null || isLicenseRootException || (!isLicenseRoot && !parent.subdirectoriesAreLicenseRoots)); 1087 } 1088 1089 License _nearestAncestorLicenseWithName(String name, { String authors }) { 1090 final License result = _localLicenseWithName(name, authors: authors); 1091 if (result != null) 1092 return result; 1093 if (_canGoUp(authors)) 1094 return parent._nearestAncestorLicenseWithName(name, authors: authors); 1095 return null; 1096 } 1097 1098 License _fullWalkUpForLicenseWithName(String name, { String authors, bool ignoreCase = false }) { 1099 return _canGoUp(authors) 1100 ? parent._fullWalkUpForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase) 1101 : _fullWalkDownForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase); 1102 } 1103 1104 License _fullWalkDownForLicenseWithName(String name, { String authors, bool ignoreCase = false }) { 1105 License result = _localLicenseWithName(name, authors: authors, ignoreCase: ignoreCase); 1106 if (result == null) { 1107 for (_RepositoryDirectory directory in _subdirectories) { 1108 result = directory._fullWalkDownForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase); 1109 if (result != null) 1110 break; 1111 } 1112 } 1113 return result; 1114 } 1115 1116 /// Unless isLicenseRootException is true, we should not walk up the tree from 1117 /// here looking for licenses. 1118 bool get isLicenseRoot => parent == null; 1119 1120 /// Unless isLicenseRootException is true on a child, the child should not 1121 /// walk up the tree to here looking for licenses. 1122 bool get subdirectoriesAreLicenseRoots => false; 1123 1124 @override 1125 String get libraryName { 1126 if (isLicenseRoot) 1127 return name; 1128 assert(parent != null); 1129 if (parent.subdirectoriesAreLicenseRoots) 1130 return name; 1131 return parent.libraryName; 1132 } 1133 1134 /// Overrides isLicenseRoot and parent.subdirectoriesAreLicenseRoots for cases 1135 /// where a directory contains license roots instead of being one. This 1136 /// allows, for example, the expat third_party directory to contain a 1137 /// subdirectory with expat while itself containing a BUILD file that points 1138 /// to the LICENSE in the root of the repo. 1139 bool get isLicenseRootException => false; 1140 1141 License _localLicenseWithName(String name, { String authors, bool ignoreCase = false }) { 1142 Map<String, _RepositoryEntry> map; 1143 if (ignoreCase) { 1144 // we get here if we're trying a last-ditch effort at finding a file. 1145 // so this should happen only rarely. 1146 map = HashMap<String, _RepositoryEntry>( 1147 equals: (String n1, String n2) => n1.toLowerCase() == n2.toLowerCase(), 1148 hashCode: (String n) => n.toLowerCase().hashCode 1149 ) 1150 ..addAll(_childrenByName); 1151 } else { 1152 map = _childrenByName; 1153 } 1154 final _RepositoryEntry entry = map[name]; 1155 License license; 1156 if (entry is _RepositoryLicensedFile) { 1157 license = entry.licenses.single; 1158 } else if (entry is _RepositoryLicenseFile) { 1159 license = entry.defaultLicense; 1160 } else if (entry != null) { 1161 if (authors == null) 1162 throw 'found "$name" in $this but it was a ${entry.runtimeType}'; 1163 } 1164 if (license != null && authors != null) { 1165 if (license.authors?.toLowerCase() != authors.toLowerCase()) 1166 license = null; 1167 } 1168 return license; 1169 } 1170 1171 _RepositoryEntry getChildByName(String name) { 1172 return _childrenByName[name]; 1173 } 1174 1175 Set<License> getLicenses(_Progress progress) { 1176 final Set<License> result = <License>{}; 1177 for (_RepositoryDirectory directory in _subdirectories) 1178 result.addAll(directory.getLicenses(progress)); 1179 for (_RepositoryLicensedFile file in _files) { 1180 if (file.isIncludedInBuildProducts) { 1181 try { 1182 progress.label = '$file'; 1183 final List<License> licenses = file.licenses; 1184 assert(licenses != null && licenses.isNotEmpty); 1185 result.addAll(licenses); 1186 progress.advance(success: true); 1187 } catch (e, stack) { 1188 system.stderr.writeln('\nerror searching for copyright in: ${file.io}\n$e'); 1189 if (e is! String) 1190 system.stderr.writeln(stack); 1191 system.stderr.writeln('\n'); 1192 progress.advance(success: false); 1193 } 1194 } 1195 } 1196 for (_RepositoryLicenseFile file in _licenses) 1197 result.addAll(file.licenses); 1198 return result; 1199 } 1200 1201 int get fileCount { 1202 int result = 0; 1203 for (_RepositoryLicensedFile file in _files) { 1204 if (file.isIncludedInBuildProducts) 1205 result += 1; 1206 } 1207 for (_RepositoryDirectory directory in _subdirectories) 1208 result += directory.fileCount; 1209 return result; 1210 } 1211 1212 Iterable<_RepositoryLicensedFile> get _signatureFiles sync* { 1213 for (_RepositoryLicensedFile file in _files) { 1214 if (file.isIncludedInBuildProducts) 1215 yield file; 1216 } 1217 for (_RepositoryDirectory directory in _subdirectories) { 1218 if (directory.includeInSignature) 1219 yield* directory._signatureFiles; 1220 } 1221 } 1222 1223 Stream<List<int>> _signatureStream(List<_RepositoryLicensedFile> files) async* { 1224 for (_RepositoryLicensedFile file in files) { 1225 yield file.io.fullName.codeUnits; 1226 yield file.io.readBytes(); 1227 } 1228 } 1229 1230 /// Compute a signature representing a hash of all the licensed files within 1231 /// this directory tree. 1232 Future<String> get signature async { 1233 final List<_RepositoryLicensedFile> allFiles = _signatureFiles.toList(); 1234 allFiles.sort((_RepositoryLicensedFile a, _RepositoryLicensedFile b) => 1235 a.io.fullName.compareTo(b.io.fullName)); 1236 final crypto.Digest digest = await crypto.md5.bind(_signatureStream(allFiles)).single; 1237 return digest.bytes.map((int e) => e.toRadixString(16).padLeft(2, '0')).join(); 1238 } 1239 1240 /// True if this directory's contents should be included when computing the signature. 1241 bool get includeInSignature => true; 1242} 1243 1244class _RepositoryGenericThirdPartyDirectory extends _RepositoryDirectory { 1245 _RepositoryGenericThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1246 1247 @override 1248 bool get subdirectoriesAreLicenseRoots => true; 1249} 1250 1251class _RepositoryReachOutFile extends _RepositoryLicensedFile { 1252 _RepositoryReachOutFile(_RepositoryDirectory parent, fs.File io, this.offset) : super(parent, io); 1253 1254 final int offset; 1255 1256 @override 1257 List<License> get licenses { 1258 _RepositoryDirectory directory = parent; 1259 int index = offset; 1260 while (index > 1) { 1261 if (directory == null) 1262 break; 1263 directory = directory.parent; 1264 index -= 1; 1265 } 1266 return directory?.nearestLicensesFor(name); 1267 } 1268} 1269 1270class _RepositoryReachOutDirectory extends _RepositoryDirectory { 1271 _RepositoryReachOutDirectory(_RepositoryDirectory parent, fs.Directory io, this.reachOutFilenames, this.offset) : super(parent, io); 1272 1273 final Set<String> reachOutFilenames; 1274 final int offset; 1275 1276 @override 1277 _RepositoryFile createFile(fs.IoNode entry) { 1278 if (reachOutFilenames.contains(entry.name)) 1279 return _RepositoryReachOutFile(this, entry, offset); 1280 return super.createFile(entry); 1281 } 1282} 1283 1284class _RepositoryExcludeSubpathDirectory extends _RepositoryDirectory { 1285 _RepositoryExcludeSubpathDirectory(_RepositoryDirectory parent, fs.Directory io, this.paths, [ this.index = 0 ]) : super(parent, io); 1286 1287 final List<String> paths; 1288 final int index; 1289 1290 @override 1291 bool shouldRecurse(fs.IoNode entry) { 1292 if (index == paths.length - 1 && entry.name == paths.last) 1293 return false; 1294 return super.shouldRecurse(entry); 1295 } 1296 1297 @override 1298 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1299 if (entry.name == paths[index] && (index < paths.length - 1)) 1300 return _RepositoryExcludeSubpathDirectory(this, entry, paths, index + 1); 1301 return super.createSubdirectory(entry); 1302 } 1303} 1304 1305 1306// WHAT TO CRAWL AND WHAT NOT TO CRAWL 1307 1308class _RepositoryAndroidPlatformDirectory extends _RepositoryDirectory { 1309 _RepositoryAndroidPlatformDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1310 1311 @override 1312 bool shouldRecurse(fs.IoNode entry) { 1313 // we don't link with or use any of the Android NDK samples 1314 return entry.name != 'webview' // not used at all 1315 && entry.name != 'development' // not linked in 1316 && super.shouldRecurse(entry); 1317 } 1318} 1319 1320class _RepositoryExpatDirectory extends _RepositoryDirectory { 1321 _RepositoryExpatDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1322 1323 @override 1324 bool get isLicenseRootException => true; 1325 1326 @override 1327 bool get subdirectoriesAreLicenseRoots => true; 1328} 1329 1330class _RepositoryFreetypeDocsDirectory extends _RepositoryDirectory { 1331 _RepositoryFreetypeDocsDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1332 1333 @override 1334 _RepositoryFile createFile(fs.IoNode entry) { 1335 if (entry.name == 'LICENSE.TXT') 1336 return _RepositoryFreetypeLicenseFile(this, entry); 1337 return super.createFile(entry); 1338 } 1339 1340 @override 1341 int get fileCount => 0; 1342 1343 @override 1344 Set<License> getLicenses(_Progress progress) { 1345 // We don't ship anything in this directory so don't bother looking for licenses there. 1346 // However, there are licenses in this directory referenced from elsewhere, so we do 1347 // want to crawl it and expose them. 1348 return <License>{}; 1349 } 1350} 1351 1352class _RepositoryFreetypeSrcGZipDirectory extends _RepositoryDirectory { 1353 _RepositoryFreetypeSrcGZipDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1354 1355 // advice was to make this directory's inffixed.h file (which has no license) 1356 // use the license in zlib.h. 1357 1358 @override 1359 List<License> nearestLicensesFor(String name) { 1360 final License zlib = nearestLicenseWithName('zlib.h'); 1361 assert(zlib != null); 1362 if (zlib != null) 1363 return <License>[zlib]; 1364 return super.nearestLicensesFor(name); 1365 } 1366 1367 @override 1368 License nearestLicenseOfType(LicenseType type) { 1369 if (type == LicenseType.zlib) { 1370 final License result = nearestLicenseWithName('zlib.h'); 1371 assert(result != null); 1372 return result; 1373 } 1374 return null; 1375 } 1376} 1377 1378class _RepositoryFreetypeSrcDirectory extends _RepositoryDirectory { 1379 _RepositoryFreetypeSrcDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1380 1381 @override 1382 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1383 if (entry.name == 'gzip') 1384 return _RepositoryFreetypeSrcGZipDirectory(this, entry); 1385 return super.createSubdirectory(entry); 1386 } 1387 1388 @override 1389 bool shouldRecurse(fs.IoNode entry) { 1390 return entry.name != 'tools' 1391 && super.shouldRecurse(entry); 1392 } 1393} 1394 1395class _RepositoryFreetypeDirectory extends _RepositoryDirectory { 1396 _RepositoryFreetypeDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1397 1398 @override 1399 List<License> nearestLicensesFor(String name) { 1400 final List<License> result = super.nearestLicensesFor(name); 1401 if (result == null) { 1402 final License license = nearestLicenseWithName('LICENSE.TXT'); 1403 assert(license != null); 1404 if (license != null) 1405 return <License>[license]; 1406 } 1407 return result; 1408 } 1409 1410 @override 1411 License nearestLicenseOfType(LicenseType type) { 1412 if (type == LicenseType.freetype) { 1413 final License result = nearestLicenseWithName('FTL.TXT'); 1414 assert(result != null); 1415 return result; 1416 } 1417 return null; 1418 } 1419 1420 @override 1421 bool shouldRecurse(fs.IoNode entry) { 1422 return entry.name != 'builds' // build files 1423 && super.shouldRecurse(entry); 1424 } 1425 1426 @override 1427 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1428 if (entry.name == 'src') 1429 return _RepositoryFreetypeSrcDirectory(this, entry); 1430 if (entry.name == 'docs') 1431 return _RepositoryFreetypeDocsDirectory(this, entry); 1432 return super.createSubdirectory(entry); 1433 } 1434} 1435 1436class _RepositoryGlfwDirectory extends _RepositoryDirectory { 1437 _RepositoryGlfwDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1438 1439 @override 1440 bool shouldRecurse(fs.IoNode entry) { 1441 return entry.name != 'examples' // Not linked in build. 1442 && entry.name != 'tests' // Not linked in build. 1443 && entry.name != 'deps' // Only used by examples and tests; not linked in build. 1444 && super.shouldRecurse(entry); 1445 } 1446} 1447 1448class _RepositoryIcuDirectory extends _RepositoryDirectory { 1449 _RepositoryIcuDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1450 1451 @override 1452 bool shouldRecurse(fs.IoNode entry) { 1453 return entry.name != 'license.html' // redundant with LICENSE file 1454 && super.shouldRecurse(entry); 1455 } 1456 1457 @override 1458 _RepositoryFile createFile(fs.IoNode entry) { 1459 if (entry.name == 'LICENSE') 1460 return _RepositoryIcuLicenseFile(this, entry); 1461 return super.createFile(entry); 1462 } 1463} 1464 1465class _RepositoryHarfbuzzDirectory extends _RepositoryDirectory { 1466 _RepositoryHarfbuzzDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1467 1468 @override 1469 bool shouldRecurse(fs.IoNode entry) { 1470 return entry.name != 'util' // utils are command line tools that do not end up in the binary 1471 && super.shouldRecurse(entry); 1472 } 1473} 1474 1475class _RepositoryJSR305Directory extends _RepositoryDirectory { 1476 _RepositoryJSR305Directory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1477 1478 @override 1479 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1480 if (entry.name == 'src') 1481 return _RepositoryJSR305SrcDirectory(this, entry); 1482 return super.createSubdirectory(entry); 1483 } 1484} 1485 1486class _RepositoryJSR305SrcDirectory extends _RepositoryDirectory { 1487 _RepositoryJSR305SrcDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1488 1489 @override 1490 bool shouldRecurse(fs.IoNode entry) { 1491 return entry.name != 'javadoc' 1492 && entry.name != 'sampleUses' 1493 && super.shouldRecurse(entry); 1494 } 1495} 1496 1497class _RepositoryLibcxxDirectory extends _RepositoryDirectory { 1498 _RepositoryLibcxxDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1499 1500 @override 1501 bool shouldRecurse(fs.IoNode entry) { 1502 return entry.name != 'utils' 1503 && super.shouldRecurse(entry); 1504 } 1505 1506 @override 1507 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1508 if (entry.name == 'src') 1509 return _RepositoryLibcxxSrcDirectory(this, entry); 1510 return super.createSubdirectory(entry); 1511 } 1512 1513 @override 1514 _RepositoryFile createFile(fs.IoNode entry) { 1515 if (entry.name == 'LICENSE.TXT') 1516 return _RepositoryCxxStlDualLicenseFile(this, entry); 1517 return super.createFile(entry); 1518 } 1519} 1520 1521class _RepositoryLibcxxSrcDirectory extends _RepositoryDirectory { 1522 _RepositoryLibcxxSrcDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1523 1524 @override 1525 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1526 if (entry.name == 'support') 1527 return _RepositoryLibcxxSrcSupportDirectory(this, entry); 1528 return super.createSubdirectory(entry); 1529 } 1530} 1531 1532class _RepositoryLibcxxSrcSupportDirectory extends _RepositoryDirectory { 1533 _RepositoryLibcxxSrcSupportDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1534 1535 @override 1536 bool shouldRecurse(fs.IoNode entry) { 1537 return entry.name != 'solaris' 1538 && super.shouldRecurse(entry); 1539 } 1540} 1541 1542class _RepositoryLibcxxabiDirectory extends _RepositoryDirectory { 1543 _RepositoryLibcxxabiDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1544 1545 @override 1546 _RepositoryFile createFile(fs.IoNode entry) { 1547 if (entry.name == 'LICENSE.TXT') 1548 return _RepositoryCxxStlDualLicenseFile(this, entry); 1549 return super.createFile(entry); 1550 } 1551} 1552 1553class _RepositoryLibJpegDirectory extends _RepositoryDirectory { 1554 _RepositoryLibJpegDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1555 1556 @override 1557 _RepositoryFile createFile(fs.IoNode entry) { 1558 if (entry.name == 'README') 1559 return _RepositoryReadmeIjgFile(this, entry); 1560 if (entry.name == 'LICENSE') 1561 return _RepositoryLicenseFileWithLeader(this, entry, RegExp(r'^\(Copied from the README\.\)\n+-+\n+')); 1562 return super.createFile(entry); 1563 } 1564} 1565 1566class _RepositoryLibJpegTurboDirectory extends _RepositoryDirectory { 1567 _RepositoryLibJpegTurboDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1568 1569 @override 1570 _RepositoryFile createFile(fs.IoNode entry) { 1571 if (entry.name == 'LICENSE.md') 1572 return _RepositoryLibJpegTurboLicense(this, entry); 1573 return super.createFile(entry); 1574 } 1575 1576 @override 1577 bool shouldRecurse(fs.IoNode entry) { 1578 return entry.name != 'release' // contains nothing that ends up in the binary executable 1579 && entry.name != 'doc' // contains nothing that ends up in the binary executable 1580 && entry.name != 'testimages' // test assets 1581 && super.shouldRecurse(entry); 1582 } 1583} 1584 1585class _RepositoryLibPngDirectory extends _RepositoryDirectory { 1586 _RepositoryLibPngDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1587 1588 @override 1589 _RepositoryFile createFile(fs.IoNode entry) { 1590 if (entry.name == 'LICENSE' || entry.name == 'png.h') 1591 return _RepositoryLibPngLicenseFile(this, entry); 1592 return super.createFile(entry); 1593 } 1594} 1595 1596class _RepositoryLibWebpDirectory extends _RepositoryDirectory { 1597 _RepositoryLibWebpDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1598 1599 @override 1600 bool shouldRecurse(fs.IoNode entry) { 1601 return entry.name != 'examples' // contains nothing that ends up in the binary executable 1602 && entry.name != 'swig' // not included in our build 1603 && entry.name != 'gradle' // not included in our build 1604 && super.shouldRecurse(entry); 1605 } 1606} 1607 1608class _RepositoryPkgDirectory extends _RepositoryDirectory { 1609 _RepositoryPkgDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1610 1611 @override 1612 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1613 if (entry.name == 'when') 1614 return _RepositoryPkgWhenDirectory(this, entry); 1615 return super.createSubdirectory(entry); 1616 } 1617} 1618 1619class _RepositoryPkgWhenDirectory extends _RepositoryDirectory { 1620 _RepositoryPkgWhenDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1621 1622 @override 1623 bool shouldRecurse(fs.IoNode entry) { 1624 return entry.name != 'example' // contains nothing that ends up in the binary executable 1625 && super.shouldRecurse(entry); 1626 } 1627} 1628 1629class _RepositorySkiaLibWebPDirectory extends _RepositoryDirectory { 1630 _RepositorySkiaLibWebPDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1631 1632 @override 1633 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1634 if (entry.name == 'webp') 1635 return _RepositoryReachOutDirectory(this, entry, const <String>{'config.h'}, 3); 1636 return super.createSubdirectory(entry); 1637 } 1638} 1639 1640class _RepositorySkiaLibSdlDirectory extends _RepositoryDirectory { 1641 _RepositorySkiaLibSdlDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1642 1643 @override 1644 bool get isLicenseRootException => true; 1645} 1646 1647class _RepositorySkiaThirdPartyDirectory extends _RepositoryGenericThirdPartyDirectory { 1648 _RepositorySkiaThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1649 1650 @override 1651 bool shouldRecurse(fs.IoNode entry) { 1652 return entry.name != 'giflib' // contains nothing that ends up in the binary executable 1653 && entry.name != 'freetype' // we use our own version 1654 && entry.name != 'freetype2' // we use our own version 1655 && entry.name != 'gif' // not linked in 1656 && entry.name != 'icu' // we use our own version 1657 && entry.name != 'libjpeg-turbo' // we use our own version 1658 && entry.name != 'libpng' // we use our own version 1659 && entry.name != 'lua' // not linked in 1660 && entry.name != 'yasm' // build tool (assembler) 1661 && super.shouldRecurse(entry); 1662 } 1663 1664 @override 1665 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1666 if (entry.name == 'ktx') 1667 return _RepositoryReachOutDirectory(this, entry, const <String>{'ktx.h', 'ktx.cpp'}, 2); 1668 if (entry.name == 'libmicrohttpd') 1669 return _RepositoryReachOutDirectory(this, entry, const <String>{'MHD_config.h'}, 2); 1670 if (entry.name == 'libwebp') 1671 return _RepositorySkiaLibWebPDirectory(this, entry); 1672 if (entry.name == 'libsdl') 1673 return _RepositorySkiaLibSdlDirectory(this, entry); 1674 return super.createSubdirectory(entry); 1675 } 1676} 1677 1678class _RepositorySkiaDirectory extends _RepositoryDirectory { 1679 _RepositorySkiaDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1680 1681 @override 1682 bool shouldRecurse(fs.IoNode entry) { 1683 return entry.name != 'platform_tools' // contains nothing that ends up in the binary executable 1684 && entry.name != 'tools' // contains nothing that ends up in the binary executable 1685 && entry.name != 'resources' // contains nothing that ends up in the binary executable 1686 && super.shouldRecurse(entry); 1687 } 1688 1689 @override 1690 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1691 if (entry.name == 'third_party') 1692 return _RepositorySkiaThirdPartyDirectory(this, entry); 1693 return super.createSubdirectory(entry); 1694 } 1695} 1696 1697class _RepositoryVulkanDirectory extends _RepositoryDirectory { 1698 _RepositoryVulkanDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1699 1700 @override 1701 bool shouldRecurse(fs.IoNode entry) { 1702 // Flutter only uses the headers in the include directory. 1703 return entry.name == 'include' 1704 && super.shouldRecurse(entry); 1705 } 1706 1707 @override 1708 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1709 if (entry.name == 'src') 1710 return _RepositoryExcludeSubpathDirectory(this, entry, const <String>['spec']); 1711 return super.createSubdirectory(entry); 1712 } 1713} 1714 1715class _RepositoryWuffsDirectory extends _RepositoryDirectory { 1716 _RepositoryWuffsDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1717 1718 @override 1719 bool shouldRecurse(fs.IoNode entry) { 1720 return entry.name != 'CONTRIBUTORS' // not linked in 1721 && super.shouldRecurse(entry); 1722 } 1723 1724 @override 1725 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1726 if (entry.name == 'src') 1727 return _RepositoryExcludeSubpathDirectory(this, entry, const <String>['spec']); 1728 return super.createSubdirectory(entry); 1729 } 1730} 1731 1732class _RepositoryRootThirdPartyDirectory extends _RepositoryGenericThirdPartyDirectory { 1733 _RepositoryRootThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1734 1735 @override 1736 bool shouldRecurse(fs.IoNode entry) { 1737 return entry.name != 'appurify-python' // only used by tests 1738 && entry.name != 'benchmark' // only used by tests 1739 && entry.name != 'dart-sdk' // redundant with //engine/dart; https://github.com/flutter/flutter/issues/2618 1740 && entry.name != 'firebase' // only used by bots; https://github.com/flutter/flutter/issues/3722 1741 && entry.name != 'gyp' // build-time only 1742 && entry.name != 'jinja2' // build-time code generation 1743 && entry.name != 'junit' // only mentioned in build files, not used 1744 && entry.name != 'libxml' // dependency of the testing system that we don't actually use 1745 && entry.name != 'llvm-build' // only used by build 1746 && entry.name != 'markupsafe' // build-time only 1747 && entry.name != 'mockito' // only used by tests 1748 && entry.name != 'pymock' // presumably only used by tests 1749 && entry.name != 'robolectric' // testing framework for android 1750 && entry.name != 'yasm' // build-time dependency only 1751 && entry.name != 'binutils' // build-time dependency only 1752 && entry.name != 'instrumented_libraries' // unused according to chinmay 1753 && entry.name != 'android_tools' // excluded on advice 1754 && entry.name != 'android_support' // build-time only 1755 && entry.name != 'googletest' // only used by tests 1756 && entry.name != 'skia' // treated as a separate component 1757 && entry.name != 'fontconfig' // not used in standard configurations 1758 && entry.name != 'swiftshader' // only used on hosts for tests 1759 && entry.name != 'ocmock' // only used for tests 1760 && super.shouldRecurse(entry); 1761 } 1762 1763 @override 1764 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1765 if (entry.name == 'android_platform') 1766 return _RepositoryAndroidPlatformDirectory(this, entry); 1767 if (entry.name == 'boringssl') 1768 return _RepositoryBoringSSLDirectory(this, entry); 1769 if (entry.name == 'catapult') 1770 return _RepositoryCatapultDirectory(this, entry); 1771 if (entry.name == 'dart') 1772 return _RepositoryDartDirectory(this, entry); 1773 if (entry.name == 'expat') 1774 return _RepositoryExpatDirectory(this, entry); 1775 if (entry.name == 'freetype-android') 1776 throw '//third_party/freetype-android is no longer part of this client: remove it'; 1777 if (entry.name == 'freetype2') 1778 return _RepositoryFreetypeDirectory(this, entry); 1779 if (entry.name == 'glfw') 1780 return _RepositoryGlfwDirectory(this, entry); 1781 if (entry.name == 'harfbuzz') 1782 return _RepositoryHarfbuzzDirectory(this, entry); 1783 if (entry.name == 'icu') 1784 return _RepositoryIcuDirectory(this, entry); 1785 if (entry.name == 'jsr-305') 1786 return _RepositoryJSR305Directory(this, entry); 1787 if (entry.name == 'libcxx') 1788 return _RepositoryLibcxxDirectory(this, entry); 1789 if (entry.name == 'libcxxabi') 1790 return _RepositoryLibcxxabiDirectory(this, entry); 1791 if (entry.name == 'libjpeg') 1792 return _RepositoryLibJpegDirectory(this, entry); 1793 if (entry.name == 'libjpeg_turbo' || entry.name == 'libjpeg-turbo') 1794 return _RepositoryLibJpegTurboDirectory(this, entry); 1795 if (entry.name == 'libpng') 1796 return _RepositoryLibPngDirectory(this, entry); 1797 if (entry.name == 'libwebp') 1798 return _RepositoryLibWebpDirectory(this, entry); 1799 if (entry.name == 'pkg') 1800 return _RepositoryPkgDirectory(this, entry); 1801 if (entry.name == 'vulkan') 1802 return _RepositoryVulkanDirectory(this, entry); 1803 if (entry.name == 'wuffs') 1804 return _RepositoryWuffsDirectory(this, entry); 1805 return super.createSubdirectory(entry); 1806 } 1807} 1808 1809class _RepositoryBoringSSLThirdPartyDirectory extends _RepositoryDirectory { 1810 _RepositoryBoringSSLThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1811 1812 @override 1813 bool shouldRecurse(fs.IoNode entry) { 1814 return entry.name != 'android-cmake' // build-time only 1815 && super.shouldRecurse(entry); 1816 } 1817} 1818 1819class _RepositoryBoringSSLSourceDirectory extends _RepositoryDirectory { 1820 _RepositoryBoringSSLSourceDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1821 1822 @override 1823 String get libraryName => 'boringssl'; 1824 1825 @override 1826 bool get isLicenseRoot => true; 1827 1828 @override 1829 bool shouldRecurse(fs.IoNode entry) { 1830 return entry.name != 'fuzz' // testing tools, not shipped 1831 && super.shouldRecurse(entry); 1832 } 1833 1834 @override 1835 _RepositoryFile createFile(fs.IoNode entry) { 1836 if (entry.name == 'LICENSE') 1837 return _RepositoryOpenSSLLicenseFile(this, entry); 1838 return super.createFile(entry); 1839 } 1840 1841 @override 1842 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1843 if (entry.name == 'third_party') 1844 return _RepositoryBoringSSLThirdPartyDirectory(this, entry); 1845 return super.createSubdirectory(entry); 1846 } 1847} 1848 1849/// The BoringSSL license file. 1850/// 1851/// This license includes 23 lines of informational header text that are not 1852/// part of the copyright notices and can be skipped. 1853/// It also has a trailer that mentions licenses that are used during build 1854/// time of BoringSSL - those can be ignored as well since they don't apply 1855/// to code that is distributed. 1856class _RepositoryOpenSSLLicenseFile extends _RepositorySingleLicenseFile { 1857 _RepositoryOpenSSLLicenseFile(_RepositoryDirectory parent, fs.TextFile io) 1858 : super(parent, io, 1859 License.fromBodyAndType( 1860 LineSplitter.split(io.readString()) 1861 .skip(23) 1862 .takeWhile((String s) => !s.startsWith('BoringSSL uses the Chromium test infrastructure to run a continuous build,')) 1863 .join('\n'), 1864 LicenseType.openssl, 1865 origin: io.fullName)) { 1866 _verifyLicense(io); 1867 } 1868 1869 static void _verifyLicense(fs.TextFile io) { 1870 final String contents = io.readString(); 1871 if (!contents.contains('BoringSSL is a fork of OpenSSL. As such, large parts of it fall under OpenSSL')) 1872 throw 'unexpected OpenSSL license file contents:\n----8<----\n$contents\n----<8----'; 1873 } 1874 1875 @override 1876 License licenseOfType(LicenseType type) { 1877 if (type == LicenseType.openssl) 1878 return license; 1879 return null; 1880 } 1881} 1882 1883class _RepositoryBoringSSLDirectory extends _RepositoryDirectory { 1884 _RepositoryBoringSSLDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1885 1886 @override 1887 _RepositoryFile createFile(fs.IoNode entry) { 1888 if (entry.name == 'README') 1889 return _RepositoryBlankLicenseFile(this, entry, 'This repository contains the files generated by boringssl for its build.'); 1890 return super.createFile(entry); 1891 } 1892 1893 @override 1894 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1895 if (entry.name == 'src') 1896 return _RepositoryBoringSSLSourceDirectory(this, entry); 1897 return super.createSubdirectory(entry); 1898 } 1899} 1900 1901class _RepositoryCatapultThirdPartyApiClientDirectory extends _RepositoryDirectory { 1902 _RepositoryCatapultThirdPartyApiClientDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1903 1904 @override 1905 _RepositoryFile createFile(fs.IoNode entry) { 1906 if (entry.name == 'LICENSE') 1907 return _RepositoryCatapultApiClientLicenseFile(this, entry); 1908 return super.createFile(entry); 1909 } 1910} 1911 1912class _RepositoryCatapultThirdPartyCoverageDirectory extends _RepositoryDirectory { 1913 _RepositoryCatapultThirdPartyCoverageDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1914 1915 @override 1916 _RepositoryFile createFile(fs.IoNode entry) { 1917 if (entry.name == 'NOTICE.txt') 1918 return _RepositoryCatapultCoverageLicenseFile(this, entry); 1919 return super.createFile(entry); 1920 } 1921} 1922 1923class _RepositoryCatapultThirdPartyDirectory extends _RepositoryDirectory { 1924 _RepositoryCatapultThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1925 1926 @override 1927 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1928 if (entry.name == 'apiclient') 1929 return _RepositoryCatapultThirdPartyApiClientDirectory(this, entry); 1930 if (entry.name == 'coverage') 1931 return _RepositoryCatapultThirdPartyCoverageDirectory(this, entry); 1932 return super.createSubdirectory(entry); 1933 } 1934} 1935 1936class _RepositoryCatapultDirectory extends _RepositoryDirectory { 1937 _RepositoryCatapultDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1938 1939 @override 1940 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1941 if (entry.name == 'third_party') 1942 return _RepositoryCatapultThirdPartyDirectory(this, entry); 1943 return super.createSubdirectory(entry); 1944 } 1945} 1946 1947class _RepositoryDartRuntimeThirdPartyDirectory extends _RepositoryGenericThirdPartyDirectory { 1948 _RepositoryDartRuntimeThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1949 1950 @override 1951 bool shouldRecurse(fs.IoNode entry) { 1952 return entry.name != 'd3' // Siva says "that is the charting library used by the binary size tool" 1953 && entry.name != 'binary_size' // not linked in either 1954 && super.shouldRecurse(entry); 1955 } 1956} 1957 1958class _RepositoryDartThirdPartyDirectory extends _RepositoryGenericThirdPartyDirectory { 1959 _RepositoryDartThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1960 1961 @override 1962 bool shouldRecurse(fs.IoNode entry) { 1963 return entry.name != 'drt_resources' // test materials 1964 && entry.name != 'firefox_jsshell' // testing tool for dart2js 1965 && entry.name != 'd8' // testing tool for dart2js 1966 && entry.name != 'pkg' 1967 && entry.name != 'pkg_tested' 1968 && entry.name != 'requirejs' // only used by DDC 1969 && super.shouldRecurse(entry); 1970 } 1971 1972 @override 1973 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1974 if (entry.name == 'boringssl') 1975 return _RepositoryBoringSSLDirectory(this, entry); 1976 return super.createSubdirectory(entry); 1977 } 1978} 1979 1980class _RepositoryDartRuntimeDirectory extends _RepositoryDirectory { 1981 _RepositoryDartRuntimeDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1982 1983 @override 1984 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 1985 if (entry.name == 'third_party') 1986 return _RepositoryDartRuntimeThirdPartyDirectory(this, entry); 1987 return super.createSubdirectory(entry); 1988 } 1989} 1990 1991class _RepositoryDartDirectory extends _RepositoryDirectory { 1992 _RepositoryDartDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 1993 1994 @override 1995 bool get isLicenseRoot => true; 1996 1997 @override 1998 _RepositoryFile createFile(fs.IoNode entry) { 1999 if (entry.name == 'LICENSE') 2000 return _RepositoryDartLicenseFile(this, entry); 2001 return super.createFile(entry); 2002 } 2003 2004 @override 2005 bool shouldRecurse(fs.IoNode entry) { 2006 return entry.name != 'pkg' // packages that don't become part of the binary (e.g. the analyzer) 2007 && entry.name != 'tests' // only used by tests, obviously 2008 && entry.name != 'docs' // not shipped in binary 2009 && entry.name != 'build' // not shipped in binary 2010 && entry.name != 'tools' // not shipped in binary 2011 && entry.name != 'samples-dev' // not shipped in binary 2012 && super.shouldRecurse(entry); 2013 } 2014 2015 @override 2016 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2017 if (entry.name == 'third_party') 2018 return _RepositoryDartThirdPartyDirectory(this, entry); 2019 if (entry.name == 'runtime') 2020 return _RepositoryDartRuntimeDirectory(this, entry); 2021 return super.createSubdirectory(entry); 2022 } 2023} 2024 2025class _RepositoryFlutterDirectory extends _RepositoryDirectory { 2026 _RepositoryFlutterDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2027 2028 @override 2029 String get libraryName => 'engine'; 2030 2031 @override 2032 bool get isLicenseRoot => true; 2033 2034 @override 2035 bool shouldRecurse(fs.IoNode entry) { 2036 return entry.name != 'testing' 2037 && entry.name != 'tools' 2038 && entry.name != 'docs' 2039 && entry.name != 'examples' 2040 && entry.name != 'build' 2041 && entry.name != 'ci' 2042 && entry.name != 'frontend_server' 2043 && super.shouldRecurse(entry); 2044 } 2045 2046 @override 2047 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2048 if (entry.name == 'sky') 2049 return _RepositoryExcludeSubpathDirectory(this, entry, const <String>['packages', 'sky_engine', 'LICENSE']); // that's the output of this script! 2050 if (entry.name == 'third_party') 2051 return _RepositoryFlutterThirdPartyDirectory(this, entry); 2052 return super.createSubdirectory(entry); 2053 } 2054} 2055 2056class _RepositoryFuchsiaDirectory extends _RepositoryDirectory { 2057 _RepositoryFuchsiaDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2058 2059 @override 2060 String get libraryName => 'fuchsia_sdk'; 2061 2062 @override 2063 bool get isLicenseRoot => true; 2064 2065 @override 2066 bool shouldRecurse(fs.IoNode entry) { 2067 return entry.name != 'toolchain' 2068 && super.shouldRecurse(entry); 2069 } 2070 2071 @override 2072 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2073 if (entry.name == 'sdk') 2074 return _RepositoryFuchsiaSdkDirectory(this, entry); 2075 return super.createSubdirectory(entry); 2076 } 2077} 2078 2079class _RepositoryFuchsiaSdkDirectory extends _RepositoryDirectory { 2080 _RepositoryFuchsiaSdkDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2081 2082 @override 2083 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2084 if (entry.name == 'linux' || entry.name == 'mac') 2085 return _RepositoryFuchsiaSdkLinuxDirectory(this, entry); 2086 return super.createSubdirectory(entry); 2087 } 2088} 2089 2090class _RepositoryFuchsiaSdkLinuxDirectory extends _RepositoryDirectory { 2091 _RepositoryFuchsiaSdkLinuxDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2092 2093 @override 2094 bool shouldRecurse(fs.IoNode entry) { 2095 return entry.name != '.build-id' 2096 && entry.name != 'docs' 2097 && entry.name != 'images' 2098 && entry.name != 'meta' 2099 && entry.name != 'tools'; 2100 } 2101} 2102 2103class _RepositoryFlutterThirdPartyDirectory extends _RepositoryDirectory { 2104 _RepositoryFlutterThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2105 2106 @override 2107 bool get subdirectoriesAreLicenseRoots => true; 2108 2109 @override 2110 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2111 if (entry.name == 'txt') 2112 return _RepositoryFlutterTxtDirectory(this, entry); 2113 return super.createSubdirectory(entry); 2114 } 2115} 2116 2117class _RepositoryFlutterTxtDirectory extends _RepositoryDirectory { 2118 _RepositoryFlutterTxtDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2119 2120 @override 2121 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2122 if (entry.name == 'third_party') 2123 return _RepositoryFlutterTxtThirdPartyDirectory(this, entry); 2124 return super.createSubdirectory(entry); 2125 } 2126} 2127 2128class _RepositoryFlutterTxtThirdPartyDirectory extends _RepositoryDirectory { 2129 _RepositoryFlutterTxtThirdPartyDirectory(_RepositoryDirectory parent, fs.Directory io) : super(parent, io); 2130 2131 @override 2132 bool shouldRecurse(fs.IoNode entry) { 2133 return entry.name != 'fonts'; 2134 } 2135} 2136 2137/// The license tool directory. 2138/// 2139/// This is a special-case root node that is not used for license aggregation, 2140/// but simply to compute a signature for the license tool itself. When this 2141/// signature changes, we force re-run license collection for all components in 2142/// order to verify the tool itself still produces the same output. 2143class _RepositoryFlutterLicenseToolDirectory extends _RepositoryDirectory { 2144 _RepositoryFlutterLicenseToolDirectory(fs.Directory io) : super(null, io); 2145 2146 @override 2147 bool shouldRecurse(fs.IoNode entry) { 2148 return entry.name != '.dart_tool' 2149 && entry.name != 'data' 2150 && super.shouldRecurse(entry); 2151 } 2152} 2153 2154class _RepositoryRoot extends _RepositoryDirectory { 2155 _RepositoryRoot(fs.Directory io) : super(null, io); 2156 2157 @override 2158 String get libraryName { 2159 assert(false); 2160 return 'engine'; 2161 } 2162 2163 @override 2164 bool get isLicenseRoot => true; 2165 2166 @override 2167 bool get subdirectoriesAreLicenseRoots => true; 2168 2169 @override 2170 bool shouldRecurse(fs.IoNode entry) { 2171 return entry.name != 'testing' // only used by tests 2172 && entry.name != 'build' // only used by build 2173 && entry.name != 'buildtools' // only used by build 2174 && entry.name != 'build_overrides' // only used by build 2175 && entry.name != 'ios_tools' // only used by build 2176 && entry.name != 'tools' // not distributed in binary 2177 && entry.name != 'out' // output of build 2178 && super.shouldRecurse(entry); 2179 } 2180 2181 @override 2182 _RepositoryDirectory createSubdirectory(fs.Directory entry) { 2183 if (entry.name == 'base') 2184 throw '//base is no longer part of this client: remove it'; 2185 if (entry.name == 'third_party') 2186 return _RepositoryRootThirdPartyDirectory(this, entry); 2187 if (entry.name == 'flutter') 2188 return _RepositoryFlutterDirectory(this, entry); 2189 if (entry.name == 'fuchsia') 2190 return _RepositoryFuchsiaDirectory(this, entry); 2191 return super.createSubdirectory(entry); 2192 } 2193 2194 @override 2195 List<_RepositoryDirectory> get virtualSubdirectories { 2196 // Skia is updated more frequently than other third party libraries and 2197 // is therefore represented as a separate top-level component. 2198 final fs.Directory thirdPartyNode = io.walk.firstWhere((fs.IoNode node) => node.name == 'third_party'); 2199 final fs.IoNode skiaNode = thirdPartyNode.walk.firstWhere((fs.IoNode node) => node.name == 'skia'); 2200 return <_RepositoryDirectory>[_RepositorySkiaDirectory(this, skiaNode)]; 2201 } 2202} 2203 2204 2205class _Progress { 2206 _Progress(this.max) { 2207 // This may happen when a git client contains left-over empty component 2208 // directories after DEPS file changes. 2209 if (max <= 0) 2210 throw ArgumentError('Progress.max must be > 0 but was: $max'); 2211 } 2212 2213 final int max; 2214 int get withLicense => _withLicense; 2215 int _withLicense = 0; 2216 int get withoutLicense => _withoutLicense; 2217 int _withoutLicense = 0; 2218 String get label => _label; 2219 String _label = ''; 2220 int _lastLength = 0; 2221 set label(String value) { 2222 if (value.length > 50) 2223 value = '.../' + value.substring(math.max(0, value.lastIndexOf('/', value.length - 45) + 1)); 2224 if (_label != value) { 2225 _label = value; 2226 update(); 2227 } 2228 } 2229 void advance({@required bool success}) { 2230 assert(success != null); 2231 if (success) 2232 _withLicense += 1; 2233 else 2234 _withoutLicense += 1; 2235 update(); 2236 } 2237 Stopwatch _lastUpdate; 2238 void update({bool flush = false}) { 2239 if (_lastUpdate == null || _lastUpdate.elapsedMilliseconds > 90 || flush) { 2240 _lastUpdate ??= Stopwatch(); 2241 final String line = toString(); 2242 system.stderr.write('\r$line'); 2243 if (_lastLength > line.length) 2244 system.stderr.write(' ' * (_lastLength - line.length)); 2245 _lastLength = line.length; 2246 _lastUpdate.reset(); 2247 _lastUpdate.start(); 2248 } 2249 } 2250 void flush() => update(flush: true); 2251 bool get hadErrors => _withoutLicense > 0; 2252 @override 2253 String toString() { 2254 final int percent = (100.0 * (_withLicense + _withoutLicense) / max).round(); 2255 return '${(_withLicense + _withoutLicense).toString().padLeft(10)} of $max ${'█' * (percent ~/ 10)}${'░' * (10 - (percent ~/ 10))} $percent% ($_withoutLicense missing licenses) $label'; 2256 } 2257} 2258 2259/// Reads the signature from a golden file. 2260Future<String> _readSignature(String goldenPath) async { 2261 try { 2262 final system.File goldenFile = system.File(goldenPath); 2263 final String goldenSignature = await utf8.decoder.bind(goldenFile.openRead()) 2264 .transform(const LineSplitter()).first; 2265 final RegExp signaturePattern = RegExp(r'Signature: (\w+)'); 2266 final Match goldenMatch = signaturePattern.matchAsPrefix(goldenSignature); 2267 if (goldenMatch != null) 2268 return goldenMatch.group(1); 2269 } on system.FileSystemException { 2270 system.stderr.writeln(' Failed to read signature file.'); 2271 return null; 2272 } 2273 return null; 2274} 2275 2276/// Writes a signature to an [system.IOSink] in the expected format. 2277void _writeSignature(String signature, system.IOSink sink) { 2278 if (signature != null) 2279 sink.writeln('Signature: $signature\n'); 2280} 2281 2282// Checks for changes to the license tool itself. 2283// 2284// Returns true if changes are detected. 2285Future<bool> _computeLicenseToolChanges(_RepositoryDirectory root, {String goldenSignaturePath, String outputSignaturePath}) async { 2286 system.stderr.writeln('Computing signature for license tool'); 2287 final fs.Directory flutterNode = root.io.walk.firstWhere((fs.IoNode node) => node.name == 'flutter'); 2288 final fs.Directory toolsNode = flutterNode.walk.firstWhere((fs.IoNode node) => node.name == 'tools'); 2289 final fs.Directory licenseNode = toolsNode.walk.firstWhere((fs.IoNode node) => node.name == 'licenses'); 2290 final _RepositoryFlutterLicenseToolDirectory licenseToolDirectory = _RepositoryFlutterLicenseToolDirectory(licenseNode); 2291 2292 final String toolSignature = await licenseToolDirectory.signature; 2293 final system.IOSink sink = system.File(outputSignaturePath).openWrite(); 2294 _writeSignature(toolSignature, sink); 2295 await sink.close(); 2296 2297 final String goldenSignature = await _readSignature(goldenSignaturePath); 2298 return toolSignature != goldenSignature; 2299} 2300 2301/// Collects licenses for the specified component. 2302/// 2303/// If [writeSignature] is set, the signature is written to the output file. 2304/// If [force] is set, collection is run regardless of whether or not the signature matches. 2305Future<void> _collectLicensesForComponent(_RepositoryDirectory componentRoot, { 2306 String inputGoldenPath, 2307 String outputGoldenPath, 2308 bool writeSignature, 2309 bool force, 2310}) async { 2311 // Check whether the golden file matches the signature of the current contents of this directory. 2312 final String goldenSignature = await _readSignature(inputGoldenPath); 2313 final String signature = await componentRoot.signature; 2314 if (!force && goldenSignature == signature) { 2315 system.stderr.writeln(' Skipping this component - no change in signature'); 2316 return; 2317 } 2318 2319 final _Progress progress = _Progress(componentRoot.fileCount); 2320 2321 final system.File outFile = system.File(outputGoldenPath); 2322 final system.IOSink sink = outFile.openWrite(); 2323 if (writeSignature) 2324 _writeSignature(signature, sink); 2325 2326 final List<License> licenses = Set<License>.from(componentRoot.getLicenses(progress).toList()).toList(); 2327 2328 if (progress.hadErrors) 2329 throw 'Had failures while collecting licenses.'; 2330 2331 sink.writeln('UNUSED LICENSES:\n'); 2332 final List<String> unusedLicenses = licenses 2333 .where((License license) => !license.isUsed) 2334 .map((License license) => license.toString()) 2335 .toList(); 2336 unusedLicenses.sort(); 2337 sink.writeln(unusedLicenses.join('\n\n')); 2338 sink.writeln('~' * 80); 2339 2340 sink.writeln('USED LICENSES:\n'); 2341 final List<License> usedLicenses = licenses.where((License license) => license.isUsed).toList(); 2342 final List<String> output = usedLicenses.map((License license) => license.toString()).toList(); 2343 for (int index = 0; index < output.length; index += 1) { 2344 // The strings we look for here are strings which we do not expect to see in 2345 // any of the licenses we use. They either represent examples of misparsing 2346 // licenses (issues we've previously run into and fixed), or licenses we 2347 // know we are trying to avoid (e.g. the GPL, or licenses that only apply to 2348 // test content which shouldn't get built at all). 2349 // If you find that one of these tests is getting hit, and it's not obvious 2350 // to you why the relevant license is a problem, please ask around (e.g. try 2351 // asking Hixie). Do not merely remove one of these checks, sometimes the 2352 // issues involved are relatively subtle. 2353 if (output[index].contains('Version: MPL 1.1/GPL 2.0/LGPL 2.1')) 2354 throw 'Unexpected trilicense block found in: ${usedLicenses[index].origin}'; 2355 if (output[index].contains('The contents of this file are subject to the Mozilla Public License Version')) 2356 throw 'Unexpected MPL block found in: ${usedLicenses[index].origin}'; 2357 if (output[index].contains('You should have received a copy of the GNU')) 2358 throw 'Unexpected GPL block found in: ${usedLicenses[index].origin}'; 2359 if (output[index].contains('BoringSSL is a fork of OpenSSL')) 2360 throw 'Unexpected legacy BoringSSL block found in: ${usedLicenses[index].origin}'; 2361 if (output[index].contains('Contents of this folder are ported from')) 2362 throw 'Unexpected block found in: ${usedLicenses[index].origin}'; 2363 if (output[index].contains('https://github.com/w3c/web-platform-tests/tree/master/selectors-api')) 2364 throw 'Unexpected W3C content found in: ${usedLicenses[index].origin}'; 2365 if (output[index].contains('http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html')) 2366 throw 'Unexpected W3C copyright found in: ${usedLicenses[index].origin}'; 2367 if (output[index].contains('It is based on commit')) 2368 throw 'Unexpected content found in: ${usedLicenses[index].origin}'; 2369 if (output[index].contains('The original code is covered by the dual-licensing approach described in:')) 2370 throw 'Unexpected old license reference found in: ${usedLicenses[index].origin}'; 2371 if (output[index].contains('must choose')) 2372 throw 'Unexpected indecisiveness found in: ${usedLicenses[index].origin}'; 2373 } 2374 2375 output.sort(); 2376 sink.writeln(output.join('\n\n')); 2377 sink.writeln('Total license count: ${licenses.length}'); 2378 2379 await sink.close(); 2380 progress.label = 'Done.'; 2381 progress.flush(); 2382 system.stderr.writeln(''); 2383} 2384 2385 2386// MAIN 2387 2388Future<void> main(List<String> arguments) async { 2389 final ArgParser parser = ArgParser() 2390 ..addOption('src', help: 'The root of the engine source') 2391 ..addOption('out', help: 'The directory where output is written') 2392 ..addOption('golden', help: 'The directory containing golden results') 2393 ..addFlag('release', help: 'Print output in the format used for product releases'); 2394 2395 final ArgResults argResults = parser.parse(arguments); 2396 final bool releaseMode = argResults['release']; 2397 if (argResults['src'] == null) { 2398 print('Flutter license script: Must provide --src directory'); 2399 print(parser.usage); 2400 system.exit(1); 2401 } 2402 if (!releaseMode) { 2403 if (argResults['out'] == null || argResults['golden'] == null) { 2404 print('Flutter license script: Must provide --out and --golden directories in non-release mode'); 2405 print(parser.usage); 2406 system.exit(1); 2407 } 2408 if (!system.FileSystemEntity.isDirectorySync(argResults['golden'])) { 2409 print('Flutter license script: Golden directory does not exist'); 2410 print(parser.usage); 2411 system.exit(1); 2412 } 2413 final system.Directory out = system.Directory(argResults['out']); 2414 if (!out.existsSync()) 2415 out.createSync(recursive: true); 2416 } 2417 2418 try { 2419 system.stderr.writeln('Finding files...'); 2420 final fs.FileSystemDirectory rootDirectory = fs.FileSystemDirectory.fromPath(argResults['src']); 2421 final _RepositoryDirectory root = _RepositoryRoot(rootDirectory); 2422 2423 if (releaseMode) { 2424 system.stderr.writeln('Collecting licenses...'); 2425 final _Progress progress = _Progress(root.fileCount); 2426 final List<License> licenses = Set<License>.from(root.getLicenses(progress).toList()).toList(); 2427 if (progress.hadErrors) 2428 throw 'Had failures while collecting licenses.'; 2429 progress.label = 'Dumping results...'; 2430 progress.flush(); 2431 final List<String> output = licenses 2432 .where((License license) => license.isUsed) 2433 .map((License license) => license.toStringFormal()) 2434 .where((String text) => text != null) 2435 .toList(); 2436 output.sort(); 2437 print(output.join('\n${"-" * 80}\n')); 2438 progress.label = 'Done.'; 2439 progress.flush(); 2440 system.stderr.writeln(''); 2441 } else { 2442 // If changes are detected to the license tool itself, force collection 2443 // for all components in order to check we're still generating correct 2444 // output. 2445 const String toolSignatureFilename = 'tool_signature'; 2446 final bool forceRunAll = await _computeLicenseToolChanges( 2447 root, 2448 goldenSignaturePath: path.join(argResults['golden'], toolSignatureFilename), 2449 outputSignaturePath: path.join(argResults['out'], toolSignatureFilename), 2450 ); 2451 if (forceRunAll) 2452 system.stderr.writeln(' Detected changes to license tool. Forcing license collection for all components.'); 2453 2454 final List<String> usedGoldens = <String>[]; 2455 bool isFirstComponent = true; 2456 for (_RepositoryDirectory component in root.subdirectories) { 2457 system.stderr.writeln('Collecting licenses for ${component.io.name}'); 2458 2459 _RepositoryDirectory componentRoot; 2460 if (isFirstComponent) { 2461 // For the first component, we can use the results of the initial repository crawl. 2462 isFirstComponent = false; 2463 componentRoot = component; 2464 } else { 2465 // For other components, we need a clean repository that does not 2466 // contain any state left over from previous components. 2467 clearLicenseRegistry(); 2468 componentRoot = _RepositoryRoot(rootDirectory).subdirectories 2469 .firstWhere((_RepositoryDirectory dir) => dir.name == component.name); 2470 } 2471 2472 // Always run the full license check on the flutter tree. The flutter 2473 // tree is relatively small and changes frequently in ways that do not 2474 // affect the license output, and we don't want to require updates to 2475 // the golden signature for those changes. 2476 final String goldenFileName = 'licenses_${component.io.name}'; 2477 await _collectLicensesForComponent( 2478 componentRoot, 2479 inputGoldenPath: path.join(argResults['golden'], goldenFileName), 2480 outputGoldenPath: path.join(argResults['out'], goldenFileName), 2481 writeSignature: component.io.name != 'flutter', 2482 force: forceRunAll || component.io.name == 'flutter', 2483 ); 2484 usedGoldens.add(goldenFileName); 2485 } 2486 2487 final Set<String> unusedGoldens = system.Directory(argResults['golden']).listSync() 2488 .map((system.FileSystemEntity file) => path.basename(file.path)).toSet() 2489 ..removeAll(usedGoldens) 2490 ..remove(toolSignatureFilename); 2491 if (unusedGoldens.isNotEmpty) { 2492 system.stderr.writeln('The following golden files in ${argResults['golden']} are unused and need to be deleted:'); 2493 unusedGoldens.map((String s) => ' * $s').forEach(system.stderr.writeln); 2494 system.exit(1); 2495 } 2496 } 2497 } catch (e, stack) { 2498 system.stderr.writeln('failure: $e\n$stack'); 2499 system.stderr.writeln('aborted.'); 2500 system.exit(1); 2501 } 2502} 2503