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 { ...@@ -243,8 +243,14 @@ class LicenseEntryWithLineBreaks extends LicenseEntry {
/// The flutter tool will automatically collect the contents of all the LICENSE /// 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 /// 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 /// default asset bundle. Each license in that file is separated from the next
/// by a line of eighty hyphens (`-`). The `services` package registers a /// by a line of eighty hyphens (`-`), and begins with a list of package names
/// license collector that splits that file and adds each entry to the registry. /// 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 { class LicenseRegistry {
LicenseRegistry._(); LicenseRegistry._();
......
...@@ -65,9 +65,12 @@ class AssetBundle { ...@@ -65,9 +65,12 @@ class AssetBundle {
return stat.modified.isAfter(_lastBuildTimestamp); return stat.modified.isAfter(_lastBuildTimestamp);
} }
Future<int> build({String manifestPath: defaultManifestPath, Future<int> build({
String workingDirPath: defaultWorkingDirPath, String manifestPath: defaultManifestPath,
bool includeRobotoFonts: true}) async { String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async {
Object manifest = _loadFlutterYamlManifest(manifestPath); Object manifest = _loadFlutterYamlManifest(manifestPath);
if (manifest == null) { if (manifest == null) {
// No manifest file found for this application. // No manifest file found for this application.
...@@ -134,16 +137,16 @@ class AssetBundle { ...@@ -134,16 +137,16 @@ class AssetBundle {
if (fontManifest != null) if (fontManifest != null)
entries.add(fontManifest); entries.add(fontManifest);
// TODO(ianh): Only do the following line if we've changed packages // TODO(ianh): Only do the following line if we've changed packages or if our LICENSE file changed
entries.add(await _obtainLicenses(packageMap, assetBasePath)); entries.add(await _obtainLicenses(packageMap, assetBasePath, reportPackages: reportLicensedPackages));
return 0; return 0;
} }
void dump() { void dump() {
print('Dumping AssetBundle:'); printTrace('Dumping AssetBundle:');
for (AssetBundleEntry entry in entries) { for (AssetBundleEntry entry in entries) {
print(entry.archivePath); printTrace(entry.archivePath);
} }
} }
} }
...@@ -219,15 +222,24 @@ final String _licenseSeparator = '\n' + ('-' * 80) + '\n'; ...@@ -219,15 +222,24 @@ final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
/// Returns a AssetBundleEntry representing the license file. /// Returns a AssetBundleEntry representing the license file.
Future<AssetBundleEntry> _obtainLicenses( Future<AssetBundleEntry> _obtainLicenses(
PackageMap packageMap, PackageMap packageMap,
String assetBase String assetBase,
{ bool reportPackages }
) async { ) async {
// Read the LICENSE file from each package in the .packages file, // Read the LICENSE file from each package in the .packages file, splitting
// splitting each one into each component license (so that we can // each one into each component license (so that we can de-dupe if possible).
// de-dupe if possible). //
// For the sky_engine package we assume each license starts with // Individual licenses inside each LICENSE file should be separated by 80
// package names. For the other packages we assume that each // hyphens on their own on a line.
// license is raw. //
// 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 Map<String, Set<String>> packageLicenses = <String, Set<String>>{};
final Set<String> allPackages = new Set<String>();
for (String packageName in packageMap.map.keys) { for (String packageName in packageMap.map.keys) {
final Uri package = packageMap.map[packageName]; final Uri package = packageMap.map[packageName];
if (package != null && package.scheme == 'file') { if (package != null && package.scheme == 'file') {
...@@ -238,7 +250,7 @@ Future<AssetBundleEntry> _obtainLicenses( ...@@ -238,7 +250,7 @@ Future<AssetBundleEntry> _obtainLicenses(
for (String rawLicense in rawLicenses) { for (String rawLicense in rawLicenses) {
List<String> packageNames; List<String> packageNames;
String licenseText; String licenseText;
if (packageName == 'sky_engine') { if (rawLicenses.length > 1) {
final int split = rawLicense.indexOf('\n\n'); final int split = rawLicense.indexOf('\n\n');
if (split >= 0) { if (split >= 0) {
packageNames = rawLicense.substring(0, split).split('\n'); packageNames = rawLicense.substring(0, split).split('\n');
...@@ -251,11 +263,18 @@ Future<AssetBundleEntry> _obtainLicenses( ...@@ -251,11 +263,18 @@ Future<AssetBundleEntry> _obtainLicenses(
} }
packageLicenses.putIfAbsent(licenseText, () => new Set<String>()) packageLicenses.putIfAbsent(licenseText, () => new Set<String>())
..addAll(packageNames); ..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( final List<String> combinedLicensesList = packageLicenses.keys.map(
(String license) { (String license) {
List<String> packageNames = packageLicenses[license].toList() List<String> packageNames = packageLicenses[license].toList()
......
...@@ -22,6 +22,7 @@ class BuildFlxCommand extends BuildSubCommand { ...@@ -22,6 +22,7 @@ class BuildFlxCommand extends BuildSubCommand {
argParser.addOption('depfile', defaultsTo: defaultDepfilePath); argParser.addOption('depfile', defaultsTo: defaultDepfilePath);
argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath); argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath);
argParser.addFlag('include-roboto-fonts', defaultsTo: true); 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(); usesPubOption();
} }
...@@ -49,7 +50,8 @@ class BuildFlxCommand extends BuildSubCommand { ...@@ -49,7 +50,8 @@ class BuildFlxCommand extends BuildSubCommand {
privateKeyPath: argResults['private-key'], privateKeyPath: argResults['private-key'],
workingDirPath: argResults['working-dir'], workingDirPath: argResults['working-dir'],
precompiledSnapshot: argResults['precompiled'], precompiledSnapshot: argResults['precompiled'],
includeRobotoFonts: argResults['include-roboto-fonts'] includeRobotoFonts: argResults['include-roboto-fonts'],
reportLicensedPackages: argResults['report-licensed-packages']
).then((int result) { ).then((int result) {
if (result == 0) if (result == 0)
printStatus('Built $outputPath.'); printStatus('Built $outputPath.');
......
...@@ -77,7 +77,8 @@ Future<int> build({ ...@@ -77,7 +77,8 @@ Future<int> build({
String privateKeyPath: defaultPrivateKeyPath, String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath, String workingDirPath: defaultWorkingDirPath,
bool precompiledSnapshot: false, bool precompiledSnapshot: false,
bool includeRobotoFonts: true bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async { }) async {
File snapshotFile; File snapshotFile;
...@@ -105,7 +106,8 @@ Future<int> build({ ...@@ -105,7 +106,8 @@ Future<int> build({
outputPath: outputPath, outputPath: outputPath,
privateKeyPath: privateKeyPath, privateKeyPath: privateKeyPath,
workingDirPath: workingDirPath, workingDirPath: workingDirPath,
includeRobotoFonts: includeRobotoFonts includeRobotoFonts: includeRobotoFonts,
reportLicensedPackages: reportLicensedPackages
); );
} }
...@@ -115,15 +117,19 @@ Future<int> assemble({ ...@@ -115,15 +117,19 @@ Future<int> assemble({
String outputPath: defaultFlxOutputPath, String outputPath: defaultFlxOutputPath,
String privateKeyPath: defaultPrivateKeyPath, String privateKeyPath: defaultPrivateKeyPath,
String workingDirPath: defaultWorkingDirPath, String workingDirPath: defaultWorkingDirPath,
bool includeRobotoFonts: true bool includeRobotoFonts: true,
bool reportLicensedPackages: false
}) async { }) async {
printTrace('Building $outputPath'); printTrace('Building $outputPath');
// Build the asset bundle. // Build the asset bundle.
AssetBundle assetBundle = new AssetBundle(); AssetBundle assetBundle = new AssetBundle();
int result = await assetBundle.build(manifestPath: manifestPath, int result = await assetBundle.build(
workingDirPath: workingDirPath, manifestPath: manifestPath,
includeRobotoFonts: includeRobotoFonts); workingDirPath: workingDirPath,
includeRobotoFonts: includeRobotoFonts,
reportLicensedPackages: reportLicensedPackages
);
if (result != 0) { if (result != 0) {
return result; 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