Skip to content

Commit

Permalink
fix(mason): hook execution after pub cache clean (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Nov 8, 2022
1 parent 0a7e074 commit cbf958c
Show file tree
Hide file tree
Showing 14 changed files with 163 additions and 24 deletions.
76 changes: 52 additions & 24 deletions packages/mason/lib/src/hooks.dart
Expand Up @@ -261,32 +261,37 @@ class GeneratorHooks {
);
}

Future<void> _dartPubGet({required String workingDirectory}) async {
final result = await Process.run(
'dart',
['pub', 'get'],
workingDirectory: workingDirectory,
runInShell: true,
);
if (result.exitCode != ExitCode.success.code) {
throw HookDependencyInstallFailure(hook.path, '${result.stderr}');
}
}

var dependenciesInstalled = false;
Directory? hookCacheDir;
Uri? packageConfigUri;
if (pubspec != null) {
final directoryHash = sha1.convert(pubspec).toString();
final directory = Directory(
hookCacheDir = Directory(
p.join(Directory.systemTemp.path, '.mason', directoryHash),
);
final packageConfigFile = File(
p.join(directory.path, '.dart_tool', 'package_config.json'),
p.join(hookCacheDir.path, '.dart_tool', 'package_config.json'),
);

if (!packageConfigFile.existsSync()) {
await directory.create(recursive: true);
await hookCacheDir.create(recursive: true);
await File(
p.join(directory.path, 'pubspec.yaml'),
p.join(hookCacheDir.path, 'pubspec.yaml'),
).writeAsBytes(pubspec);

final result = await Process.run(
'dart',
['pub', 'get'],
workingDirectory: directory.path,
runInShell: true,
);

if (result.exitCode != 0) {
throw HookDependencyInstallFailure(hook.path, '${result.stderr}');
}
await _dartPubGet(workingDirectory: hookCacheDir.path);
dependenciesInstalled = true;
}

packageConfigUri = packageConfigFile.uri;
Expand All @@ -302,22 +307,45 @@ class GeneratorHooks {

if (uri == null) throw HookMissingRunException(hook.path);

final cwd = Directory.current;
Isolate? isolate;
try {
if (workingDirectory != null) Directory.current = workingDirectory;
isolate = await Isolate.spawnUri(
Future<Isolate> spawnIsolate(Uri uri) {
return Isolate.spawnUri(
uri,
[json.encode(vars)],
messagePort.sendPort,
paused: true,
packageConfig: packageConfigUri,
);
}

final cwd = Directory.current;
Isolate? isolate;
try {
if (workingDirectory != null) Directory.current = workingDirectory;
isolate = await spawnIsolate(uri);
} on IsolateSpawnException catch (error) {
Directory.current = cwd;
final msg = error.message;
final content = msg.contains('Error: ') ? msg.split('Error: ').last : msg;
throw HookRunException(hook.path, content.trim());
Never throwHookRunException(IsolateSpawnException error) {
Directory.current = cwd;
final msg = error.message;
final content =
msg.contains('Error: ') ? msg.split('Error: ').last : msg;
throw HookRunException(hook.path, content.trim());
}

final shouldRetry = !dependenciesInstalled && hookCacheDir != null;

if (!shouldRetry) throwHookRunException(error);

// Failure to spawn the isolate could be due to changes in the pub cache.
// We attempt to reinstall hook dependencies.
await _dartPubGet(workingDirectory: hookCacheDir.path);

// Retry spawning the isolate if the hook dependencies
// have been successfully reinstalled.
try {
isolate = await spawnIsolate(uri);
} on IsolateSpawnException catch (error) {
throwHookRunException(error);
}
}

isolate
Expand Down
Empty file.
3 changes: 3 additions & 0 deletions packages/mason/test/fixtures/basic/brick.yaml
@@ -0,0 +1,3 @@
name: basic_hook
description: A basic Hook
version: 0.1.0+1
3 changes: 3 additions & 0 deletions packages/mason/test/fixtures/basic/hooks/pre_gen.dart
@@ -0,0 +1,3 @@
import 'package:mason/mason.dart';

void run(HookContext context) {}
10 changes: 10 additions & 0 deletions packages/mason/test/fixtures/basic/hooks/pubspec.yaml
@@ -0,0 +1,10 @@
name: basic_hooks

environment:
sdk: ">=2.12.0 <3.0.0"

dependencies:
mason:
git:
url: https://github.com/felangel/mason
path: packages/mason
Empty file.
@@ -0,0 +1,3 @@
name: dependency_install_failure_hook
description: A Test Hook
version: 0.1.0+1
@@ -0,0 +1,3 @@
import 'package:mason/mason.dart';

void run(HookContext context) {}
@@ -0,0 +1,12 @@
name: dependency_install_failure_hooks

environment:
sdk: ">=2.12.0 <3.0.0"

dependencies:
foo:
path: ./foo
mason:
git:
url: https://github.com/felangel/mason
path: packages/mason
Empty file.
3 changes: 3 additions & 0 deletions packages/mason/test/fixtures/spawn_exception/brick.yaml
@@ -0,0 +1,3 @@
name: spawn_exception_hook
description: A Test Hook
version: 0.1.0+1
@@ -0,0 +1 @@
void run(HookContext context) {}
@@ -0,0 +1,4 @@
name: spawn_exception_hooks

environment:
sdk: ">=2.12.0 <3.0.0"
69 changes: 69 additions & 0 deletions packages/mason/test/src/hooks_test.dart
@@ -1,3 +1,5 @@
import 'dart:io';

import 'package:mason/mason.dart';
import 'package:mason/src/generator.dart';
import 'package:path/path.dart' as path;
Expand Down Expand Up @@ -37,6 +39,45 @@ void main() {
}
});

test(
'throws HookRunException '
'when unable to resolve a type', () async {
final brick = Brick.path(
path.join('test', 'fixtures', 'spawn_exception'),
);
final generator = await MasonGenerator.fromBrick(brick);

try {
await generator.hooks.preGen();
fail('should throw');
} catch (error) {
expect(error, isA<HookRunException>());
}
});

test(
'throws HookRunException '
'when unable to resolve a type (back-to-back)', () async {
final brick = Brick.path(
path.join('test', 'fixtures', 'spawn_exception'),
);
final generator = await MasonGenerator.fromBrick(brick);

try {
await generator.hooks.preGen();
fail('should throw');
} catch (error) {
expect(error, isA<HookRunException>());
}

try {
await generator.hooks.preGen();
fail('should throw');
} catch (error) {
expect(error, isA<HookRunException>());
}
});

test(
'throws HookMissingRunException '
'when hook does not contain a valid run method', () async {
Expand Down Expand Up @@ -80,5 +121,33 @@ void main() {
expect(error, isA<HookExecutionException>());
}
});

test(
'throws HookDependencyInstallFailure '
'when dependencies cannot be resolved', () async {
final brick = Brick.path(
path.join('test', 'fixtures', 'dependency_install_failure'),
);
final generator = await MasonGenerator.fromBrick(brick);

try {
await generator.hooks.preGen();
fail('should throw');
} catch (error) {
expect(error, isA<HookDependencyInstallFailure>());
}
});

test('recovers from cleared pub cache', () async {
final brick = Brick.path(path.join('test', 'fixtures', 'basic'));
final generator = await MasonGenerator.fromBrick(brick);

await expectLater(generator.hooks.preGen(), completes);

final result = await Process.run('dart', ['pub', 'cache', 'clean']);
expect(result.exitCode, equals(ExitCode.success.code));

await expectLater(generator.hooks.preGen(), completes);
});
});
}

0 comments on commit cbf958c

Please sign in to comment.