update(conduit): updating hashing, runtime and isolate packages
This commit is contained in:
parent
bbbb6ab314
commit
9dcc2f5282
28 changed files with 1821 additions and 62 deletions
29
packages/hashing/lib/hashing.dart
Normal file
29
packages/hashing/lib/hashing.dart
Normal 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';
|
140
packages/hashing/lib/src/pbkdf2.dart
Normal file
140
packages/hashing/lib/src/pbkdf2.dart
Normal 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() {}
|
||||||
|
}
|
34
packages/hashing/lib/src/salt.dart
Normal file
34
packages/hashing/lib/src/salt.dart
Normal 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));
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ environment:
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
# path: ^1.8.0
|
crypto: ^3.0.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^3.0.0
|
lints: ^3.0.0
|
||||||
|
|
|
@ -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.
|
/// These components work together to facilitate concurrent programming and
|
||||||
library;
|
/// improve performance in Dart applications by leveraging isolates.
|
||||||
|
library isolate;
|
||||||
|
|
||||||
export 'src/isolate_base.dart';
|
export 'package:protevus_isolate/src/executable.dart';
|
||||||
|
export 'package:protevus_isolate/src/executor.dart';
|
||||||
// TODO: Export any libraries intended for clients of this package.
|
export 'package:protevus_isolate/src/source_generator.dart';
|
||||||
|
|
63
packages/isolate/lib/src/executable.dart
Normal file
63
packages/isolate/lib/src/executable.dart
Normal 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});
|
||||||
|
}
|
||||||
|
}
|
128
packages/isolate/lib/src/executor.dart
Normal file
128
packages/isolate/lib/src/executor.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
104
packages/isolate/lib/src/source_generator.dart
Normal file
104
packages/isolate/lib/src/source_generator.dart
Normal 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);
|
||||||
|
}
|
|
@ -1,14 +1,18 @@
|
||||||
name: protevus_isolate
|
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
|
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:
|
environment:
|
||||||
sdk: ^3.4.3
|
sdk: ^3.4.3
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
# path: ^1.8.0
|
analyzer: ^6.5.0
|
||||||
|
glob: ^2.1.2
|
||||||
|
path: ^1.9.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^3.0.0
|
lints: ^3.0.0
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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.
|
/// Removes dart:mirror from a replica of this package, and adds
|
||||||
library;
|
/// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,14 +1,23 @@
|
||||||
name: protevus_runtime
|
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
|
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:
|
environment:
|
||||||
sdk: ^3.4.3
|
sdk: ^3.4.3
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
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:
|
dev_dependencies:
|
||||||
lints: ^3.0.0
|
lints: ^3.0.0
|
||||||
|
|
0
packages/runtime/test/.gitkeep
Normal file
0
packages/runtime/test/.gitkeep
Normal 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in a new issue