drift/docs/tool/ci_build.dart

147 lines
4.5 KiB
Dart

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<void> 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<void> _waitForProcess(Process p, String name) async {
Future<void> forward(Stream<List<int>> 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<void> _runBuildAndCopyFiles(Directory output, bool isRelease) async {
final siteEnv = isRelease ? 'prod' : 'preview';
final buildArgs = [
'run',
'build_runner',
'build',
'--release',
'--define=built_site=environment=$siteEnv',
'--delete-conflicting-outputs'
];
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<void> _createApiDocumentation(Directory output) async {
print('Globally activating dartdoc');
await Process.run('dart', ['pub', 'global', 'activate', 'dartdoc']);
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(
'dart',
[
'pub',
'global',
'run',
'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',
],
// This should run in the `drift` directory to properly recognize packages.
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<File> _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;
}