| // 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'; |
| } |
| } |