Skip to content
This repository was archived by the owner on Jan 17, 2024. It is now read-only.

Does ffi have function "-Wl,rpath"? #118

Closed
fayfive opened this issue Aug 31, 2021 · 21 comments
Closed

Does ffi have function "-Wl,rpath"? #118

fayfive opened this issue Aug 31, 2021 · 21 comments

Comments

@fayfive
Copy link

fayfive commented Aug 31, 2021

Does ffi have function "-Wl,rpath" to redirect the *.so or similar function?

@dcharkes
Copy link
Contributor

What are you trying to accomplish? Can you give more information? Are you doing Dart standalone or Flutter?

@fayfive
Copy link
Author

fayfive commented Aug 31, 2021

I want to use ffmpeg to display net streams.
I will do it flutter linux android ios and windows.

@dcharkes
Copy link
Contributor

Do you want to bundle the .so files in the package? or have users install it on their machine?

For Android and iOS see the bundling documentation on https://dart.dev/guides/libraries/c-interop (I don't think we have Linux and Windows documentation as of yet.) Also see https://pub.dev/packages/webcrypto which ships a part of BoringSSL to learn how to set things up.

@fayfive
Copy link
Author

fayfive commented Aug 31, 2021

I see.
But there is one more thing.
For example, when I DynamicLibrary.open libavformat.so, the libavformat.so will call libavcodec.so, and it will show that libavcodec.so not found.
In C, when I compile, I can do it like that "gcc -Wl,-rpath,'$ORIGIN/lib'" to redefine the path of the *.so

@dcharkes
Copy link
Contributor

In a Flutter app, both Android and iOS you will need to package all required .so into the respective places where an app expects them. When you ship the app to the store they need to be contained in the final .app or .apk. See the documentation.

@fayfive
Copy link
Author

fayfive commented Aug 31, 2021

I have read the link https://dart.dev/guides/libraries/c-interop
I think my state is Closed-source third-party library. Because I have got all *.so files and transformed all headers to *.dart files.
But How to process the Closed-source third-party library is not very detail in the link.
Can you show me the detail method of Closed-source third-party library or the link?

@dcharkes
Copy link
Contributor

dcharkes commented Sep 3, 2021

But what about linux and windows?

Linux when already having an .so file:

Add the path of the .so files to: linux/CMakeLists.txt. For example for native/lib/linux_x64/libmylib_dylib.so:

# List of absolute paths to libraries that should be bundled with the plugin
set(mylib_dylib_bundled_libraries
  "${CMAKE_CURRENT_SOURCE_DIR}/../native/lib/linux_x64/libmylib_dylib.so"
  PARENT_SCOPE
)

This will copy the .so file to the bundle/lib (the main executable is in bundle itself).

And then find the .so file relative to the executable:

      if (Platform.isLinux) {
        // The default include path is from the root of the application folder.
        // Opening 'build/linux/x64/debug/bundle/lib/lib$libName.so' works but
        // is not portable for when the app is shipped.
        // Instead, find it relative to the executable in the bundle.
        final path = File(Platform.resolvedExecutable)
            .parent
            .uri
            .resolve('lib/lib$libName.so')
            .path;
        return DynamicLibrary.open(path);
      }

Just opening DynamicLibrary.open('lib/lib$libName.so') or DynamicLibrary.open('lib$libName.so') does not seem to work. So there is something going on with the include path on Linux for Flutter desktop.

@fayfive
Copy link
Author

fayfive commented Sep 3, 2021

@dcharkes
Cannot set "mylib_dylib_bundled_libraries": current scope has no parent.

@dcharkes
Copy link
Contributor

dcharkes commented Sep 3, 2021

Can you elaborate? (Please do so in general, half of my posts are asking you for more information.)

The boilerplate generated by flutter create -t plugin --platforms=linux already has the CMakeLists.txt generated:

# List of absolute paths to libraries that should be bundled with the plugin
set(<your plugin name>_bundled_libraries
  ""
  PARENT_SCOPE
)

So you can add it in there.

@fayfive
Copy link
Author

fayfive commented Sep 4, 2021

@dcharkes
I see. Here is my test code. https://github.com/fayfive/ffitetst

