| OLD | NEW | 
|---|
| 1 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a | 
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 /** | 5 /** | 
|  | 6  * **Note**: If you already have a `build.dart` in your application, we | 
|  | 7  * recommend to use the `package:polymer/builder.dart` library instead. | 
|  | 8 | 
| 6  * Temporary deploy command used to create a version of the app that can be | 9  * Temporary deploy command used to create a version of the app that can be | 
| 7  * compiled with dart2js and deployed. Following pub layout conventions, this | 10  * compiled with dart2js and deployed. Following pub layout conventions, this | 
| 8  * script will treat any HTML file under a package 'web/' and 'test/' | 11  * script will treat any HTML file under a package 'web/' and 'test/' | 
| 9  * directories as entry points. | 12  * directories as entry points. | 
| 10  * | 13  * | 
| 11  * From an application package you can run deploy by creating a small program | 14  * From an application package you can run deploy by creating a small program | 
| 12  * as follows: | 15  * as follows: | 
| 13  * | 16  * | 
| 14  *    import "package:polymer/deploy.dart" as deploy; | 17  *    import "package:polymer/deploy.dart" as deploy; | 
| 15  *    main() => deploy.main(); | 18  *    main() => deploy.main(); | 
| 16  * | 19  * | 
| 17  * This library should go away once `pub deploy` can be configured to run | 20  * This library should go away once `pub deploy` can be configured to run | 
| 18  * barback transformers. | 21  * barback transformers. | 
| 19  */ | 22  */ | 
| 20 library polymer.deploy; | 23 library polymer.deploy; | 
| 21 | 24 | 
| 22 import 'dart:async'; | 25 import 'dart:async'; | 
| 23 import 'dart:convert'; |  | 
| 24 import 'dart:io'; | 26 import 'dart:io'; | 
| 25 | 27 | 
| 26 import 'package:barback/barback.dart'; | 28 import 'package:args/args.dart'; | 
| 27 import 'package:path/path.dart' as path; | 29 import 'package:path/path.dart' as path; | 
| 28 import 'package:polymer/src/transform.dart' show phases; | 30 import 'package:polymer/src/barback_runner.dart'; | 
| 29 import 'package:stack_trace/stack_trace.dart'; | 31 import 'package:polymer/src/transform.dart'; | 
| 30 import 'package:yaml/yaml.dart'; |  | 
| 31 import 'package:args/args.dart'; |  | 
| 32 | 32 | 
| 33 main() { | 33 main() { | 
| 34   var args = _parseArgs(new Options().arguments); | 34   var args = _parseArgs(new Options().arguments); | 
| 35   if (args == null) return; | 35   if (args == null) exit(1); | 
| 36 | 36 | 
| 37   var test = args['test']; | 37   var test = args['test']; | 
| 38   if (test != null) { | 38   var outDir = args['out']; | 
| 39     _initForTest(test); | 39   var options = (test == null) | 
| 40   } | 40     ? new BarbackOptions(createDeployPhases(new TransformOptions()), outDir) | 
|  | 41     : _createTestOptions(test, outDir); | 
|  | 42   if (options == null) exit(1); | 
| 41 | 43 | 
| 42   print('polymer/deploy.dart: creating a deploy target for "$_currentPackage"'); | 44   print('polymer/deploy.dart: creating a deploy target for ' | 
| 43   var outDir = args['out']; | 45       '"${options.currentPackage}"'); | 
| 44   _run(outDir, test != null).then( | 46 | 
| 45       (_) => print('Done! All files written to "$outDir"')); | 47   runBarback(options) | 
|  | 48       .then((_) => print('Done! All files written to "$outDir"')) | 
|  | 49       .catchError(_reportErrorAndExit); | 
| 46 } | 50 } | 
| 47 | 51 | 
| 48 // TODO(jmesserly): the current deploy/barback architecture is very unfriendly | 52 BarbackOptions _createTestOptions(String testFile, String outDir) { | 
| 49 // to deploying a single test. We need to fix it somehow but it isn't clear yet. |  | 
| 50 void _initForTest(String testFile) { |  | 
| 51   var testDir = path.normalize(path.dirname(testFile)); | 53   var testDir = path.normalize(path.dirname(testFile)); | 
| 52 | 54 | 
| 53   // A test must be allowed to import things in the package. | 55   // A test must be allowed to import things in the package. | 
| 54   // So we must find its package root, given the entry point. We can do this | 56   // So we must find its package root, given the entry point. We can do this | 
| 55   // by walking up to find pubspec.yaml. | 57   // by walking up to find pubspec.yaml. | 
| 56   var pubspecDir = _findDirWithFile(path.absolute(testDir), 'pubspec.yaml'); | 58   var pubspecDir = _findDirWithFile(path.absolute(testDir), 'pubspec.yaml'); | 
| 57   if (pubspecDir == null) { | 59   if (pubspecDir == null) { | 
| 58     print('error: pubspec.yaml file not found, please run this script from ' | 60     print('error: pubspec.yaml file not found, please run this script from ' | 
| 59         'your package root directory or a subdirectory.'); | 61         'your package root directory or a subdirectory.'); | 
| 60     exit(1); | 62     return null; | 
| 61   } | 63   } | 
| 62 | 64 | 
| 63   _currentPackage = '_test'; | 65   var phases = createDeployPhases(new TransformOptions( | 
| 64   _packageDirs = {'_test' : pubspecDir}; | 66         '_test', [path.relative(testFile, from: pubspecDir)])); | 
|  | 67   return new BarbackOptions(phases, outDir, | 
|  | 68       currentPackage: '_test', | 
|  | 69       packageDirs: {'_test' : pubspecDir}, | 
|  | 70       transformTests: true); | 
| 65 } | 71 } | 
| 66 | 72 | 
| 67 String _findDirWithFile(String dir, String filename) { | 73 String _findDirWithFile(String dir, String filename) { | 
| 68   while (!new File(path.join(dir, filename)).existsSync()) { | 74   while (!new File(path.join(dir, filename)).existsSync()) { | 
| 69     var parentDir = path.dirname(dir); | 75     var parentDir = path.dirname(dir); | 
| 70     // If we reached root and failed to find it, bail. | 76     // If we reached root and failed to find it, bail. | 
| 71     if (parentDir == dir) return null; | 77     if (parentDir == dir) return null; | 
| 72     dir = parentDir; | 78     dir = parentDir; | 
| 73   } | 79   } | 
| 74   return dir; | 80   return dir; | 
| 75 } | 81 } | 
| 76 | 82 | 
| 77 /** | 83 void _reportErrorAndExit(e) { | 
| 78  * API exposed for testing purposes. Runs this deploy command but prentend that | 84   var trace = getAttachedStackTrace(e); | 
| 79  * the sources under [webDir] belong to package 'test'. | 85   print('Uncaught error: $e'); | 
| 80  */ | 86   if (trace != null) print(trace); | 
| 81 Future runForTest(String webDir, String outDir) { | 87   exit(1); | 
| 82   _currentPackage = 'test'; |  | 
| 83 |  | 
| 84   // associate package dirs with their location in the repo: |  | 
| 85   _packageDirs = {'test' : '.'}; |  | 
| 86   _addPackages('..'); |  | 
| 87   _addPackages('../third_party'); |  | 
| 88   _addPackages('../../third_party/pkg'); |  | 
| 89   return _run(webDir, outDir); |  | 
| 90 } | 88 } | 
| 91 | 89 | 
| 92 _addPackages(String dir) { |  | 
| 93   for (var packageDir in new Directory(dir).listSync().map((d) => d.path)) { |  | 
| 94     _packageDirs[path.basename(packageDir)] = packageDir; |  | 
| 95   } |  | 
| 96 } |  | 
| 97 |  | 
| 98 Future _run(String outDir, bool includeTests) { |  | 
| 99   var barback = new Barback(new _PolymerDeployProvider()); |  | 
| 100   _initializeBarback(barback, includeTests); |  | 
| 101   _attachListeners(barback); |  | 
| 102   return _emitAllFiles(barback, 'web', outDir).then( |  | 
| 103       (_) => includeTests ? _emitAllFiles(barback, 'test', outDir) : null); |  | 
| 104 } |  | 
| 105 |  | 
| 106 /** Tell barback which transformers to use and which assets to process. */ |  | 
| 107 void _initializeBarback(Barback barback, bool includeTests) { |  | 
| 108   var assets = []; |  | 
| 109   void addAssets(String package, String subDir) { |  | 
| 110     for (var filepath in _listDir(package, subDir)) { |  | 
| 111       assets.add(new AssetId(package, filepath)); |  | 
| 112     } |  | 
| 113   } |  | 
| 114 |  | 
| 115   for (var package in _packageDirs.keys) { |  | 
| 116     // Do not process packages like 'polymer' where there is nothing to do. |  | 
| 117     if (_ignoredPackages.contains(package)) continue; |  | 
| 118     barback.updateTransformers(package, phases); |  | 
| 119 |  | 
| 120     // notify barback to process anything under 'lib' and 'asset' |  | 
| 121     addAssets(package, 'lib'); |  | 
| 122     addAssets(package, 'asset'); |  | 
| 123   } |  | 
| 124 |  | 
| 125   // In case of the current package, include also 'web'. |  | 
| 126   addAssets(_currentPackage, 'web'); |  | 
| 127   if (includeTests) addAssets(_currentPackage, 'test'); |  | 
| 128 |  | 
| 129   barback.updateSources(assets); |  | 
| 130 } |  | 
| 131 |  | 
| 132 /** Return the relative path of each file under [subDir] in a [package]. */ |  | 
| 133 Iterable<String> _listDir(String package, String subDir) { |  | 
| 134   var packageDir = _packageDirs[package]; |  | 
| 135   if (packageDir == null) return const []; |  | 
| 136   var dir = new Directory(path.join(packageDir, subDir)); |  | 
| 137   if (!dir.existsSync()) return const []; |  | 
| 138   return dir.listSync(recursive: true, followLinks: false) |  | 
| 139       .where((f) => f is File) |  | 
| 140       .map((f) => path.relative(f.path, from: packageDir)); |  | 
| 141 } |  | 
| 142 |  | 
| 143 /** Attach error listeners on [barback] so we can report errors. */ |  | 
| 144 void _attachListeners(Barback barback) { |  | 
| 145   // Listen for errors and results |  | 
| 146   barback.errors.listen((e) { |  | 
| 147     var trace = getAttachedStackTrace(e); |  | 
| 148     if (trace != null) { |  | 
| 149       print(Trace.format(trace)); |  | 
| 150     } |  | 
| 151     print('error running barback: $e'); |  | 
| 152     exit(1); |  | 
| 153   }); |  | 
| 154 |  | 
| 155   barback.results.listen((result) { |  | 
| 156     if (!result.succeeded) { |  | 
| 157       print("build failed with errors: ${result.errors}"); |  | 
| 158       exit(1); |  | 
| 159     } |  | 
| 160   }); |  | 
| 161 } |  | 
| 162 |  | 
| 163 /** Ensure [dirpath] exists. */ |  | 
| 164 void _ensureDir(var dirpath) { |  | 
| 165   new Directory(dirpath).createSync(recursive: true); |  | 
| 166 } |  | 
| 167 |  | 
| 168 /** |  | 
| 169  * Emits all outputs of [barback] and copies files that we didn't process (like |  | 
| 170  * polymer's libraries). |  | 
| 171  */ |  | 
| 172 Future _emitAllFiles(Barback barback, String webDir, String outDir) { |  | 
| 173   return barback.getAllAssets().then((assets) { |  | 
| 174     // Copy all the assets we transformed |  | 
| 175     var futures = []; |  | 
| 176     for (var asset in assets) { |  | 
| 177       var id = asset.id; |  | 
| 178       var filepath; |  | 
| 179       if (id.package == _currentPackage && id.path.startsWith('$webDir/')) { |  | 
| 180         filepath = path.join(outDir, id.path); |  | 
| 181       } else if (id.path.startsWith('lib/')) { |  | 
| 182         filepath = path.join(outDir, webDir, 'packages', id.package, |  | 
| 183             id.path.substring(4)); |  | 
| 184       } else { |  | 
| 185         // TODO(sigmund): do something about other assets? |  | 
| 186         continue; |  | 
| 187       } |  | 
| 188 |  | 
| 189       _ensureDir(path.dirname(filepath)); |  | 
| 190       var writer = new File(filepath).openWrite(); |  | 
| 191       futures.add(writer.addStream(asset.read()).then((_) => writer.close())); |  | 
| 192     } |  | 
| 193     return Future.wait(futures); |  | 
| 194   }).then((_) { |  | 
| 195     // Copy also all the files we didn't process |  | 
| 196     var futures = []; |  | 
| 197     for (var package in _ignoredPackages) { |  | 
| 198       for (var relpath in _listDir(package, 'lib')) { |  | 
| 199         var inpath = path.join(_packageDirs[package], relpath); |  | 
| 200         var outpath = path.join(outDir, webDir, 'packages', package, |  | 
| 201             relpath.substring(4)); |  | 
| 202         _ensureDir(path.dirname(outpath)); |  | 
| 203 |  | 
| 204         var writer = new File(outpath).openWrite(); |  | 
| 205         futures.add(writer.addStream(new File(inpath).openRead()) |  | 
| 206           .then((_) => writer.close())); |  | 
| 207       } |  | 
| 208     } |  | 
| 209     return Future.wait(futures); |  | 
| 210   }); |  | 
| 211 } |  | 
| 212 |  | 
| 213 /** A simple provider that reads files directly from the pub cache. */ |  | 
| 214 class _PolymerDeployProvider implements PackageProvider { |  | 
| 215 |  | 
| 216   Iterable<String> get packages => _packageDirs.keys; |  | 
| 217   _PolymerDeployProvider(); |  | 
| 218 |  | 
| 219   Future<Asset> getAsset(AssetId id) => |  | 
| 220       new Future.value(new Asset.fromPath(id, path.join( |  | 
| 221               _packageDirs[id.package], |  | 
| 222               // Assets always use the posix style paths |  | 
| 223               path.joinAll(path.posix.split(id.path))))); |  | 
| 224 } |  | 
| 225 |  | 
| 226 |  | 
| 227 /** The current package extracted from the pubspec.yaml file. */ |  | 
| 228 String _currentPackage = () { |  | 
| 229   var pubspec = new File('pubspec.yaml'); |  | 
| 230   if (!pubspec.existsSync()) { |  | 
| 231     print('error: pubspec.yaml file not found, please run this script from ' |  | 
| 232         'your package root directory.'); |  | 
| 233     return null; |  | 
| 234   } |  | 
| 235   return loadYaml(pubspec.readAsStringSync())['name']; |  | 
| 236 }(); |  | 
| 237 |  | 
| 238 /** |  | 
| 239  * Maps package names to the path in the file system where to find the sources |  | 
| 240  * of such package. This map will contain an entry for the current package and |  | 
| 241  * everything it depends on (extracted via `pub list-pacakge-dirs`). |  | 
| 242  */ |  | 
| 243 Map<String, String> _packageDirs = () { |  | 
| 244   var pub = path.join(path.dirname(new Options().executable), |  | 
| 245       Platform.isWindows ? 'pub.bat' : 'pub'); |  | 
| 246   var result = Process.runSync(pub, ['list-package-dirs']); |  | 
| 247   if (result.exitCode != 0) { |  | 
| 248     print("unexpected error invoking 'pub':"); |  | 
| 249     print(result.stdout); |  | 
| 250     print(result.stderr); |  | 
| 251     exit(result.exitCode); |  | 
| 252   } |  | 
| 253   var map = JSON.decode(result.stdout)["packages"]; |  | 
| 254   map.forEach((k, v) { map[k] = path.dirname(v); }); |  | 
| 255   map[_currentPackage] = '.'; |  | 
| 256   return map; |  | 
| 257 }(); |  | 
| 258 |  | 
| 259 /** |  | 
| 260  * Internal packages used by polymer which we can copy directly to the output |  | 
| 261  * folder without having to process them with barback. |  | 
| 262  */ |  | 
| 263 // TODO(sigmund): consider computing this list by recursively parsing |  | 
| 264 // pubspec.yaml files in the [_packageDirs]. |  | 
| 265 final Set<String> _ignoredPackages = |  | 
| 266     (const [ 'analyzer_experimental', 'args', 'barback', 'browser', 'csslib', |  | 
| 267              'custom_element', 'fancy_syntax', 'html5lib', 'html_import', 'js', |  | 
| 268              'logging', 'mdv', 'meta', 'mutation_observer', 'observe', 'path', |  | 
| 269              'polymer', 'polymer_expressions', 'serialization', 'shadow_dom', |  | 
| 270              'source_maps', 'stack_trace', 'unittest', |  | 
| 271              'unmodifiable_collection', 'yaml' |  | 
| 272            ]).toSet(); |  | 
| 273 |  | 
| 274 ArgResults _parseArgs(arguments) { | 90 ArgResults _parseArgs(arguments) { | 
| 275   var parser = new ArgParser() | 91   var parser = new ArgParser() | 
| 276       ..addFlag('help', abbr: 'h', help: 'Displays this help message.', | 92       ..addFlag('help', abbr: 'h', help: 'Displays this help message.', | 
| 277           defaultsTo: false, negatable: false) | 93           defaultsTo: false, negatable: false) | 
| 278       ..addOption('out', abbr: 'o', help: 'Directory where to generated files.', | 94       ..addOption('out', abbr: 'o', help: 'Directory to generate files into.', | 
| 279           defaultsTo: 'out') | 95           defaultsTo: 'out') | 
| 280       ..addOption('test', help: 'Deploy the test at the given path.\n' | 96       ..addOption('test', help: 'Deploy the test at the given path.\n' | 
| 281           'Note: currently this will deploy all tests in its directory,\n' | 97           'Note: currently this will deploy all tests in its directory,\n' | 
| 282           'but it will eventually deploy only the specified test.'); | 98           'but it will eventually deploy only the specified test.'); | 
| 283   try { | 99   try { | 
| 284     var results = parser.parse(arguments); | 100     var results = parser.parse(arguments); | 
| 285     if (results['help']) { | 101     if (results['help']) { | 
| 286       _showUsage(parser); | 102       _showUsage(parser); | 
| 287       return null; | 103       return null; | 
| 288     } | 104     } | 
| 289     return results; | 105     return results; | 
| 290   } on FormatException catch (e) { | 106   } on FormatException catch (e) { | 
| 291     print(e.message); | 107     print(e.message); | 
| 292     _showUsage(parser); | 108     _showUsage(parser); | 
| 293     return null; | 109     return null; | 
| 294   } | 110   } | 
| 295 } | 111 } | 
| 296 | 112 | 
| 297 _showUsage(parser) { | 113 _showUsage(parser) { | 
| 298   print('Usage: dart package:polymer/deploy.dart [options]'); | 114   print('Usage: dart --package-root=packages/ ' | 
|  | 115       'package:polymer/deploy.dart [options]'); | 
| 299   print(parser.getUsage()); | 116   print(parser.getUsage()); | 
| 300 } | 117 } | 
| OLD | NEW | 
|---|