Chromium Code Reviews| 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 * Temporary deploy command used to create a version of the app that can be | 6 * 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 | 7 * compiled with dart2js and deployed. Following pub layout conventions, this |
| 8 * script will treat any HTML file under a package 'web/' and 'test/' | 8 * script will treat any HTML file under a package 'web/' and 'test/' |
| 9 * directories as entry points. | 9 * directories as entry points. |
| 10 * | 10 * |
| 11 * From an application package you can run deploy by creating a small program | 11 * From an application package you can run deploy by creating a small program |
| 12 * as follows: | 12 * as follows: |
| 13 * | 13 * |
| 14 * import "package:polymer/deploy.dart" as deploy; | 14 * import "package:polymer/deploy.dart" as deploy; |
| 15 * main() => deploy.main(); | 15 * main() => deploy.main(); |
| 16 * | 16 * |
| 17 * This library should go away once `pub deploy` can be configured to run | 17 * This library should go away once `pub deploy` can be configured to run |
| 18 * barback transformers. | 18 * barback transformers. |
| 19 * | |
| 20 * **Note**: If you already have a `build.dart` in your application, we | |
|
Jennifer Messerly
2013/09/10 04:16:51
suggestion: move up front
Siggi Cherem (dart-lang)
2013/09/11 01:45:26
Done.
| |
| 21 * recommend to use the `build_helper.dart` library instead. | |
| 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'; | |
| 27 import 'package:path/path.dart' as path; | 28 import 'package:path/path.dart' as path; |
| 28 import 'package:polymer/src/transform.dart' show phases; | 29 import 'package:polymer/src/transform.dart'; |
| 29 import 'package:stack_trace/stack_trace.dart'; | 30 import 'package:polymer/src/barback_helper.dart'; |
| 30 import 'package:yaml/yaml.dart'; | |
| 31 import 'package:args/args.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) { | |
|
Siggi Cherem (dart-lang)
2013/09/10 03:40:57
most code below here basically moved to lib/src/ba
| |
| 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 where to generate files.', |
|
Jennifer Messerly
2013/09/10 04:16:51
"directory to generate files" ?
or "directory to g
Siggi Cherem (dart-lang)
2013/09/11 01:45:26
Done.
| |
| 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/ ' |
|
Jennifer Messerly
2013/09/10 04:16:51
is the --package-root actually required? even for
Siggi Cherem (dart-lang)
2013/09/11 01:45:26
Yeah, this is because the 'package-root' is resolv
Jennifer Messerly
2013/09/11 20:07:54
strange :| perhaps we should just tell people to r
| |
| 115 'package:polymer/deploy.dart [options]'); | |
| 299 print(parser.getUsage()); | 116 print(parser.getUsage()); |
| 300 } | 117 } |
| OLD | NEW |