blob: 720a1117ae7e7b478e74afaddd9c78f9ea455c8c [file] [log] [blame]
// Copyright (c) 2015, 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.
import 'dart:io';
import 'package:markdown/markdown.dart';
import 'package:path/path.dart';
import 'package:pub_semver/pub_semver.dart';
import 'common/generate_common.dart';
import 'dart/generate_dart_client.dart';
import 'dart/generate_dart_common.dart';
import 'dart/generate_dart_interface.dart';
import 'java/generate_java.dart' as java show Api, api, JavaGenerator;
final bool _stampPubspecVersion = false;
/// Parse the 'service.md' into a model and generate both Dart and Java
/// libraries.
Future<void> main(List<String> args) async {
final codeGeneratorDir = dirname(Platform.script.toFilePath());
// Parse service.md into a model.
final file = File(
normalize(join(codeGeneratorDir, '../../../runtime/vm/service/service.md')),
);
final document = Document();
final buf = StringBuffer(file.readAsStringSync());
final nodes = document.parseLines(buf.toString().split('\n'));
print('Parsed ${file.path}.');
print('Service protocol version ${ApiParseUtil.parseVersionString(nodes)}.');
// Generate code from the model.
print('');
await _generateDartClient(codeGeneratorDir, nodes);
await _generateDartInterface(codeGeneratorDir, nodes);
await _generateJava(codeGeneratorDir, nodes);
}
Future<void> _generateDartClient(
String codeGeneratorDir, List<Node> nodes) async {
final outputFilePath = await _generateDartCommon(
api: VmServiceApi(),
nodes: nodes,
codeGeneratorDir: codeGeneratorDir,
packageName: 'vm_service',
interfaceName: 'VmService',
);
print('Wrote Dart client to $outputFilePath.');
}
Future<void> _generateDartInterface(
String codeGeneratorDir, List<Node> nodes) async {
final outputFilePath = await _generateDartCommon(
api: VmServiceInterfaceApi(),
nodes: nodes,
codeGeneratorDir: codeGeneratorDir,
packageName: 'vm_service_interface',
interfaceName: 'VmServiceInterface',
);
print('Wrote Dart interface to $outputFilePath.');
}
Future<String> _generateDartCommon({
required Api api,
required List<Node> nodes,
required String codeGeneratorDir,
required String packageName,
required String interfaceName,
}) async {
final outDirPath = normalize(
join(
codeGeneratorDir,
'../..',
packageName,
'lib/src',
),
);
final outDir = Directory(outDirPath);
if (!outDir.existsSync()) {
outDir.createSync(recursive: true);
}
final outputFile = File(
join(
outDirPath,
'$packageName.dart',
),
);
final generator = DartGenerator(interfaceName: interfaceName);
// Generate the code.
api.parse(nodes);
api.generate(generator);
outputFile.writeAsStringSync(generator.toString());
// Clean up the code.
await _runDartFormat(outDirPath);
if (_stampPubspecVersion) {
// Update the pubspec file.
Version version = ApiParseUtil.parseVersionSemVer(nodes);
_stampPubspec(version);
// Validate that the changelog contains an entry for the current version.
_checkUpdateChangelog(version);
}
return outputFile.path;
}
Future<void> _runDartFormat(String outDirPath) async {
ProcessResult result = Process.runSync('dart', ['format', outDirPath]);
if (result.exitCode != 0) {
print('dart format: ${result.stdout}\n${result.stderr}');
throw result.exitCode;
}
}
Future<void> _generateJava(String codeGeneratorDir, List<Node> nodes) async {
var srcDirPath = normalize(join(codeGeneratorDir, '..', 'java', 'src'));
var generator = java.JavaGenerator(srcDirPath);
final scriptPath = Platform.script.toFilePath();
final kSdk = '/sdk/';
final scriptLocation =
scriptPath.substring(scriptPath.indexOf(kSdk) + kSdk.length);
java.api = java.Api(scriptLocation);
java.api.parse(nodes);
java.api.generate(generator);
// We generate files into the java/src/ folder; ensure the generated files
// aren't committed to git (but manually maintained files in the same
// directory are).
List<String> generatedPaths = generator.allWrittenFiles
.map((path) => relative(path, from: 'java'))
.toList();
generatedPaths.sort();
File gitignoreFile = File(join(codeGeneratorDir, '..', 'java', '.gitignore'));
gitignoreFile.writeAsStringSync('''
# This is a generated file.
${generatedPaths.join('\n')}
''');
// Generate a version file.
Version version = ApiParseUtil.parseVersionSemVer(nodes);
File file = File(join('java', 'version.properties'));
file.writeAsStringSync('version=${version.major}.${version.minor}\n');
print('Wrote Java to $srcDirPath.');
}
// Push the major and minor versions into the pubspec.
void _stampPubspec(Version version) {
final String pattern = 'version: ';
File file = File('pubspec.yaml');
String text = file.readAsStringSync();
bool found = false;
text = text.split('\n').map((line) {
if (line.startsWith(pattern)) {
found = true;
Version v = Version.parse(line.substring(pattern.length));
String? pre = v.preRelease.isEmpty ? null : v.preRelease.join('-');
String? build = v.build.isEmpty ? null : v.build.join('+');
v = Version(version.major, version.minor, v.patch,
pre: pre, build: build);
return '$pattern${v.toString()}';
} else {
return line;
}
}).join('\n');
if (!found) throw '`$pattern` not found';
file.writeAsStringSync(text);
}
void _checkUpdateChangelog(Version version) {
// Look for `## major.minor`.
String check = '## ${version.major}.${version.minor}';
File file = File('CHANGELOG.md');
String text = file.readAsStringSync();
bool containsReleaseNotes =
text.split('\n').any((line) => line.startsWith(check));
if (!containsReleaseNotes) {
throw '`$check` not found in the CHANGELOG.md file';
}
}