blob: 12db6b5595076189a5737dcd8afab2e67586e35a [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// See README in this directory for information on how this code is organised.
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io' as system;
import 'dart:math' as math;
import 'package:args/args.dart';
import 'package:crypto/crypto.dart' as crypto;
import 'package:licenses/patterns.dart';
import 'package:path/path.dart' as path;
import 'filesystem.dart' as fs;
import 'licenses.dart';
// REPOSITORY OBJECTS
abstract class RepositoryEntry implements Comparable<RepositoryEntry> {
RepositoryEntry(this.parent, this.io);
final RepositoryDirectory parent;
final fs.IoNode io;
String get name => io.name;
String get libraryName;
@override
int compareTo(RepositoryEntry other) => toString().compareTo(other.toString());
@override
String toString() => io.fullName;
}
abstract class RepositoryFile extends RepositoryEntry {
RepositoryFile(RepositoryDirectory parent, fs.File io) : super(parent, io);
Iterable<License> get licenses;
@override
String get libraryName => parent.libraryName;
@override
fs.File get io => super.io;
}
abstract class RepositoryLicensedFile extends RepositoryFile {
RepositoryLicensedFile(RepositoryDirectory parent, fs.File io) : super(parent, io);
// file names that we are confident won't be included in the final build product
static final RegExp _readmeNamePattern = new RegExp(r'\b_*(?:readme|contributing|patents)_*\b', caseSensitive: false);
static final RegExp _buildTimePattern = new 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);
static final RegExp _docsPattern = new RegExp(r'^(?:INSTALL|NEWS|OWNERS|AUTHORS|ChangeLog(?:\.rst|\.[0-9]+)?|.+\.txt|.+\.md|.+\.log|.+\.css|.+\.1|doxygen\.config|.+\.spec(?:\.in)?)$', caseSensitive: false);
static final RegExp _devPattern = new RegExp(r'^(?:codereview\.settings|.+\.~|.+\.~[0-9]+~|\.clang-format|\.gitattributes|\.landmines|\.DS_Store|\.travis\.yml|\.cirrus\.yml)$', caseSensitive: false);
static final RegExp _testsPattern = new RegExp(r'^(?:tj(?:bench|example)test\.(?:java\.)?in|example\.c)$', caseSensitive: false);
bool get isIncludedInBuildProducts {
return !io.name.contains(_readmeNamePattern)
&& !io.name.contains(_buildTimePattern)
&& !io.name.contains(_docsPattern)
&& !io.name.contains(_devPattern)
&& !io.name.contains(_testsPattern)
&& !isShellScript;
}
bool get isShellScript => false;
}
class RepositorySourceFile extends RepositoryLicensedFile {
RepositorySourceFile(RepositoryDirectory parent, fs.TextFile io) : super(parent, io);
@override
fs.TextFile get io => super.io;
static final RegExp _hashBangPattern = new RegExp(r'^#! *(?:/bin/sh|/bin/bash|/usr/bin/env +(?:python|bash))\b');
@override
bool get isShellScript {
return io.readString().startsWith(_hashBangPattern);
}
List<License> _licenses;
@override
Iterable<License> get licenses {
if (_licenses != null)
return _licenses;
String contents;
try {
contents = io.readString();
} on FormatException {
print('non-UTF8 data in $io');
system.exit(2);
}
_licenses = determineLicensesFor(contents, name, parent, origin: '$this');
if (_licenses == null || _licenses.isEmpty) {
_licenses = parent.nearestLicensesFor(name);
if (_licenses == null || _licenses.isEmpty)
throw 'file has no detectable license and no in-scope default license file';
}
_licenses.sort();
_licenses.forEach((License license) => license.markUsed(io.fullName, libraryName));
assert(_licenses != null && _licenses.isNotEmpty);
return _licenses;
}
}
class RepositoryBinaryFile extends RepositoryLicensedFile {
RepositoryBinaryFile(RepositoryDirectory parent, fs.File io) : super(parent, io);
@override
fs.File get io => super.io;
List<License> _licenses;
@override
List<License> get licenses {
if (_licenses == null) {
_licenses = parent.nearestLicensesFor(name);
if (_licenses == null || _licenses.isEmpty)
throw 'no license file found in scope for ${io.fullName}';
_licenses.forEach((License license) => license.markUsed(io.fullName, libraryName));
}
return _licenses;
}
}
// LICENSES
abstract class RepositoryLicenseFile extends RepositoryFile {
RepositoryLicenseFile(RepositoryDirectory parent, fs.File io) : super(parent, io);
List<License> licensesFor(String name);
License licenseOfType(LicenseType type);
License licenseWithName(String name);
License get defaultLicense;
}
abstract class RepositorySingleLicenseFile extends RepositoryLicenseFile {
RepositorySingleLicenseFile(RepositoryDirectory parent, fs.TextFile io, this.license)
: super(parent, io);
final License license;
@override
List<License> licensesFor(String name) {
if (license != null)
return <License>[license];
return null;
}
@override
License licenseWithName(String name) {
if (this.name == name)
return license;
return null;
}
@override
License get defaultLicense => license;
@override
Iterable<License> get licenses sync* { yield license; }
}
class RepositoryGeneralSingleLicenseFile extends RepositorySingleLicenseFile {
RepositoryGeneralSingleLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, new License.fromBodyAndName(io.readString(), io.name, origin: io.fullName));
RepositoryGeneralSingleLicenseFile.fromLicense(RepositoryDirectory parent, fs.TextFile io, License license)
: super(parent, io, license);
@override
License licenseOfType(LicenseType type) {
if (type == license.type)
return license;
return null;
}
}
class RepositoryApache4DNoticeFile extends RepositorySingleLicenseFile {
RepositoryApache4DNoticeFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, _parseLicense(io));
@override
License licenseOfType(LicenseType type) => null;
static final RegExp _pattern = new RegExp(
r'^(// ------------------------------------------------------------------\n'
r'// NOTICE file corresponding to the section 4d of The Apache License,\n'
r'// Version 2\.0, in this case for (?:.+)\n'
r'// ------------------------------------------------------------------\n)'
r'((?:.|\n)+)$',
multiLine: false,
caseSensitive: false
);
static bool consider(fs.TextFile io) {
return io.readString().contains(_pattern);
}
static License _parseLicense(fs.TextFile io) {
final Match match = _pattern.allMatches(io.readString()).single;
assert(match.groupCount == 2);
return new License.unique(match.group(2), LicenseType.apacheNotice, origin: io.fullName);
}
}
class RepositoryLicenseRedirectFile extends RepositorySingleLicenseFile {
RepositoryLicenseRedirectFile(RepositoryDirectory parent, fs.TextFile io, License license)
: super(parent, io, license);
@override
License licenseOfType(LicenseType type) {
if (type == license.type)
return license;
return null;
}
static RepositoryLicenseRedirectFile maybeCreateFrom(RepositoryDirectory parent, fs.TextFile io) {
String contents = io.readString();
License license = interpretAsRedirectLicense(contents, parent, origin: io.fullName);
if (license != null)
return new RepositoryLicenseRedirectFile(parent, io, license);
return null;
}
}
class RepositoryLicenseFileWithLeader extends RepositorySingleLicenseFile {
RepositoryLicenseFileWithLeader(RepositoryDirectory parent, fs.TextFile io, RegExp leader)
: super(parent, io, _parseLicense(io, leader));
@override
License licenseOfType(LicenseType type) => null;
static License _parseLicense(fs.TextFile io, RegExp leader) {
final String body = io.readString();
final Match match = leader.firstMatch(body);
if (match == null)
throw 'failed to strip leader from $io\nleader: /$leader/\nbody:\n---\n$body\n---';
return new License.fromBodyAndName(body.substring(match.end), io.name, origin: io.fullName);
}
}
class RepositoryReadmeIjgFile extends RepositorySingleLicenseFile {
RepositoryReadmeIjgFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, _parseLicense(io));
static final RegExp _pattern = new RegExp(
r'Permission is hereby granted to use, copy, modify, and distribute this\n'
r'software \(or portions thereof\) for any purpose, without fee, subject to these\n'
r'conditions:\n'
r'\(1\) If any part of the source code for this software is distributed, then this\n'
r'README file must be included, with this copyright and no-warranty notice\n'
r'unaltered; and any additions, deletions, or changes to the original files\n'
r'must be clearly indicated in accompanying documentation\.\n'
r'\(2\) If only executable code is distributed, then the accompanying\n'
r'documentation must state that "this software is based in part on the work of\n'
r'the Independent JPEG Group"\.\n'
r'\(3\) Permission for use of this software is granted only if the user accepts\n'
r'full responsibility for any undesirable consequences; the authors accept\n'
r'NO LIABILITY for damages of any kind\.\n',
caseSensitive: false
);
static License _parseLicense(fs.TextFile io) {
String body = io.readString();
if (!body.contains(_pattern))
throw 'unexpected contents in IJG README';
return new License.message(body, LicenseType.ijg, origin: io.fullName);
}
@override
License licenseWithName(String name) {
if (this.name == name)
return license;
return null;
}
@override
License licenseOfType(LicenseType type) {
return null;
}
}
class RepositoryDartLicenseFile extends RepositorySingleLicenseFile {
RepositoryDartLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, _parseLicense(io));
static final RegExp _pattern = new RegExp(
r'^This license applies to all parts of Dart that are not externally\n'
r'maintained libraries\. The external maintained libraries used by\n'
r'Dart are:\n'
r'\n'
r'(?:.+\n)+'
r'\n'
r'The libraries may have their own licenses; we recommend you read them,\n'
r'as their terms may differ from the terms below\.\n'
r'\n'
r'(Copyright (?:.|\n)+)$',
caseSensitive: false
);
static License _parseLicense(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null || match.groupCount != 1)
throw 'unexpected Dart license file contents';
return new License.template(match.group(1), LicenseType.bsd, origin: io.fullName);
}
@override
License licenseOfType(LicenseType type) {
return null;
}
}
class RepositoryLibPngLicenseFile extends RepositorySingleLicenseFile {
RepositoryLibPngLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, new License.blank(io.readString(), LicenseType.libpng, origin: io.fullName)) {
_verifyLicense(io);
}
static void _verifyLicense(fs.TextFile io) {
final String contents = io.readString();
if (!contents.contains('COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:') ||
!contents.contains('png') ||
!contents.contains('END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE.'))
throw 'unexpected libpng license file contents:\n----8<----\n$contents\n----<8----';
}
@override
License licenseOfType(LicenseType type) {
if (type == LicenseType.libpng)
return license;
return null;
}
}
class RepositoryBlankLicenseFile extends RepositorySingleLicenseFile {
RepositoryBlankLicenseFile(RepositoryDirectory parent, fs.TextFile io, String sanityCheck)
: super(parent, io, new License.blank(io.readString(), LicenseType.unknown)) {
_verifyLicense(io, sanityCheck);
}
static void _verifyLicense(fs.TextFile io, String sanityCheck) {
final String contents = io.readString();
if (!contents.contains(sanityCheck))
throw 'unexpected file contents; wanted "$sanityCheck", but got:\n----8<----\n$contents\n----<8----';
}
@override
License licenseOfType(LicenseType type) => null;
}
class RepositoryCatapultApiClientLicenseFile extends RepositorySingleLicenseFile {
RepositoryCatapultApiClientLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, _parseLicense(io));
static final RegExp _pattern = new RegExp(
r' *Licensed under the Apache License, Version 2\.0 \(the "License"\);\n'
r' *you may not use this file except in compliance with the License\.\n'
r' *You may obtain a copy of the License at\n'
r' *\n'
r' *(http://www\.apache\.org/licenses/LICENSE-2\.0)\n'
r' *\n'
r' *Unless required by applicable law or agreed to in writing, software\n'
r' *distributed under the License is distributed on an "AS IS" BASIS,\n'
r' *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n'
r' *See the License for the specific language governing permissions and\n'
r' *limitations under the License\.\n',
multiLine: true,
caseSensitive: false,
);
static License _parseLicense(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null || match.groupCount != 1)
throw 'unexpected apiclient license file contents';
return new License.fromUrl(match.group(1), origin: io.fullName);
}
@override
License licenseOfType(LicenseType type) {
return null;
}
}
class RepositoryCatapultCoverageLicenseFile extends RepositorySingleLicenseFile {
RepositoryCatapultCoverageLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io, _parseLicense(io));
static final RegExp _pattern = new RegExp(
r' *Except where noted otherwise, this software is licensed under the Apache\n'
r' *License, Version 2.0 \(the "License"\); you may not use this work except in\n'
r' *compliance with the License\. You may obtain a copy of the License at\n'
r' *\n'
r' *(http://www\.apache\.org/licenses/LICENSE-2\.0)\n'
r' *\n'
r' *Unless required by applicable law or agreed to in writing, software\n'
r' *distributed under the License is distributed on an "AS IS" BASIS,\n'
r' *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\n'
r' *See the License for the specific language governing permissions and\n'
r' *limitations under the License\.\n',
multiLine: true,
caseSensitive: false,
);
static License _parseLicense(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null || match.groupCount != 1)
throw 'unexpected coverage license file contents';
return new License.fromUrl(match.group(1), origin: io.fullName);
}
@override
License licenseOfType(LicenseType type) {
return null;
}
}
class RepositoryLibJpegTurboLicense extends RepositoryLicenseFile {
RepositoryLibJpegTurboLicense(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io) {
_parseLicense(io);
}
static final RegExp _pattern = new RegExp(
r'libjpeg-turbo is covered by three compatible BSD-style open source licenses:\n'
r'\n'
r'- The IJG \(Independent JPEG Group\) License, which is listed in\n'
r' \[README\.ijg\]\(README\.ijg\)\n'
r'\n'
r' This license applies to the libjpeg API library and associated programs\n'
r' \(any code inherited from libjpeg, and any modifications to that code\.\)\n'
r'\n'
r'- The Modified \(3-clause\) BSD License, which is listed in\n'
r' \[turbojpeg\.c\]\(turbojpeg\.c\)\n'
r'\n'
r' This license covers the TurboJPEG API library and associated programs\.\n'
r'\n'
r'- The zlib License, which is listed in \[simd/jsimdext\.inc\]\(simd/jsimdext\.inc\)\n'
r'\n'
r' This license is a subset of the other two, and it covers the libjpeg-turbo\n'
r' SIMD extensions\.\n'
);
static void _parseLicense(fs.TextFile io) {
String body = io.readString();
if (!body.contains(_pattern))
throw 'unexpected contents in libjpeg-turbo LICENSE';
}
List<License> _licenses;
@override
List<License> get licenses {
if (_licenses == null) {
final RepositoryReadmeIjgFile readme = parent.getChildByName('README.ijg');
final RepositorySourceFile main = parent.getChildByName('turbojpeg.c');
final RepositoryDirectory simd = parent.getChildByName('simd');
final RepositorySourceFile zlib = simd.getChildByName('jsimdext.inc');
_licenses = <License>[];
_licenses.add(readme.license);
_licenses.add(main.licenses.single);
_licenses.add(zlib.licenses.single);
}
return _licenses;
}
@override
License licenseWithName(String name) {
return null;
}
@override
List<License> licensesFor(String name) {
return licenses;
}
@override
License licenseOfType(LicenseType type) {
return null;
}
@override
License get defaultLicense => null;
}
class RepositoryFreetypeLicenseFile extends RepositoryLicenseFile {
RepositoryFreetypeLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: _target = _parseLicense(io), super(parent, io);
static final RegExp _pattern = new RegExp(
r"The FreeType 2 font engine is copyrighted work and cannot be used\n"
r"legally without a software license\. In order to make this project\n"
r"usable to a vast majority of developers, we distribute it under two\n"
r"mutually exclusive open-source licenses\.\n"
r"\n"
r"This means that \*you\* must choose \*one\* of the two licenses described\n"
r"below, then obey all its terms and conditions when using FreeType 2 in\n"
r"any of your projects or products.\n"
r"\n"
r" - The FreeType License, found in the file `(FTL\.TXT)', which is similar\n"
r" to the original BSD license \*with\* an advertising clause that forces\n"
r" you to explicitly cite the FreeType project in your product's\n"
r" documentation\. All details are in the license file\. This license\n"
r" is suited to products which don't use the GNU General Public\n"
r" License\.\n"
r"\n"
r" Note that this license is compatible to the GNU General Public\n"
r" License version 3, but not version 2\.\n"
r"\n"
r" - The GNU General Public License version 2, found in `GPLv2\.TXT' \(any\n"
r" later version can be used also\), for programs which already use the\n"
r" GPL\. Note that the FTL is incompatible with GPLv2 due to its\n"
r" advertisement clause\.\n"
r"\n"
r"The contributed BDF and PCF drivers come with a license similar to that\n"
r"of the X Window System\. It is compatible to the above two licenses \(see\n"
r"file src/bdf/README and src/pcf/README\)\.\n"
r"\n"
r"The gzip module uses the zlib license \(see src/gzip/zlib\.h\) which too is\n"
r"compatible to the above two licenses\.\n"
r"\n"
r"The MD5 checksum support \(only used for debugging in development builds\)\n"
r"is in the public domain\.\n"
r"\n*"
r"--- end of LICENSE\.TXT ---\n*$"
);
static String _parseLicense(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null || match.groupCount != 1)
throw 'unexpected Freetype license file contents';
return match.group(1);
}
final String _target;
List<License> _targetLicense;
void _warmCache() {
_targetLicense ??= <License>[parent.nearestLicenseWithName(_target)];
}
@override
List<License> licensesFor(String name) {
_warmCache();
return _targetLicense;
}
@override
License licenseOfType(LicenseType type) => null;
@override
License licenseWithName(String name) => null;
@override
License get defaultLicense {
_warmCache();
return _targetLicense.single;
}
@override
Iterable<License> get licenses sync* { }
}
class RepositoryIcuLicenseFile extends RepositoryLicenseFile {
RepositoryIcuLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: _licenses = _parseLicense(io),
super(parent, io);
@override
fs.TextFile get io => super.io;
final List<License> _licenses;
static final RegExp _pattern = new RegExp(
r'^COPYRIGHT AND PERMISSION NOTICE \(ICU 58 and later\)\n+'
r'( *Copyright (?:.|\n)+?)\n+' // 1
r'Third-Party Software Licenses\n+'
r' *This section contains third-party software notices and/or additional\n'
r' *terms for licensed third-party software components included within ICU\n'
r' *libraries\.\n+'
r' *1\. ICU License - ICU 1.8.1 to ICU 57.1[ \n]+?'
r' *COPYRIGHT AND PERMISSION NOTICE\n+'
r'(Copyright (?:.|\n)+?)\n+' //2
r' *2\. Chinese/Japanese Word Break Dictionary Data \(cjdict\.txt\)\n+'
r' # The Google Chrome software developed by Google is licensed under\n?'
r' # the BSD license\. Other software included in this distribution is\n?'
r' # provided under other licenses, as set forth below\.\n'
r' #\n'
r'( # The BSD License\n'
r' # http://opensource\.org/licenses/bsd-license\.php\n'
r' # +Copyright(?:.|\n)+?)\n' // 3
r' #\n'
r' #\n'
r' # The word list in cjdict.txt are generated by combining three word lists\n?'
r' # listed below with further processing for compound word breaking\. The\n?'
r' # frequency is generated with an iterative training against Google web\n?'
r' # corpora\.\n'
r' #\n'
r' # \* Libtabe \(Chinese\)\n'
r' # - https://sourceforge\.net/project/\?group_id=1519\n'
r' # - Its license terms and conditions are shown below\.\n'
r' #\n'
r' # \* IPADIC \(Japanese\)\n'
r' # - http://chasen\.aist-nara\.ac\.jp/chasen/distribution\.html\n'
r' # - Its license terms and conditions are shown below\.\n'
r' #\n'
r' # ---------COPYING\.libtabe ---- BEGIN--------------------\n'
r' #\n'
r' # +/\*\n'
r'( # +\* Copyright (?:.|\n)+?)\n' // 4
r' # +\*/\n'
r' #\n'
r' # +/\*\n'
r'( # +\* Copyright (?:.|\n)+?)\n' // 5
r' # +\*/\n'
r' #\n'
r'( # +Copyright (?:.|\n)+?)\n' // 6
r' #\n'
r' # +---------------COPYING\.libtabe-----END--------------------------------\n'
r' #\n'
r' #\n'
r' # +---------------COPYING\.ipadic-----BEGIN-------------------------------\n'
r' #\n'
r'( # +Copyright (?:.|\n)+?)\n' // 7
r' #\n'
r' # +---------------COPYING\.ipadic-----END----------------------------------\n'
r'\n'
r' *3\. Lao Word Break Dictionary Data \(laodict\.txt\)\n'
r'\n'
r'( # +Copyright(?:.|\n)+?)\n' // 8
r'\n'
r' *4\. Burmese Word Break Dictionary Data \(burmesedict\.txt\)\n'
r'\n'
r'( # +Copyright(?:.|\n)+?)\n' // 9
r'\n'
r' *5\. Time Zone Database\n'
r'((?:.|\n)+)\n' // 10
r'\n'
r' *6\. Google double-conversion\n'
r'\n'
r'(Copyright(?:.|\n)+)\n$', // 11
multiLine: true,
caseSensitive: false
);
static final RegExp _unexpectedHash = new RegExp(r'^.+ #', multiLine: true);
static final RegExp _newlineHash = new RegExp(r' # ?');
static String _dewrap(String s) {
if (!s.startsWith(' # '))
return s;
if (s.contains(_unexpectedHash))
throw 'ICU license file contained unexpected hash sequence';
if (s.contains('\x2028'))
throw 'ICU license file contained unexpected line separator';
return s.replaceAll(_newlineHash, '\x2028').replaceAll('\n', '').replaceAll('\x2028', '\n');
}
static List<License> _parseLicense(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null)
throw 'could not parse ICU license file';
assert(match.groupCount == 11);
if (match.group(10).contains(copyrightMentionPattern) || match.group(11).contains('7.'))
throw 'unexpected copyright in ICU license file';
final List<License> result = <License>[
new License.fromBodyAndType(_dewrap(match.group(1)), LicenseType.unknown, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(2)), LicenseType.icu, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(3)), LicenseType.bsd, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(4)), LicenseType.bsd, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(5)), LicenseType.bsd, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(6)), LicenseType.unknown, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(7)), LicenseType.unknown, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(8)), LicenseType.bsd, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(9)), LicenseType.bsd, origin: io.fullName),
new License.fromBodyAndType(_dewrap(match.group(11)), LicenseType.bsd, origin: io.fullName),
];
return result;
}
@override
List<License> licensesFor(String name) {
return _licenses;
}
@override
License licenseOfType(LicenseType type) {
if (type == LicenseType.icu)
return _licenses[0];
throw 'tried to use ICU license file to find a license by type but type wasn\'t ICU';
}
@override
License licenseWithName(String name) {
throw 'tried to use ICU license file to find a license by name';
}
@override
License get defaultLicense => _licenses[0];
@override
Iterable<License> get licenses => _licenses;
}
Iterable<List<int>> splitIntList(List<int> data, int boundary) sync* {
int index = 0;
List<int> getOne() {
int start = index;
int end = index;
while ((end < data.length) && (data[end] != boundary))
end += 1;
end += 1;
index = end;
return data.sublist(start, end).toList();
}
while (index < data.length)
yield getOne();
}
class RepositoryMultiLicenseNoticesForFilesFile extends RepositoryLicenseFile {
RepositoryMultiLicenseNoticesForFilesFile(RepositoryDirectory parent, fs.File io)
: _licenses = _parseLicense(io),
super(parent, io);
@override
fs.File get io => super.io;
final Map<String, License> _licenses;
static Map<String, License> _parseLicense(fs.File io) {
final Map<String, License> result = <String, License>{};
// Files of this type should begin with:
// "Notices for files contained in the"
// ...then have a second line which is 60 "=" characters
final List<List<int>> contents = splitIntList(io.readBytes(), 0x0A).toList();
if (!ascii.decode(contents[0]).startsWith('Notices for files contained in') ||
ascii.decode(contents[1]) != '============================================================\n')
throw 'unrecognised syntax: ${io.fullName}';
int index = 2;
while (index < contents.length) {
if (ascii.decode(contents[index]) != 'Notices for file(s):\n')
throw 'unrecognised syntax on line ${index + 1}: ${io.fullName}';
index += 1;
final List<String> names = <String>[];
do {
names.add(ascii.decode(contents[index]));
index += 1;
} while (ascii.decode(contents[index]) != '------------------------------------------------------------\n');
index += 1;
final List<List<int>> body = <List<int>>[];
do {
body.add(contents[index]);
index += 1;
} while (index < contents.length &&
ascii.decode(contents[index], allowInvalid: true) != '============================================================\n');
index += 1;
final List<int> bodyBytes = body.expand((List<int> line) => line).toList();
String bodyText;
try {
bodyText = utf8.decode(bodyBytes);
} on FormatException {
bodyText = latin1.decode(bodyBytes);
}
License license = new License.unique(bodyText, LicenseType.unknown, origin: io.fullName);
for (String name in names) {
if (result[name] != null)
throw 'conflicting license information for $name in ${io.fullName}';
result[name] = license;
}
}
return result;
}
@override
List<License> licensesFor(String name) {
License license = _licenses[name];
if (license != null)
return <License>[license];
return null;
}
@override
License licenseOfType(LicenseType type) {
throw 'tried to use multi-license license file to find a license by type';
}
@override
License licenseWithName(String name) {
throw 'tried to use multi-license license file to find a license by name';
}
@override
License get defaultLicense {
assert(false);
throw '$this ($runtimeType) does not have a concept of a "default" license';
}
@override
Iterable<License> get licenses => _licenses.values;
}
class RepositoryCxxStlDualLicenseFile extends RepositoryLicenseFile {
RepositoryCxxStlDualLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: _licenses = _parseLicenses(io), super(parent, io);
static final RegExp _pattern = new RegExp(
r'^'
r'==============================================================================\n'
r'.+ License\n'
r'==============================================================================\n'
r'\n'
r'The .+ library is dual licensed under both the University of Illinois\n'
r'"BSD-Like" license and the MIT license\. +As a user of this code you may choose\n'
r'to use it under either license\. +As a contributor, you agree to allow your code\n'
r'to be used under both\.\n'
r'\n'
r'Full text of the relevant licenses is included below\.\n'
r'\n'
r'==============================================================================\n'
r'((?:.|\n)+)\n'
r'==============================================================================\n'
r'((?:.|\n)+)'
r'$'
);
static List<License> _parseLicenses(fs.TextFile io) {
final Match match = _pattern.firstMatch(io.readString());
if (match == null || match.groupCount != 2)
throw 'unexpected dual license file contents';
return <License>[
new License.fromBodyAndType(match.group(1), LicenseType.bsd),
new License.fromBodyAndType(match.group(2), LicenseType.mit),
];
}
List<License> _licenses;
@override
List<License> licensesFor(String name) {
return _licenses;
}
@override
License licenseOfType(LicenseType type) {
throw 'tried to look up a dual-license license by type ("$type")';
}
@override
License licenseWithName(String name) {
throw 'tried to look up a dual-license license by name ("$name")';
}
@override
License get defaultLicense => _licenses[0];
@override
Iterable<License> get licenses => _licenses;
}
// DIRECTORIES
class RepositoryDirectory extends RepositoryEntry implements LicenseSource {
RepositoryDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io) {
crawl();
}
@override
fs.Directory get io => super.io;
final List<RepositoryDirectory> _subdirectories = <RepositoryDirectory>[];
final List<RepositoryLicensedFile> _files = <RepositoryLicensedFile>[];
final List<RepositoryLicenseFile> _licenses = <RepositoryLicenseFile>[];
List<RepositoryDirectory> get subdirectories => _subdirectories;
final Map<String, RepositoryEntry> _childrenByName = <String, RepositoryEntry>{};
// the bit at the beginning excludes files like "license.py".
static final RegExp _licenseNamePattern = new RegExp(r'^(?!.*\.py$)(?!.*(?:no|update)-copyright)(?!.*mh-bsd-gcc).*\b_*(?:license(?!\.html)|copying|copyright|notice|l?gpl|bsd|mpl?|ftl\.txt)_*\b', caseSensitive: false);
void crawl() {
for (fs.IoNode entry in io.walk) {
if (shouldRecurse(entry)) {
assert(!_childrenByName.containsKey(entry.name));
if (entry is fs.Directory) {
RepositoryDirectory child = createSubdirectory(entry);
_subdirectories.add(child);
_childrenByName[child.name] = child;
} else if (entry is fs.File) {
try {
RepositoryFile child = createFile(entry);
assert(child != null);
if (child is RepositoryLicensedFile) {
_files.add(child);
} else {
assert(child is RepositoryLicenseFile);
_licenses.add(child);
}
_childrenByName[child.name] = child;
} catch (e) {
system.stderr.writeln('failed to handle $entry: $e');
rethrow;
}
} else {
assert(entry is fs.Link);
}
}
}
for (RepositoryDirectory child in virtualSubdirectories) {
_subdirectories.add(child);
_childrenByName[child.name] = child;
}
}
// Override this to add additional child directories that do not represent a
// direct child of this directory's filesystem node.
List<RepositoryDirectory> get virtualSubdirectories => <RepositoryDirectory>[];
bool shouldRecurse(fs.IoNode entry) {
return entry.name != '.cipd' &&
entry.name != '.git' &&
entry.name != '.github' &&
entry.name != '.gitignore' &&
entry.name != 'test' &&
entry.name != 'test.disabled' &&
entry.name != 'test_support' &&
entry.name != 'tests' &&
entry.name != 'javatests' &&
entry.name != 'testing';
}
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryGenericThirdPartyDirectory(this, entry);
return new RepositoryDirectory(this, entry);
}
RepositoryFile createFile(fs.IoNode entry) {
if (entry is fs.TextFile) {
if (RepositoryApache4DNoticeFile.consider(entry)) {
return new RepositoryApache4DNoticeFile(this, entry);
} else {
RepositoryFile result;
if (entry.name == 'NOTICE')
result = RepositoryLicenseRedirectFile.maybeCreateFrom(this, entry);
if (result != null) {
return result;
} else if (entry.name.contains(_licenseNamePattern)) {
return new RepositoryGeneralSingleLicenseFile(this, entry);
} else if (entry.name == 'README.ijg') {
return new RepositoryReadmeIjgFile(this, entry);
} else {
return new RepositorySourceFile(this, entry);
}
}
} else if (entry.name == 'NOTICE.txt') {
return new RepositoryMultiLicenseNoticesForFilesFile(this, entry);
} else {
return new RepositoryBinaryFile(this, entry);
}
}
int get count => _files.length + _subdirectories.fold<int>(0, (int count, RepositoryDirectory child) => count + child.count);
@override
List<License> nearestLicensesFor(String name) {
if (_licenses.isEmpty) {
if (_canGoUp(null))
return parent.nearestLicensesFor('${io.name}/$name');
return null;
}
if (_licenses.length == 1)
return _licenses.single.licensesFor(name);
List<License> licenses = _licenses.expand/*License*/((RepositoryLicenseFile license) sync* {
List<License> licenses = license.licensesFor(name);
if (licenses != null)
yield* licenses;
}).toList();
if (licenses.isEmpty)
return null;
if (licenses.length > 1) {
//print('unexpectedly found multiple matching licenses for: $name');
return licenses; // TODO(ianh): disambiguate them, in case we have e.g. a dual GPL/BSD situation
}
return licenses;
}
@override
License nearestLicenseOfType(LicenseType type) {
License result = _nearestAncestorLicenseWithType(type);
if (result == null) {
for (RepositoryDirectory directory in _subdirectories) {
result = directory._localLicenseWithType(type);
if (result != null)
break;
}
}
result ??= _fullWalkUpForLicenseWithType(type);
return result;
}
/// Searches the current and all parent directories (up to the license root)
/// for a license of the specified type.
License _nearestAncestorLicenseWithType(LicenseType type) {
License result = _localLicenseWithType(type);
if (result != null)
return result;
if (_canGoUp(null))
return parent._nearestAncestorLicenseWithType(type);
return null;
}
/// Searches all subdirectories below the current license root for a license
/// of the specified type.
License _fullWalkUpForLicenseWithType(LicenseType type) {
return _canGoUp(null)
? parent._fullWalkUpForLicenseWithType(type)
: _fullWalkDownForLicenseWithType(type);
}
/// Searches the current directory and all subdirectories for a license of
/// the specified type.
License _fullWalkDownForLicenseWithType(LicenseType type) {
License result = _localLicenseWithType(type);
if (result == null) {
for (RepositoryDirectory directory in _subdirectories) {
result = directory._fullWalkDownForLicenseWithType(type);
if (result != null)
break;
}
}
return result;
}
/// Searches the current directory for licenses of the specified type.
License _localLicenseWithType(LicenseType type) {
List<License> licenses = _licenses.expand/*License*/((RepositoryLicenseFile license) sync* {
License result = license.licenseOfType(type);
if (result != null)
yield result;
}).toList();
if (licenses.length > 1) {
print('unexpectedly found multiple matching licenses in $name of type $type');
return null;
}
if (licenses.isNotEmpty)
return licenses.single;
return null;
}
@override
License nearestLicenseWithName(String name, { String authors }) {
License result = _nearestAncestorLicenseWithName(name, authors: authors);
if (result == null) {
for (RepositoryDirectory directory in _subdirectories) {
result = directory._localLicenseWithName(name, authors: authors);
if (result != null)
break;
}
}
result ??= _fullWalkUpForLicenseWithName(name, authors: authors);
result ??= _fullWalkUpForLicenseWithName(name, authors: authors, ignoreCase: true);
if (authors != null && result == null) {
// if (result == null)
// print('could not find $name for authors "$authors", now looking for any $name in $this');
result = nearestLicenseWithName(name);
// if (result == null)
// print('completely failed to find $name for authors "$authors"');
// else
// print('ended up finding a $name for "${result.authors}" instead');
}
return result;
}
bool _canGoUp(String authors) {
return parent != null && (authors != null || isLicenseRootException || (!isLicenseRoot && !parent.subdirectoriesAreLicenseRoots));
}
License _nearestAncestorLicenseWithName(String name, { String authors }) {
License result = _localLicenseWithName(name, authors: authors);
if (result != null)
return result;
if (_canGoUp(authors))
return parent._nearestAncestorLicenseWithName(name, authors: authors);
return null;
}
License _fullWalkUpForLicenseWithName(String name, { String authors, bool ignoreCase: false }) {
return _canGoUp(authors)
? parent._fullWalkUpForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase)
: _fullWalkDownForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase);
}
License _fullWalkDownForLicenseWithName(String name, { String authors, bool ignoreCase: false }) {
License result = _localLicenseWithName(name, authors: authors, ignoreCase: ignoreCase);
if (result == null) {
for (RepositoryDirectory directory in _subdirectories) {
result = directory._fullWalkDownForLicenseWithName(name, authors: authors, ignoreCase: ignoreCase);
if (result != null)
break;
}
}
return result;
}
/// Unless isLicenseRootException is true, we should not walk up the tree from
/// here looking for licenses.
bool get isLicenseRoot => parent == null;
/// Unless isLicenseRootException is true on a child, the child should not
/// walk up the tree to here looking for licenses.
bool get subdirectoriesAreLicenseRoots => false;
@override
String get libraryName {
if (isLicenseRoot)
return name;
assert(parent != null);
if (parent.subdirectoriesAreLicenseRoots)
return name;
return parent.libraryName;
}
/// Overrides isLicenseRoot and parent.subdirectoriesAreLicenseRoots for cases
/// where a directory contains license roots instead of being one. This
/// allows, for example, the expat third_party directory to contain a
/// subdirectory with expat while itself containing a BUILD file that points
/// to the LICENSE in the root of the repo.
bool get isLicenseRootException => false;
License _localLicenseWithName(String name, { String authors, bool ignoreCase: false }) {
Map<String, RepositoryEntry> map;
if (ignoreCase) {
// we get here if we're trying a last-ditch effort at finding a file.
// so this should happen only rarely.
map = new HashMap<String, RepositoryEntry>(
equals: (String n1, String n2) => n1.toLowerCase() == n2.toLowerCase(),
hashCode: (String n) => n.toLowerCase().hashCode
)
..addAll(_childrenByName);
} else {
map = _childrenByName;
}
final RepositoryEntry entry = map[name];
License license;
if (entry is RepositoryLicensedFile) {
license = entry.licenses.single;
} else if (entry is RepositoryLicenseFile) {
license = entry.defaultLicense;
} else if (entry != null) {
if (authors == null)
throw 'found "$name" in $this but it was a ${entry.runtimeType}';
}
if (license != null && authors != null) {
if (license.authors?.toLowerCase() != authors.toLowerCase())
license = null;
}
return license;
}
RepositoryEntry getChildByName(String name) {
return _childrenByName[name];
}
Set<License> getLicenses(Progress progress) {
Set<License> result = new Set<License>();
for (RepositoryDirectory directory in _subdirectories)
result.addAll(directory.getLicenses(progress));
for (RepositoryLicensedFile file in _files) {
if (file.isIncludedInBuildProducts) {
try {
progress.label = '$file';
List<License> licenses = file.licenses;
assert(licenses != null && licenses.isNotEmpty);
result.addAll(licenses);
progress.advance(true);
} catch (e, stack) {
system.stderr.writeln('error searching for copyright in: ${file.io}\n$e');
if (e is! String)
system.stderr.writeln(stack);
system.stderr.writeln('\n');
progress.advance(false);
}
}
}
for (RepositoryLicenseFile file in _licenses)
result.addAll(file.licenses);
return result;
}
int get fileCount {
int result = 0;
for (RepositoryLicensedFile file in _files) {
if (file.isIncludedInBuildProducts)
result += 1;
}
for (RepositoryDirectory directory in _subdirectories)
result += directory.fileCount;
return result;
}
Iterable<RepositoryLicensedFile> get _signatureFiles sync* {
for (RepositoryLicensedFile file in _files) {
if (file.isIncludedInBuildProducts)
yield file;
}
for (RepositoryDirectory directory in _subdirectories) {
if (directory.includeInSignature)
yield* directory._signatureFiles;
}
}
Stream<List<int>> _signatureStream(List<RepositoryLicensedFile> files) async* {
for (RepositoryLicensedFile file in files) {
yield file.io.fullName.codeUnits;
yield file.io.readBytes();
}
}
/// Compute a signature representing a hash of all the licensed files within
/// this directory tree.
Future<String> get signature async {
List<RepositoryLicensedFile> allFiles = _signatureFiles.toList();
allFiles.sort((RepositoryLicensedFile a, RepositoryLicensedFile b) =>
a.io.fullName.compareTo(b.io.fullName));
crypto.Digest digest = await crypto.md5.bind(_signatureStream(allFiles)).single;
return digest.bytes.map((int e) => e.toRadixString(16).padLeft(2, '0')).join();
}
/// True if this directory's contents should be included when computing the signature.
bool get includeInSignature => true;
}
class RepositoryGenericThirdPartyDirectory extends RepositoryDirectory {
RepositoryGenericThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get subdirectoriesAreLicenseRoots => true;
}
class RepositoryReachOutFile extends RepositoryLicensedFile {
RepositoryReachOutFile(RepositoryDirectory parent, fs.File io, this.offset) : super(parent, io);
@override
fs.File get io => super.io;
final int offset;
@override
List<License> get licenses {
RepositoryDirectory directory = parent;
int index = offset;
while (index > 1) {
if (directory == null)
break;
directory = directory.parent;
index -= 1;
}
return directory?.nearestLicensesFor(name);
}
}
class RepositoryReachOutDirectory extends RepositoryDirectory {
RepositoryReachOutDirectory(RepositoryDirectory parent, fs.Directory io, this.reachOutFilenames, this.offset) : super(parent, io);
final Set<String> reachOutFilenames;
final int offset;
@override
RepositoryFile createFile(fs.IoNode entry) {
if (reachOutFilenames.contains(entry.name))
return new RepositoryReachOutFile(this, entry, offset);
return super.createFile(entry);
}
}
class RepositoryExcludeSubpathDirectory extends RepositoryDirectory {
RepositoryExcludeSubpathDirectory(RepositoryDirectory parent, fs.Directory io, this.paths, [ this.index = 0 ]) : super(parent, io);
final List<String> paths;
final int index;
@override
bool shouldRecurse(fs.IoNode entry) {
if (index == paths.length - 1 && entry.name == paths.last)
return false;
return super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == paths[index] && (index < paths.length - 1))
return new RepositoryExcludeSubpathDirectory(this, entry, paths, index + 1);
return super.createSubdirectory(entry);
}
}
// WHAT TO CRAWL AND WHAT NOT TO CRAWL
class RepositoryAndroidSdkPlatformsWithJarDirectory extends RepositoryDirectory {
RepositoryAndroidSdkPlatformsWithJarDirectory(RepositoryDirectory parent, fs.Directory io)
: _jarLicense = <License>[new License.fromUrl('http://www.apache.org/licenses/LICENSE-2.0', origin: 'implicit android.jar license')],
super(parent, io);
final List<License> _jarLicense;
@override
List<License> nearestLicensesFor(String name) => _jarLicense;
@override
License nearestLicenseOfType(LicenseType type) {
if (_jarLicense.single.type == type)
return _jarLicense.single;
return null;
}
@override
License nearestLicenseWithName(String name, { String authors }) {
return null;
}
@override
bool shouldRecurse(fs.IoNode entry) {
// we only use android.jar from the SDK, everything else we ignore
return entry.name == 'android.jar';
}
}
class RepositoryAndroidSdkPlatformsDirectory extends RepositoryDirectory {
RepositoryAndroidSdkPlatformsDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'android-22') // chinmay says we only use 22 for the SDK
return new RepositoryAndroidSdkPlatformsWithJarDirectory(this, entry);
throw 'unknown Android SDK version: ${entry.name}';
}
}
class RepositoryAndroidSdkDirectory extends RepositoryDirectory {
RepositoryAndroidSdkDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
// We don't link with any of the Android SDK tools, Google-specific
// packages, system images, samples, etc, when building the engine. We do
// use some (especially those in build-tools/), but it is our understanding
// that nothing from those files actually ends up in our final build output,
// and therefore we don't worry about their licenses.
return entry.name != 'add-ons'
&& entry.name != 'build-tools'
&& entry.name != 'extras'
&& entry.name != 'platform-tools'
&& entry.name != 'samples'
&& entry.name != 'system-images'
&& entry.name != 'tools'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'platforms')
return new RepositoryAndroidSdkPlatformsDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidNdkPlatformsDirectory extends RepositoryDirectory {
RepositoryAndroidNdkPlatformsDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
if (entry.name == 'android-9' ||
entry.name == 'android-12' ||
entry.name == 'android-13' ||
entry.name == 'android-14' ||
entry.name == 'android-15' ||
entry.name == 'android-17' ||
entry.name == 'android-18' ||
entry.name == 'android-19' ||
entry.name == 'android-21' ||
entry.name == 'android-23' ||
entry.name == 'android-24')
return false;
if (entry.name == 'android-16' || // chinmay says we use this for armv7
entry.name == 'android-22') // chinmay says we use this for everything else
return true;
throw 'unknown Android NDK version: ${entry.name}';
}
}
class RepositoryAndroidNdkSourcesAndroidSupportDirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesAndroidSupportDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'NOTICE' && entry is fs.TextFile) {
return new RepositoryGeneralSingleLicenseFile.fromLicense(
this,
entry,
new License.unique(
entry.readString(),
LicenseType.unknown,
origin: entry.fullName,
yesWeKnowWhatItLooksLikeButItIsNot: true, // lawyer said to include this file verbatim
)
);
}
return super.createFile(entry);
}
}
class RepositoryAndroidNdkSourcesAndroidDirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesAndroidDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'libthread_db' // README in that directory says we aren't using this
&& entry.name != 'crazy_linker' // build-time only (not that we use it anyway)
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'support')
return new RepositoryAndroidNdkSourcesAndroidSupportDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidNdkSourcesCxxStlSubsubdirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesCxxStlSubsubdirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE.TXT')
return new RepositoryCxxStlDualLicenseFile(this, entry);
return super.createFile(entry);
}
}
class RepositoryAndroidNdkSourcesCxxStlSubdirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesCxxStlSubdirectory(RepositoryDirectory parent, fs.Directory io, this.subdirectoryName) : super(parent, io);
final String subdirectoryName;
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == subdirectoryName)
return new RepositoryAndroidNdkSourcesCxxStlSubsubdirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidNdkSourcesCxxStlDirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesCxxStlDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get subdirectoriesAreLicenseRoots => true;
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'gabi++' // abarth says jamesr says we don't use these two
&& entry.name != 'stlport'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'llvm-libc++abi')
return new RepositoryAndroidNdkSourcesCxxStlSubdirectory(this, entry, 'libcxxabi');
if (entry.name == 'llvm-libc++')
return new RepositoryAndroidNdkSourcesCxxStlSubdirectory(this, entry, 'libcxx');
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidNdkSourcesThirdPartyDirectory extends RepositoryGenericThirdPartyDirectory {
RepositoryAndroidNdkSourcesThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
if (entry.name == 'googletest')
return false; // testing infrastructure, not shipped with flutter engine
if (entry.name == 'shaderc')
return false; // abarth says we don't use any shader stuff
if (entry.name == 'vulkan')
return false; // abath says we do use vulkan so might use this
throw 'unexpected Android NDK third-party package: ${entry.name}';
}
}
class RepositoryAndroidNdkSourcesDirectory extends RepositoryDirectory {
RepositoryAndroidNdkSourcesDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'android')
return new RepositoryAndroidNdkSourcesAndroidDirectory(this, entry);
if (entry.name == 'cxx-stl')
return new RepositoryAndroidNdkSourcesCxxStlDirectory(this, entry);
if (entry.name == 'third_party')
return new RepositoryAndroidNdkSourcesThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidNdkDirectory extends RepositoryDirectory {
RepositoryAndroidNdkDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
// we don't link with or use any of the Android NDK samples
return entry.name != 'build'
&& entry.name != 'docs'
&& entry.name != 'prebuilt' // only used by engine debug builds, which we don't ship
&& entry.name != 'samples'
&& entry.name != 'tests'
&& entry.name != 'toolchains' // only used at build time, doesn't seem to contain anything that gets shipped with the build output
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'platforms')
return new RepositoryAndroidNdkPlatformsDirectory(this, entry);
if (entry.name == 'sources')
return new RepositoryAndroidNdkSourcesDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryAndroidToolsDirectory extends RepositoryDirectory {
RepositoryAndroidToolsDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get subdirectoriesAreLicenseRoots => true;
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'VERSION_LINUX_SDK'
&& entry.name != 'VERSION_LINUX_NDK'
&& entry.name != 'VERSION_MACOSX_SDK'
&& entry.name != 'VERSION_MACOSX_NDK'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'sdk')
return new RepositoryAndroidSdkDirectory(this, entry);
if (entry.name == 'ndk')
return new RepositoryAndroidNdkDirectory(this, entry);
return super.createSubdirectory(entry);
}
// This directory's contents are different on each host platform. We assume
// that the components of the Android SDK that are linked into our releases
// are consistent among all host platforms. Given that the host SDK will not
// affect the signature, be sure to force a regeneration of the third_party
// golden licenses if the SDK is ever updated.
@override
bool get includeInSignature => false;
}
class RepositoryAndroidPlatformDirectory extends RepositoryDirectory {
RepositoryAndroidPlatformDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
// we don't link with or use any of the Android NDK samples
return entry.name != 'webview' // not used at all
&& entry.name != 'development' // not linked in
&& super.shouldRecurse(entry);
}
}
class RepositoryExpatDirectory extends RepositoryDirectory {
RepositoryExpatDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get isLicenseRootException => true;
@override
bool get subdirectoriesAreLicenseRoots => true;
}
class RepositoryFreetypeDocsDirectory extends RepositoryDirectory {
RepositoryFreetypeDocsDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE.TXT')
return new RepositoryFreetypeLicenseFile(this, entry);
return super.createFile(entry);
}
@override
int get fileCount => 0;
@override
Set<License> getLicenses(Progress progress) {
// We don't ship anything in this directory so don't bother looking for licenses there.
// However, there are licenses in this directory referenced from elsewhere, so we do
// want to crawl it and expose them.
return new Set<License>();
}
}
class RepositoryFreetypeSrcGZipDirectory extends RepositoryDirectory {
RepositoryFreetypeSrcGZipDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
// advice was to make this directory's inffixed.h file (which has no license)
// use the license in zlib.h.
@override
List<License> nearestLicensesFor(String name) {
License zlib = nearestLicenseWithName('zlib.h');
assert(zlib != null);
if (zlib != null)
return <License>[zlib];
return super.nearestLicensesFor(name);
}
@override
License nearestLicenseOfType(LicenseType type) {
if (type == LicenseType.zlib) {
License result = nearestLicenseWithName('zlib.h');
assert(result != null);
return result;
}
return null;
}
}
class RepositoryFreetypeSrcDirectory extends RepositoryDirectory {
RepositoryFreetypeSrcDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'gzip')
return new RepositoryFreetypeSrcGZipDirectory(this, entry);
return super.createSubdirectory(entry);
}
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'tools'
&& super.shouldRecurse(entry);
}
}
class RepositoryFreetypeDirectory extends RepositoryDirectory {
RepositoryFreetypeDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
List<License> nearestLicensesFor(String name) {
List<License> result = super.nearestLicensesFor(name);
if (result == null) {
License license = nearestLicenseWithName('LICENSE.TXT');
assert(license != null);
if (license != null)
return <License>[license];
}
return result;
}
@override
License nearestLicenseOfType(LicenseType type) {
if (type == LicenseType.freetype) {
License result = nearestLicenseWithName('FTL.TXT');
assert(result != null);
return result;
}
return null;
}
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'builds' // build files
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'src')
return new RepositoryFreetypeSrcDirectory(this, entry);
if (entry.name == 'docs')
return new RepositoryFreetypeDocsDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryIcuDirectory extends RepositoryDirectory {
RepositoryIcuDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'license.html' // redundant with LICENSE file
&& super.shouldRecurse(entry);
}
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE')
return new RepositoryIcuLicenseFile(this, entry);
return super.createFile(entry);
}
}
class RepositoryHarfbuzzDirectory extends RepositoryDirectory {
RepositoryHarfbuzzDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'util' // utils are command line tools that do not end up in the binary
&& super.shouldRecurse(entry);
}
}
class RepositoryJSR305Directory extends RepositoryDirectory {
RepositoryJSR305Directory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'src')
return new RepositoryJSR305SrcDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryJSR305SrcDirectory extends RepositoryDirectory {
RepositoryJSR305SrcDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'javadoc'
&& entry.name != 'sampleUses'
&& super.shouldRecurse(entry);
}
}
class RepositoryLibJpegDirectory extends RepositoryDirectory {
RepositoryLibJpegDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'README')
return new RepositoryReadmeIjgFile(this, entry);
if (entry.name == 'LICENSE')
return new RepositoryLicenseFileWithLeader(this, entry, new RegExp(r'^\(Copied from the README\.\)\n+-+\n+'));
return super.createFile(entry);
}
}
class RepositoryLibJpegTurboDirectory extends RepositoryDirectory {
RepositoryLibJpegTurboDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE.md')
return new RepositoryLibJpegTurboLicense(this, entry);
return super.createFile(entry);
}
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'release' // contains nothing that ends up in the binary executable
&& entry.name != 'doc' // contains nothing that ends up in the binary executable
&& entry.name != 'testimages' // test assets
&& super.shouldRecurse(entry);
}
}
class RepositoryLibPngDirectory extends RepositoryDirectory {
RepositoryLibPngDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE' || entry.name == 'png.h')
return new RepositoryLibPngLicenseFile(this, entry);
return super.createFile(entry);
}
}
class RepositoryLibWebpDirectory extends RepositoryDirectory {
RepositoryLibWebpDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'examples' // contains nothing that ends up in the binary executable
&& entry.name != 'swig' // not included in our build
&& entry.name != 'gradle' // not included in our build
&& super.shouldRecurse(entry);
}
}
class RepositoryPkgDirectory extends RepositoryDirectory {
RepositoryPkgDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'when')
return new RepositoryPkgWhenDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryPkgWhenDirectory extends RepositoryDirectory {
RepositoryPkgWhenDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'example' // contains nothing that ends up in the binary executable
&& super.shouldRecurse(entry);
}
}
class RepositorySkiaLibWebPDirectory extends RepositoryDirectory {
RepositorySkiaLibWebPDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'webp')
return new RepositoryReachOutDirectory(this, entry, new Set<String>.from(const <String>['config.h']), 3);
return super.createSubdirectory(entry);
}
}
class RepositorySkiaLibSdlDirectory extends RepositoryDirectory {
RepositorySkiaLibSdlDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get isLicenseRootException => true;
}
class RepositorySkiaThirdPartyDirectory extends RepositoryGenericThirdPartyDirectory {
RepositorySkiaThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'giflib' // contains nothing that ends up in the binary executable
&& entry.name != 'freetype' // we use our own version
&& entry.name != 'freetype2' // we use our own version
&& entry.name != 'icu' // we use our own version
&& entry.name != 'libjpeg-turbo' // we use our own version
&& entry.name != 'libpng' // we use our own version
&& entry.name != 'lua' // not linked in
&& entry.name != 'yasm' // build tool (assembler)
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'ktx')
return new RepositoryReachOutDirectory(this, entry, new Set<String>.from(const <String>['ktx.h', 'ktx.cpp']), 2);
if (entry.name == 'libmicrohttpd')
return new RepositoryReachOutDirectory(this, entry, new Set<String>.from(const <String>['MHD_config.h']), 2);
if (entry.name == 'libwebp')
return new RepositorySkiaLibWebPDirectory(this, entry);
if (entry.name == 'libsdl')
return new RepositorySkiaLibSdlDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositorySkiaDirectory extends RepositoryDirectory {
RepositorySkiaDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'platform_tools' // contains nothing that ends up in the binary executable
&& entry.name != 'tools' // contains nothing that ends up in the binary executable
&& entry.name != 'resources' // contains nothing that ends up in the binary executable
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositorySkiaThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryVulkanDirectory extends RepositoryDirectory {
RepositoryVulkanDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'doc' // documentation
&& entry.name != 'out' // documentation
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'src')
return new RepositoryExcludeSubpathDirectory(this, entry, const <String>['spec']);
return super.createSubdirectory(entry);
}
}
class RepositoryRootThirdPartyDirectory extends RepositoryGenericThirdPartyDirectory {
RepositoryRootThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'appurify-python' // only used by tests
&& entry.name != 'benchmark' // only used by tests
&& entry.name != 'dart-sdk' // redundant with //engine/dart; https://github.com/flutter/flutter/issues/2618
&& entry.name != 'firebase' // only used by bots; https://github.com/flutter/flutter/issues/3722
&& entry.name != 'gyp' // build-time only
&& entry.name != 'jinja2' // build-time code generation
&& entry.name != 'junit' // only mentioned in build files, not used
&& entry.name != 'libxml' // dependency of the testing system that we don't actually use
&& entry.name != 'llvm-build' // only used by build
&& entry.name != 'markupsafe' // build-time only
&& entry.name != 'mockito' // only used by tests
&& entry.name != 'pymock' // presumably only used by tests
&& entry.name != 'robolectric' // testing framework for android
&& entry.name != 'yasm' // build-time dependency only
&& entry.name != 'binutils' // build-time dependency only
&& entry.name != 'instrumented_libraries' // unused according to chinmay
&& entry.name != 'android_tools' // excluded on advice
&& entry.name != 'googletest' // only used by tests
&& entry.name != 'skia' // treated as a separate component
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'android_platform')
return new RepositoryAndroidPlatformDirectory(this, entry);
if (entry.name == 'boringssl')
return new RepositoryBoringSSLDirectory(this, entry);
if (entry.name == 'catapult')
return new RepositoryCatapultDirectory(this, entry);
if (entry.name == 'dart')
return new RepositoryDartDirectory(this, entry);
if (entry.name == 'expat')
return new RepositoryExpatDirectory(this, entry);
if (entry.name == 'freetype-android')
throw '//third_party/freetype-android is no longer part of this client: remove it';
if (entry.name == 'freetype2')
return new RepositoryFreetypeDirectory(this, entry);
if (entry.name == 'harfbuzz')
return new RepositoryHarfbuzzDirectory(this, entry);
if (entry.name == 'icu')
return new RepositoryIcuDirectory(this, entry);
if (entry.name == 'jsr-305')
return new RepositoryJSR305Directory(this, entry);
if (entry.name == 'libjpeg')
return new RepositoryLibJpegDirectory(this, entry);
if (entry.name == 'libjpeg_turbo' || entry.name == 'libjpeg-turbo')
return new RepositoryLibJpegTurboDirectory(this, entry);
if (entry.name == 'libpng')
return new RepositoryLibPngDirectory(this, entry);
if (entry.name == 'libwebp')
return new RepositoryLibWebpDirectory(this, entry);
if (entry.name == 'pkg')
return new RepositoryPkgDirectory(this, entry);
if (entry.name == 'vulkan')
return new RepositoryVulkanDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryBoringSSLThirdPartyDirectory extends RepositoryDirectory {
RepositoryBoringSSLThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'android-cmake' // build-time only
&& super.shouldRecurse(entry);
}
}
class RepositoryBoringSSLSourceDirectory extends RepositoryDirectory {
RepositoryBoringSSLSourceDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
String get libraryName => 'boringssl';
@override
bool get isLicenseRoot => true;
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'fuzz' // testing tools, not shipped
&& super.shouldRecurse(entry);
}
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE')
return new RepositoryOpenSSLLicenseFile(this, entry);
return super.createFile(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryBoringSSLThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
/// The BoringSSL license file.
///
/// This license includes 23 lines of informational header text that are not
/// part of the copyright notices and can be skipped.
/// It also has a trailer that mentions licenses that are used during build
/// time of BoringSSL - those can be ignored as well since they don't apply
/// to code that is distributed.
class RepositoryOpenSSLLicenseFile extends RepositorySingleLicenseFile {
RepositoryOpenSSLLicenseFile(RepositoryDirectory parent, fs.TextFile io)
: super(parent, io,
new License.fromBodyAndType(
LineSplitter.split(io.readString())
.skip(23)
.takeWhile((String s) => !s.startsWith('BoringSSL uses the Chromium test infrastructure to run a continuous build,'))
.join('\n'),
LicenseType.openssl,
origin: io.fullName)) {
_verifyLicense(io);
}
static void _verifyLicense(fs.TextFile io) {
final String contents = io.readString();
if (!contents.contains('BoringSSL is a fork of OpenSSL. As such, large parts of it fall under OpenSSL'))
throw 'unexpected OpenSSL license file contents:\n----8<----\n$contents\n----<8----';
}
@override
License licenseOfType(LicenseType type) {
if (type == LicenseType.openssl)
return license;
return null;
}
}
class RepositoryBoringSSLDirectory extends RepositoryDirectory {
RepositoryBoringSSLDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'README')
return new RepositoryBlankLicenseFile(this, entry, 'This repository contains the files generated by boringssl for its build.');
return super.createFile(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'src')
return new RepositoryBoringSSLSourceDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryCatapultThirdPartyApiClientDirectory extends RepositoryDirectory {
RepositoryCatapultThirdPartyApiClientDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE')
return new RepositoryCatapultApiClientLicenseFile(this, entry);
return super.createFile(entry);
}
}
class RepositoryCatapultThirdPartyCoverageDirectory extends RepositoryDirectory {
RepositoryCatapultThirdPartyCoverageDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'NOTICE.txt')
return new RepositoryCatapultCoverageLicenseFile(this, entry);
return super.createFile(entry);
}
}
class RepositoryCatapultThirdPartyDirectory extends RepositoryDirectory {
RepositoryCatapultThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'apiclient')
return new RepositoryCatapultThirdPartyApiClientDirectory(this, entry);
if (entry.name == 'coverage')
return new RepositoryCatapultThirdPartyCoverageDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryCatapultDirectory extends RepositoryDirectory {
RepositoryCatapultDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryCatapultThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryDartRuntimeThirdPartyDirectory extends RepositoryGenericThirdPartyDirectory {
RepositoryDartRuntimeThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'd3' // Siva says "that is the charting library used by the binary size tool"
&& entry.name != 'binary_size' // not linked in either
&& super.shouldRecurse(entry);
}
}
class RepositoryDartThirdPartyDirectory extends RepositoryGenericThirdPartyDirectory {
RepositoryDartThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'drt_resources' // test materials
&& entry.name != 'firefox_jsshell' // testing tool for dart2js
&& entry.name != 'd8' // testing tool for dart2js
&& entry.name != 'pkg'
&& entry.name != 'pkg_tested'
&& entry.name != 'requirejs' // only used by DDC
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'boringssl')
return new RepositoryBoringSSLDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryDartRuntimeDirectory extends RepositoryDirectory {
RepositoryDartRuntimeDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryDartRuntimeThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryDartDirectory extends RepositoryDirectory {
RepositoryDartDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get isLicenseRoot => true;
@override
RepositoryFile createFile(fs.IoNode entry) {
if (entry.name == 'LICENSE')
return new RepositoryDartLicenseFile(this, entry);
return super.createFile(entry);
}
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'pkg' // packages that don't become part of the binary (e.g. the analyzer)
&& entry.name != 'tests' // only used by tests, obviously
&& entry.name != 'docs' // not shipped in binary
&& entry.name != 'build' // not shipped in binary
&& entry.name != 'tools' // not shipped in binary
&& entry.name != 'samples-dev' // not shipped in binary
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryDartThirdPartyDirectory(this, entry);
if (entry.name == 'runtime')
return new RepositoryDartRuntimeDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryFlutterDirectory extends RepositoryDirectory {
RepositoryFlutterDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
String get libraryName => 'engine';
@override
bool get isLicenseRoot => true;
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'testing'
&& entry.name != 'tools'
&& entry.name != 'docs'
&& entry.name != 'examples'
&& entry.name != 'build'
&& entry.name != 'ci'
&& entry.name != 'frontend_server'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'sky')
return new RepositoryExcludeSubpathDirectory(this, entry, const <String>['packages', 'sky_engine', 'LICENSE']); // that's the output of this script!
if (entry.name == 'third_party')
return new RepositoryFlutterThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryFlutterThirdPartyDirectory extends RepositoryDirectory {
RepositoryFlutterThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool get subdirectoriesAreLicenseRoots => true;
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'txt')
return new RepositoryFlutterTxtDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryFlutterTxtDirectory extends RepositoryDirectory {
RepositoryFlutterTxtDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryFlutterTxtThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryFlutterTxtThirdPartyDirectory extends RepositoryDirectory {
RepositoryFlutterTxtThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'fonts';
}
}
class RepositoryGarnetDirectory extends RepositoryDirectory {
RepositoryGarnetDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'bin'
&& entry.name != 'docs'
&& entry.name != 'drivers'
&& entry.name != 'examples'
&& entry.name != 'go'
&& entry.name != 'lib'
&& entry.name != 'packages'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'public')
return new RepositoryGarnetPublicDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryGarnetPublicDirectory extends RepositoryDirectory {
RepositoryGarnetPublicDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'dart-pkg'
&& entry.name != 'build'
&& entry.name != 'rust'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'lib')
return new RepositoryGarnetLibDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryGarnetLibDirectory extends RepositoryDirectory {
RepositoryGarnetLibDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'app'
&& entry.name != 'escher'
&& entry.name != 'url'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'fidl')
return new RepositoryGarnetFidlDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryGarnetFidlDirectory extends RepositoryDirectory {
RepositoryGarnetFidlDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'compiler'
&& entry.name != 'fuzz'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'public')
return new RepositoryGarnetPublicDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryTopazDirectory extends RepositoryDirectory {
RepositoryTopazDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'tools'
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'shell')
return new RepositoryTopazShellDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryTopazShellDirectory extends RepositoryDirectory {
RepositoryTopazShellDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'third_party')
return new RepositoryTopazShellThirdPartyDirectory(this, entry);
return super.createSubdirectory(entry);
}
}
class RepositoryTopazShellThirdPartyDirectory extends RepositoryDirectory {
RepositoryTopazShellThirdPartyDirectory(RepositoryDirectory parent, fs.Directory io) : super(parent, io);
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'QR-Code-generator'
&& super.shouldRecurse(entry);
}
}
class RepositoryRoot extends RepositoryDirectory {
RepositoryRoot(fs.Directory io) : super(null, io);
@override
String get libraryName {
assert(false);
return 'engine';
}
@override
bool get isLicenseRoot => true;
@override
bool get subdirectoriesAreLicenseRoots => true;
@override
bool shouldRecurse(fs.IoNode entry) {
return entry.name != 'testing' // only used by tests
&& entry.name != 'build' // only used by build
&& entry.name != 'buildtools' // only used by build
&& entry.name != 'ios_tools' // only used by build
&& entry.name != 'tools' // not distributed in binary
&& entry.name != 'out' // output of build
&& super.shouldRecurse(entry);
}
@override
RepositoryDirectory createSubdirectory(fs.Directory entry) {
if (entry.name == 'base')
throw '//base is no longer part of this client: remove it';
if (entry.name == 'third_party')
return new RepositoryRootThirdPartyDirectory(this, entry);
if (entry.name == 'flutter')
return new RepositoryFlutterDirectory(this, entry);
if (entry.name == 'garnet')
return new RepositoryGarnetDirectory(this, entry);
if (entry.name == 'topaz')
return new RepositoryTopazDirectory(this, entry);
return super.createSubdirectory(entry);
}
@override
List<RepositoryDirectory> get virtualSubdirectories {
// Skia is updated more frequently than other third party libraries and
// is therefore represented as a separate top-level component.
fs.Directory thirdPartyNode = io.walk.firstWhere((fs.IoNode node) => node.name == 'third_party');
fs.IoNode skiaNode = thirdPartyNode.walk.firstWhere((fs.IoNode node) => node.name == 'skia');
return <RepositoryDirectory>[new RepositorySkiaDirectory(this, skiaNode)];
}
}
class Progress {
Progress(this.max) {
// This may happen when a git client contains left-over empty component
// directories after DEPS file changes.
if (max <= 0)
throw new ArgumentError('Progress.max must be > 0 but was: $max');
}
final int max;
int get withLicense => _withLicense;
int _withLicense = 0;
int get withoutLicense => _withoutLicense;
int _withoutLicense = 0;
String get label => _label;
String _label = '';
int _lastLength = 0;
set label(String value) {
if (value.length > 50)
value = '.../' + value.substring(math.max(0, value.lastIndexOf('/', value.length - 45) + 1));
if (_label != value) {
_label = value;
update();
}
}
void advance(bool success) {
if (success)
_withLicense += 1;
else
_withoutLicense += 1;
update();
}
Stopwatch _lastUpdate;
void update({bool flush = false}) {
if (_lastUpdate == null || _lastUpdate.elapsedMilliseconds > 90 || flush) {
_lastUpdate ??= new Stopwatch();
final String line = toString();
system.stderr.write('\r$line');
if (_lastLength > line.length)
system.stderr.write(' ' * (_lastLength - line.length));
_lastLength = line.length;
_lastUpdate.reset();
_lastUpdate.start();
}
}
void flush() => update(flush: true);
bool get hadErrors => _withoutLicense > 0;
@override
String toString() {
int percent = (100.0 * (_withLicense + _withoutLicense) / max).round();
return '${(_withLicense + _withoutLicense).toString().padLeft(10)} of $max ${'' * (percent ~/ 10)}${'' * (10 - (percent ~/ 10))} $percent% ($_withoutLicense missing licenses) $label';
}
}
// MAIN
Future<Null> main(List<String> arguments) async {
final ArgParser parser = new ArgParser()
..addOption('src', help: 'The root of the engine source')
..addOption('out', help: 'The directory where output is written')
..addOption('golden', help: 'The directory containing golden results')
..addFlag('release', help: 'Print output in the format used for product releases');
ArgResults argResults = parser.parse(arguments);
bool releaseMode = argResults['release'];
if (argResults['src'] == null) {
print('Flutter license script: Must provide --src directory');
print(parser.usage);
system.exit(1);
}
if (!releaseMode) {
if (argResults['out'] == null || argResults['golden'] == null) {
print('Flutter license script: Must provide --out and --golden directories in non-release mode');
print(parser.usage);
system.exit(1);
}
if (!system.FileSystemEntity.isDirectorySync(argResults['golden'])) {
print('Flutter license script: Golden directory does not exist');
print(parser.usage);
system.exit(1);
}
system.Directory out = new system.Directory(argResults['out']);
if (!out.existsSync())
out.createSync(recursive: true);
}
try {
system.stderr.writeln('Finding files...');
fs.FileSystemDirectory rootDirectory = new fs.FileSystemDirectory.fromPath(argResults['src']);
final RepositoryDirectory root = new RepositoryRoot(rootDirectory);
if (releaseMode) {
system.stderr.writeln('Collecting licenses...');
Progress progress = new Progress(root.fileCount);
List<License> licenses = new Set<License>.from(root.getLicenses(progress).toList()).toList();
if (progress.hadErrors)
throw 'Had failures while collecting licenses.';
progress.label = 'Dumping results...';
progress.flush();
List<String> output = licenses
.where((License license) => license.isUsed)
.map((License license) => license.toStringFormal())
.where((String text) => text != null)
.toList();
output.sort();
print(output.join('\n${"-" * 80}\n'));
progress.label = 'Done.';
progress.flush();
system.stderr.writeln('');
} else {
RegExp signaturePattern = new RegExp(r'Signature: (\w+)');
bool isFirstComponent = true;
for (RepositoryDirectory component in root.subdirectories) {
system.stderr.writeln('Collecting licenses for ${component.io.name}');
String signature;
if (component.io.name == 'flutter') {
// Always run the full license check on the flutter tree. This tree is
// relatively small but changes frequently in ways that do not affect
// the license output, and we don't want to require updates to the golden
// signature for those changes.
signature = null;
} else {
signature = await component.signature;
}
// Check whether the golden file matches the signature of the current contents
// of this directory.
try {
system.File goldenFile = new system.File(
path.join(argResults['golden'], 'licenses_${component.io.name}'));
String goldenSignature = await goldenFile.openRead()
.transform(utf8.decoder).transform(new LineSplitter()).first;
Match goldenMatch = signaturePattern.matchAsPrefix(goldenSignature);
if (goldenMatch != null && goldenMatch.group(1) == signature) {
system.stderr.writeln(' Skipping this component - no change in signature');
continue;
}
} on system.FileSystemException {
system.stderr.writeln(' Failed to read signature file, scanning directory.');
}
Progress progress = new Progress(component.fileCount);
system.File outFile = new system.File(
path.join(argResults['out'], 'licenses_${component.name}'));
system.IOSink sink = outFile.openWrite();
if (signature != null)
sink.writeln('Signature: $signature\n');
RepositoryDirectory componentRoot;
if (isFirstComponent) {
// For the first component, we can use the results of the initial
// repository crawl.
isFirstComponent = false;
componentRoot = component;
} else {
// For other components, we need a clean repository that does not
// contain any state left over from previous components.
clearLicenseRegistry();
componentRoot = new RepositoryRoot(rootDirectory).subdirectories.firstWhere(
(RepositoryDirectory dir) => dir.name == component.name
);
}
List<License> licenses = new Set<License>.from(
componentRoot.getLicenses(progress).toList()).toList();
sink.writeln('UNUSED LICENSES:\n');
List<String> unusedLicenses = licenses
.where((License license) => !license.isUsed)
.map((License license) => license.toString())
.toList();
unusedLicenses.sort();
sink.writeln(unusedLicenses.join('\n\n'));
sink.writeln('~' * 80);
sink.writeln('USED LICENSES:\n');
List<License> usedLicenses = licenses.where((License license) => license.isUsed).toList();
List<String> output = usedLicenses.map((License license) => license.toString()).toList();
output.sort();
sink.writeln(output.join('\n\n'));
sink.writeln('Total license count: ${licenses.length}');
await sink.close();
progress.label = 'Done.';
progress.flush();
system.stderr.writeln('');
}
}
} catch (e, stack) {
system.stderr.writeln('failure: $e\n$stack');
system.stderr.writeln('aborted.');
system.exit(1);
}
}
// Sanity checks:
//
// The following substrings shouldn't be in the output:
// Version: MPL 1.1/GPL 2.0/LGPL 2.1
// The contents of this file are subject to the Mozilla Public License Version
// You should have received a copy of the GNU
// BoringSSL is a fork of OpenSSL
// Contents of this folder are ported from
// https://github.com/w3c/web-platform-tests/tree/master/selectors-api
// It is based on commit
// The original code is covered by the dual-licensing approach described in:
// http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html
// must choose