add(conduit): refactoring conduit

This commit is contained in:
Patrick Stewart 2024-09-03 13:19:16 -07:00
parent ceb360eb9c
commit 7a095316dc
18 changed files with 1379 additions and 0 deletions

7
packages/runtime/.gitignore vendored Normal file
View 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

View file

@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View 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.

View 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

View 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);
}
}

View 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);
}
}

View 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),
);
}
}

View 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]!;
}
}

View 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;
}
}

View 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);
}
}
}

View 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();
}
}

View 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();
}

View 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')";
}
}

View 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()}";
}
}

View 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;
}

View 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;
}
}

View 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