Assume relative paths are relative to the config file (#465)
* WIP relative paths
* wip
* Migrate tests
* Fix swift test
* Update changelog and fix analysis
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 38be297..28b0435 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 7.0.0-dev
+
+- Relative paths in ffigen config files are now assumed to be relative to the
+ config file, rather than the working directory of the tool invocation.
+
# 6.1.2
- Fix bug where function bindings were not deduped correctly.
diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart
index 1fdb77e..3457c91 100644
--- a/lib/src/config_provider/config.dart
+++ b/lib/src/config_provider/config.dart
@@ -4,8 +4,9 @@
/// Validates the yaml input by the user, prints useful info for the user
-import 'package:ffigen/src/code_generator.dart';
+import 'dart:io';
+import 'package:ffigen/src/code_generator.dart';
import 'package:logging/logging.dart';
import 'package:yaml/yaml.dart';
@@ -19,6 +20,10 @@
///
/// Handles validation, extraction of confiurations from yaml file.
class Config {
+ /// Input filename.
+ String? get filename => _filename;
+ String? _filename;
+
/// Location for llvm/lib folder.
String get libclangDylib => _libclangDylib;
late String _libclangDylib;
@@ -154,11 +159,11 @@
FfiNativeConfig get ffiNativeConfig => _ffiNativeConfig;
late FfiNativeConfig _ffiNativeConfig;
- Config._();
+ Config._(this._filename);
/// Create config from Yaml map.
- factory Config.fromYaml(YamlMap map) {
- final configspecs = Config._();
+ factory Config.fromYaml(YamlMap map, [String? filename]) {
+ final configspecs = Config._(filename);
_logger.finest('Config Map: ' + map.toString());
final specs = configspecs._getSpecs();
@@ -172,6 +177,14 @@
return configspecs;
}
+ /// Create config from a file.
+ factory Config.fromFile(File file) {
+ // Throws a [YamlException] if it's unable to parse the Yaml.
+ final configYaml = loadYaml(file.readAsStringSync()) as YamlMap;
+
+ return Config.fromYaml(configYaml, file.path);
+ }
+
/// Add compiler options for clang. If [highPriority] is true these are added
/// to the front of the list.
void addCompilerOpts(String compilerOpts, {bool highPriority = false}) {
@@ -235,7 +248,7 @@
[strings.output]: Specification<String>(
requirement: Requirement.yes,
validator: outputValidator,
- extractor: outputExtractor,
+ extractor: (dynamic value) => outputExtractor(value, filename),
extractedResult: (dynamic result) => _output = result as String,
),
[strings.language]: Specification<Language>(
@@ -248,7 +261,7 @@
[strings.headers]: Specification<Headers>(
requirement: Requirement.yes,
validator: headersValidator,
- extractor: headersExtractor,
+ extractor: (dynamic value) => headersExtractor(value, filename),
extractedResult: (dynamic result) => _headers = result as Headers,
),
[strings.compilerOpts]: Specification<List<String>>(
diff --git a/lib/src/config_provider/spec_utils.dart b/lib/src/config_provider/spec_utils.dart
index 56cc872..53c8ce9 100644
--- a/lib/src/config_provider/spec_utils.dart
+++ b/lib/src/config_provider/spec_utils.dart
@@ -26,6 +26,14 @@
}
}
+/// Replaces the path separators according to current platform. If a relative
+/// path is passed in, it is resolved relative to the config path, and the
+/// absolute path is returned.
+String _normalizePath(String path, String? configFilename) {
+ return _replaceSeparators(
+ configFilename == null ? path : p.join(p.dirname(configFilename), path));
+}
+
/// Checks if type of value is [T], logs an error if it's not.
///
/// [key] is printed as `'item1 -> item2 => item3'` in log message.
@@ -305,23 +313,23 @@
return _result;
}
-Headers headersExtractor(dynamic yamlConfig) {
+Headers headersExtractor(dynamic yamlConfig, String? configFilename) {
final entryPoints = <String>[];
final includeGlobs = <quiver.Glob>[];
for (final key in (yamlConfig as YamlMap).keys) {
if (key == strings.entryPoints) {
for (final h in (yamlConfig[key] as YamlList)) {
- final headerGlob = h as String;
+ final headerGlob = _normalizePath(h as String, configFilename);
// Add file directly to header if it's not a Glob but a File.
if (File(headerGlob).existsSync()) {
- final osSpecificPath = _replaceSeparators(headerGlob);
+ final osSpecificPath = headerGlob;
entryPoints.add(osSpecificPath);
_logger.fine('Adding header/file: $headerGlob');
} else {
final glob = Glob(headerGlob);
for (final file in glob.listFileSystemSync(const LocalFileSystem(),
followLinks: true)) {
- final fixedPath = _replaceSeparators(file.path);
+ final fixedPath = file.path;
entryPoints.add(fixedPath);
_logger.fine('Adding header/file: $fixedPath');
}
@@ -331,7 +339,7 @@
if (key == strings.includeDirectives) {
for (final h in (yamlConfig[key] as YamlList)) {
final headerGlob = h as String;
- final fixedGlob = _replaceSeparators(headerGlob);
+ final fixedGlob = _normalizePath(headerGlob, configFilename);
includeGlobs.add(quiver.Glob(fixedGlob));
}
}
@@ -366,36 +374,6 @@
}
}
-String libclangDylibExtractor(dynamic value) => getDylibPath(value as String);
-
-bool libclangDylibValidator(List<String> name, dynamic value) {
- if (!checkType<String>(name, value)) {
- return false;
- } else {
- final dylibPath = getDylibPath(value as String);
- if (!File(dylibPath).existsSync()) {
- _logger.severe(
- 'Dynamic library: $dylibPath does not exist or is corrupt, input folder: $value.');
- return false;
- } else {
- return true;
- }
- }
-}
-
-String getDylibPath(String dylibParentFoler) {
- dylibParentFoler = _replaceSeparators(dylibParentFoler);
- String dylibPath;
- if (Platform.isMacOS) {
- dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_macos);
- } else if (Platform.isWindows) {
- dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_windows);
- } else {
- dylibPath = p.join(dylibParentFoler, strings.libclang_dylib_linux);
- }
- return dylibPath;
-}
-
/// Returns location of dynamic library by searching default locations. Logs
/// error and throws an Exception if not found.
String findDylibAtDefaultLocations() {
@@ -504,7 +482,8 @@
return true;
}
-String outputExtractor(dynamic value) => _replaceSeparators(value as String);
+String outputExtractor(dynamic value, String? configFilename) =>
+ _normalizePath(value as String, configFilename);
bool outputValidator(List<String> name, dynamic value) =>
checkType<String>(name, value);
diff --git a/lib/src/executables/ffigen.dart b/lib/src/executables/ffigen.dart
index 12dd868..b3e7c41 100644
--- a/lib/src/executables/ffigen.dart
+++ b/lib/src/executables/ffigen.dart
@@ -101,7 +101,7 @@
_logger.severe("Couldn't find an entry for '$configKey' in $pubspecName.");
exit(1);
}
- return Config.fromYaml(bindingsConfigMap);
+ return Config.fromYaml(bindingsConfigMap, pubspecFile.path);
}
/// Extracts configuration from a custom yaml file.
@@ -113,11 +113,7 @@
exit(1);
}
- // Throws a [YamlException] if it's unable to parse the Yaml.
- final bindingsConfigMap =
- yaml.loadYaml(yamlFile.readAsStringSync()) as yaml.YamlMap;
-
- return Config.fromYaml(bindingsConfigMap);
+ return Config.fromFile(yamlFile);
}
/// Parses the cmd line arguments.
diff --git a/pubspec.yaml b/pubspec.yaml
index f144be1..302f458 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: ffigen
-version: 6.1.2
+version: 7.0.0-dev
description: Generator for FFI bindings, using LibClang to parse C header files.
repository: https://github.com/dart-lang/ffigen
diff --git a/test/example_tests/swift_example_test.dart b/test/example_tests/swift_example_test.dart
new file mode 100644
index 0000000..151b895
--- /dev/null
+++ b/test/example_tests/swift_example_test.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Swift support is only available on mac.
+@TestOn('mac-os')
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:ffigen/src/config_provider/config.dart';
+import 'package:ffigen/src/header_parser.dart';
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart';
+
+import '../test_utils.dart';
+
+void main() {
+ group('swift_example_test', () {
+ setUpAll(() {
+ logWarnings(Level.SEVERE);
+ });
+
+ test('swift', () async {
+ // Run the swiftc command from the example README, to generate the header.
+ final process = await Process.start(
+ 'swiftc',
+ [
+ '-c',
+ 'swift_api.swift',
+ '-module-name',
+ 'swift_module',
+ '-emit-objc-header-path',
+ 'swift_api.h',
+ '-emit-library',
+ '-o',
+ 'libswiftapi.dylib',
+ ],
+ workingDirectory: p.join(Directory.current.path, 'example/swift'));
+ unawaited(stdout.addStream(process.stdout));
+ unawaited(stderr.addStream(process.stderr));
+ final result = await process.exitCode;
+ expect(result, 0);
+
+ final pubspecFile = File('example/swift/pubspec.yaml');
+ final pubspecYaml = loadYaml(pubspecFile.readAsStringSync()) as YamlMap;
+ final config =
+ Config.fromYaml(pubspecYaml['ffigen'] as YamlMap, pubspecFile.path);
+ final output = parse(config).generate();
+
+ // Verify that the output contains all the methods and classes that the
+ // example app uses.
+ expect(output, contains('class SwiftLibrary{'));
+ expect(output, contains('class NSString extends NSObject {'));
+ expect(output, contains('class SwiftClass extends NSObject {'));
+ expect(output, contains('static SwiftClass new1(SwiftLibrary _lib) {'));
+ expect(output, contains('NSString sayHello() {'));
+ expect(output, contains('int get someField {'));
+ expect(output, contains('set someField(int value) {'));
+
+ // Verify that SwiftClass is loaded using the swift_module prefix.
+ expect(
+ output,
+ contains(RegExp(r'late final _class_SwiftClass.* = '
+ r'_getClass.*\("swift_module\.SwiftClass"\)')));
+ });
+ });
+}
diff --git a/test/native_objc_test/automated_ref_count_config.yaml b/test/native_objc_test/automated_ref_count_config.yaml
index 8884260..600d33d 100644
--- a/test/native_objc_test/automated_ref_count_config.yaml
+++ b/test/native_objc_test/automated_ref_count_config.yaml
@@ -1,7 +1,7 @@
name: AutomatedRefCountTestObjCLibrary
description: 'Tests automatic reference counting of Objective-C objects'
language: objc
-output: 'test/native_objc_test/automated_ref_count_bindings.dart'
+output: 'automated_ref_count_bindings.dart'
exclude-all-by-default: true
functions:
include:
@@ -13,6 +13,6 @@
- RefCounted
headers:
entry-points:
- - 'test/native_objc_test/automated_ref_count_test.m'
+ - 'automated_ref_count_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/bad_method_config.yaml b/test/native_objc_test/bad_method_config.yaml
index fa71887..5ad9c1b 100644
--- a/test/native_objc_test/bad_method_config.yaml
+++ b/test/native_objc_test/bad_method_config.yaml
@@ -9,13 +9,13 @@
name: NativeObjCLibrary
description: 'Native Objective C test'
language: objc
-output: 'test/native_objc_test/bad_method_test_bindings.dart'
+output: 'bad_method_test_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- 'BadMethodTestObject'
headers:
entry-points:
- - 'test/native_objc_test/bad_method_test.m'
+ - 'bad_method_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/block_config.yaml b/test/native_objc_test/block_config.yaml
index 3ea866d..e83c6b7 100644
--- a/test/native_objc_test/block_config.yaml
+++ b/test/native_objc_test/block_config.yaml
@@ -1,13 +1,13 @@
name: BlockTestObjCLibrary
description: 'Tests calling Objective-C blocks'
language: objc
-output: 'test/native_objc_test/block_bindings.dart'
+output: 'block_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- BlockTester
headers:
entry-points:
- - 'test/native_objc_test/block_test.m'
+ - 'block_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/cast_config.yaml b/test/native_objc_test/cast_config.yaml
index da7ecb3..af33941 100644
--- a/test/native_objc_test/cast_config.yaml
+++ b/test/native_objc_test/cast_config.yaml
@@ -1,13 +1,13 @@
name: CastTestObjCLibrary
description: 'Tests casting objects'
language: objc
-output: 'test/native_objc_test/cast_bindings.dart'
+output: 'cast_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- Castaway
headers:
entry-points:
- - 'test/native_objc_test/cast_test.m'
+ - 'cast_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/category_config.yaml b/test/native_objc_test/category_config.yaml
index f70b10a..5c74e1a 100644
--- a/test/native_objc_test/category_config.yaml
+++ b/test/native_objc_test/category_config.yaml
@@ -1,15 +1,15 @@
name: CategoryTestObjCLibrary
description: 'Tests handling Objective-C categories'
language: objc
-output: 'test/native_objc_test/category_bindings.dart'
+output: 'category_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- Thing
headers:
entry-points:
- - 'test/native_objc_test/category_test.m'
+ - 'category_test.m'
# Include it twice, as a regression test for #353
- - 'test/native_objc_test/category_test.m'
+ - 'category_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/failed_to_load_config.yaml b/test/native_objc_test/failed_to_load_config.yaml
index 11ddbb9..c96702d 100644
--- a/test/native_objc_test/failed_to_load_config.yaml
+++ b/test/native_objc_test/failed_to_load_config.yaml
@@ -1,13 +1,13 @@
name: FailedToLoadTestObjCLibrary
description: 'Tests failing to load an Objective-C library'
language: objc
-output: 'test/native_objc_test/failed_to_load_bindings.dart'
+output: 'failed_to_load_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- ClassThatWillFailToLoad
headers:
entry-points:
- - 'test/native_objc_test/failed_to_load_test.m'
+ - 'failed_to_load_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/forward_decl_config.yaml b/test/native_objc_test/forward_decl_config.yaml
index 2ccb3f6..7a5c815 100644
--- a/test/native_objc_test/forward_decl_config.yaml
+++ b/test/native_objc_test/forward_decl_config.yaml
@@ -1,14 +1,14 @@
name: ForwardDeclTestObjCLibrary
description: 'Test that forward declared ObjC classes are correctly filled'
language: objc
-output: 'test/native_objc_test/forward_decl_bindings.dart'
+output: 'forward_decl_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- ForwardDeclaredClass
headers:
entry-points:
- - 'test/native_objc_test/forward_decl_test.h'
- - 'test/native_objc_test/forward_decl_test.m'
+ - 'forward_decl_test.h'
+ - 'forward_decl_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/is_instance_config.yaml b/test/native_objc_test/is_instance_config.yaml
index 647ff04..2673c0b 100644
--- a/test/native_objc_test/is_instance_config.yaml
+++ b/test/native_objc_test/is_instance_config.yaml
@@ -1,7 +1,7 @@
name: IsInstanceTestObjCLibrary
description: 'Tests isInstance'
language: objc
-output: 'test/native_objc_test/is_instance_bindings.dart'
+output: 'is_instance_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
@@ -9,6 +9,6 @@
- UnrelatedClass
headers:
entry-points:
- - 'test/native_objc_test/is_instance_test.m'
+ - 'is_instance_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/method_config.yaml b/test/native_objc_test/method_config.yaml
index b841187..969ab7c 100644
--- a/test/native_objc_test/method_config.yaml
+++ b/test/native_objc_test/method_config.yaml
@@ -1,13 +1,13 @@
name: MethodTestObjCLibrary
description: 'Tests calling Objective-C methods'
language: objc
-output: 'test/native_objc_test/method_bindings.dart'
+output: 'method_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- MethodInterface
headers:
entry-points:
- - 'test/native_objc_test/method_test.m'
+ - 'method_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/native_objc_config.yaml b/test/native_objc_test/native_objc_config.yaml
index 89a4943..58bcf50 100644
--- a/test/native_objc_test/native_objc_config.yaml
+++ b/test/native_objc_test/native_objc_config.yaml
@@ -9,13 +9,13 @@
name: NativeObjCLibrary
description: 'Native Objective C test'
language: objc
-output: 'test/native_objc_test/native_objc_test_bindings.dart'
+output: 'native_objc_test_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- Foo
headers:
entry-points:
- - 'test/native_objc_test/native_objc_test.m'
+ - 'native_objc_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/nullable_config.yaml b/test/native_objc_test/nullable_config.yaml
index 58de440..e46cf06 100644
--- a/test/native_objc_test/nullable_config.yaml
+++ b/test/native_objc_test/nullable_config.yaml
@@ -1,13 +1,13 @@
name: NullableTestObjCLibrary
description: 'Tests calling Objective-C methods'
language: objc
-output: 'test/native_objc_test/nullable_bindings.dart'
+output: 'nullable_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- NullableInterface
headers:
entry-points:
- - 'test/native_objc_test/nullable_test.m'
+ - 'nullable_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/property_config.yaml b/test/native_objc_test/property_config.yaml
index ee30815..220a83a 100644
--- a/test/native_objc_test/property_config.yaml
+++ b/test/native_objc_test/property_config.yaml
@@ -1,13 +1,13 @@
name: PropertyTestObjCLibrary
description: 'Tests calling Objective-C properties i.e. getters and setters'
language: objc
-output: 'test/native_objc_test/property_bindings.dart'
+output: 'property_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- PropertyInterface
headers:
entry-points:
- - 'test/native_objc_test/property_test.m'
+ - 'property_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/rename_config.yaml b/test/native_objc_test/rename_config.yaml
index 1d0b98a..8dbae03 100644
--- a/test/native_objc_test/rename_config.yaml
+++ b/test/native_objc_test/rename_config.yaml
@@ -9,7 +9,7 @@
name: RenameLibrary
description: 'Rename test'
language: objc
-output: 'test/native_objc_test/rename_test_bindings.dart'
+output: 'rename_test_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
@@ -18,6 +18,6 @@
'_(.*)': '$1'
headers:
entry-points:
- - 'test/native_objc_test/rename_test.m'
+ - 'rename_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/string_config.yaml b/test/native_objc_test/string_config.yaml
index 55110a3..016b2bd 100644
--- a/test/native_objc_test/string_config.yaml
+++ b/test/native_objc_test/string_config.yaml
@@ -1,13 +1,13 @@
name: StringTestObjCLibrary
description: 'Tests calling Objective-C string methods'
language: objc
-output: 'test/native_objc_test/string_bindings.dart'
+output: 'string_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
- StringUtil
headers:
entry-points:
- - 'test/native_objc_test/string_test.m'
+ - 'string_test.m'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/test/native_objc_test/swift_class_config.yaml b/test/native_objc_test/swift_class_config.yaml
index e845ed1..20bd288 100644
--- a/test/native_objc_test/swift_class_config.yaml
+++ b/test/native_objc_test/swift_class_config.yaml
@@ -1,7 +1,7 @@
name: SwiftClassTestLibrary
description: 'Tests Swift classes'
language: objc
-output: 'test/native_objc_test/swift_class_bindings.dart'
+output: 'swift_class_bindings.dart'
exclude-all-by-default: true
objc-interfaces:
include:
@@ -10,6 +10,6 @@
'MySwiftClass': 'swift_class_test'
headers:
entry-points:
- - 'test/native_objc_test/swift_class_test-Swift.h'
+ - 'swift_class_test-Swift.h'
preamble: |
// ignore_for_file: camel_case_types, non_constant_identifier_names, unused_element, unused_field
diff --git a/tool/libclang_config.yaml b/tool/libclang_config.yaml
index 79cbbd6..b2ea7b4 100644
--- a/tool/libclang_config.yaml
+++ b/tool/libclang_config.yaml
@@ -11,13 +11,13 @@
name: Clang
description: Holds bindings to LibClang.
-output: 'lib/src/header_parser/clang_bindings/clang_bindings.dart'
+output: '../lib/src/header_parser/clang_bindings/clang_bindings.dart'
compiler-opts:
- '-Ithird_party/libclang/include'
- '-Wno-nullability-completeness'
headers:
entry-points:
- - 'third_party/libclang/include/clang-c/Index.h'
+ - '../third_party/libclang/include/clang-c/Index.h'
include-directives:
- '**wrapper.c'
- '**Index.h'