onepub-dev / dcli

An extensive library and tooling for building console/cli applications and scripts using the Dart programming language.
236 stars 26 forks source link

Add support for a watcher that incrementally compiles files in the bin directory #170

Closed bsutton closed 3 months ago

bsutton commented 2 years ago

The following gist was posted on reddit:

https://gist.github.com/ykmnkmi/02f4810ecc6e501b2939bf9adfefa116

The post makes me wonder if it might be worth while having a dcli mode that can incrementally compile files in the bin directory as the project code changes. A problem would be that it would have to watch the entire project which means that it would be compiling almost continually. Still it would be nice finish editing and be able to immediately run compiled version of the modified command.

import 'dart:io' show Platform, Process, exit, stdin;

import 'package:frontend_server_client/frontend_server_client.dart' show FrontendServerClient;
import 'package:path/path.dart' as path;
import 'package:stack_trace/stack_trace.dart' show Trace;
import 'package:watcher/watcher.dart' show Watcher;

const String platformDill = 'lib/_internal/vm_platform_strong.dill';

Future<void> main(List<String> arguments) async {
  if (arguments.isEmpty) {
    print('usage: dart main.dart path-to-dart-file [arguments]');
    throw StateError('no dart file');
  }

  var filePath = arguments[0];
  var fileUri = Uri.file(filePath);

  var executable = Platform.resolvedExecutable;
  var sdkRoot = path.dirname(path.dirname(executable));
  var outputDill = path.join('.dart_tool', 'incremental_build.dill');
  arguments[0] = outputDill;

  var client = await FrontendServerClient.start(filePath, outputDill, platformDill, sdkRoot: sdkRoot);

  Future<void> run() async {
    try {
      var result = await Process.run(executable, arguments);

      if (result.stdout != null) {
        print(result.stdout.toString().trimRight());
      }

      if (result.stderr != null) {
        print(result.stderr);
      }
    } catch (error, trace) {
      print(error);
      print(Trace.format(trace));
    }
  }

  var invalidated = <Uri>{};

  Future<void> reload({bool runAfter = true}) async {
    try {
      var result = await client.compile([fileUri, ...invalidated]);
      invalidated.clear();

      if (result == null) {
        print('no compilation result, rejecting');
        return client.reject();
      }

      if (result.errorCount > 0) {
        print('compiled with ${result.errorCount} error(s)');
        return client.reject();
      }

      for (var line in result.compilerOutputLines) {
        print(line);
      }

      client.accept();
      client.reset();

      if (runAfter) {
        await run();
      }
    } catch (error, trace) {
      print(error);
      print(Trace.format(trace));
    }
  }

  await reload();
  await watch(invalidated);

  stdin.echoMode = false;
  stdin.lineMode = false;

  stdin.listen((bytes) async {
    switch (bytes[0]) {
      // r
      case 114:
        print('reloading...');
        await reload();
        break;
      // q
      case 113:
        exit(0);
      // h
      case 104:
        print('usage: press r to reload and q to exit');
        break;
      default:
    }
  });
}

Future<void> watch(Set<Uri> invalidated, [Duration? pollingDelay]) {
  var watcher = Watcher('lib');

  watcher.events.listen((event) {
    print(event);
    invalidated.add(Uri.file(event.path));
  });

  return watcher.ready;
}

// ignore_for_file: avoid_print
bsutton commented 3 months ago

doesn't look like this is viable.