@@ -6,6 +6,7 @@ import 'dart:async';
6
6
import 'dart:io' ;
7
7
8
8
import 'package:logging/logging.dart' ;
9
+ import 'package:native_assets_cli/native_assets_cli.dart' as api;
9
10
import 'package:native_assets_cli/native_assets_cli_internal.dart' ;
10
11
import 'package:package_config/package_config.dart' ;
11
12
@@ -15,7 +16,9 @@ import '../model/hook_result.dart';
15
16
import '../model/link_dry_run_result.dart' ;
16
17
import '../model/link_result.dart' ;
17
18
import '../package_layout/package_layout.dart' ;
19
+ import '../utils/file.dart' ;
18
20
import '../utils/run_process.dart' ;
21
+ import '../utils/uri.dart' ;
19
22
import 'build_planner.dart' ;
20
23
21
24
typedef DependencyMetadata = Map <String , Metadata >;
@@ -24,6 +27,10 @@ typedef DependencyMetadata = Map<String, Metadata>;
24
27
///
25
28
/// These methods are invoked by launchers such as dartdev (for `dart run` )
26
29
/// and flutter_tools (for `flutter run` and `flutter build` ).
30
+ ///
31
+ /// The native assets build runner does not support reentrancy for identical
32
+ /// [api.BuildConfig] and [api.LinkConfig] ! For more info see:
33
+ /// https://github.com/dart-lang/native/issues/1319
27
34
class NativeAssetsBuildRunner {
28
35
final Logger logger;
29
36
final Uri dartExecutable;
@@ -40,6 +47,10 @@ class NativeAssetsBuildRunner {
40
47
///
41
48
/// If provided, only assets of all transitive dependencies of
42
49
/// [runPackageName] are built.
50
+ ///
51
+ /// The native assets build runner does not support reentrancy for identical
52
+ /// [api.BuildConfig] and [api.LinkConfig] ! For more info see:
53
+ /// https://github.com/dart-lang/native/issues/1319
43
54
Future <BuildResult > build ({
44
55
required LinkModePreferenceImpl linkModePreference,
45
56
required Target target,
@@ -81,6 +92,10 @@ class NativeAssetsBuildRunner {
81
92
///
82
93
/// If provided, only assets of all transitive dependencies of
83
94
/// [runPackageName] are linked.
95
+ ///
96
+ /// The native assets build runner does not support reentrancy for identical
97
+ /// [api.BuildConfig] and [api.LinkConfig] ! For more info see:
98
+ /// https://github.com/dart-lang/native/issues/1319
84
99
Future <LinkResult > link ({
85
100
required LinkModePreferenceImpl linkModePreference,
86
101
required Target target,
@@ -371,6 +386,7 @@ class NativeAssetsBuildRunner {
371
386
var hookResult = HookResult ();
372
387
for (final package in buildPlan) {
373
388
final config = await _cliConfigDryRun (
389
+ package: package,
374
390
packageName: package.name,
375
391
packageRoot: packageLayout.packageRoot (package.name),
376
392
targetOS: targetOS,
@@ -381,13 +397,30 @@ class NativeAssetsBuildRunner {
381
397
buildDryRunResult: buildDryRunResult,
382
398
linkingEnabled: linkingEnabled,
383
399
);
400
+ final packageConfigUri = packageLayout.packageConfigUri;
401
+ final (
402
+ compileSuccess,
403
+ hookKernelFile,
404
+ _,
405
+ ) = await _compileHookForPackageCached (
406
+ config,
407
+ packageConfigUri,
408
+ workingDirectory,
409
+ includeParentEnvironment,
410
+ );
411
+ if (! compileSuccess) {
412
+ hookResult.copyAdd (HookOutputImpl (), false );
413
+ continue ;
414
+ }
415
+ // TODO(https://github.com/dart-lang/native/issues/1321): Should dry runs be cached?
384
416
var (buildOutput, packageSuccess) = await _runHookForPackage (
385
417
hook,
386
418
config,
387
- packageLayout. packageConfigUri,
419
+ packageConfigUri,
388
420
workingDirectory,
389
421
includeParentEnvironment,
390
422
null ,
423
+ hookKernelFile,
391
424
);
392
425
buildOutput = _expandArchsNativeCodeAssets (buildOutput);
393
426
hookResult = hookResult.copyAdd (buildOutput, packageSuccess);
@@ -427,16 +460,33 @@ class NativeAssetsBuildRunner {
427
460
Uri ? resources,
428
461
) async {
429
462
final outDir = config.outputDirectory;
463
+ final (
464
+ compileSuccess,
465
+ hookKernelFile,
466
+ hookLastSourceChange,
467
+ ) = await _compileHookForPackageCached (
468
+ config,
469
+ packageConfigUri,
470
+ workingDirectory,
471
+ includeParentEnvironment,
472
+ );
473
+ if (! compileSuccess) {
474
+ return (HookOutputImpl (), false );
475
+ }
430
476
431
477
final hookOutput = HookOutputImpl .readFromFile (file: config.outputFile);
432
478
if (hookOutput != null ) {
433
479
final lastBuilt = hookOutput.timestamp.roundDownToSeconds ();
434
- final lastChange = await hookOutput.dependenciesModel.lastModified ();
435
-
436
- if (lastBuilt.isAfter (lastChange)) {
437
- logger
438
- .info ('Skipping ${hook .name } for ${config .packageName } in $outDir . '
439
- 'Last build on $lastBuilt , last input change on $lastChange .' );
480
+ final dependenciesLastChange =
481
+ await hookOutput.dependenciesModel.lastModified ();
482
+ if (lastBuilt.isAfter (dependenciesLastChange) &&
483
+ lastBuilt.isAfter (hookLastSourceChange)) {
484
+ logger.info (
485
+ 'Skipping ${hook .name } for ${config .packageName } in $outDir . '
486
+ 'Last build on $lastBuilt . '
487
+ 'Last dependencies change on $dependenciesLastChange . '
488
+ 'Last hook change on $hookLastSourceChange .' ,
489
+ );
440
490
// All build flags go into [outDir]. Therefore we do not have to check
441
491
// here whether the config is equal.
442
492
return (hookOutput, true );
@@ -450,6 +500,7 @@ class NativeAssetsBuildRunner {
450
500
workingDirectory,
451
501
includeParentEnvironment,
452
502
resources,
503
+ hookKernelFile,
453
504
);
454
505
}
455
506
@@ -460,6 +511,7 @@ class NativeAssetsBuildRunner {
460
511
Uri workingDirectory,
461
512
bool includeParentEnvironment,
462
513
Uri ? resources,
514
+ File hookKernelFile,
463
515
) async {
464
516
final configFile = config.outputDirectory.resolve ('../config.json' );
465
517
final configFileContents = config.toJsonString ();
@@ -473,7 +525,7 @@ class NativeAssetsBuildRunner {
473
525
474
526
final arguments = [
475
527
'--packages=${packageConfigUri .toFilePath ()}' ,
476
- config.script. toFilePath () ,
528
+ hookKernelFile.path ,
477
529
'--config=${configFile .toFilePath ()}' ,
478
530
if (resources != null ) resources.toFilePath (),
479
531
];
@@ -484,6 +536,7 @@ class NativeAssetsBuildRunner {
484
536
logger: logger,
485
537
includeParentEnvironment: includeParentEnvironment,
486
538
);
539
+
487
540
var success = true ;
488
541
if (result.exitCode != 0 ) {
489
542
final printWorkingDir = workingDirectory != Directory .current.uri;
@@ -542,7 +595,114 @@ ${e.message}
542
595
}
543
596
}
544
597
598
+ /// Compiles the hook to dill and caches the dill.
599
+ ///
600
+ /// It does not reuse the cached dill for different [config] s, due to
601
+ /// reentrancy requirements. For more info see:
602
+ /// https://github.com/dart-lang/native/issues/1319
603
+ Future <(bool success, File kernelFile, DateTime lastSourceChange)>
604
+ _compileHookForPackageCached (
605
+ HookConfigImpl config,
606
+ Uri packageConfigUri,
607
+ Uri workingDirectory,
608
+ bool includeParentEnvironment,
609
+ ) async {
610
+ final kernelFile = File .fromUri (
611
+ config.outputDirectory.resolve ('../hook.dill' ),
612
+ );
613
+ final depFile = File .fromUri (
614
+ config.outputDirectory.resolve ('../hook.dill.d' ),
615
+ );
616
+ final bool mustCompile;
617
+ final DateTime sourceLastChange;
618
+ if (! await depFile.exists ()) {
619
+ mustCompile = true ;
620
+ sourceLastChange = DateTime .now ();
621
+ } else {
622
+ // Format: `path/to/my.dill: path/to/my.dart, path/to/more.dart`
623
+ final depFileContents = await depFile.readAsString ();
624
+ final dartSourceFiles = depFileContents
625
+ .trim ()
626
+ .split (' ' )
627
+ .skip (1 ) // '<dill>:'
628
+ .map ((u) => Uri .file (u).fileSystemEntity)
629
+ .toList ();
630
+ final dartFilesLastChange = await dartSourceFiles.lastModified ();
631
+ final packageConfigLastChange =
632
+ await packageConfigUri.fileSystemEntity.lastModified ();
633
+ sourceLastChange = packageConfigLastChange.isAfter (dartFilesLastChange)
634
+ ? packageConfigLastChange
635
+ : dartFilesLastChange;
636
+ final dillLastChange = await kernelFile.lastModified ();
637
+ mustCompile = sourceLastChange.isAfter (dillLastChange);
638
+ }
639
+ final bool success;
640
+ if (! mustCompile) {
641
+ success = true ;
642
+ } else {
643
+ success = await _compileHookForPackage (
644
+ config,
645
+ packageConfigUri,
646
+ workingDirectory,
647
+ includeParentEnvironment,
648
+ kernelFile,
649
+ depFile,
650
+ );
651
+ }
652
+ return (success, kernelFile, sourceLastChange);
653
+ }
654
+
655
+ Future <bool > _compileHookForPackage (
656
+ HookConfigImpl config,
657
+ Uri packageConfigUri,
658
+ Uri workingDirectory,
659
+ bool includeParentEnvironment,
660
+ File kernelFile,
661
+ File depFile,
662
+ ) async {
663
+ final compileArguments = [
664
+ 'compile' ,
665
+ 'kernel' ,
666
+ '--packages=${packageConfigUri .toFilePath ()}' ,
667
+ '--output=${kernelFile .path }' ,
668
+ '--depfile=${depFile .path }' ,
669
+ config.script.toFilePath (),
670
+ ];
671
+ final compileResult = await runProcess (
672
+ workingDirectory: workingDirectory,
673
+ executable: dartExecutable,
674
+ arguments: compileArguments,
675
+ logger: logger,
676
+ includeParentEnvironment: includeParentEnvironment,
677
+ );
678
+ var success = true ;
679
+ if (compileResult.exitCode != 0 ) {
680
+ final printWorkingDir = workingDirectory != Directory .current.uri;
681
+ final commandString = [
682
+ if (printWorkingDir) '(cd ${workingDirectory .toFilePath ()};' ,
683
+ dartExecutable.toFilePath (),
684
+ ...compileArguments.map ((a) => a.contains (' ' ) ? "'$a '" : a),
685
+ if (printWorkingDir) ')' ,
686
+ ].join (' ' );
687
+ logger.severe (
688
+ '''
689
+ Building native assets for package:${config .packageName } failed.
690
+ Compilation of hook returned with exit code: ${compileResult .exitCode }.
691
+ To reproduce run:
692
+ $commandString
693
+ stderr:
694
+ ${compileResult .stderr }
695
+ stdout:
696
+ ${compileResult .stdout }
697
+ ''' ,
698
+ );
699
+ success = false ;
700
+ }
701
+ return success;
702
+ }
703
+
545
704
static Future <HookConfigImpl > _cliConfigDryRun ({
705
+ required Package package,
546
706
required String packageName,
547
707
required Uri packageRoot,
548
708
required OSImpl targetOS,
@@ -553,8 +713,16 @@ ${e.message}
553
713
Iterable <String >? supportedAssetTypes,
554
714
required bool ? linkingEnabled,
555
715
}) async {
556
- final hookDirName = 'dry_run_${hook .name }_${targetOS }_$linkMode ' ;
557
- final outDirUri = buildParentDir.resolve ('$hookDirName /out/' );
716
+ final buildDirName = HookConfigImpl .checksumDryRun (
717
+ packageName: package.name,
718
+ packageRoot: package.root,
719
+ targetOS: targetOS,
720
+ linkModePreference: linkMode,
721
+ supportedAssetTypes: supportedAssetTypes,
722
+ hook: hook,
723
+ linkingEnabled: linkingEnabled,
724
+ );
725
+ final outDirUri = buildParentDir.resolve ('$buildDirName /out/' );
558
726
final outDir = Directory .fromUri (outDirUri);
559
727
if (! await outDir.exists ()) {
560
728
await outDir.create (recursive: true );
0 commit comments