[ffigen] Added key `llvm-path`. Deprecate `llvm-lib`. (#194)

diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md
index 40ae92a..1f2afa4 100644
--- a/pkgs/ffigen/CHANGELOG.md
+++ b/pkgs/ffigen/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 2.4.0
+- Added new config key `llvm-path` that accepts a list of `path/to/llvm`.
+- Deprecated config key `llvm-lib`.
+
 # 2.3.0
 - Added config key `compiler-opts-automatic -> macos -> include-c-standard-library`
 (default: true) to automatically find and add C standard library on macOS.
diff --git a/pkgs/ffigen/README.md b/pkgs/ffigen/README.md
index e3faf97..19cbf56 100644
--- a/pkgs/ffigen/README.md
+++ b/pkgs/ffigen/README.md
@@ -87,12 +87,15 @@
   </td>
   </tr>
   <tr>
-    <td>llvm-lib</td>
-    <td>Path to <i>llvm/lib</i> folder. Required if ffigen is unable to find this at default locations.</td>
+    <td>llvm-path</td>
+    <td>Path to <i>llvm</i> folder. ffigen will sequentially search all the specified paths. Required if ffigen is unable to find this at default locations.</td>
     <td>
 
 ```yaml
-llvm-lib: '/usr/local/opt/llvm/lib'
+llvm-path:
+  - '/usr/local/opt/llvm/lib'
+  - 'C:\Program Files\llvm`
+  - '/usr/lib/llvm-11'
 ```
   </td>
   </tr>
diff --git a/pkgs/ffigen/lib/src/config_provider/config.dart b/pkgs/ffigen/lib/src/config_provider/config.dart
index 673daea..2830366 100644
--- a/pkgs/ffigen/lib/src/config_provider/config.dart
+++ b/pkgs/ffigen/lib/src/config_provider/config.dart
@@ -181,12 +181,25 @@
   /// Key: Name, Value: [Specification]
   Map<List<String>, Specification> _getSpecs() {
     return <List<String>, Specification>{
+      //TODO: Deprecated, remove in next major update.
       [strings.llvmLib]: Specification<String>(
         requirement: Requirement.no,
         validator: llvmLibValidator,
         extractor: llvmLibExtractor,
+        defaultValue: () => '',
+        extractedResult: (dynamic result) {
+          _libclangDylib = result as String;
+        },
+      ),
+      [strings.llvmPath]: Specification<String>(
+        requirement: Requirement.no,
+        validator: llvmPathValidator,
+        extractor: llvmPathExtractor,
         defaultValue: () => findDylibAtDefaultLocations(),
-        extractedResult: (dynamic result) => _libclangDylib = result as String,
+        extractedResult: (dynamic result) {
+          // If this key wasn't already set by `llvm-lib` use this result.
+          if (_libclangDylib.isEmpty) _libclangDylib = result as String;
+        },
       ),
       [strings.output]: Specification<String>(
         requirement: Requirement.yes,
diff --git a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
index ce6aae5..68246b4 100644
--- a/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
+++ b/pkgs/ffigen/lib/src/config_provider/spec_utils.dart
@@ -302,7 +302,7 @@
 }
 
 /// Returns location of dynamic library by searching default locations. Logs
-/// error and exits if not found.
+/// error and throws an Exception if not found.
 String findDylibAtDefaultLocations() {
   String? k;
   if (Platform.isLinux) {
@@ -326,7 +326,7 @@
 
   _logger.severe("Couldn't find dynamic library in default locations.");
   _logger.severe(
-      "Please supply the path/to/llvm/lib in ffigen's config under the key 'llvm-lib'.");
+      "Please supply one or more path/to/llvm in ffigen's config under the key '${strings.llvmPath}'.");
   throw Exception("Couldn't find dynamic library in default locations.");
 }
 
@@ -351,6 +351,8 @@
 }
 
 bool llvmLibValidator(List<String> name, dynamic value) {
+  _logger.warning(
+      'Deprecated ${strings.llvmLib}: please use ${strings.llvmPath} instead.');
   if (!checkType<String>(name, value) ||
       !Directory(value as String).existsSync()) {
     _logger.severe('Expected $name to be a valid folder Path.');
@@ -359,6 +361,37 @@
   return true;
 }
 
+String llvmPathExtractor(dynamic value) {
+  // Extract libclang's dylib from user specified paths.
+  for (final path in (value as YamlList)) {
+    if (path is! String) continue;
+    final dylibPath =
+        findLibclangDylib(p.join(path, strings.dynamicLibParentName));
+    if (dylibPath != null) {
+      _logger.fine('Found dynamic library at: $dylibPath');
+      return dylibPath;
+    }
+  }
+  _logger.fine(
+      "Couldn't find dynamic library under paths specified by ${strings.llvmPath}.");
+  // Extract path from default locations.
+  try {
+    final res = findDylibAtDefaultLocations();
+    return res;
+  } catch (e) {
+    _logger.severe(
+        "Couldn't find libclang dynamic library in specified locations.");
+    exit(1);
+  }
+}
+
+bool llvmPathValidator(List<String> name, dynamic value) {
+  if (!checkType<YamlList>(name, value)) {
+    return false;
+  }
+  return true;
+}
+
 String outputExtractor(dynamic value) => _replaceSeparators(value as String);
 
 bool outputValidator(List<String> name, dynamic value) =>
diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart
index a4f1d68..dcbae34 100644
--- a/pkgs/ffigen/lib/src/strings.dart
+++ b/pkgs/ffigen/lib/src/strings.dart
@@ -23,6 +23,10 @@
 }
 
 const llvmLib = 'llvm-lib';
+const llvmPath = 'llvm-path';
+
+/// Name of the parent folder of dynamic library `lib` or `bin` (on windows).
+String get dynamicLibParentName => Platform.isWindows ? 'bin' : 'lib';
 
 const output = 'output';
 
diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml
index 4104cd3..bc514bf 100644
--- a/pkgs/ffigen/pubspec.yaml
+++ b/pkgs/ffigen/pubspec.yaml
@@ -3,7 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 name: ffigen
-version: 2.3.0
+version: 2.4.0
 homepage: https://github.com/dart-lang/ffigen
 description: Generator for FFI bindings, using LibClang to parse C header files.