add(conduit): refactoring conduit
This commit is contained in:
parent
ceb360eb9c
commit
7a095316dc
18 changed files with 1379 additions and 0 deletions
7
packages/runtime/.gitignore
vendored
Normal file
7
packages/runtime/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
3
packages/runtime/CHANGELOG.md
Normal file
3
packages/runtime/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Initial version.
|
39
packages/runtime/README.md
Normal file
39
packages/runtime/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!--
|
||||||
|
This README describes the package. If you publish this package to pub.dev,
|
||||||
|
this README's contents appear on the landing page for your package.
|
||||||
|
|
||||||
|
For information about how to write a good package README, see the guide for
|
||||||
|
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||||
|
|
||||||
|
For general information about developing packages, see the Dart guide for
|
||||||
|
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||||
|
and the Flutter guide for
|
||||||
|
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||||
|
-->
|
||||||
|
|
||||||
|
TODO: Put a short description of the package here that helps potential users
|
||||||
|
know whether this package might be useful for them.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
TODO: List prerequisites and provide or point to information on how to
|
||||||
|
start using the package.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Include short and useful examples for package users. Add longer examples
|
||||||
|
to `/example` folder.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
const like = 'sample';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
TODO: Tell users more about the package: where to find more information, how to
|
||||||
|
contribute to the package, how to file issues, what response they can expect
|
||||||
|
from the package authors, and more.
|
30
packages/runtime/analysis_options.yaml
Normal file
30
packages/runtime/analysis_options.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file configures the static analysis results for your project (errors,
|
||||||
|
# warnings, and lints).
|
||||||
|
#
|
||||||
|
# This enables the 'recommended' set of lints from `package:lints`.
|
||||||
|
# This set helps identify many issues that may lead to problems when running
|
||||||
|
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
|
||||||
|
# style and format.
|
||||||
|
#
|
||||||
|
# If you want a smaller set of lints you can change this to specify
|
||||||
|
# 'package:lints/core.yaml'. These are just the most critical lints
|
||||||
|
# (the recommended set includes the core lints).
|
||||||
|
# The core lints are also what is used by pub.dev for scoring packages.
|
||||||
|
|
||||||
|
include: package:lints/recommended.yaml
|
||||||
|
|
||||||
|
# Uncomment the following section to specify additional rules.
|
||||||
|
|
||||||
|
# linter:
|
||||||
|
# rules:
|
||||||
|
# - camel_case_types
|
||||||
|
|
||||||
|
# analyzer:
|
||||||
|
# exclude:
|
||||||
|
# - path/to/excluded/files/**
|
||||||
|
|
||||||
|
# For more information about the core and recommended set of lints, see
|
||||||
|
# https://dart.dev/go/core-lints
|
||||||
|
|
||||||
|
# For additional information about configuring this file, see
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
64
packages/runtime/lib/runtime.dart
Normal file
64
packages/runtime/lib/runtime.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
library runtime;
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
export 'package:protevus_runtime/src/analyzer.dart';
|
||||||
|
export 'package:protevus_runtime/src/build.dart';
|
||||||
|
export 'package:protevus_runtime/src/build_context.dart';
|
||||||
|
export 'package:protevus_runtime/src/build_manager.dart';
|
||||||
|
export 'package:protevus_runtime/src/compiler.dart';
|
||||||
|
export 'package:protevus_runtime/src/context.dart';
|
||||||
|
export 'package:protevus_runtime/src/exceptions.dart';
|
||||||
|
export 'package:protevus_runtime/src/generator.dart';
|
||||||
|
export 'package:protevus_runtime/src/mirror_coerce.dart';
|
||||||
|
export 'package:protevus_runtime/src/mirror_context.dart';
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
/// Compiler for the runtime package itself.
|
||||||
|
///
|
||||||
|
/// Removes dart:mirror from a replica of this package, and adds
|
||||||
|
/// a generated runtime to the replica's pubspec.
|
||||||
|
class RuntimePackageCompiler extends Compiler {
|
||||||
|
@override
|
||||||
|
Map<String, Object> compile(MirrorContext context) => {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deflectPackage(Directory destinationDirectory) {
|
||||||
|
final libraryFile = File.fromUri(
|
||||||
|
destinationDirectory.uri.resolve("lib/").resolve("runtime.dart"),
|
||||||
|
);
|
||||||
|
libraryFile.writeAsStringSync(
|
||||||
|
"library runtime;\nexport 'src/context.dart';\nexport 'src/exceptions.dart';",
|
||||||
|
);
|
||||||
|
|
||||||
|
final contextFile = File.fromUri(
|
||||||
|
destinationDirectory.uri
|
||||||
|
.resolve("lib/")
|
||||||
|
.resolve("src/")
|
||||||
|
.resolve("context.dart"),
|
||||||
|
);
|
||||||
|
final contextFileContents = contextFile.readAsStringSync().replaceFirst(
|
||||||
|
"import 'package:protevus_runtime/src/mirror_context.dart';",
|
||||||
|
"import 'package:generated_runtime/generated_runtime.dart';",
|
||||||
|
);
|
||||||
|
contextFile.writeAsStringSync(contextFileContents);
|
||||||
|
|
||||||
|
final pubspecFile =
|
||||||
|
File.fromUri(destinationDirectory.uri.resolve("pubspec.yaml"));
|
||||||
|
final pubspecContents = pubspecFile.readAsStringSync().replaceFirst(
|
||||||
|
"\ndependencies:",
|
||||||
|
"\ndependencies:\n generated_runtime:\n path: ../../generated_runtime/",
|
||||||
|
);
|
||||||
|
pubspecFile.writeAsStringSync(pubspecContents);
|
||||||
|
}
|
||||||
|
}
|
86
packages/runtime/lib/slow_coerce.dart
Normal file
86
packages/runtime/lib/slow_coerce.dart
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
const String _listPrefix = "List<";
|
||||||
|
const String _mapPrefix = "Map<String,";
|
||||||
|
|
||||||
|
T cast<T>(dynamic input) {
|
||||||
|
try {
|
||||||
|
var typeString = T.toString();
|
||||||
|
if (typeString.endsWith('?')) {
|
||||||
|
if (input == null) {
|
||||||
|
return null as T;
|
||||||
|
} else {
|
||||||
|
typeString = typeString.substring(0, typeString.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeString.startsWith(_listPrefix)) {
|
||||||
|
if (input is! List) {
|
||||||
|
throw TypeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeString.startsWith("List<int>")) {
|
||||||
|
return List<int>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<num>")) {
|
||||||
|
return List<num>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<double>")) {
|
||||||
|
return List<double>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<String>")) {
|
||||||
|
return List<String>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<bool>")) {
|
||||||
|
return List<bool>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<int?>")) {
|
||||||
|
return List<int?>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<num?>")) {
|
||||||
|
return List<num?>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<double?>")) {
|
||||||
|
return List<double?>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<String?>")) {
|
||||||
|
return List<String?>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<bool?>")) {
|
||||||
|
return List<bool?>.from(input) as T;
|
||||||
|
} else if (typeString.startsWith("List<Map<String, dynamic>>")) {
|
||||||
|
return List<Map<String, dynamic>>.from(input) as T;
|
||||||
|
}
|
||||||
|
} else if (typeString.startsWith(_mapPrefix)) {
|
||||||
|
if (input is! Map) {
|
||||||
|
throw TypeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
final inputMap = input as Map<String, dynamic>;
|
||||||
|
if (typeString.startsWith("Map<String, int>")) {
|
||||||
|
return Map<String, int>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, num>")) {
|
||||||
|
return Map<String, num>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, double>")) {
|
||||||
|
return Map<String, double>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, String>")) {
|
||||||
|
return Map<String, String>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, bool>")) {
|
||||||
|
return Map<String, bool>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, int?>")) {
|
||||||
|
return Map<String, int?>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, num?>")) {
|
||||||
|
return Map<String, num?>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, double?>")) {
|
||||||
|
return Map<String, double?>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, String?>")) {
|
||||||
|
return Map<String, String?>.from(inputMap) as T;
|
||||||
|
} else if (typeString.startsWith("Map<String, bool?>")) {
|
||||||
|
return Map<String, bool?>.from(inputMap) as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return input as T;
|
||||||
|
} on TypeError {
|
||||||
|
throw TypeCoercionException(T, input.runtimeType);
|
||||||
|
}
|
||||||
|
}
|
143
packages/runtime/lib/src/analyzer.dart
Normal file
143
packages/runtime/lib/src/analyzer.dart
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
||||||
|
import 'package:analyzer/dart/analysis/results.dart';
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
|
||||||
|
class CodeAnalyzer {
|
||||||
|
CodeAnalyzer(this.uri) {
|
||||||
|
if (!uri.isAbsolute) {
|
||||||
|
throw ArgumentError("'uri' must be absolute for CodeAnalyzer");
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts = AnalysisContextCollection(includedPaths: [path]);
|
||||||
|
|
||||||
|
if (contexts.contexts.isEmpty) {
|
||||||
|
throw ArgumentError("no analysis context found for path '$path'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get path {
|
||||||
|
return getPath(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final Uri uri;
|
||||||
|
|
||||||
|
late AnalysisContextCollection contexts;
|
||||||
|
|
||||||
|
final _resolvedAsts = <String, AnalysisResult>{};
|
||||||
|
|
||||||
|
Future<AnalysisResult> resolveUnitOrLibraryAt(Uri uri) async {
|
||||||
|
if (FileSystemEntity.isFileSync(
|
||||||
|
uri.toFilePath(windows: Platform.isWindows),
|
||||||
|
)) {
|
||||||
|
return resolveUnitAt(uri);
|
||||||
|
} else {
|
||||||
|
return resolveLibraryAt(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ResolvedLibraryResult> resolveLibraryAt(Uri uri) async {
|
||||||
|
assert(
|
||||||
|
FileSystemEntity.isDirectorySync(
|
||||||
|
uri.toFilePath(windows: Platform.isWindows),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (final ctx in contexts.contexts) {
|
||||||
|
final path = getPath(uri);
|
||||||
|
if (_resolvedAsts.containsKey(path)) {
|
||||||
|
return _resolvedAsts[path]! as ResolvedLibraryResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
final output = await ctx.currentSession.getResolvedLibrary(path)
|
||||||
|
as ResolvedLibraryResult;
|
||||||
|
return _resolvedAsts[path] = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ArgumentError("'uri' could not be resolved (contexts: "
|
||||||
|
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ResolvedUnitResult> resolveUnitAt(Uri uri) async {
|
||||||
|
assert(
|
||||||
|
FileSystemEntity.isFileSync(
|
||||||
|
uri.toFilePath(windows: Platform.isWindows),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
for (final ctx in contexts.contexts) {
|
||||||
|
final path = getPath(uri);
|
||||||
|
if (_resolvedAsts.containsKey(path)) {
|
||||||
|
return _resolvedAsts[path]! as ResolvedUnitResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
final output =
|
||||||
|
await ctx.currentSession.getResolvedUnit(path) as ResolvedUnitResult;
|
||||||
|
return _resolvedAsts[path] = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ArgumentError("'uri' could not be resolved (contexts: "
|
||||||
|
"${contexts.contexts.map((c) => c.contextRoot.root.toUri()).join(", ")})");
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassDeclaration? getClassFromFile(String className, Uri fileUri) {
|
||||||
|
try {
|
||||||
|
return _getFileAstRoot(fileUri)
|
||||||
|
.declarations
|
||||||
|
.whereType<ClassDeclaration>()
|
||||||
|
.firstWhere((c) => c.name.value() == className);
|
||||||
|
} catch (e) {
|
||||||
|
if (e is StateError || e is TypeError || e is ArgumentError) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ClassDeclaration> getSubclassesFromFile(
|
||||||
|
String superclassName,
|
||||||
|
Uri fileUri,
|
||||||
|
) {
|
||||||
|
return _getFileAstRoot(fileUri)
|
||||||
|
.declarations
|
||||||
|
.whereType<ClassDeclaration>()
|
||||||
|
.where((c) =>
|
||||||
|
c.extendsClause?.superclass.name2.toString() == superclassName)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompilationUnit _getFileAstRoot(Uri fileUri) {
|
||||||
|
assert(
|
||||||
|
FileSystemEntity.isFileSync(
|
||||||
|
fileUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final path = getPath(fileUri);
|
||||||
|
if (_resolvedAsts.containsKey(path)) {
|
||||||
|
return (_resolvedAsts[path]! as ResolvedUnitResult).unit;
|
||||||
|
}
|
||||||
|
} finally {}
|
||||||
|
final unit = contexts.contextFor(path).currentSession.getParsedUnit(
|
||||||
|
normalize(
|
||||||
|
absolute(fileUri.toFilePath(windows: Platform.isWindows)),
|
||||||
|
),
|
||||||
|
) as ParsedUnitResult;
|
||||||
|
return unit.unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getPath(dynamic inputUri) {
|
||||||
|
return PhysicalResourceProvider.INSTANCE.pathContext.normalize(
|
||||||
|
PhysicalResourceProvider.INSTANCE.pathContext.fromUri(inputUri),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
214
packages/runtime/lib/src/build.dart
Normal file
214
packages/runtime/lib/src/build.dart
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ignore_for_file: avoid_print
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
import 'package:io/io.dart';
|
||||||
|
import 'package:package_config/package_config.dart';
|
||||||
|
|
||||||
|
class Build {
|
||||||
|
Build(this.context);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
Future execute() async {
|
||||||
|
final compilers = context.context.compilers;
|
||||||
|
|
||||||
|
print("Resolving ASTs...");
|
||||||
|
final astsToResolve = <Uri>{
|
||||||
|
...compilers.expand((c) => c.getUrisToResolve(context))
|
||||||
|
};
|
||||||
|
await Future.forEach<Uri>(
|
||||||
|
astsToResolve,
|
||||||
|
(astUri) async {
|
||||||
|
final package = await context.getPackageFromUri(astUri);
|
||||||
|
final Uri packageUri =
|
||||||
|
package?.packageUriRoot.resolve(package.name) ?? astUri;
|
||||||
|
return context.analyzer.resolveUnitOrLibraryAt(packageUri);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
print("Generating runtime...");
|
||||||
|
|
||||||
|
final runtimeGenerator = RuntimeGenerator();
|
||||||
|
for (final MapEntry<String, dynamic> entry
|
||||||
|
in context.context.runtimes.map.entries) {
|
||||||
|
if (entry.value is SourceCompiler) {
|
||||||
|
await (entry.value as SourceCompiler).compile(context).then(
|
||||||
|
(source) =>
|
||||||
|
runtimeGenerator.addRuntime(name: entry.key, source: source),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await runtimeGenerator.writeTo(context.buildRuntimeDirectory.uri);
|
||||||
|
print("Generated runtime at '${context.buildRuntimeDirectory.uri}'.");
|
||||||
|
|
||||||
|
final nameOfPackageBeingCompiled = context.sourceApplicationPubspec.name;
|
||||||
|
final pubspecMap = <String, Object>{
|
||||||
|
'name': 'runtime_target',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'environment': {'sdk': '>=3.4.0 <4.0.0'},
|
||||||
|
'dependency_overrides': {}
|
||||||
|
};
|
||||||
|
final overrides = pubspecMap['dependency_overrides'] as Map;
|
||||||
|
var sourcePackageIsCompiled = false;
|
||||||
|
|
||||||
|
for (final compiler in compilers) {
|
||||||
|
final packageInfo = await _getPackageInfoForCompiler(compiler);
|
||||||
|
final sourceDirUri = packageInfo.root;
|
||||||
|
final targetDirUri =
|
||||||
|
context.buildPackagesDirectory.uri.resolve("${packageInfo.name}/");
|
||||||
|
print("Compiling package '${packageInfo.name}'...");
|
||||||
|
await copyPackage(sourceDirUri, targetDirUri);
|
||||||
|
compiler.deflectPackage(Directory.fromUri(targetDirUri));
|
||||||
|
|
||||||
|
if (packageInfo.name != nameOfPackageBeingCompiled) {
|
||||||
|
overrides[packageInfo.name] = {
|
||||||
|
"path": targetDirUri.toFilePath(windows: Platform.isWindows)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
sourcePackageIsCompiled = true;
|
||||||
|
}
|
||||||
|
print("Package '${packageInfo.name}' compiled to '$targetDirUri'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final appDst = context.buildApplicationDirectory.uri;
|
||||||
|
if (!sourcePackageIsCompiled) {
|
||||||
|
print(
|
||||||
|
"Copying application package (from '${context.sourceApplicationDirectory.uri}')...",
|
||||||
|
);
|
||||||
|
await copyPackage(context.sourceApplicationDirectory.uri, appDst);
|
||||||
|
print("Application packaged copied to '$appDst'.");
|
||||||
|
}
|
||||||
|
pubspecMap['dependencies'] = {
|
||||||
|
nameOfPackageBeingCompiled: {
|
||||||
|
"path": appDst.toFilePath(windows: Platform.isWindows)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context.forTests) {
|
||||||
|
final devDeps = context.sourceApplicationPubspecMap['dev_dependencies'];
|
||||||
|
if (devDeps != null) {
|
||||||
|
pubspecMap['dev_dependencies'] = devDeps;
|
||||||
|
}
|
||||||
|
|
||||||
|
overrides['conduit_core'] = {
|
||||||
|
'path': appDst.toFilePath(windows: Platform.isWindows)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
File.fromUri(context.buildDirectoryUri.resolve("pubspec.yaml"))
|
||||||
|
.writeAsStringSync(json.encode(pubspecMap));
|
||||||
|
|
||||||
|
context
|
||||||
|
.getFile(context.targetScriptFileUri)
|
||||||
|
.writeAsStringSync(context.source);
|
||||||
|
|
||||||
|
for (final compiler in context.context.compilers) {
|
||||||
|
compiler.didFinishPackageGeneration(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Fetching dependencies (--offline --no-precompile)...");
|
||||||
|
await getDependencies();
|
||||||
|
print("Finished fetching dependencies.");
|
||||||
|
if (!context.forTests) {
|
||||||
|
print("Compiling...");
|
||||||
|
await compile(context.targetScriptFileUri, context.executableUri);
|
||||||
|
print("Success. Executable is located at '${context.executableUri}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future getDependencies() async {
|
||||||
|
const String cmd = "dart";
|
||||||
|
|
||||||
|
final res = await Process.run(
|
||||||
|
cmd,
|
||||||
|
["pub", "get", "--offline", "--no-precompile"],
|
||||||
|
workingDirectory:
|
||||||
|
context.buildDirectoryUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
runInShell: true,
|
||||||
|
);
|
||||||
|
if (res.exitCode != 0) {
|
||||||
|
print("${res.stdout}");
|
||||||
|
throw StateError(
|
||||||
|
"'pub get' failed with the following message: ${res.stderr}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future compile(Uri srcUri, Uri dstUri) async {
|
||||||
|
final res = await Process.run(
|
||||||
|
"dart",
|
||||||
|
[
|
||||||
|
"compile",
|
||||||
|
"exe",
|
||||||
|
...(context.environment?.entries.map((e) => "-D${e.key}=${e.value}") ??
|
||||||
|
[]),
|
||||||
|
"-v",
|
||||||
|
srcUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
"-o",
|
||||||
|
dstUri.toFilePath(windows: Platform.isWindows)
|
||||||
|
],
|
||||||
|
workingDirectory: context.buildApplicationDirectory.uri
|
||||||
|
.toFilePath(windows: Platform.isWindows),
|
||||||
|
runInShell: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.exitCode != 0) {
|
||||||
|
throw StateError(
|
||||||
|
"'dart2native' failed with the following message: ${res.stderr}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
print("${res.stdout}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future copyPackage(Uri srcUri, Uri dstUri) async {
|
||||||
|
final dstDir = Directory.fromUri(dstUri);
|
||||||
|
if (!dstDir.existsSync()) {
|
||||||
|
dstDir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await copyPath(
|
||||||
|
srcUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
dstUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
);
|
||||||
|
} on FileSystemException catch (e) {
|
||||||
|
if (Platform.isWindows) {
|
||||||
|
final File f = File(e.path!);
|
||||||
|
if (f.existsSync()) {
|
||||||
|
f.deleteSync();
|
||||||
|
}
|
||||||
|
File(e.path!).writeAsStringSync('dummy');
|
||||||
|
await copyPath(
|
||||||
|
srcUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
dstUri.toFilePath(windows: Platform.isWindows),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.getFile(srcUri.resolve("pubspec.yaml")).copy(
|
||||||
|
dstUri
|
||||||
|
.resolve("pubspec.yaml")
|
||||||
|
.toFilePath(windows: Platform.isWindows),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Package> _getPackageInfoForCompiler(Compiler compiler) async {
|
||||||
|
final compilerUri = reflect(compiler).type.location!.sourceUri;
|
||||||
|
|
||||||
|
return (await context.packageConfig)[compilerUri.pathSegments.first]!;
|
||||||
|
}
|
||||||
|
}
|
252
packages/runtime/lib/src/build_context.dart
Normal file
252
packages/runtime/lib/src/build_context.dart
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:mirrors';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
import 'package:package_config/package_config.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
||||||
|
import 'package:yaml/yaml.dart';
|
||||||
|
|
||||||
|
/// Configuration and context values used during [Build.execute].
|
||||||
|
class BuildContext {
|
||||||
|
BuildContext(
|
||||||
|
this.rootLibraryFileUri,
|
||||||
|
this.buildDirectoryUri,
|
||||||
|
this.executableUri,
|
||||||
|
this.source, {
|
||||||
|
this.environment,
|
||||||
|
this.forTests = false,
|
||||||
|
}) {
|
||||||
|
analyzer = CodeAnalyzer(sourceApplicationDirectory.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory BuildContext.fromMap(Map<String, dynamic> map) {
|
||||||
|
return BuildContext(
|
||||||
|
Uri.parse(map['rootLibraryFileUri']),
|
||||||
|
Uri.parse(map['buildDirectoryUri']),
|
||||||
|
Uri.parse(map['executableUri']),
|
||||||
|
map['source'],
|
||||||
|
environment: map['environment'],
|
||||||
|
forTests: map['forTests'] ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> get safeMap => {
|
||||||
|
'rootLibraryFileUri': sourceLibraryFile.uri.toString(),
|
||||||
|
'buildDirectoryUri': buildDirectoryUri.toString(),
|
||||||
|
'source': source,
|
||||||
|
'executableUri': executableUri.toString(),
|
||||||
|
'environment': environment,
|
||||||
|
'forTests': forTests
|
||||||
|
};
|
||||||
|
|
||||||
|
late final CodeAnalyzer analyzer;
|
||||||
|
|
||||||
|
/// A [Uri] to the library file of the application to be compiled.
|
||||||
|
final Uri rootLibraryFileUri;
|
||||||
|
|
||||||
|
/// A [Uri] to the executable build product file.
|
||||||
|
final Uri executableUri;
|
||||||
|
|
||||||
|
/// A [Uri] to directory where build artifacts are stored during the build process.
|
||||||
|
final Uri buildDirectoryUri;
|
||||||
|
|
||||||
|
/// The source script for the executable.
|
||||||
|
final String source;
|
||||||
|
|
||||||
|
/// Whether dev dependencies of the application package are included in the dependencies of the compiled executable.
|
||||||
|
final bool forTests;
|
||||||
|
|
||||||
|
PackageConfig? _packageConfig;
|
||||||
|
|
||||||
|
final Map<String, String>? environment;
|
||||||
|
|
||||||
|
/// The [RuntimeContext] available during the build process.
|
||||||
|
MirrorContext get context => RuntimeContext.current as MirrorContext;
|
||||||
|
|
||||||
|
Uri get targetScriptFileUri => forTests
|
||||||
|
? getDirectory(buildDirectoryUri.resolve("test/"))
|
||||||
|
.uri
|
||||||
|
.resolve("main_test.dart")
|
||||||
|
: buildDirectoryUri.resolve("main.dart");
|
||||||
|
|
||||||
|
Pubspec get sourceApplicationPubspec => Pubspec.parse(
|
||||||
|
File.fromUri(sourceApplicationDirectory.uri.resolve("pubspec.yaml"))
|
||||||
|
.readAsStringSync(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<dynamic, dynamic> get sourceApplicationPubspecMap => loadYaml(
|
||||||
|
File.fromUri(
|
||||||
|
sourceApplicationDirectory.uri.resolve("pubspec.yaml"),
|
||||||
|
).readAsStringSync(),
|
||||||
|
) as Map<dynamic, dynamic>;
|
||||||
|
|
||||||
|
/// The directory of the application being compiled.
|
||||||
|
Directory get sourceApplicationDirectory =>
|
||||||
|
getDirectory(rootLibraryFileUri.resolve("../"));
|
||||||
|
|
||||||
|
/// The library file of the application being compiled.
|
||||||
|
File get sourceLibraryFile => getFile(rootLibraryFileUri);
|
||||||
|
|
||||||
|
/// The directory where build artifacts are stored.
|
||||||
|
Directory get buildDirectory => getDirectory(buildDirectoryUri);
|
||||||
|
|
||||||
|
/// The generated runtime directory
|
||||||
|
Directory get buildRuntimeDirectory =>
|
||||||
|
getDirectory(buildDirectoryUri.resolve("generated_runtime/"));
|
||||||
|
|
||||||
|
/// Directory for compiled packages
|
||||||
|
Directory get buildPackagesDirectory =>
|
||||||
|
getDirectory(buildDirectoryUri.resolve("packages/"));
|
||||||
|
|
||||||
|
/// Directory for compiled application
|
||||||
|
Directory get buildApplicationDirectory => getDirectory(
|
||||||
|
buildPackagesDirectory.uri.resolve("${sourceApplicationPubspec.name}/"),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Gets dependency package location relative to [sourceApplicationDirectory].
|
||||||
|
Future<PackageConfig> get packageConfig async {
|
||||||
|
return _packageConfig ??=
|
||||||
|
(await findPackageConfig(sourceApplicationDirectory))!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [Directory] at [uri], creates it recursively if it doesn't exist.
|
||||||
|
Directory getDirectory(Uri uri) {
|
||||||
|
final dir = Directory.fromUri(uri);
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
dir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a [File] at [uri], creates all parent directories recursively if necessary.
|
||||||
|
File getFile(Uri uri) {
|
||||||
|
final file = File.fromUri(uri);
|
||||||
|
if (!file.parent.existsSync()) {
|
||||||
|
file.parent.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Package?> getPackageFromUri(Uri? uri) async {
|
||||||
|
if (uri == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (uri.scheme == "package") {
|
||||||
|
final segments = uri.pathSegments;
|
||||||
|
return (await packageConfig)[segments.first]!;
|
||||||
|
} else if (!uri.isAbsolute) {
|
||||||
|
throw ArgumentError("'uri' must be absolute or a package URI");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> getImportDirectives({
|
||||||
|
Uri? uri,
|
||||||
|
String? source,
|
||||||
|
bool alsoImportOriginalFile = false,
|
||||||
|
}) async {
|
||||||
|
if (uri != null && source != null) {
|
||||||
|
throw ArgumentError(
|
||||||
|
"either uri or source must be non-null, but not both",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null && source == null) {
|
||||||
|
throw ArgumentError(
|
||||||
|
"either uri or source must be non-null, but not both",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alsoImportOriginalFile == true && uri == null) {
|
||||||
|
throw ArgumentError(
|
||||||
|
"flag 'alsoImportOriginalFile' may only be set if 'uri' is also set",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final Package? package = await getPackageFromUri(uri);
|
||||||
|
final String? trailingSegments = uri?.pathSegments.sublist(1).join('/');
|
||||||
|
final fileUri =
|
||||||
|
package?.packageUriRoot.resolve(trailingSegments ?? '') ?? uri;
|
||||||
|
final text = source ?? File.fromUri(fileUri!).readAsStringSync();
|
||||||
|
final importRegex = RegExp("import [\\'\\\"]([^\\'\\\"]*)[\\'\\\"];");
|
||||||
|
|
||||||
|
final imports = importRegex.allMatches(text).map((m) {
|
||||||
|
final importedUri = Uri.parse(m.group(1)!);
|
||||||
|
|
||||||
|
if (!importedUri.isAbsolute) {
|
||||||
|
final path = fileUri
|
||||||
|
?.resolve(importedUri.path)
|
||||||
|
.toFilePath(windows: Platform.isWindows);
|
||||||
|
return "import 'file:${absolute(path!)}';";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.substring(m.start, m.end);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (alsoImportOriginalFile) {
|
||||||
|
imports.add("import '$uri';");
|
||||||
|
}
|
||||||
|
|
||||||
|
return imports;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ClassDeclaration?> getClassDeclarationFromType(Type type) async {
|
||||||
|
final classMirror = reflectType(type);
|
||||||
|
Uri uri = classMirror.location!.sourceUri;
|
||||||
|
if (!classMirror.location!.sourceUri.isAbsolute) {
|
||||||
|
final Package? package = await getPackageFromUri(uri);
|
||||||
|
uri = package!.packageUriRoot;
|
||||||
|
}
|
||||||
|
return analyzer.getClassFromFile(
|
||||||
|
MirrorSystem.getName(classMirror.simpleName),
|
||||||
|
uri,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<FieldDeclaration?> _getField(ClassMirror type, String propertyName) {
|
||||||
|
return getClassDeclarationFromType(type.reflectedType).then((cd) {
|
||||||
|
try {
|
||||||
|
return cd!.members.firstWhere(
|
||||||
|
(m) => (m as FieldDeclaration)
|
||||||
|
.fields
|
||||||
|
.variables
|
||||||
|
.any((v) => v.name.value() == propertyName),
|
||||||
|
) as FieldDeclaration;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Annotation>> getAnnotationsFromField(
|
||||||
|
Type type1,
|
||||||
|
String propertyName,
|
||||||
|
) async {
|
||||||
|
var type = reflectClass(type1);
|
||||||
|
FieldDeclaration? field = await _getField(type, propertyName);
|
||||||
|
while (field == null) {
|
||||||
|
type = type.superclass!;
|
||||||
|
if (type.reflectedType == Object) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
field = await _getField(type, propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.metadata;
|
||||||
|
}
|
||||||
|
}
|
89
packages/runtime/lib/src/build_manager.dart
Normal file
89
packages/runtime/lib/src/build_manager.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:analyzer/dart/analysis/results.dart';
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:protevus_isolate/isolate.dart';
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
import 'package:io/io.dart';
|
||||||
|
|
||||||
|
class BuildExecutable extends Executable {
|
||||||
|
BuildExecutable(Map<String, dynamic> message) : super(message) {
|
||||||
|
context = BuildContext.fromMap(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final BuildContext context;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future execute() async {
|
||||||
|
final build = Build(context);
|
||||||
|
await build.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BuildManager {
|
||||||
|
/// Creates a new build manager to compile a non-mirrored build.
|
||||||
|
BuildManager(this.context);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
|
||||||
|
Uri get sourceDirectoryUri => context.sourceApplicationDirectory.uri;
|
||||||
|
|
||||||
|
Future build() async {
|
||||||
|
if (!context.buildDirectory.existsSync()) {
|
||||||
|
context.buildDirectory.createSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here is where we need to provide a temporary copy of the script file with the main function stripped;
|
||||||
|
// this is because when the RuntimeGenerator loads, it needs Mirror access to any declarations in this file
|
||||||
|
var scriptSource = context.source;
|
||||||
|
final strippedScriptFile = File.fromUri(context.targetScriptFileUri)
|
||||||
|
..writeAsStringSync(scriptSource);
|
||||||
|
final analyzer = CodeAnalyzer(strippedScriptFile.absolute.uri);
|
||||||
|
final analyzerContext = analyzer.contexts.contextFor(analyzer.path);
|
||||||
|
final parsedUnit = analyzerContext.currentSession
|
||||||
|
.getParsedUnit(analyzer.path) as ParsedUnitResult;
|
||||||
|
|
||||||
|
final mainFunctions = parsedUnit.unit.declarations
|
||||||
|
.whereType<FunctionDeclaration>()
|
||||||
|
.where((f) => f.name.value() == "main")
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (final f in mainFunctions.reversed) {
|
||||||
|
scriptSource = scriptSource.replaceRange(f.offset, f.end, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
strippedScriptFile.writeAsStringSync(scriptSource);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await copyPath(
|
||||||
|
context.sourceApplicationDirectory.uri.resolve('test/not_tests').path,
|
||||||
|
context.buildDirectoryUri.resolve('not_tests').path);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
await IsolateExecutor.run(
|
||||||
|
BuildExecutable(context.safeMap),
|
||||||
|
packageConfigURI:
|
||||||
|
sourceDirectoryUri.resolve('.dart_tool/package_config.json'),
|
||||||
|
imports: [
|
||||||
|
"package:conduit_runtime/runtime.dart",
|
||||||
|
context.targetScriptFileUri.toString()
|
||||||
|
],
|
||||||
|
logHandler: (s) => print(s), //ignore: avoid_print
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future clean() async {
|
||||||
|
if (context.buildDirectory.existsSync()) {
|
||||||
|
context.buildDirectory.deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
packages/runtime/lib/src/compiler.dart
Normal file
40
packages/runtime/lib/src/compiler.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
abstract class Compiler {
|
||||||
|
/// Modifies a package on the filesystem in order to remove dart:mirrors from the package.
|
||||||
|
///
|
||||||
|
/// A copy of this compiler's package will be written to [destinationDirectory].
|
||||||
|
/// This method is overridden to modify the contents of that directory
|
||||||
|
/// to remove all uses of dart:mirrors.
|
||||||
|
///
|
||||||
|
/// Packages should export their [Compiler] in their main library file and only
|
||||||
|
/// import mirrors in files directly or transitively imported by the Compiler file.
|
||||||
|
/// This method should remove that export statement and therefore remove all transitive mirror imports.
|
||||||
|
void deflectPackage(Directory destinationDirectory);
|
||||||
|
|
||||||
|
/// Returns a map of runtime objects that can be used at runtime while running in mirrored mode.
|
||||||
|
Map<String, Object> compile(MirrorContext context);
|
||||||
|
|
||||||
|
void didFinishPackageGeneration(BuildContext context) {}
|
||||||
|
|
||||||
|
List<Uri> getUrisToResolve(BuildContext context) => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runtimes that generate source code implement this method.
|
||||||
|
abstract class SourceCompiler {
|
||||||
|
/// The source code, including directives, that declare a class that is equivalent in behavior to this runtime.
|
||||||
|
Future<String> compile(BuildContext ctx) async {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
}
|
76
packages/runtime/lib/src/context.dart
Normal file
76
packages/runtime/lib/src/context.dart
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
/// Contextual values used during runtime.
|
||||||
|
abstract class RuntimeContext {
|
||||||
|
/// The current [RuntimeContext] available to the executing application.
|
||||||
|
///
|
||||||
|
/// Is either a `MirrorContext` or a `GeneratedContext`,
|
||||||
|
/// depending on the execution type.
|
||||||
|
static final RuntimeContext current = instance;
|
||||||
|
|
||||||
|
/// The runtimes available to the executing application.
|
||||||
|
late RuntimeCollection runtimes;
|
||||||
|
|
||||||
|
/// Gets a runtime object for [type].
|
||||||
|
///
|
||||||
|
/// Callers typically invoke this method, passing their [runtimeType]
|
||||||
|
/// in order to retrieve their runtime object.
|
||||||
|
///
|
||||||
|
/// It is important to note that a runtime object must exist for every
|
||||||
|
/// class that extends a class that has a runtime. Use `MirrorContext.getSubclassesOf` when compiling.
|
||||||
|
///
|
||||||
|
/// In other words, if the type `Base` has a runtime and the type `Subclass` extends `Base`,
|
||||||
|
/// `Subclass` must also have a runtime. The runtime objects for both `Subclass` and `Base`
|
||||||
|
/// must be the same type.
|
||||||
|
dynamic operator [](Type type) => runtimes[type];
|
||||||
|
|
||||||
|
T coerce<T>(dynamic input);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RuntimeCollection {
|
||||||
|
RuntimeCollection(this.map);
|
||||||
|
|
||||||
|
final Map<String, Object> map;
|
||||||
|
|
||||||
|
Iterable<Object> get iterable => map.values;
|
||||||
|
|
||||||
|
Object operator [](Type t) {
|
||||||
|
//todo: optimize by keeping a cache where keys are of type [Type] to avoid the
|
||||||
|
// expensive indexOf and substring calls in this method
|
||||||
|
final typeName = t.toString();
|
||||||
|
final r = map[typeName];
|
||||||
|
if (r != null) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
final genericIndex = typeName.indexOf("<");
|
||||||
|
if (genericIndex == -1) {
|
||||||
|
throw ArgumentError("Runtime not found for type '$t'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final genericTypeName = typeName.substring(0, genericIndex);
|
||||||
|
final out = map[genericTypeName];
|
||||||
|
if (out == null) {
|
||||||
|
throw ArgumentError("Runtime not found for type '$t'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prevents a type from being compiled when it otherwise would be.
|
||||||
|
///
|
||||||
|
/// Annotate a type with the const instance of this type to prevent its
|
||||||
|
/// compilation.
|
||||||
|
class PreventCompilation {
|
||||||
|
const PreventCompilation();
|
||||||
|
}
|
20
packages/runtime/lib/src/exceptions.dart
Normal file
20
packages/runtime/lib/src/exceptions.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TypeCoercionException implements Exception {
|
||||||
|
TypeCoercionException(this.expectedType, this.actualType);
|
||||||
|
|
||||||
|
final Type expectedType;
|
||||||
|
final Type actualType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "input is not expected type '$expectedType' (input is '$actualType')";
|
||||||
|
}
|
||||||
|
}
|
123
packages/runtime/lib/src/generator.dart
Normal file
123
packages/runtime/lib/src/generator.dart
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
const String _directiveToken = "___DIRECTIVES___";
|
||||||
|
const String _assignmentToken = "___ASSIGNMENTS___";
|
||||||
|
|
||||||
|
class RuntimeGenerator {
|
||||||
|
final _elements = <_RuntimeElement>[];
|
||||||
|
|
||||||
|
void addRuntime({required String name, required String source}) {
|
||||||
|
_elements.add(_RuntimeElement(name, source));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> writeTo(Uri directoryUri) async {
|
||||||
|
final dir = Directory.fromUri(directoryUri);
|
||||||
|
final libDir = Directory.fromUri(dir.uri.resolve("lib/"));
|
||||||
|
final srcDir = Directory.fromUri(libDir.uri.resolve("src/"));
|
||||||
|
if (!libDir.existsSync()) {
|
||||||
|
libDir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
if (!srcDir.existsSync()) {
|
||||||
|
srcDir.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final libraryFile =
|
||||||
|
File.fromUri(libDir.uri.resolve("generated_runtime.dart"));
|
||||||
|
await libraryFile.writeAsString(loaderSource);
|
||||||
|
|
||||||
|
final pubspecFile = File.fromUri(dir.uri.resolve("pubspec.yaml"));
|
||||||
|
await pubspecFile.writeAsString(pubspecSource);
|
||||||
|
|
||||||
|
await Future.forEach(_elements, (_RuntimeElement e) async {
|
||||||
|
final file = File.fromUri(srcDir.uri.resolveUri(e.relativeUri));
|
||||||
|
if (!file.parent.existsSync()) {
|
||||||
|
file.parent.createSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await file.writeAsString(e.source);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String get pubspecSource => """
|
||||||
|
name: generated_runtime
|
||||||
|
description: A runtime generated by package:conduit_runtime
|
||||||
|
version: 1.0.0
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.4.0 <4.0.0'
|
||||||
|
""";
|
||||||
|
|
||||||
|
String get _loaderShell => """
|
||||||
|
import 'package:conduit_runtime/runtime.dart';
|
||||||
|
import 'package:conduit_runtime/slow_coerce.dart' as runtime_cast;
|
||||||
|
$_directiveToken
|
||||||
|
|
||||||
|
RuntimeContext instance = GeneratedContext._();
|
||||||
|
|
||||||
|
class GeneratedContext extends RuntimeContext {
|
||||||
|
GeneratedContext._() {
|
||||||
|
final map = <String, Object>{};
|
||||||
|
|
||||||
|
$_assignmentToken
|
||||||
|
|
||||||
|
runtimes = RuntimeCollection(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
T coerce<T>(dynamic input) {
|
||||||
|
return runtime_cast.cast<T>(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
String get loaderSource {
|
||||||
|
return _loaderShell
|
||||||
|
.replaceFirst(_directiveToken, _directives)
|
||||||
|
.replaceFirst(_assignmentToken, _assignments);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _directives {
|
||||||
|
final buf = StringBuffer();
|
||||||
|
|
||||||
|
for (final e in _elements) {
|
||||||
|
buf.writeln(
|
||||||
|
"import 'src/${e.relativeUri.toFilePath(windows: Platform.isWindows)}' as ${e.importAlias};",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get _assignments {
|
||||||
|
final buf = StringBuffer();
|
||||||
|
|
||||||
|
for (final e in _elements) {
|
||||||
|
buf.writeln("map['${e.typeName}'] = ${e.importAlias}.instance;");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RuntimeElement {
|
||||||
|
_RuntimeElement(this.typeName, this.source);
|
||||||
|
|
||||||
|
final String typeName;
|
||||||
|
final String source;
|
||||||
|
|
||||||
|
Uri get relativeUri => Uri.file("${typeName.toLowerCase()}.dart");
|
||||||
|
|
||||||
|
String get importAlias {
|
||||||
|
return "g_${typeName.toLowerCase()}";
|
||||||
|
}
|
||||||
|
}
|
79
packages/runtime/lib/src/mirror_coerce.dart
Normal file
79
packages/runtime/lib/src/mirror_coerce.dart
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:mirrors';
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
Object runtimeCast(Object object, TypeMirror intoType) {
|
||||||
|
final exceptionToThrow =
|
||||||
|
TypeCoercionException(intoType.reflectedType, object.runtimeType);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final objectType = reflect(object).type;
|
||||||
|
if (objectType.isAssignableTo(intoType)) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intoType.isSubtypeOf(reflectType(List))) {
|
||||||
|
if (object is! List) {
|
||||||
|
throw exceptionToThrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final elementType = intoType.typeArguments.first;
|
||||||
|
final elements = object.map((e) => runtimeCast(e, elementType));
|
||||||
|
return (intoType as ClassMirror).newInstance(#from, [elements]).reflectee;
|
||||||
|
} else if (intoType.isSubtypeOf(reflectType(Map, [String, dynamic]))) {
|
||||||
|
if (object is! Map<String, dynamic>) {
|
||||||
|
throw exceptionToThrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
final output = (intoType as ClassMirror)
|
||||||
|
.newInstance(Symbol.empty, []).reflectee as Map<String, dynamic>;
|
||||||
|
final valueType = intoType.typeArguments.last;
|
||||||
|
object.forEach((key, val) {
|
||||||
|
output[key] = runtimeCast(val, valueType);
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
} on TypeError {
|
||||||
|
throw exceptionToThrow;
|
||||||
|
} on TypeCoercionException {
|
||||||
|
throw exceptionToThrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exceptionToThrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTypeFullyPrimitive(TypeMirror type) {
|
||||||
|
if (type == reflectType(dynamic)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.isSubtypeOf(reflectType(List))) {
|
||||||
|
return isTypeFullyPrimitive(type.typeArguments.first);
|
||||||
|
} else if (type.isSubtypeOf(reflectType(Map))) {
|
||||||
|
return isTypeFullyPrimitive(type.typeArguments.first) &&
|
||||||
|
isTypeFullyPrimitive(type.typeArguments.last);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.isSubtypeOf(reflectType(num))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.isSubtypeOf(reflectType(String))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.isSubtypeOf(reflectType(bool))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
90
packages/runtime/lib/src/mirror_context.dart
Normal file
90
packages/runtime/lib/src/mirror_context.dart
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the Protevus Platform.
|
||||||
|
*
|
||||||
|
* (C) Protevus <developers@protevus.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'dart:mirrors';
|
||||||
|
|
||||||
|
import 'package:protevus_runtime/runtime.dart';
|
||||||
|
|
||||||
|
RuntimeContext instance = MirrorContext._();
|
||||||
|
|
||||||
|
class MirrorContext extends RuntimeContext {
|
||||||
|
MirrorContext._() {
|
||||||
|
final m = <String, Object>{};
|
||||||
|
|
||||||
|
for (final c in compilers) {
|
||||||
|
final compiledRuntimes = c.compile(this);
|
||||||
|
if (m.keys.any((k) => compiledRuntimes.keys.contains(k))) {
|
||||||
|
final matching = m.keys.where((k) => compiledRuntimes.keys.contains(k));
|
||||||
|
throw StateError(
|
||||||
|
'Could not compile. Type conflict for the following types: ${matching.join(", ")}.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
m.addAll(compiledRuntimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
runtimes = RuntimeCollection(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<ClassMirror> types = currentMirrorSystem()
|
||||||
|
.libraries
|
||||||
|
.values
|
||||||
|
.where((lib) => lib.uri.scheme == "package" || lib.uri.scheme == "file")
|
||||||
|
.expand((lib) => lib.declarations.values)
|
||||||
|
.whereType<ClassMirror>()
|
||||||
|
.where((cm) => firstMetadataOfType<PreventCompilation>(cm) == null)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Compiler> get compilers {
|
||||||
|
return types
|
||||||
|
.where((b) => b.isSubclassOf(reflectClass(Compiler)) && !b.isAbstract)
|
||||||
|
.map((b) => b.newInstance(Symbol.empty, []).reflectee as Compiler)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ClassMirror> getSubclassesOf(Type type) {
|
||||||
|
final mirror = reflectClass(type);
|
||||||
|
return types.where((decl) {
|
||||||
|
if (decl.isAbstract) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decl.isSubclassOf(mirror)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decl.hasReflectedType) {
|
||||||
|
if (decl.reflectedType == type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
T coerce<T>(dynamic input) {
|
||||||
|
try {
|
||||||
|
return input as T;
|
||||||
|
} catch (_) {
|
||||||
|
return runtimeCast(input, reflectType(T)) as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
T? firstMetadataOfType<T>(DeclarationMirror dm, {TypeMirror? dynamicType}) {
|
||||||
|
final tMirror = dynamicType ?? reflectType(T);
|
||||||
|
try {
|
||||||
|
return dm.metadata
|
||||||
|
.firstWhere((im) => im.type.isSubtypeOf(tMirror))
|
||||||
|
.reflectee as T;
|
||||||
|
} on StateError {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
24
packages/runtime/pubspec.yaml
Normal file
24
packages/runtime/pubspec.yaml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
name: protevus_runtime
|
||||||
|
description: Provides behaviors and base types for packages that can use mirrors and be AOT compiled.
|
||||||
|
version: 0.0.1
|
||||||
|
homepage: https://protevus.com
|
||||||
|
documentation: https://docs.protevus.com
|
||||||
|
repository: https://git.protevus.com/protevus/platform
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ^3.4.3
|
||||||
|
|
||||||
|
# Add regular dependencies here.
|
||||||
|
dependencies:
|
||||||
|
analyzer: ^6.5.0
|
||||||
|
args: ^2.0.0
|
||||||
|
protevus_isolate: ^0.0.1
|
||||||
|
io: ^1.0.4
|
||||||
|
package_config: ^2.1.0
|
||||||
|
path: ^1.9.0
|
||||||
|
pubspec_parse: ^1.2.3
|
||||||
|
yaml: ^3.1.2
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
lints: ^3.0.0
|
||||||
|
test: ^1.24.0
|
Loading…
Reference in a new issue