update(conduit): updating hashing, runtime and isolate packages

This commit is contained in:
Patrick Stewart 2024-08-04 19:55:10 -07:00
parent bbbb6ab314
commit 9dcc2f5282
28 changed files with 1821 additions and 62 deletions

View file

@ -0,0 +1,29 @@
/*
* 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.
*/
/*
* 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.
*/
/// This library provides hashing functionality for the Protevus Platform.
///
/// It exports two main components:
/// - PBKDF2 (Password-Based Key Derivation Function 2) implementation
/// - Salt generation utilities
///
/// These components are essential for secure password hashing and storage.
library hashing;
export 'package:protevus_hashing/src/pbkdf2.dart';
export 'package:protevus_hashing/src/salt.dart';

View file

@ -0,0 +1,140 @@
/*
* 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:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
/// Instances of this type derive a key from a password, salt, and hash function.
///
/// https://en.wikipedia.org/wiki/PBKDF2
class PBKDF2 {
/// Creates instance capable of generating a key.
///
/// [hashAlgorithm] defaults to [sha256].
PBKDF2({Hash? hashAlgorithm}) {
this.hashAlgorithm = hashAlgorithm ?? sha256;
}
Hash get hashAlgorithm => _hashAlgorithm;
set hashAlgorithm(Hash algorithm) {
_hashAlgorithm = algorithm;
_blockSize = _hashAlgorithm.convert([1, 2, 3]).bytes.length;
}
late Hash _hashAlgorithm;
late int _blockSize;
/// Hashes a [password] with a given [salt].
///
/// The length of this return value will be [keyLength].
///
/// See [generateAsBase64String] for generating a random salt.
///
/// See also [generateBase64Key], which base64 encodes the key returned from this method for storage.
List<int> generateKey(
String password,
String salt,
int rounds,
int keyLength,
) {
if (keyLength > (pow(2, 32) - 1) * _blockSize) {
throw PBKDF2Exception("Derived key too long");
}
final numberOfBlocks = (keyLength / _blockSize).ceil();
final hmac = Hmac(hashAlgorithm, utf8.encode(password));
final key = ByteData(keyLength);
var offset = 0;
final saltBytes = utf8.encode(salt);
final saltLength = saltBytes.length;
final inputBuffer = ByteData(saltBytes.length + 4)
..buffer.asUint8List().setRange(0, saltBytes.length, saltBytes);
for (var blockNumber = 1; blockNumber <= numberOfBlocks; blockNumber++) {
inputBuffer.setUint8(saltLength, blockNumber >> 24);
inputBuffer.setUint8(saltLength + 1, blockNumber >> 16);
inputBuffer.setUint8(saltLength + 2, blockNumber >> 8);
inputBuffer.setUint8(saltLength + 3, blockNumber);
final block = _XORDigestSink.generate(inputBuffer, hmac, rounds);
var blockLength = _blockSize;
if (offset + blockLength > keyLength) {
blockLength = keyLength - offset;
}
key.buffer.asUint8List().setRange(offset, offset + blockLength, block);
offset += blockLength;
}
return key.buffer.asUint8List();
}
/// Hashed a [password] with a given [salt] and base64 encodes the result.
///
/// This method invokes [generateKey] and base64 encodes the result.
String generateBase64Key(
String password,
String salt,
int rounds,
int keyLength,
) {
const converter = Base64Encoder();
return converter.convert(generateKey(password, salt, rounds, keyLength));
}
}
/// Thrown when [PBKDF2] throws an exception.
class PBKDF2Exception implements Exception {
PBKDF2Exception(this.message);
String message;
@override
String toString() => "PBKDF2Exception: $message";
}
class _XORDigestSink implements Sink<Digest> {
_XORDigestSink(ByteData inputBuffer, Hmac hmac) {
lastDigest = hmac.convert(inputBuffer.buffer.asUint8List()).bytes;
bytes = ByteData(lastDigest.length)
..buffer.asUint8List().setRange(0, lastDigest.length, lastDigest);
}
static Uint8List generate(ByteData inputBuffer, Hmac hmac, int rounds) {
final hashSink = _XORDigestSink(inputBuffer, hmac);
// If rounds == 1, we have already run the first hash in the constructor
// so this loop won't run.
for (var round = 1; round < rounds; round++) {
final hmacSink = hmac.startChunkedConversion(hashSink);
hmacSink.add(hashSink.lastDigest);
hmacSink.close();
}
return hashSink.bytes.buffer.asUint8List();
}
late ByteData bytes;
late List<int> lastDigest;
@override
void add(Digest digest) {
lastDigest = digest.bytes;
for (var i = 0; i < digest.bytes.length; i++) {
bytes.setUint8(i, bytes.getUint8(i) ^ lastDigest[i]);
}
}
@override
void close() {}
}

View file

@ -0,0 +1,34 @@
/*
* 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:convert';
import 'dart:math';
import 'dart:typed_data';
/// Generates a random salt of [length] bytes from a cryptographically secure random number generator.
///
/// Each element of this list is a byte.
List<int> generate(int length) {
final buffer = Uint8List(length);
final rng = Random.secure();
for (var i = 0; i < length; i++) {
buffer[i] = rng.nextInt(256);
}
return buffer;
}
/// Generates a random salt of [length] bytes from a cryptographically secure random number generator and encodes it to Base64.
///
/// [length] is the number of bytes generated, not the [length] of the base64 encoded string returned. Decoding
/// the base64 encoded string will yield [length] number of bytes.
String generateAsBase64String(int length) {
const encoder = Base64Encoder();
return encoder.convert(generate(length));
}

View file

@ -10,7 +10,7 @@ environment:
# Add regular dependencies here.
dependencies:
# path: ^1.8.0
crypto: ^3.0.3
dev_dependencies:
lints: ^3.0.0

View file

@ -1,8 +1,31 @@
/// Support for doing something awesome.
/*
* 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.
*/
/*
* 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.
*/
/// This library provides functionality for working with isolates in Dart.
/// It exports three main components:
/// 1. Executable: Defines the structure for tasks that can be executed in isolates.
/// 2. Executor: Provides mechanisms for running executables in isolates.
/// 3. SourceGenerator: Offers utilities for generating source code for isolates.
///
/// More dartdocs go here.
library;
/// These components work together to facilitate concurrent programming and
/// improve performance in Dart applications by leveraging isolates.
library isolate;
export 'src/isolate_base.dart';
// TODO: Export any libraries intended for clients of this package.
export 'package:protevus_isolate/src/executable.dart';
export 'package:protevus_isolate/src/executor.dart';
export 'package:protevus_isolate/src/source_generator.dart';

