diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c470a4a6..f5db2d11c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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) diff --git a/lib/plugin/package.go b/lib/plugin/package.go new file mode 100644 index 000000000..72598680f --- /dev/null +++ b/lib/plugin/package.go @@ -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". diff --git a/lib/plugin/plugin.go b/lib/plugin/plugin.go new file mode 100644 index 000000000..37ac5b94c --- /dev/null +++ b/lib/plugin/plugin.go @@ -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) + } + } +} diff --git a/rclone.go b/rclone.go index a1e745f4a..74fde16ab 100644 --- a/rclone.go +++ b/rclone.go @@ -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() {