[jnigen] Make jni:setup build all native libraries (https://github.com/dart-lang/jnigen/issues/96)
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index a578787..554577f 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml
@@ -404,7 +404,7 @@ - name: Run standalone example run: | dart pub get - dart run jni:setup && dart run jni:setup -p pdfbox_plugin + dart run jni:setup wget 'https://dart.dev/guides/language/specifications/DartLangSpec-v2.2.pdf' dart run bin/pdf_info.dart DartLangSpec-v2.2.pdf working-directory: ./pkgs/jnigen/example/pdfbox_plugin/dart_example
diff --git a/pkgs/jni/bin/setup.dart b/pkgs/jni/bin/setup.dart index 8f12c6b..23226c3 100644 --- a/pkgs/jni/bin/setup.dart +++ b/pkgs/jni/bin/setup.dart
@@ -10,11 +10,14 @@ const ansiRed = '\x1b[31m'; const ansiDefault = '\x1b[39;49m'; +const jniNativeBuildDirective = + '# jni_native_build (Build with jni:setup. Do not delete this line.)'; + const _defaultRelativeBuildPath = "build/jni_libs"; const _buildPath = "build-path"; -const _srcPath = "source-path"; -const _packageName = 'package-name'; +const _srcPath = "add-source"; +const _packageName = 'add-package'; const _verbose = "verbose"; const _cmakeArgs = "cmake-args"; @@ -65,13 +68,14 @@ class Options { Options(ArgResults arg) : buildPath = arg[_buildPath], - srcPath = arg[_srcPath], - packageName = arg[_packageName] ?? 'jni', + sources = arg[_srcPath], + packages = arg[_packageName], cmakeArgs = arg[_cmakeArgs], verbose = arg[_verbose] ?? false; - String? buildPath, srcPath; - String packageName; + String? buildPath; + List<String> sources; + List<String> packages; List<String> cmakeArgs; bool verbose; } @@ -88,18 +92,40 @@ /// /// It's assumed C FFI sources are in "src/" relative to package root. /// If package cannot be found, null is returned. -Future<String?> findSources(String packageName) async { +Future<String> findSources(String packageName) async { final packageConfig = await findPackageConfig(Directory.current); if (packageConfig == null) { - return null; + throw UnsupportedError("Please run from project root."); } - final package = packageConfig[options.packageName]; + final package = packageConfig[packageName]; if (package == null) { - return null; + throw UnsupportedError("Cannot find package: $packageName"); } return package.root.resolve("src").toFilePath(); } +/// Return '/src' directories of all dependencies which has a CMakeLists.txt +/// file. +Future<Map<String, String>> findDependencySources() async { + final packageConfig = await findPackageConfig(Directory.current); + if (packageConfig == null) { + throw UnsupportedError("Please run the command from project root."); + } + final sources = <String, String>{}; + for (var package in packageConfig.packages) { + final src = package.root.resolve("src/"); + final cmakeLists = src.resolve("CMakeLists.txt"); + final cmakeListsFile = File.fromUri(cmakeLists); + if (cmakeListsFile.existsSync()) { + final firstLine = cmakeListsFile.readAsLinesSync().first; + if (firstLine == jniNativeBuildDirective) { + sources[package.name] = src.toFilePath(); + } + } + } + return sources; +} + /// Returns true if [artifact] does not exist, or any file in [sourceDir] is /// newer than [artifact]. bool needsBuild(File artifact, Directory sourceDir) { @@ -129,13 +155,12 @@ final parser = ArgParser() ..addOption(_buildPath, abbr: 'b', help: 'Directory to place built artifacts') - ..addOption(_srcPath, + ..addMultiOption(_srcPath, abbr: 's', help: 'alternative path to package:jni sources') - ..addOption(_packageName, + ..addMultiOption(_packageName, abbr: 'p', help: 'package for which native' - 'library should be built', - defaultsTo: 'jni') + 'library should be built') ..addFlag(_verbose, abbr: 'v', help: 'Enable verbose output') ..addMultiOption(_cmakeArgs, abbr: 'm', help: 'Pass additional argument to CMake'); @@ -151,69 +176,73 @@ return; } - final srcPath = options.srcPath ?? await findSources(options.packageName); - - if (srcPath == null) { - stderr.writeln("Cannot find sources for package ${options.packageName} " - "and no sources were manually specified."); + final sources = options.sources; + for (var packageName in options.packages) { + sources.add(await findSources(packageName)); + } + if (sources.isEmpty) { + final dependencySources = await findDependencySources(); + stderr.writeln("selecting source directories for dependencies: " + "${dependencySources.keys}"); + sources.addAll(dependencySources.values); + } else { + stderr.writeln("selecting source directories: $sources"); + } + if (sources.isEmpty) { + stderr.writeln('No source paths to build!'); exitCode = 1; return; } - - final srcDir = Directory(srcPath); - if (!srcDir.existsSync()) { - stderr.writeln('Directory $srcPath does not exist'); - exitCode = 1; - return; - } - - verboseLog("srcPath: $srcPath"); - - final currentDirUri = Uri.directory("."); - final buildPath = options.buildPath ?? - currentDirUri.resolve(_defaultRelativeBuildPath).toFilePath(); - final buildDir = Directory(buildPath); - await buildDir.create(recursive: true); - verboseLog("buildPath: $buildPath"); - - if (buildDir.absolute.uri == srcDir.absolute.uri) { - stderr.writeln("Please build in a directory different than source."); - exitCode = 1; - return; - } - - final targetFileUri = buildDir.uri.resolve(getTargetName(srcDir)); - final targetFile = File.fromUri(targetFileUri); - if (!needsBuild(targetFile, srcDir)) { - verboseLog("last modified of ${targetFile.path}: " - "${targetFile.lastModifiedSync()}"); - stderr.writeln("target newer than source, skipping build"); - return; - } - - // Note: creating temp dir in .dart_tool/jni instead of SystemTemp - // because latter can fail tests on Windows CI, when system temp is on - // separate drive or something. - final jniDirUri = Uri.directory(".dart_tool").resolve("jni"); - final jniDir = Directory.fromUri(jniDirUri); - await jniDir.create(recursive: true); - final tempDir = await jniDir.createTemp("jni_native_build_"); - final cmakeArgs = <String>[]; - cmakeArgs.addAll(options.cmakeArgs); - // Pass absolute path of srcDir because cmake command is run in temp dir - cmakeArgs.add(srcDir.absolute.path); - await runCommand("cmake", cmakeArgs, tempDir.path); - await runCommand("cmake", ["--build", "."], tempDir.path); - final dllDirUri = - Platform.isWindows ? tempDir.uri.resolve("Debug") : tempDir.uri; - final dllDir = Directory.fromUri(dllDirUri); - for (var entry in dllDir.listSync()) { - final dllSuffix = Platform.isWindows ? "dll" : "so"; - if (entry.path.endsWith(dllSuffix)) { - final dllName = entry.uri.pathSegments.last; - final target = buildDir.uri.resolve(dllName); - entry.renameSync(target.toFilePath()); + for (var srcPath in sources) { + final srcDir = Directory(srcPath); + if (!srcDir.existsSync()) { + stderr.writeln('Directory $srcPath does not exist'); + exitCode = 1; + return; } + + verboseLog("srcPath: $srcPath"); + + final currentDirUri = Uri.directory("."); + final buildPath = options.buildPath ?? + currentDirUri.resolve(_defaultRelativeBuildPath).toFilePath(); + final buildDir = Directory(buildPath); + await buildDir.create(recursive: true); + verboseLog("buildPath: $buildPath"); + + final targetFileUri = buildDir.uri.resolve(getTargetName(srcDir)); + final targetFile = File.fromUri(targetFileUri); + if (!needsBuild(targetFile, srcDir)) { + verboseLog("last modified of ${targetFile.path}: " + "${targetFile.lastModifiedSync()}"); + stderr.writeln("target newer than source, skipping build"); + continue; + } + + // Note: creating temp dir in .dart_tool/jni instead of SystemTemp + // because latter can fail tests on Windows CI, when system temp is on + // separate drive or something. + final jniDirUri = Uri.directory(".dart_tool").resolve("jni"); + final jniDir = Directory.fromUri(jniDirUri); + await jniDir.create(recursive: true); + final tempDir = await jniDir.createTemp("jni_native_build_"); + final cmakeArgs = <String>[]; + cmakeArgs.addAll(options.cmakeArgs); + // Pass absolute path of srcDir because cmake command is run in temp dir + cmakeArgs.add(srcDir.absolute.path); + await runCommand("cmake", cmakeArgs, tempDir.path); + await runCommand("cmake", ["--build", "."], tempDir.path); + final dllDirUri = + Platform.isWindows ? tempDir.uri.resolve("Debug") : tempDir.uri; + final dllDir = Directory.fromUri(dllDirUri); + for (var entry in dllDir.listSync()) { + final dllSuffix = Platform.isWindows ? "dll" : "so"; + if (entry.path.endsWith(dllSuffix)) { + final dllName = entry.uri.pathSegments.last; + final target = buildDir.uri.resolve(dllName); + entry.renameSync(target.toFilePath()); + } + } + await tempDir.delete(recursive: true); } - await tempDir.delete(recursive: true); }
diff --git a/pkgs/jni/lib/jni.dart b/pkgs/jni/lib/jni.dart index 49485a5..4437e8e 100644 --- a/pkgs/jni/lib/jni.dart +++ b/pkgs/jni/lib/jni.dart
@@ -30,14 +30,15 @@ /// This module depends on a shared library written in C. Therefore on dart /// standalone: /// -/// * Run `dart run jni:setup` to build the shared library. The default output -/// directory is build/jni_libs, which can be changed using `-B` switch. +/// * Run `dart run jni:setup` to build the shared library. This command builds +/// all dependency libraries with native code (package:jni and jnigen-generated) +/// libraries if any. +/// +/// The default output directory is build/jni_libs, which can be changed +/// using `-B` switch. /// /// * Provide the location of library to `Jni.spawn` call. /// -/// * If there are generated libraries (using jnigen), also build them using -/// the command: `dart run jni:setup -p package_name` in the same directory. -/// /// ## JNIEnv /// `GlobalJniEnv` type provides a thin wrapper over `JNIEnv*` which can be used /// from across threads, and always returns JNI global references. This is
diff --git a/pkgs/jni/src/CMakeLists.txt b/pkgs/jni/src/CMakeLists.txt index 23ac23b..d0c3ee6 100644 --- a/pkgs/jni/src/CMakeLists.txt +++ b/pkgs/jni/src/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/cmake/CMakeLists.txt.tmpl b/pkgs/jnigen/cmake/CMakeLists.txt.tmpl index cf7b53d..d0b8a73 100644 --- a/pkgs/jnigen/cmake/CMakeLists.txt.tmpl +++ b/pkgs/jnigen/cmake/CMakeLists.txt.tmpl
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/example/README.md b/pkgs/jnigen/example/README.md index 821e883..2380dcb 100644 --- a/pkgs/jnigen/example/README.md +++ b/pkgs/jnigen/example/README.md
@@ -20,7 +20,7 @@ * Generate JNI bindings by running `dart run jnigen --config jnigen.yaml`. * In the CLI project which uses this package, add this package, and `jni` as a dependency. -* Run `dart run jni:setup && dart run jni:setup -p <generated_package_name>` to build native libraries for JNI base library and your package respectively. +* Run `dart run jni:setup` to build native libraries for JNI base library and jnigen generated package. * Import the package. See [pdf_info.dart](pdfbox_plugin/dart_example/bin/pdf_info.dart) for how to use the JNI from dart standalone. ### Flutter FFI plugin
diff --git a/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt b/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt index fe6f925..0c47c28 100644 --- a/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt +++ b/pkgs/jnigen/example/in_app_java/src/android_utils/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt b/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt index c733845..39c9850 100644 --- a/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt +++ b/pkgs/jnigen/example/notification_plugin/src/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md b/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md index f111493..6a6755e 100644 --- a/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md +++ b/pkgs/jnigen/example/pdfbox_plugin/dart_example/README.md
@@ -1,7 +1,7 @@ ## Running After generating `pdfbox_plugin` bindings in the parent directory -* setup native libraries: `dart run jni:setup && dart run jni:setup -p pdfbox_plugin` +* setup native libraries: `dart run jni:setup` * `dart run bin/pdf_info.dart <Path_to_PDF_File>`
diff --git a/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt b/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt index 4ed5b3d..f353765 100644 --- a/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt +++ b/pkgs/jnigen/example/pdfbox_plugin/src/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/test/bindings_test.dart b/pkgs/jnigen/test/bindings_test.dart index 015c1a3..799add7 100644 --- a/pkgs/jnigen/test/bindings_test.dart +++ b/pkgs/jnigen/test/bindings_test.dart
@@ -27,11 +27,16 @@ final simplePackageTestJava = join(simplePackageTest, 'java'); Future<void> setupDylibsAndClasses() async { - await runCommand('dart', ['run', 'jni:setup']); - await runCommand( - 'dart', ['run', 'jni:setup', '-s', join(simplePackageTest, 'src')]); - await runCommand('dart', - ['run', 'jni:setup', '-s', join(jacksonCoreTest, 'third_party', 'src')]); + await runCommand('dart', [ + 'run', + 'jni:setup', + '-p', + 'jni', + '-s', + join(simplePackageTest, 'src'), + '-s', + join(jacksonCoreTest, 'third_party', 'src') + ]); final group = join('com', 'github', 'dart_lang', 'jnigen'); await runCommand( 'javac',
diff --git a/pkgs/jnigen/test/jackson_core_test/third_party/src/CMakeLists.txt b/pkgs/jnigen/test/jackson_core_test/third_party/src/CMakeLists.txt index 475daed..1df24fc 100644 --- a/pkgs/jnigen/test/jackson_core_test/third_party/src/CMakeLists.txt +++ b/pkgs/jnigen/test/jackson_core_test/third_party/src/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.
diff --git a/pkgs/jnigen/test/simple_package_test/src/CMakeLists.txt b/pkgs/jnigen/test/simple_package_test/src/CMakeLists.txt index a029be9..eeb352d 100644 --- a/pkgs/jnigen/test/simple_package_test/src/CMakeLists.txt +++ b/pkgs/jnigen/test/simple_package_test/src/CMakeLists.txt
@@ -1,3 +1,5 @@ +# jni_native_build (Build with jni:setup. Do not delete this line.) + # The Flutter tooling requires that developers have CMake 3.10 or later # installed. You should not increase this version, as doing so will cause # the plugin to fail to compile for some customers of the plugin.