[native assets] Support user-defines from pubspec

Change-Id: I9978bbb3bae0170b76f9419e18f4b18f75dab577
Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-arm64-try,pkg-linux-release-try,pkg-mac-release-arm64-try,pkg-win-release-arm64-try,pkg-mac-release-try,pkg-win-release-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/420700
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Moritz Sümmermann <mosum@google.com>
diff --git a/pkg/dartdev/lib/src/commands/build.dart b/pkg/dartdev/lib/src/commands/build.dart
index a164305..44bdd16 100644
--- a/pkg/dartdev/lib/src/commands/build.dart
+++ b/pkg/dartdev/lib/src/commands/build.dart
@@ -15,6 +15,7 @@
 import 'package:native_assets_cli/code_assets_builder.dart';
 import 'package:path/path.dart' as path;
 import 'package:vm/target_os.dart'; // For possible --target-os values.
+import 'package:yaml/yaml.dart';
 
 import '../core.dart';
 import '../native_assets.dart';
@@ -134,7 +135,22 @@
     final runPackageName = await DartNativeAssetsBuilder.findRootPackageName(
       sourceUri,
     );
+    final pubspecUri = await DartNativeAssetsBuilder.findPubspec(sourceUri);
+    final Map? pubspec;
+    if (pubspecUri == null) {
+      pubspec = null;
+    } else {
+      pubspec = loadYaml(File.fromUri(pubspecUri).readAsStringSync()) as Map;
+      final pubspecErrors =
+          DartNativeAssetsBuilder.validateHooksUserDefinesFromPubspec(pubspec);
+      if (pubspecErrors.isNotEmpty) {
+        log.stderr('Errors in pubspec:');
+        pubspecErrors.forEach(log.stderr);
+        return 255;
+      }
+    }
     final builder = DartNativeAssetsBuilder(
+      pubspec: pubspec,
       packageConfigUri: packageConfig!,
       runPackageName: runPackageName!,
       verbose: verbose,
diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart
index f3c6854..856991d 100644
--- a/pkg/dartdev/lib/src/commands/compile.dart
+++ b/pkg/dartdev/lib/src/commands/compile.dart
@@ -15,6 +15,7 @@
     show Architecture, OS, Target;
 import 'package:path/path.dart' as path;
 import 'package:vm/target_os.dart';
+import 'package:yaml/yaml.dart';
 
 import '../core.dart';
 import '../experiments.dart';
@@ -628,7 +629,25 @@
         Directory.current.uri,
       );
       if (runPackageName != null) {
+        final pubspecUri =
+            await DartNativeAssetsBuilder.findPubspec(Directory.current.uri);
+        final Map? pubspec;
+        if (pubspecUri == null) {
+          pubspec = null;
+        } else {
+          pubspec =
+              loadYaml(File.fromUri(pubspecUri).readAsStringSync()) as Map;
+          final pubspecErrors =
+              DartNativeAssetsBuilder.validateHooksUserDefinesFromPubspec(
+                  pubspec);
+          if (pubspecErrors.isNotEmpty) {
+            log.stderr('Errors in pubspec:');
+            pubspecErrors.forEach(log.stderr);
+            return 255;
+          }
+        }
         final builder = DartNativeAssetsBuilder(
+            pubspec: pubspec,
             packageConfigUri: packageConfig,
             runPackageName: runPackageName,
             verbose: verbose,
diff --git a/pkg/dartdev/lib/src/commands/run.dart b/pkg/dartdev/lib/src/commands/run.dart
index 74817db..03a075e 100644
--- a/pkg/dartdev/lib/src/commands/run.dart
+++ b/pkg/dartdev/lib/src/commands/run.dart
@@ -12,6 +12,7 @@
     show invokeReplaceCachedDill;
 import 'package:path/path.dart';
 import 'package:pub/pub.dart';
+import 'package:yaml/yaml.dart';
 
 import '../core.dart';
 import '../experiments.dart';
@@ -380,7 +381,25 @@
             Directory.current.uri,
           );
       if (runPackageName != null) {
+        final pubspecUri =
+            await DartNativeAssetsBuilder.findPubspec(Directory.current.uri);
+        final Map? pubspec;
+        if (pubspecUri == null) {
+          pubspec = null;
+        } else {
+          pubspec =
+              loadYaml(File.fromUri(pubspecUri).readAsStringSync()) as Map;
+          final pubspecErrors =
+              DartNativeAssetsBuilder.validateHooksUserDefinesFromPubspec(
+                  pubspec);
+          if (pubspecErrors.isNotEmpty) {
+            log.stderr('Errors in pubspec:');
+            pubspecErrors.forEach(log.stderr);
+            return errorExitCode;
+          }
+        }
         final builder = DartNativeAssetsBuilder(
+          pubspec: pubspec,
           packageConfigUri: packageConfig,
           runPackageName: runPackageName,
           verbose: verbose,
diff --git a/pkg/dartdev/lib/src/commands/test.dart b/pkg/dartdev/lib/src/commands/test.dart
index cfa73ee..1b8f91f 100644
--- a/pkg/dartdev/lib/src/commands/test.dart
+++ b/pkg/dartdev/lib/src/commands/test.dart
@@ -8,6 +8,7 @@
 import 'package:args/args.dart';
 import 'package:dartdev/src/experiments.dart';
 import 'package:pub/pub.dart';
+import 'package:yaml/yaml.dart';
 
 import '../core.dart';
 import '../native_assets.dart';
@@ -55,7 +56,25 @@
         Directory.current.uri,
       );
       if (runPackageName != null) {
+        final pubspecUri =
+            await DartNativeAssetsBuilder.findPubspec(Directory.current.uri);
+        final Map? pubspec;
+        if (pubspecUri == null) {
+          pubspec = null;
+        } else {
+          pubspec =
+              loadYaml(File.fromUri(pubspecUri).readAsStringSync()) as Map;
+          final pubspecErrors =
+              DartNativeAssetsBuilder.validateHooksUserDefinesFromPubspec(
+                  pubspec);
+          if (pubspecErrors.isNotEmpty) {
+            log.stderr('Errors in pubspec:');
+            pubspecErrors.forEach(log.stderr);
+            return DartdevCommand.errorExitCode;
+          }
+        }
         final builder = DartNativeAssetsBuilder(
+          pubspec: pubspec,
           packageConfigUri: packageConfig,
           runPackageName: runPackageName,
           verbose: verbose,
diff --git a/pkg/dartdev/lib/src/native_assets.dart b/pkg/dartdev/lib/src/native_assets.dart
index b58e33b..6e6c3e2 100644
--- a/pkg/dartdev/lib/src/native_assets.dart
+++ b/pkg/dartdev/lib/src/native_assets.dart
@@ -19,6 +19,7 @@
 import 'core.dart';
 
 class DartNativeAssetsBuilder {
+  final Map<Object?, Object?>? pubspec;
   final Uri packageConfigUri;
   final String runPackageName;
   final bool verbose;
@@ -50,21 +51,34 @@
 
   late final Future<NativeAssetsBuildRunner> _nativeAssetsBuildRunner =
       () async {
+    final Map<String, Map<String, Object?>?> userDefines;
+    if (pubspec == null) {
+      userDefines = {};
+    } else {
+      userDefines =
+          NativeAssetsBuildRunner.readHooksUserDefinesFromPubspec(pubspec!);
+    }
     return NativeAssetsBuildRunner(
       // This always runs in JIT mode.
       dartExecutable: Uri.file(sdk.dart),
       logger: _logger,
       fileSystem: const LocalFileSystem(),
       packageLayout: await _packageLayout,
+      userDefines: userDefines,
     );
   }();
 
-  DartNativeAssetsBuilder(
-      {required this.packageConfigUri,
-      required this.runPackageName,
-      required this.verbose,
-      Target? target})
-      : target = target ?? Target.current;
+  static List<String> validateHooksUserDefinesFromPubspec(
+          Map<Object?, Object?> pubspec) =>
+      NativeAssetsBuildRunner.validateHooksUserDefinesFromPubspec(pubspec);
+
+  DartNativeAssetsBuilder({
+    this.pubspec,
+    required this.packageConfigUri,
+    required this.runPackageName,
+    required this.verbose,
+    Target? target,
+  }) : target = target ?? Target.current;
 
   /// Compiles all native assets for host OS in JIT mode.
   ///
@@ -223,7 +237,7 @@
     // TODO(https://github.com/dart-lang/package_config/issues/126): Use
     // package config resolution from package:package_config.
     if (packageConfig == null) {
-      final pubspecMaybe = await _findPubspec(uri);
+      final pubspecMaybe = await findPubspec(uri);
       if (pubspecMaybe != null) {
         // Silently run `pub get`, this is what would happen in
         // `getExecutableForCommand` later.
@@ -259,7 +273,7 @@
     }
   }
 
-  static Future<Uri?> _findPubspec(Uri uri) async {
+  static Future<Uri?> findPubspec(Uri uri) async {
     while (true) {
       final candidate = uri.resolve('pubspec.yaml');
       if (await File.fromUri(candidate).exists()) {
@@ -277,7 +291,7 @@
   ///
   /// Returns `null` if package cannnot be determined.
   static Future<String?> findRootPackageName(Uri uri) async {
-    final pubspecUri = await _findPubspec(uri);
+    final pubspecUri = await findPubspec(uri);
     if (pubspecUri == null) {
       return null;
     }
diff --git a/pkg/dartdev/test/native_assets/build_test.dart b/pkg/dartdev/test/native_assets/build_test.dart
index c1ca339..f324514 100644
--- a/pkg/dartdev/test/native_assets/build_test.dart
+++ b/pkg/dartdev/test/native_assets/build_test.dart
@@ -327,6 +327,35 @@
       });
     },
   );
+
+  test(
+    'dart build with user defines',
+    timeout: longTimeout,
+    () async {
+      await nativeAssetsTest('user_defines', (packageUri) async {
+        await runDart(
+          arguments: [
+            '--enable-experiment=native-assets',
+            'build',
+            'bin/user_defines.dart',
+          ],
+          workingDirectory: packageUri,
+          logger: logger,
+        );
+
+        final outputDirectory =
+            Directory.fromUri(packageUri.resolve('bin/user_defines'));
+        expect(outputDirectory.existsSync(), true);
+
+        final proccessResult = await runProcess(
+          executable: outputDirectory.uri.resolve('user_defines.exe'),
+          logger: logger,
+          throwOnUnexpectedExitCode: true,
+        );
+        expect(proccessResult.stdout, contains('Hello world!'));
+      });
+    },
+  );
 }
 
 Future<void> _withTempDir(Future<void> Function(Uri tempUri) fun) async {
diff --git a/pkg/dartdev/test/native_assets/helpers.dart b/pkg/dartdev/test/native_assets/helpers.dart
index 267b2e0..0d88ad4 100644
--- a/pkg/dartdev/test/native_assets/helpers.dart
+++ b/pkg/dartdev/test/native_assets/helpers.dart
@@ -191,6 +191,7 @@
         'native_dynamic_linking',
         'system_library',
         'treeshaking_native_libs',
+        'user_defines',
       ],
       Platform.script.resolve(
           '../../../../third_party/pkg/native/pkgs/native_assets_builder/'),
diff --git a/pkg/dartdev/test/native_assets/run_test.dart b/pkg/dartdev/test/native_assets/run_test.dart
index cfd0fb3..60d143e 100644
--- a/pkg/dartdev/test/native_assets/run_test.dart
+++ b/pkg/dartdev/test/native_assets/run_test.dart
@@ -192,4 +192,23 @@
       expect(result.stdout, contains('42'));
     });
   });
+
+  test(
+    'dart run with user defines',
+    timeout: longTimeout,
+    () async {
+      await nativeAssetsTest('user_defines', (packageUri) async {
+        final result = await runDart(
+          arguments: [
+            '--enable-experiment=native-assets',
+            'run',
+            'bin/user_defines.dart',
+          ],
+          workingDirectory: packageUri,
+          logger: logger,
+        );
+        expect(result.stdout, contains('Hello world!'));
+      });
+    },
+  );
 }
diff --git a/pkg/dartdev/test/native_assets/test_test.dart b/pkg/dartdev/test/native_assets/test_test.dart
index c83e956..47fc5cc 100644
--- a/pkg/dartdev/test/native_assets/test_test.dart
+++ b/pkg/dartdev/test/native_assets/test_test.dart
@@ -123,4 +123,28 @@
       );
     });
   });
+  test(
+    'dart test with user defines',
+    timeout: longTimeout,
+    () async {
+      await nativeAssetsTest('user_defines', (packageUri) async {
+        final result = await runDart(
+          arguments: [
+            '--enable-experiment=native-assets',
+            'test',
+          ],
+          workingDirectory: packageUri,
+          logger: logger,
+        );
+        expect(
+          result.stdout,
+          stringContainsInOrder(
+            [
+              'All tests passed!',
+            ],
+          ),
+        );
+      });
+    },
+  );
 }