// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library kernel.treeshaker_dump; import 'dart:io'; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/core_types.dart'; import 'package:kernel/kernel.dart'; import 'package:kernel/transformations/treeshaker.dart'; import 'package:args/args.dart'; import 'package:path/path.dart' as pathlib; import 'package:kernel/text/ast_to_text.dart'; ArgParser parser = new ArgParser(allowTrailingOptions: true) ..addFlag('used', help: 'Print used members', negatable: false) ..addFlag('unused', help: 'Print unused members', negatable: false) ..addFlag('instantiated', help: 'Print instantiated classes', negatable: false) ..addFlag('types', help: 'Print classes used as a type', negatable: false) ..addFlag('summary', help: 'Print short summary of tree shaking results', defaultsTo: true) ..addFlag('diff', help: 'Print textual output before and after tree shaking.\n' 'Files are written to FILE.before.txt and FILE.after.txt', negatable: false) ..addOption('output', help: 'The --diff files are written to the given directory instead of ' 'the working directory') ..addFlag('strong', help: 'Run the tree shaker in strong mode'); String usage = ''' Usage: treeshaker_dump [options] FILE.dill Runs tree shaking on the given component and prints information about the results. Example: treeshaker_dump --instantiated foo.dill Example: treeshaker_dump --diff foo.dill diff -cd foo.{before,after}.txt > diff.txt # open diff.txt in an editor Options: ${parser.usage} '''; main(List args) { if (args.isEmpty) { print(usage); exit(1); } ArgResults options = parser.parse(args); if (options.rest.length != 1) { print('Exactly one file should be given.'); exit(1); } String filename = options.rest.single; if (options['output'] != null && !options['diff']) { print('--output must be used with --diff'); exit(1); } bool strong = options['strong']; Component component = loadComponentFromBinary(filename); CoreTypes coreTypes = new CoreTypes(component); ClassHierarchy hierarchy = new ClassHierarchy(component); TreeShaker shaker = new TreeShaker(coreTypes, hierarchy, component, strongMode: strong); int totalClasses = 0; int totalInstantiationCandidates = 0; int totalMembers = 0; int usedClasses = 0; int instantiatedClasses = 0; int usedMembers = 0; void visitMember(Member member) { if (member.isAbstract) return; // Abstract members are not relevant. ++totalMembers; bool isUsed = shaker.isMemberBodyUsed(member); if (isUsed) { ++usedMembers; } if (isUsed && options['used'] || !isUsed && options['unused']) { String prefix = (options['used'] && options['unused']) ? (isUsed ? 'USED ' : 'UNUSED ') : ''; print('$prefix$member'); } } for (var library in component.libraries) { library.members.forEach(visitMember); for (Class classNode in library.classes) { ++totalClasses; if (shaker.isInstantiated(classNode)) { ++instantiatedClasses; ++totalInstantiationCandidates; } else if (!classNode.isAbstract && classNode.members.any((m) => m.isInstanceMember)) { ++totalInstantiationCandidates; } if (shaker.isHierarchyUsed(classNode)) { ++usedClasses; } classNode.members.forEach(visitMember); if (options['instantiated'] && shaker.isInstantiated(classNode)) { print(classNode); } if (options['types'] && shaker.isHierarchyUsed(classNode)) { print(classNode); } } } if (options['summary']) { print('Classes used: ${ratio(usedClasses, totalClasses)}'); print('Classes instantiated: ' '${ratio(instantiatedClasses, totalInstantiationCandidates)}'); print('Members used: ${ratio(usedMembers, totalMembers)}'); } if (options['diff']) { String name = pathlib.basenameWithoutExtension(filename); String outputDir = options['output'] ?? ''; String beforeFile = pathlib.join(outputDir, '$name.before.txt'); String afterFile = pathlib.join(outputDir, '$name.after.txt'); NameSystem names = new NameSystem(); StringBuffer before = new StringBuffer(); new Printer(before, syntheticNames: names).writeComponentFile(component); new File(beforeFile).writeAsStringSync('$before'); new TreeShaker(coreTypes, hierarchy, component, strongMode: strong) .transform(component); StringBuffer after = new StringBuffer(); new Printer(after, syntheticNames: names).writeComponentFile(component); new File(afterFile).writeAsStringSync('$after'); print('Text written to $beforeFile and $afterFile'); } } String ratio(num x, num total) { return '$x / $total (${percent(x, total)})'; } String percent(num x, num total) { return total == 0 ? '0%' : ((100 * x / total).toStringAsFixed(0) + '%'); }