// 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 main(List 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 _generateDartClient( String codeGeneratorDir, List nodes) async { final outputFilePath = await _generateDartCommon( api: VmServiceApi(), nodes: nodes, codeGeneratorDir: codeGeneratorDir, packageName: 'vm_service', interfaceName: 'VmService', ); print('Wrote Dart client to $outputFilePath.'); } Future _generateDartInterface( String codeGeneratorDir, List 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 _generateDartCommon({ required Api api, required List 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 _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 _generateJava(String codeGeneratorDir, List 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 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'; } }