import 'dart:convert'; import 'dart:io'; import 'package:build_runner_core/build_runner_core.dart'; import 'package:build_runner_core/src/asset/build_cache.dart'; import 'package:build_runner_core/src/asset_graph/graph.dart'; import 'package:build_runner_core/src/asset_graph/node.dart'; import 'package:path/path.dart' as p; Future main() async { final isReleaseEnv = Platform.environment['IS_RELEASE']; print('Is release build: $isReleaseEnv'); final isRelease = isReleaseEnv == 'true'; final output = Directory('deploy'); if (output.existsSync()) { output.deleteSync(recursive: true); } output.createSync(); await Future.wait([ _runBuildAndCopyFiles(output, isRelease), _createApiDocumentation(output), ]); } Future _waitForProcess(Process p, String name) async { Future forward(Stream> source, IOSink out) { return source .transform(utf8.decoder) .transform(const LineSplitter()) .forEach((line) => out.writeln('$name: $line')); } await Future.wait([ forward(p.stdout, stdout), forward(p.stderr, stderr), ]); await stdout.flush(); await stderr.flush(); } Future _runBuildAndCopyFiles(Directory output, bool isRelease) async { final buildArgs = [ 'run', 'build_runner', 'build', '--release', if (isRelease) '--config=deploy', ]; final build = await Process.start('dart', buildArgs); await _waitForProcess(build, 'build'); print('Copying generated sources into deploy/'); // Advanced build magic because --output creates weird files that we don't // want. final graph = await PackageGraph.forThisPackage(); final env = IOEnvironment(graph); final assets = AssetGraph.deserialize(await (await _findAssetGraph()).readAsBytes()); final reader = BuildCacheReader(env.reader, assets, graph.root.name); final idsToRead = assets.allNodes .where((node) => !_shouldSkipNode(node, graph)) .map((e) => e.id); for (final id in idsToRead) { final file = File(p.join('deploy', p.relative(id.path, from: 'web/'))); file.parent.createSync(recursive: true); await file.writeAsBytes(await reader.readAsBytes(id)); } } Future _createApiDocumentation(Directory output) async { final gitRevResult = await Process.run('git', ['rev-parse', 'HEAD'], stdoutEncoding: utf8); final rev = (gitRevResult.stdout as String).replaceAll('\n', ''); // Dartdoc supports %r% for the revision, %f% for the file name and %l% for // the line number. const source = 'https://github.com/simolus3/drift/blob/%r%/%f%/#L%l%'; // todo: Use `dart doc` after https://github.com/dart-lang/sdk/issues/46100#issuecomment-1033899215 // gets clarified. final dartDoc = await Process.start( 'dartdoc', [ '--rel-canonical-prefix=https://pub.dev/documentation/drift/latest', '--link-to-source-revision=$rev', '--link-to-source-root=..', '--link-to-source-uri-template=$source', ], workingDirectory: '../drift', ); await _waitForProcess(dartDoc, 'dartdoc'); final docOutput = Directory('../drift/doc/api'); final targetForDocs = p.join(output.path, 'api'); await for (final file in docOutput.list(recursive: true)) { if (file is! File) continue; final target = p.join(targetForDocs, p.relative(file.path, from: docOutput.path)); File(target).parent.createSync(recursive: true); await file.copy(target); } } Future _findAssetGraph() { final dir = Directory('.dart_tool/build'); return dir.list().firstWhere((e) { final base = p.basename(e.path); return base != 'entrypoint' && base != 'generated'; }).then((dir) => File(p.join(dir.path, 'asset_graph.json'))); } bool _shouldSkipNode(AssetNode node, PackageGraph packageGraph) { if (!node.isReadable) return true; if (node.isDeleted) return true; if (!node.id.path.startsWith('web/') || node.id.package != packageGraph.root.name) { return true; } if (node is InternalAssetNode) return true; if (node is GeneratedAssetNode) { if (!node.wasOutput || node.isFailure || node.state == NodeState.definitelyNeedsUpdate) { return true; } } return false; }