Commit a194e593 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Support multilicense LICENSE files generically. (#5310)

Also, add a "flutter build flx --report-licensed-packages" option for
when you need to get the list of the packages affected by licenses.
parent 8d5c2540
......@@ -243,8 +243,14 @@ class LicenseEntryWithLineBreaks extends LicenseEntry {
/// The flutter tool will automatically collect the contents of all the LICENSE
/// files found at the root of each package into a single LICENSE file in the
/// default asset bundle. Each license in that file is separated from the next
/// by a line of eighty hyphens (`-`). The `services` package registers a
/// license collector that splits that file and adds each entry to the registry.
/// by a line of eighty hyphens (`-`), and begins with a list of package names
/// that the license applies to, one to a line, separated from the next by a
/// blank line. The `services` package registers a license collector that splits
/// that file and adds each entry to the registry.
///
/// The LICENSE files in each package can either consist of a single license, or
/// can be in the format described above. In the latter case, each component
/// license and list of package names is merged independently.
class LicenseRegistry {
LicenseRegistry._();
......
......@@ -65,9 +65,12 @@ class AssetBundle {
return stat.modified.isAfter(_lastBuildTimestamp);
}
Future<int> build({String manifestPath: defaultManifestPath,
String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true}) async {
Future<int> build({
String manifestPath: defaultManifestPath,
String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
Object manifest = _loadFlutterYamlManifest(manifestPath);
if (manifest == null) {
// No manifest file found for this application.
......@@ -134,16 +137,16 @@ class AssetBundle {
if (fontManifest != null)
entries.add(fontManifest);
// TODO(ianh): Only do the following line if we've changed packages
entries.add(await _obtainLicenses(packageMap, assetBasePath));
// TODO(ianh): Only do the following line if we've changed packages or if our LICENSE file changed
entries.add(await _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages));
return 0;
}
void dump() {
print('Dumping AssetBundle:');
printTrace('Dumping AssetBundle:');
for (AssetBundleEntry entry in entries) {
print(entry.archivePath);
printTrace(entry.archivePath);
}
}
}
......@@ -219,15 +222,24 @@ final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
/// Returns a AssetBundleEntry representing the license file.
Future<AssetBundleEntry> _obtainLicenses(
PackageMap packageMap,
String assetBase
String assetBase,
{ bool reportPackages }
) async {
// Read the LICENSE file from each package in the .packages file,
// splitting each one into each component license (so that we can
// de-dupe if possible).
// For the sky_engine package we assume each license starts with
// package names. For the other packages we assume that each
// license is raw.
// Read the LICENSE file from each package in the .packages file, splitting
// each one into each component license (so that we can de-dupe if possible).
//
// Individual licenses inside each LICENSE file should be separated by 80
// hyphens on their own on a line.
//
// If a LICENSE file contains more than one component license, then each
// component license must start with the names of the packages to which the
// component license applies, with each package name on its own line, and the
// list of package names separated from the actual license text by a blank
// line. (The packages need not match the names of the pub package. For
// example, a package might itself contain code from multiple third-party
// sources, and might need to include a license for each one.)
final Map<String, Set<String>> packageLicenses = <String, Set<String>>{};
final Set<String> allPackages = new Set<String>();
for (String packageName in packageMap.map.keys) {
final Uri package = packageMap.map[packageName];
if (package != null && package.scheme == 'file') {
......@@ -238,7 +250,7 @@ Future<AssetBundleEntry> _obtainLicenses(
for (String rawLicense in rawLicenses) {
List<String> packageNames;
String licenseText;
if (packageName == 'sky_engine') {
if (rawLicenses.length > 1) {
final int split = rawLicense.indexOf('\n\n');
if (split >= 0) {
packageNames = rawLicense.substring(0, split).split('\n');
......@@ -251,11 +263,18 @@ Future<AssetBundleEntry> _obtainLicenses(
}
packageLicenses.putIfAbsent(licenseText, () => new Set<String>())
..addAll(packageNames);
allPackages.addAll(packageNames);
}
}
}
}
if (reportPackages) {
final List<String> allPackagesList = allPackages.toList()..sort();
printStatus('Licenses were found for the following packages:');
printStatus(allPackagesList.join(', '));
}
final List<String> combinedLicensesList = packageLicenses.keys.map(
(String license) {
List<String> packageNames = packageLicenses[license].toList()
......
......@@ -22,6 +22,7 @@ class BuildFlxCommand extends BuildSubCommand {
argParser.addOption('depfile', defaultsTo: defaultDepfilePath);
argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath);
argParser.addFlag('include-roboto-fonts', defaultsTo: true);
argParser.addFlag('report-licensed-packages', help: 'Whether to report the names of all the packages that are included in the application\'s LICENSE file.', defaultsTo: false);
usesPubOption();
}
......@@ -49,7 +50,8 @@ class BuildFlxCommand extends BuildSubCommand {
privateKeyPath: argResults['private-key'],
workingDirPath: argResults['working-dir'],
precompiledSnapshot: argResults['precompiled'],
includeRobotoFonts: argResults['include-roboto-fonts']
includeRobotoFonts: argResults['include-roboto-fonts'],
reportLicensedPackages: argResults['report-licensed-packages']
).then((int result) {
if (result == 0)
printStatus('Built $outputPath.');
......
......@@ -77,7 +77,8 @@ Future<int> build({
String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath,
bool precompiledSnapshot: false,
bool includeRobotoFonts: true
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
File snapshotFile;
......@@ -105,7 +106,8 @@ Future<int> build({
outputPath: outputPath,
privateKeyPath: privateKeyPath,
workingDirPath: workingDirPath,
includeRobotoFonts: includeRobotoFonts
includeRobotoFonts: includeRobotoFonts,
reportLicensedPackages: reportLicensedPackages
);
}
......@@ -115,15 +117,19 @@ Future<int> assemble({
String outputPath: defaultFlxOutputPath,
String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
printTrace('Building $outputPath');
// Build the asset bundle.
AssetBundle assetBundle = new AssetBundle();
int result = await assetBundle.build(manifestPath: manifestPath,
workingDirPath: workingDirPath,
includeRobotoFonts: includeRobotoFonts);
int result = await assetBundle.build(
manifestPath: manifestPath,
workingDirPath: workingDirPath,
includeRobotoFonts: includeRobotoFonts,
reportLicensedPackages: reportLicensedPackages
);
if (result != 0) {
return result;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment