From e8c2801f05c4aa0644f4adabc6b871ee230f467e Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 16 Oct 2023 13:36:14 -0700 Subject: [PATCH] in-line shared libraries into dart_services (#2677) * in-line shared libraries into dart_services * don't show copied code in PRs --- .cloud_build/dart-services/README.md | 12 +- .cloud_build/dart-services/stable.yaml | 2 +- .gitattributes | 1 + ...Dockerfile => cloud_run_stable.Dockerfile} | 0 .../lib/src/analysis_server.dart | 2 +- .../lib/src/analyzer_wrapper.dart | 2 +- .../lib/src/common_server_api.dart | 2 +- pkgs/dart_services/lib/src/shared/model.dart | 204 ++++++++++++++++++ .../dart_services/lib/src/shared/model.g.dart | 171 +++++++++++++++ .../lib/src/shared/services.dart | 103 +++++++++ pkgs/dart_services/pubspec.lock | 7 - pkgs/dart_services/pubspec.yaml | 5 - pkgs/dart_services/test/server_v3_test.dart | 2 +- pkgs/dart_services/tool/grind.dart | 6 +- 14 files changed, 494 insertions(+), 25 deletions(-) rename pkgs/dart_services/{cloud_run.Dockerfile => cloud_run_stable.Dockerfile} (100%) create mode 100644 pkgs/dart_services/lib/src/shared/model.dart create mode 100644 pkgs/dart_services/lib/src/shared/model.g.dart create mode 100644 pkgs/dart_services/lib/src/shared/services.dart diff --git a/.cloud_build/dart-services/README.md b/.cloud_build/dart-services/README.md index db8881134..a2a9f008d 100644 --- a/.cloud_build/dart-services/README.md +++ b/.cloud_build/dart-services/README.md @@ -7,12 +7,10 @@ To deploy a new version manually, run: ``` gcloud builds submit \ ---config $FLUTTER_CHANNEL.yaml \ ---project=dart-services \ ---substitutions \ - REPO_NAME=dart-pad \ - COMMIT_SHA=$COMMIT_SHA + --config $FLUTTER_CHANNEL.yaml \ + --project=dart-services \ + --substitutions REPO_NAME=dart-pad COMMIT_SHA=$COMMIT_SHA ``` -Where `$FLUTTER_CHANNEL` is `stable`, `beta`, `main`, or `old`. The REPO_NAME -and COMMIT_SHA are for adding tags to the Docker image. +Where `$FLUTTER_CHANNEL` is `stable`, `beta`, or `main`. The REPO_NAME and +COMMIT_SHA are for adding tags to the Docker image. diff --git a/.cloud_build/dart-services/stable.yaml b/.cloud_build/dart-services/stable.yaml index e1acd6d9a..1d3a328c6 100644 --- a/.cloud_build/dart-services/stable.yaml +++ b/.cloud_build/dart-services/stable.yaml @@ -7,7 +7,7 @@ steps: - '$_GCR_HOSTNAME/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA' - . - '-f' - - cloud_run.Dockerfile + - cloud_run_stable.Dockerfile id: Build dir: pkgs/dart_services - name: gcr.io/cloud-builders/docker diff --git a/.gitattributes b/.gitattributes index 20c8c8906..841f0dca8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ pkgs/dart_pad/lib/src/protos/** linguist-generated=true +pkgs/dart_services/lib/src/shared/** linguist-generated=true pkgs/sketch_pad/web/codemirror/** linguist-generated=true diff --git a/pkgs/dart_services/cloud_run.Dockerfile b/pkgs/dart_services/cloud_run_stable.Dockerfile similarity index 100% rename from pkgs/dart_services/cloud_run.Dockerfile rename to pkgs/dart_services/cloud_run_stable.Dockerfile diff --git a/pkgs/dart_services/lib/src/analysis_server.dart b/pkgs/dart_services/lib/src/analysis_server.dart index 164cf582b..8f26c291f 100644 --- a/pkgs/dart_services/lib/src/analysis_server.dart +++ b/pkgs/dart_services/lib/src/analysis_server.dart @@ -10,7 +10,6 @@ import 'dart:convert'; import 'package:analysis_server_lib/analysis_server_lib.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:dartpad_shared/model.dart' as api; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; @@ -18,6 +17,7 @@ import 'common.dart'; import 'project.dart'; import 'protos/dart_services.pb.dart' as proto; import 'pub.dart'; +import 'shared/model.dart' as api; import 'utils.dart' as utils; final Logger _logger = Logger('analysis_server'); diff --git a/pkgs/dart_services/lib/src/analyzer_wrapper.dart b/pkgs/dart_services/lib/src/analyzer_wrapper.dart index aa53cef8d..1a8bc91cb 100644 --- a/pkgs/dart_services/lib/src/analyzer_wrapper.dart +++ b/pkgs/dart_services/lib/src/analyzer_wrapper.dart @@ -9,7 +9,6 @@ import 'dart:async'; import 'dart:io'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:dartpad_shared/model.dart' as api; import 'package:logging/logging.dart'; import 'analysis_server.dart'; @@ -18,6 +17,7 @@ import 'common_server_impl.dart' show BadRequest; import 'project.dart' as project; import 'protos/dart_services.pb.dart' as proto; import 'pub.dart'; +import 'shared/model.dart' as api; final Logger _logger = Logger('analysis_servers'); diff --git a/pkgs/dart_services/lib/src/common_server_api.dart b/pkgs/dart_services/lib/src/common_server_api.dart index b6013303a..bf1895af2 100644 --- a/pkgs/dart_services/lib/src/common_server_api.dart +++ b/pkgs/dart_services/lib/src/common_server_api.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'dart:convert'; -import 'package:dartpad_shared/model.dart' as api; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:protobuf/protobuf.dart'; @@ -17,6 +16,7 @@ import 'project.dart'; import 'protos/dart_services.pb.dart' as proto; import 'pub.dart'; import 'scheduler.dart'; +import 'shared/model.dart' as api; import 'shelf_cors.dart' as shelf_cors; export 'common_server_impl.dart' show log; diff --git a/pkgs/dart_services/lib/src/shared/model.dart b/pkgs/dart_services/lib/src/shared/model.dart new file mode 100644 index 000000000..aa624606e --- /dev/null +++ b/pkgs/dart_services/lib/src/shared/model.dart @@ -0,0 +1,204 @@ +// Copyright (c) 2023, 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 'package:json_annotation/json_annotation.dart'; + +part 'model.g.dart'; + +@JsonSerializable() +class SourceRequest { + final String source; + final int? offset; + + SourceRequest({required this.source, this.offset}); + + factory SourceRequest.fromJson(Map json) => + _$SourceRequestFromJson(json); + + Map toJson() => _$SourceRequestToJson(this); + + @override + String toString() => 'SourceRequest[source=$source,offset=$offset]'; +} + +@JsonSerializable() +class AnalysisResponse { + final List issues; + + AnalysisResponse({this.issues = const []}); + + factory AnalysisResponse.fromJson(Map json) => + _$AnalysisResponseFromJson(json); + + Map toJson() => _$AnalysisResponseToJson(this); +} + +@JsonSerializable() +class AnalysisIssue { + final String kind; + final String message; + final String? correction; + final int charStart; + final int charLength; + final int line; + final int column; + + AnalysisIssue({ + required this.kind, + required this.message, + this.correction, + this.charStart = -1, + this.charLength = 0, + this.line = -1, + this.column = -1, + }); + + factory AnalysisIssue.fromJson(Map json) => + _$AnalysisIssueFromJson(json); + + Map toJson() => _$AnalysisIssueToJson(this); + + @override + String toString() => '[$kind] $message'; +} + +@JsonSerializable() +class CompileRequest { + final String source; + + CompileRequest({required this.source}); + + factory CompileRequest.fromJson(Map json) => + _$CompileRequestFromJson(json); + + Map toJson() => _$CompileRequestToJson(this); +} + +@JsonSerializable() +class CompileResponse { + final String result; + + CompileResponse({required this.result}); + + factory CompileResponse.fromJson(Map json) => + _$CompileResponseFromJson(json); + + Map toJson() => _$CompileResponseToJson(this); +} + +@JsonSerializable() +class FormatResponse { + final String source; + final int? offset; + + FormatResponse({ + required this.source, + required this.offset, + }); + + factory FormatResponse.fromJson(Map json) => + _$FormatResponseFromJson(json); + + Map toJson() => _$FormatResponseToJson(this); + + @override + String toString() => 'FormatResponse[source=$source,offset=$offset]'; +} + +@JsonSerializable() +class FlutterBuildResponse { + final Map artifacts; + + FlutterBuildResponse({required this.artifacts}); + + factory FlutterBuildResponse.fromJson(Map json) => + _$FlutterBuildResponseFromJson(json); + + Map toJson() => _$FlutterBuildResponseToJson(this); +} + +@JsonSerializable() +class CompleteResponse { + final int replacementOffset; + final int replacementLength; + final List suggestions; + + CompleteResponse({ + required this.replacementOffset, + required this.replacementLength, + required this.suggestions, + }); + + factory CompleteResponse.fromJson(Map json) => + _$CompleteResponseFromJson(json); + + Map toJson() => _$CompleteResponseToJson(this); +} + +@JsonSerializable() +class CompletionSuggestion { + final String kind; + final int relevance; + final String completion; + final bool deprecated; + final String? displayText; + final String? returnType; + final String? elementKind; + + CompletionSuggestion({ + required this.kind, + required this.relevance, + required this.completion, + required this.deprecated, + required this.displayText, + required this.returnType, + required this.elementKind, + }); + + factory CompletionSuggestion.fromJson(Map json) => + _$CompletionSuggestionFromJson(json); + + Map toJson() => _$CompletionSuggestionToJson(this); + + @override + String toString() => '[$relevance] [$kind] $completion'; +} + +@JsonSerializable() +class VersionResponse { + final String dartVersion; + final String flutterVersion; + final List experiments; + final List packages; + + VersionResponse({ + required this.dartVersion, + required this.flutterVersion, + required this.experiments, + required this.packages, + }); + + factory VersionResponse.fromJson(Map json) => + _$VersionResponseFromJson(json); + + Map toJson() => _$VersionResponseToJson(this); +} + +@JsonSerializable() +class PackageInfo { + final String name; + final String version; + final bool supported; + + PackageInfo({ + required this.name, + required this.version, + required this.supported, + }); + + factory PackageInfo.fromJson(Map json) => + _$PackageInfoFromJson(json); + + Map toJson() => _$PackageInfoToJson(this); +} diff --git a/pkgs/dart_services/lib/src/shared/model.g.dart b/pkgs/dart_services/lib/src/shared/model.g.dart new file mode 100644 index 000000000..801f6ca5d --- /dev/null +++ b/pkgs/dart_services/lib/src/shared/model.g.dart @@ -0,0 +1,171 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SourceRequest _$SourceRequestFromJson(Map json) => + SourceRequest( + source: json['source'] as String, + offset: json['offset'] as int?, + ); + +Map _$SourceRequestToJson(SourceRequest instance) => + { + 'source': instance.source, + 'offset': instance.offset, + }; + +AnalysisResponse _$AnalysisResponseFromJson(Map json) => + AnalysisResponse( + issues: (json['issues'] as List?) + ?.map((e) => AnalysisIssue.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$AnalysisResponseToJson(AnalysisResponse instance) => + { + 'issues': instance.issues, + }; + +AnalysisIssue _$AnalysisIssueFromJson(Map json) => + AnalysisIssue( + kind: json['kind'] as String, + message: json['message'] as String, + correction: json['correction'] as String?, + charStart: json['charStart'] as int? ?? -1, + charLength: json['charLength'] as int? ?? 0, + line: json['line'] as int? ?? -1, + column: json['column'] as int? ?? -1, + ); + +Map _$AnalysisIssueToJson(AnalysisIssue instance) => + { + 'kind': instance.kind, + 'message': instance.message, + 'correction': instance.correction, + 'charStart': instance.charStart, + 'charLength': instance.charLength, + 'line': instance.line, + 'column': instance.column, + }; + +CompileRequest _$CompileRequestFromJson(Map json) => + CompileRequest( + source: json['source'] as String, + ); + +Map _$CompileRequestToJson(CompileRequest instance) => + { + 'source': instance.source, + }; + +CompileResponse _$CompileResponseFromJson(Map json) => + CompileResponse( + result: json['result'] as String, + ); + +Map _$CompileResponseToJson(CompileResponse instance) => + { + 'result': instance.result, + }; + +FormatResponse _$FormatResponseFromJson(Map json) => + FormatResponse( + source: json['source'] as String, + offset: json['offset'] as int?, + ); + +Map _$FormatResponseToJson(FormatResponse instance) => + { + 'source': instance.source, + 'offset': instance.offset, + }; + +FlutterBuildResponse _$FlutterBuildResponseFromJson( + Map json) => + FlutterBuildResponse( + artifacts: Map.from(json['artifacts'] as Map), + ); + +Map _$FlutterBuildResponseToJson( + FlutterBuildResponse instance) => + { + 'artifacts': instance.artifacts, + }; + +CompleteResponse _$CompleteResponseFromJson(Map json) => + CompleteResponse( + replacementOffset: json['replacementOffset'] as int, + replacementLength: json['replacementLength'] as int, + suggestions: (json['suggestions'] as List) + .map((e) => CompletionSuggestion.fromJson(e as Map)) + .toList(), + ); + +Map _$CompleteResponseToJson(CompleteResponse instance) => + { + 'replacementOffset': instance.replacementOffset, + 'replacementLength': instance.replacementLength, + 'suggestions': instance.suggestions, + }; + +CompletionSuggestion _$CompletionSuggestionFromJson( + Map json) => + CompletionSuggestion( + kind: json['kind'] as String, + relevance: json['relevance'] as int, + completion: json['completion'] as String, + deprecated: json['deprecated'] as bool, + displayText: json['displayText'] as String?, + returnType: json['returnType'] as String?, + elementKind: json['elementKind'] as String?, + ); + +Map _$CompletionSuggestionToJson( + CompletionSuggestion instance) => + { + 'kind': instance.kind, + 'relevance': instance.relevance, + 'completion': instance.completion, + 'deprecated': instance.deprecated, + 'displayText': instance.displayText, + 'returnType': instance.returnType, + 'elementKind': instance.elementKind, + }; + +VersionResponse _$VersionResponseFromJson(Map json) => + VersionResponse( + dartVersion: json['dartVersion'] as String, + flutterVersion: json['flutterVersion'] as String, + experiments: (json['experiments'] as List) + .map((e) => e as String) + .toList(), + packages: (json['packages'] as List) + .map((e) => PackageInfo.fromJson(e as Map)) + .toList(), + ); + +Map _$VersionResponseToJson(VersionResponse instance) => + { + 'dartVersion': instance.dartVersion, + 'flutterVersion': instance.flutterVersion, + 'experiments': instance.experiments, + 'packages': instance.packages, + }; + +PackageInfo _$PackageInfoFromJson(Map json) => PackageInfo( + name: json['name'] as String, + version: json['version'] as String, + supported: json['supported'] as bool, + ); + +Map _$PackageInfoToJson(PackageInfo instance) => + { + 'name': instance.name, + 'version': instance.version, + 'supported': instance.supported, + }; diff --git a/pkgs/dart_services/lib/src/shared/services.dart b/pkgs/dart_services/lib/src/shared/services.dart new file mode 100644 index 000000000..7ac82928c --- /dev/null +++ b/pkgs/dart_services/lib/src/shared/services.dart @@ -0,0 +1,103 @@ +// Copyright (c) 2023, 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:convert'; + +import 'package:http/http.dart'; + +import 'model.dart'; + +export 'model.dart'; + +class ServicesClient { + final Client client; + final String rootUrl; + + ServicesClient(this.client, {required this.rootUrl}); + + Future version() => + _requestGet('version', VersionResponse.fromJson); + + Future analyze(SourceRequest request) => + _requestPost('analyze', request.toJson(), AnalysisResponse.fromJson); + + Future complete(SourceRequest request) => + _requestPost('complete', request.toJson(), CompleteResponse.fromJson); + + // TODO: Implement document(). + // Future document(SourceRequest request) => + // _request('document', request.toJson(), DocumentResponse.fromJson); + + // TODO: Implement fixes(). + // Future fixes(SourceRequest request) => + // _request('fixes', request.toJson(), FixesResponse.fromJson); + + Future format(SourceRequest request) => + _requestPost('format', request.toJson(), FormatResponse.fromJson); + + Future compile(CompileRequest request) => + _requestPost('compile', request.toJson(), CompileResponse.fromJson); + + /// Note that this call is experimental and can change at any time. + Future flutterBuild(SourceRequest request) => + _requestPost( + '_flutterBuild', request.toJson(), FlutterBuildResponse.fromJson); + + void dispose() => client.close(); + + Future _requestGet( + String action, + T Function(Map json) responseFactory, + ) async { + final response = + await client.get(Uri.parse('${rootUrl}api/dartservices/v3/$action')); + if (response.statusCode != 200) { + throw ApiRequestError( + '$action: ${response.statusCode}: ${response.reasonPhrase}', + response.body, + ); + } + + try { + return responseFactory( + json.decode(response.body) as Map); + } on FormatException catch (e) { + throw ApiRequestError('$action: $e', response.body); + } + } + + Future _requestPost( + String action, + Map request, + T Function(Map json) responseFactory, + ) async { + final response = await client.post( + Uri.parse('${rootUrl}api/dartservices/v3/$action'), + encoding: utf8, + body: json.encode(request)); + if (response.statusCode != 200) { + throw ApiRequestError( + '$action: ${response.statusCode}: ${response.reasonPhrase}', + response.body, + ); + } + + try { + return responseFactory( + json.decode(response.body) as Map); + } on FormatException catch (e) { + throw ApiRequestError('$action: $e', response.body); + } + } +} + +class ApiRequestError implements Exception { + ApiRequestError(this.message, this.body); + + final String message; + final String body; + + @override + String toString() => '$message\n$body'; +} diff --git a/pkgs/dart_services/pubspec.lock b/pkgs/dart_services/pubspec.lock index 98d43db1f..8a546b3d2 100644 --- a/pkgs/dart_services/pubspec.lock +++ b/pkgs/dart_services/pubspec.lock @@ -225,13 +225,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - dartpad_shared: - dependency: "direct main" - description: - path: "../dartpad_shared" - relative: true - source: path - version: "0.0.0" encrypt: dependency: "direct main" description: diff --git a/pkgs/dart_services/pubspec.yaml b/pkgs/dart_services/pubspec.yaml index b69d2fce5..1070efe11 100644 --- a/pkgs/dart_services/pubspec.yaml +++ b/pkgs/dart_services/pubspec.yaml @@ -12,7 +12,6 @@ dependencies: collection: ^1.18.0 convert: ^3.1.1 crypto: ^3.0.3 - dartpad_shared: any encrypt: ^5.0.2 http: ^1.0.0 json_annotation: any @@ -40,7 +39,3 @@ dev_dependencies: executables: services: null - -dependency_overrides: - dartpad_shared: - path: ../dartpad_shared diff --git a/pkgs/dart_services/test/server_v3_test.dart b/pkgs/dart_services/test/server_v3_test.dart index 8150569b8..c99085447 100644 --- a/pkgs/dart_services/test/server_v3_test.dart +++ b/pkgs/dart_services/test/server_v3_test.dart @@ -7,7 +7,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; import 'package:dart_services/server.dart'; import 'package:dart_services/src/sdk.dart'; -import 'package:dartpad_shared/services.dart'; +import 'package:dart_services/src/shared/services.dart'; import 'package:http/http.dart'; import 'package:test/test.dart'; diff --git a/pkgs/dart_services/tool/grind.dart b/pkgs/dart_services/tool/grind.dart index a66114b73..16b26bd48 100644 --- a/pkgs/dart_services/tool/grind.dart +++ b/pkgs/dart_services/tool/grind.dart @@ -57,7 +57,7 @@ final _dockerVersionMatcher = RegExp('^FROM $_dartImageName:(.*)\$'); const _dockerFileNames = [ 'cloud_run_beta.Dockerfile', 'cloud_run_main.Dockerfile', - 'cloud_run.Dockerfile', + 'cloud_run_stable.Dockerfile', ]; /// Returns the Flutter channel provided in environment variables. @@ -369,6 +369,10 @@ void generateProtos() async { // Copy to the front-end packages. copy(getDir('lib/src/protos'), getDir('../dart_pad/lib/src/protos')); copy(getDir('lib/src/protos'), getDir('../sketch_pad/lib/src/protos')); + + // TODO: We'd like to remove this copy operation; that will require work in + // the cloud build configuration. + copy(getDir('../dartpad_shared/lib'), getDir('lib/src/shared')); } Future _run(