View file

@ -0,0 +1,63 @@
/*
* 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:isolate';
import 'dart:mirrors';
abstract class Executable<T extends Object?> {
Executable(this.message) : _sendPort = message["_sendPort"];
Future<T> execute();
final Map<String, dynamic> message;
final SendPort? _sendPort;
U instanceOf<U>(
String typeName, {
List positionalArguments = const [],
Map<Symbol, dynamic> namedArguments = const {},
Symbol constructorName = Symbol.empty,
}) {
ClassMirror? typeMirror = currentMirrorSystem()
.isolate
.rootLibrary
.declarations[Symbol(typeName)] as ClassMirror?;
typeMirror ??= currentMirrorSystem()
.libraries
.values
.where((lib) => lib.uri.scheme == "package" || lib.uri.scheme == "file")
.expand((lib) => lib.declarations.values)
.firstWhere(
(decl) =>
decl is ClassMirror &&
MirrorSystem.getName(decl.simpleName) == typeName,
orElse: () => throw ArgumentError(
"Unknown type '$typeName'. Did you forget to import it?",
),
) as ClassMirror?;
return typeMirror!
.newInstance(
constructorName,
positionalArguments,
namedArguments,
)
.reflectee as U;
}
void send(dynamic message) {
_sendPort!.send(message);
}
void log(String message) {
_sendPort!.send({"_line_": message});
}
}

View file

@ -0,0 +1,128 @@
/*
* 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';
import 'dart:isolate';
import 'package:protevus_isolate/isolate.dart';
class IsolateExecutor<U> {
IsolateExecutor(
this.generator, {
this.packageConfigURI,
this.message = const {},
});
final SourceGenerator generator;
final Map<String, dynamic> message;
final Uri? packageConfigURI;
final Completer completer = Completer();
Stream<dynamic> get events => _eventListener.stream;
Stream<String> get console => _logListener.stream;
final StreamController<String> _logListener = StreamController<String>();
final StreamController<dynamic> _eventListener = StreamController<dynamic>();
Future<U> execute() async {
if (packageConfigURI != null &&
!File.fromUri(packageConfigURI!).existsSync()) {
throw StateError(
"Package file '$packageConfigURI' not found. Run 'pub get' and retry.",
);
}
final scriptSource = Uri.encodeComponent(await generator.scriptSource);
final onErrorPort = ReceivePort()
..listen((err) async {
if (err is List) {
final stack =
StackTrace.fromString(err.last.replaceAll(scriptSource, ""));
completer.completeError(StateError(err.first), stack);
} else {
completer.completeError(err);
}
});
final controlPort = ReceivePort()
..listen((results) {
if (results is Map && results.length == 1) {
if (results.containsKey("_result")) {
completer.complete(results['_result']);
return;
} else if (results.containsKey("_line_")) {
_logListener.add(results["_line_"]);
return;
}
}
_eventListener.add(results);
});
try {
message["_sendPort"] = controlPort.sendPort;
final dataUri = Uri.parse(
"data:application/dart;charset=utf-8,$scriptSource",
);
await Isolate.spawnUri(
dataUri,
[],
message,
onError: onErrorPort.sendPort,
packageConfig: packageConfigURI,
automaticPackageResolution: packageConfigURI == null,
);
return await completer.future;
} catch (e) {
print(e);
rethrow;
} finally {
onErrorPort.close();
controlPort.close();
_eventListener.close();
_logListener.close();
}
}
static Future<T> run<T>(
Executable<T> executable, {
List<String> imports = const [],
Uri? packageConfigURI,
String? additionalContents,
List<Type> additionalTypes = const [],
void Function(dynamic event)? eventHandler,
void Function(String line)? logHandler,
}) async {
final source = SourceGenerator(
executable.runtimeType,
imports: imports,
additionalContents: additionalContents,
additionalTypes: additionalTypes,
);
final executor = IsolateExecutor<T>(
source,
packageConfigURI: packageConfigURI,
message: executable.message,
);
if (eventHandler != null) {
executor.events.listen(eventHandler);
}
if (logHandler != null) {
executor.console.listen(logHandler);
}
return executor.execute();
}
}

View file

@ -1,6 +0,0 @@
// TODO: Put public facing types in this file.
/// Checks if you are awesome. Spoiler: you are.
class Awesome {
bool get isAwesome => true;
}

View file

@ -0,0 +1,104 @@
/*
* 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:isolate';
import 'dart:mirrors';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/context_builder.dart';
import 'package:analyzer/dart/analysis/context_locator.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:path/path.dart';
import 'package:protevus_isolate/isolate.dart';
class SourceGenerator {
SourceGenerator(
this.executableType, {
this.imports = const [],
this.additionalTypes = const [],
this.additionalContents,
});
Type executableType;
String get typeName =>
MirrorSystem.getName(reflectType(executableType).simpleName);
final List<String> imports;
final String? additionalContents;
final List<Type> additionalTypes;
Future<String> get scriptSource async {
final typeSource = (await _getClass(executableType)).toSource();
final builder = StringBuffer();
builder.writeln("import 'dart:async';");
builder.writeln("import 'dart:isolate';");
builder.writeln("import 'dart:mirrors';");
for (final anImport in imports) {
builder.writeln("import '$anImport';");
}
builder.writeln(
"""
Future main (List<String> args, Map<String, dynamic> message) async {
final sendPort = message['_sendPort'];
final executable = $typeName(message);
final result = await executable.execute();
sendPort.send({"_result": result});
}
""",
);
builder.writeln(typeSource);
builder.writeln((await _getClass(Executable)).toSource());
for (final type in additionalTypes) {
final source = await _getClass(type);
builder.writeln(source.toSource());
}
if (additionalContents != null) {
builder.writeln(additionalContents);
}
return builder.toString();
}
static Future<ClassDeclaration> _getClass(Type type) async {
final uri =
await Isolate.resolvePackageUri(reflectClass(type).location!.sourceUri);
final path =
absolute(normalize(uri!.toFilePath(windows: Platform.isWindows)));
final context = _createContext(path);
final session = context.currentSession;
final unit = session.getParsedUnit(path) as ParsedUnitResult;
final typeName = MirrorSystem.getName(reflectClass(type).simpleName);
return unit.unit.declarations
.whereType<ClassDeclaration>()
.firstWhere((classDecl) => classDecl.name.value() == typeName);
}
}
AnalysisContext _createContext(
String path, {
ResourceProvider? resourceProvider,
}) {
resourceProvider ??= PhysicalResourceProvider.INSTANCE;
final builder = ContextBuilder(resourceProvider: resourceProvider);
final contextLocator = ContextLocator(
resourceProvider: resourceProvider,
);
final root = contextLocator.locateRoots(
includedPaths: [path],
);
return builder.createContext(contextRoot: root.first);
}

View file

@ -1,14 +1,18 @@
name: protevus_isolate
description: A starting point for Dart libraries or applications.
description: This library contains types that allow for executing code in a spawned isolate, perhaps with additional imports.
version: 0.0.1
# repository: https://github.com/my_org/my_repo
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:
# path: ^1.8.0
analyzer: ^6.5.0
glob: ^2.1.2
path: ^1.9.0
dev_dependencies:
lints: ^3.0.0

View file

@ -1,16 +0,0 @@
import 'package:protevus_isolate/isolate.dart';
import 'package:test/test.dart';
void main() {
group('A group of tests', () {
final awesome = Awesome();
setUp(() {
// Additional setup goes here.
});
test('First Test', () {
expect(awesome.isAwesome, isTrue);
});
});
}

View file

@ -1,8 +1,65 @@
/// Support for doing something awesome.
/*
* 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/src/compiler.dart';
import 'package:protevus_runtime/src/mirror_context.dart';
/// Compiler for the runtime package itself.
///
/// More dartdocs go here.
library;
/// 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) => {};
export 'src/runtime_base.dart';
@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';",
);
// TODO: Export any libraries intended for clients of this package.
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

@ -1,6 +0,0 @@
// TODO: Put public facing types in this file.
/// Checks if you are awesome. Spoiler: you are.
class Awesome {
bool get isAwesome => true;
}

View file

@ -1,14 +1,23 @@
name: protevus_runtime
description: A starting point for Dart libraries or applications.
description: Provides behaviors and base types for packages that can use mirrors and be AOT compiled.
version: 0.0.1
# repository: https://github.com/my_org/my_repo
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:
# path: ^1.8.0
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

View file

View file

@ -1,16 +0,0 @@
import 'package:protevus_runtime/runtime.dart';
import 'package:test/test.dart';
void main() {
group('A group of tests', () {
final awesome = Awesome();
setUp(() {
// Additional setup goes here.
});
test('First Test', () {
expect(awesome.isAwesome, isTrue);
});
});
}