I just want to use libffmpeg on flutter.
My system is ubunt18.04.
I create the project by command "flutter create".
I used ffigen to generate the *.dart in the folder
When I run it, it will report the error "libavcodec.so not found".
I know that is because libavformat.so contains libavcodec.so.
In C, I can use -Wl,rpath,'$ORIGIN' to solve it.
I can see linux/CMakeLists.txt has contianed set(CMAKE_INSTALL_RPATH "$ORIGIN/lib"), but it doesn't seem to work.

Thank you for your help!

@dcharkes
Copy link
Contributor

dcharkes commented Sep 5, 2021

Please use flutter create -t plugin --platforms=... for creating the project. The plugin template generates the necessary code to include the native libraries.

Are all the dependencies in bundle/lib? If so, you're probably running into flutter/flutter#78819 (comment). You could try dlopening the dependencies first.

@fayfive
Copy link
Author

fayfive commented Sep 6, 2021

@dcharkes
I use flutter create -t plugin --platforms=linux to generate the project.
But it still could not find the *.so.
All the dependencies are in bundle/lib.
I use "ldd" to see the app's dependencies, but I can't see the private *.so
The app path is "example/build/linux/x64/debug/bundle/ffiplugin_example"
Here is my main.dart
import 'package:flutter/services.dart';
import 'package:ffiplugin/ffiplugin.dart';
import 'package:ffiplugin/ffmpeg/libavformat.dart';
void main() {
final path = File(Platform.resolvedExecutable)
.parent
.uri
.resolve('lib/libavformat.so')
.path;
var avformate = libavformat(DynamicLibrary.open(path));
avformate.av_version_info();
runApp(MyApp());
}

libavformat.so contains libavcodec.so, it can find libavformat.so,but libavcodec.so can't be found.

[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: Invalid argument(s): Failed to load dynamic library 'ffiplugin/example/build/linux/x64/debug/bundle/lib/libavformat.so': libavcodec.so.58: cannot open shared object file: No such file or directory
#0 _open (dart:ffi-patch/ffi_dynamic_library_patch.dart:11:55)
#1 new DynamicLibrary.open (dart:ffi-patch/ffi_dynamic_library_patch.dart:20:12)
#2 main (package:ffiplugin_example/main.dart:17:47)
#3 _runMainZoned.. (dart:ui/hooks.dart:142:25)
#4 _rootRun (dart:async/zone.dart:1354:13)
#5 _CustomZone.run (dart:async/zone.dart:1258:19)
#6 _runZoned (dart:async/zone.dart:1789:10)
#7 runZonedGuarded (dart:async/zone.dart:1777:12)
#8 _runMainZoned. (dart:ui/hooks.dart:138:5)
#9 _delayEntrypointInvocation. (dart:isolate-patch/isolate_patch.dart:283:19)
#10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

@fayfive
Copy link
Author

fayfive commented Sep 8, 2021

@dcharkes
I solve it by adding
target_link_libraries(${BINARY_NAME} PRIVATE "${INSTALL_BUNDLE_LIB_DIR}/libavformat.so")
target_link_libraries(${BINARY_NAME} PRIVATE "${INSTALL_BUNDLE_LIB_DIR}/libavcodec.so")
target_link_libraries(${BINARY_NAME} PRIVATE "${INSTALL_BUNDLE_LIB_DIR}/libavutil.so")
to the end of the example/linux/CMakeLists.txt
This method is not too standard, Because I should add them after I build the example. That means first I should generate the bundle/lib. If I "flutter clean" and debug, it will report error.
Can you show me the method?

@dcharkes
Copy link
Contributor

dcharkes commented Sep 8, 2021

This is my WIP:

# Link bundled libraries from plugins into main executable to enable dlopen
# with only library name.
foreach(plugin ${FLUTTER_PLUGIN_LIST})
  if(${plugin}_bundled_libraries)
    target_link_libraries(
      ${BINARY_NAME}
      PRIVATE
      ${${plugin}_bundled_libraries}
    )
  endif()
endforeach(plugin)

It links the libraries from their source location in the build step, before the install step copies them to the final location.

When the libraries are linked in the build step, we can do DynamicLibrary.open('lib/lib$libName.so') instead of opening an absolute path constructed via Platform.resolvedExecutable.

(I am not sure why shared libraries that are not linked during building are not working, this requires more digging.)

I still find this somewhat hacky, and it requires developers making a flutter_app to modify their flutter_app/linux/CMakeLists.txt if they use a plugin package that ships dynamic libraries. I'm looking into making a linux/lib folder in plugin packages, and always linking and bundling all .so files from the lib folder in any flutter app in which it is used.

@dcharkes
Copy link
Contributor

dcharkes commented Sep 9, 2021

@fayfive It turns out we were looking at the wrong RUNPATH.

(build flutter app)
$ ./build/linux/x64/release/bundle/flutter_app
(fails)
$ readelf -d build/linux/x64/release/bundle/flutter_app | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/lib]
$ readelf -d build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so | grep RUNPATH
(not result)
$ patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so
$ readelf -d build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN]
$ ./build/linux/x64/release/bundle/flutter_app
(works)

