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