lib: add plugin support

This enables loading plugins from RCLONE_PLUGIN_PATH if set.
This commit is contained in:
Richard Patel 2019-09-23 02:41:02 +02:00 committed by Nick Craig-Wood
parent 349112df6b
commit 44b603d2bd
4 changed files with 93 additions and 1 deletions

View file

@ -376,3 +376,37 @@ Add your fs to the docs - you'll need to pick an icon for it from [fontawesome](
* `docs/content/about.md` - front page of rclone.org
* `docs/layouts/chrome/navbar.html` - add it to the website navigation
* `bin/make_manual.py` - add the page to the `docs` constant
## Writing a plugin ##
New features (backends, commands) can also be added "out-of-tree", through Go plugins.
Changes will be kept in a dynamically loaded file instead of being compiled into the main binary.
This is useful if you can't merge your changes upstream or don't want to maintain a fork of rclone.
Usage
- Naming
- Plugins names must have the pattern `librcloneplugin_KIND_NAME.so`.
- `KIND` should be one of `backend`, `command` or `bundle`.
- Example: A plugin with backend support for PiFS would be called
`librcloneplugin_backend_pifs.so`.
- Loading
- Supported on macOS & Linux as of now. ([Go issue for Windows support](https://github.com/golang/go/issues/19282))
- Supported on rclone v1.50 or greater.
- All plugins in the folder specified by variable `$RCLONE_PLUGIN_PATH` are loaded.
- If this variable doesn't exist, plugin support is disabled.
- Plugins must be compiled against the exact version of rclone to work.
(The rclone used during building the plugin must be the same as the source of rclone)
Building
To turn your existing additions into a Go plugin, move them to an external repository
and change the top-level package name to `main`.
Check `rclone --version` and make sure that the plugin's rclone dependency and host Go version match.
Then, run `go build -buildmode=plugin -o PLUGIN_NAME.so .` to build the plugin.
[Go reference](https://godoc.org/github.com/rclone/rclone/lib/plugin)
[Minimal example](https://gist.github.com/terorie/21b517ee347828e899e1913efc1d684f)

16
lib/plugin/package.go Normal file
View file

@ -0,0 +1,16 @@
// Package plugin implements loading out-of-tree storage backends
// using https://golang.org/pkg/plugin/ on Linux and macOS.
//
// If the $RCLONE_PLUGIN_PATH is present, any Go plugins in that dir
// named like librcloneplugin_NAME.so will be loaded.
//
// To create a plugin, write the backend package like it was in-tree
// but set the package name to "main". Then, build the plugin with
//
// go build -buildmode=plugin -o librcloneplugin_NAME.so
//
// where NAME equals the plugin's fs.RegInfo.Name.
package plugin
// Build for plugin for unsupported platforms to stop go complaining
// about "no buildable Go source files".

41
lib/plugin/plugin.go Normal file
View file

@ -0,0 +1,41 @@
// +build darwin linux
package plugin
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"plugin"
"strings"
)
func init() {
dir := os.Getenv("RCLONE_PLUGIN_PATH")
if dir == "" {
return
}
// Get file names of plugin dir
listing, err := ioutil.ReadDir(dir)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to open plugin directory:", err)
}
// Enumerate file names, load valid plugins
for _, file := range listing {
// Match name
fileName := file.Name()
if !strings.HasPrefix(fileName, "librcloneplugin_") {
continue
}
if !strings.HasSuffix(fileName, ".so") {
continue
}
// Try to load plugin
_, err := plugin.Open(filepath.Join(dir, fileName))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load plugin %s: %s\n",
fileName, err)
}
}
}

View file

@ -6,7 +6,8 @@ package main
import (
_ "github.com/rclone/rclone/backend/all" // import all backends
"github.com/rclone/rclone/cmd"
_ "github.com/rclone/rclone/cmd/all" // import all commands
_ "github.com/rclone/rclone/cmd/all" // import all commands
_ "github.com/rclone/rclone/lib/plugin" // import plugins
)
func main() {