When the RUNPATH is set in libflutter_linux_gtk.so, we don't need to dynamically link the libraries at all, we can just dynamically load them.

@fayfive
Copy link
Author

fayfive commented Sep 9, 2021

@dcharkes
That's great! Thanks for your help!
I think it will be fixed next flutter version^_^

@maks
Copy link

maks commented Oct 7, 2021

@dcharkes thanks for your work on this! Especially documenting the comments the workarounds.

I just wanted to report that combining your instructions from this comment to add to my plugin's linux/CMakeLists.txt:

# List of absolute paths to libraries that should be bundled with the plugin
set(mylib_dylib_bundled_libraries
  "${CMAKE_CURRENT_SOURCE_DIR}/my_native_lib.so"
  PARENT_SCOPE
)

and then also your workaround described in the PR in my apps linux/CMakeLists.txt:

# Link bundled libraries from plugins into main executable to enable dlopen
# with only library name.
# Will NOT be needed once: https://github.com/flutter/engine/pull/28525 lands in Flutter channel we are using
foreach(plugin ${FLUTTER_PLUGIN_LIST})
  if(${plugin}_bundled_libraries)
    target_link_libraries(
      ${BINARY_NAME}
      PRIVATE
      ${${plugin}_bundled_libraries}
    )
  endif()
endforeach(plugin)

Gets me a working Flutter Linux Desktop using a plugin which calls into a (Zig compiled) native lib via FFI 🎉

I'll look out for your recently merged PR to make its way into Flutter beta channel to test that out later on too.

@maks
Copy link

maks commented Oct 21, 2021

@dcharkes following up on this, I'm now trying to use a prebuilt .so in my plugin. What I did was follow your example code in previous comment here and added to my plugin's CMakeLists.txt:

set(mylib_dylib_bundled_libraries
  "${CMAKE_CURRENT_SOURCE_DIR}libmyspecial.so"
  PARENT_SCOPE
)

I then depend on this plugin in my Flutter Linux desktop app and it all works except I found that in the Flutter apps build/linux/x64/debug/bundle/lib I have the so named as: libmyspecial_plugin_plugin.so

As I said, it all works, but I wanted to check if it's expected that the plugin_plugin suffix gets added or am I doing something wrong? BTW This is using beta channel, version 2.6.0-5.2.pre

@maks
Copy link

maks commented Oct 21, 2021

oops sorry, I have been rather dense here! Of course I just spotted that I have this in my CMake file:

# This value is used when generating builds using this plugin, so it must
# not be changed
set(PLUGIN_NAME "myspecial_plugin_plugin")

@Sunbreak
Copy link

Sunbreak commented Apr 29, 2022

@fayfive It turns out we were looking at the wrong RUNPATH.

(build flutter app)
$ ./build/linux/x64/release/bundle/flutter_app
(fails)
$ readelf -d build/linux/x64/release/bundle/flutter_app | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN/lib]
$ readelf -d build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so | grep RUNPATH
(not result)
$ patchelf --set-rpath '$ORIGIN' build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so
$ readelf -d build/linux/x64/release/bundle/lib/libflutter_linux_gtk.so | grep RUNPATH
 0x000000000000001d (RUNPATH)            Library runpath: [$ORIGIN]
$ ./build/linux/x64/release/bundle/flutter_app
(works)

When the RUNPATH is set in libflutter_linux_gtk.so, we don't need to dynamically link the libraries at all, we can just dynamically load them.

Any PR we could track which Flutter version fixes the problem?

@dcharkes
Copy link
Contributor

This has been fixed since flutter/engine#28525.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

4 participants