Skip to content

Commit 5a89806

Browse files
feat!: Prebuilt binaries (no more need in cargo build) (#156)
closes #15
1 parent 7cdc71d commit 5a89806

5 files changed

Lines changed: 296 additions & 27 deletions

File tree

.github/workflows/release.yaml

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
name: Release
1+
name: Prebuild
22

33
on:
44
push:
5-
tags:
6-
- "v*"
5+
branches: [main, feat/prebuild]
76

87
jobs:
98
build:
@@ -18,44 +17,47 @@ jobs:
1817
# Glibc 2.21
1918
- os: ubuntu-latest
2019
target: x86_64-unknown-linux-gnu
21-
artifact_name: target/x86_64-unknown-linux-gnu/release/libfff_fuzzy.so
20+
artifact_name: target/x86_64-unknown-linux-gnu/release/libfff_nvim.so
2221
- os: ubuntu-latest
2322
target: aarch64-unknown-linux-gnu
24-
artifact_name: target/aarch64-unknown-linux-gnu/release/libfff_fuzzy.so
23+
artifact_name: target/aarch64-unknown-linux-gnu/release/libfff_nvim.so
2524
# Musl 1.2.3
2625
- os: ubuntu-latest
2726
target: x86_64-unknown-linux-musl
28-
artifact_name: target/x86_64-unknown-linux-musl/release/libfff_fuzzy.so
27+
artifact_name: target/x86_64-unknown-linux-musl/release/libfff_nvim.so
2928
- os: ubuntu-latest
3029
target: aarch64-unknown-linux-musl
31-
artifact_name: target/aarch64-unknown-linux-musl/release/libfff_fuzzy.so
32-
# Android (Termux)
33-
- os: ubuntu-latest
34-
target: aarch64-linux-android
35-
artifact_name: target/aarch64-linux-android/release/libfff_fuzzy.so
30+
artifact_name: target/aarch64-unknown-linux-musl/release/libfff_nvim.so
31+
# # Android (Termux)
32+
# - os: ubuntu-latest
33+
# target: aarch64-linux-android
34+
# artifact_name: target/aarch64-linux-android/release/libfff_nvim.so
3635

3736
## macOS builds
3837
- os: macos-latest
3938
target: x86_64-apple-darwin
40-
artifact_name: target/x86_64-apple-darwin/release/libfff_fuzzy.dylib
39+
artifact_name: target/x86_64-apple-darwin/release/libfff_nvim.dylib
4140
- os: macos-latest
4241
target: aarch64-apple-darwin
43-
artifact_name: target/aarch64-apple-darwin/release/libfff_fuzzy.dylib
42+
artifact_name: target/aarch64-apple-darwin/release/libfff_nvim.dylib
4443

4544
## Windows builds
4645
- os: windows-latest
4746
target: x86_64-pc-windows-msvc
48-
artifact_name: target/x86_64-pc-windows-msvc/release/fff_fuzzy.dll
47+
artifact_name: target/x86_64-pc-windows-msvc/release/fff_nvim.dll
48+
- os: windows-latest
49+
target: aarch64-pc-windows-msvc
50+
artifact_name: target/aarch64-pc-windows-msvc/release/fff_nvim.dll
4951

5052
steps:
5153
- uses: actions/checkout@v4
5254
with:
5355
persist-credentials: false
5456

55-
- name: Set Rust toolchain
56-
if: contains(matrix.target, 'linux')
57-
# https://github.com/rust-cross/cargo-zigbuild/issues/327
58-
run: echo -e '[toolchain]\nchannel = "nightly-2025-02-19"' > rust-toolchain.toml
57+
# - name: Set Rust toolchain
58+
# if: contains(matrix.target, 'linux')
59+
# # https://github.com/rust-cross/cargo-zigbuild/issues/327
60+
# run: echo -e '[toolchain]\nchannel = "nightly-2025-02-19"' > rust-toolchain.toml
5961

6062
- name: Install Rust
6163
run: |
@@ -88,7 +90,7 @@ jobs:
8890
uses: actions/upload-artifact@v4
8991
with:
9092
name: ${{ matrix.target }}
91-
path: ${{ matrix.target }}.*
93+
path: ${{ matrix.target }}*
9294

9395
release:
9496
name: Release
@@ -97,22 +99,37 @@ jobs:
9799
permissions:
98100
contents: write
99101
steps:
102+
- uses: actions/checkout@v4
103+
100104
- name: Download artifacts
101105
uses: actions/download-artifact@v4
106+
with:
107+
path: ./binaries
102108

103109
- name: Generate checksums
110+
working-directory: ./binaries
104111
run: |
112+
ls -a
105113
for file in ./**/*; do
106114
sha256sum "$file" > "${file}.sha256"
107115
done
108116
117+
- name: Prepare tag
118+
id: vars
119+
shell: bash
120+
run: |
121+
sha="$(git rev-parse --short HEAD)"
122+
echo "tag=$sha" >> $GITHUB_OUTPUT
123+
109124
- name: Upload Release Assets
110125
uses: softprops/action-gh-release@v2
111126
with:
112-
name: ${{ github.ref_name }}
113-
tag_name: ${{ github.ref_name }}
127+
name: "${{ steps.vars.outputs.tag }}"
128+
tag_name: "${{ steps.vars.outputs.tag }}"
114129
token: ${{ github.token }}
115-
files: ./**/*
130+
files: ./binaries/**/*
116131
draft: false
117-
prerelease: false
118-
generate_release_notes: true
132+
prerelease: true
133+
generate_release_notes: false
134+
body: |
135+
Nightly release from commit: ${{ github.sha }}

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ FFF.nvim requires:
4949
```lua
5050
{
5151
'dmtrKovalenko/fff.nvim',
52-
build = 'cargo build --release',
53-
-- or if you are using nixos
52+
build = function()
53+
-- this will download prebuild binary or try to use existing rustup toolchain to build from source
54+
-- (if you are using lazy you can use gb for rebuilding a plugin if needed)
55+
require("fff.download").download_or_build_binary()
56+
end,
57+
-- if you are using nixos
5458
-- build = "nix run .#release",
5559
opts = { -- (optional)
5660
debug = {

lua/fff/download.lua

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
local M = {}
2+
local system = require('fff.utils.system')
3+
local uv = vim and vim.uv or require('luv')
4+
5+
local GITHUB_REPO = 'dmtrKovalenko/fff.nvim'
6+
7+
local function get_current_version(plugin_dir, callback)
8+
vim.system({ 'git', 'rev-parse', '--short', 'HEAD' }, { cwd = plugin_dir }, function(result)
9+
if result.code ~= 0 or not result.stdout or result.stdout == '' then
10+
callback(nil)
11+
return
12+
end
13+
callback(result.stdout:gsub('%s+', ''))
14+
end)
15+
end
16+
17+
local function get_binary_dir(plugin_dir) return plugin_dir .. '/../target' end
18+
19+
local function get_binary_path(plugin_dir)
20+
local binary_dir = get_binary_dir(plugin_dir)
21+
local extension = system.get_lib_extension()
22+
return binary_dir .. '/libfff_nvim.' .. extension
23+
end
24+
25+
local function binary_exists(plugin_dir)
26+
local binary_path = get_binary_path(plugin_dir)
27+
local stat = uv.fs_stat(binary_path)
28+
return stat and stat.type == 'file'
29+
end
30+
31+
local function download_file(url, output_path, opts, callback)
32+
opts = opts or {}
33+
34+
local dir = vim.fn.fnamemodify(output_path, ':h')
35+
uv.fs_mkdir(dir, 493, function(err) -- 493 = 0755 octal
36+
if err and not err:match('EEXIST') then
37+
callback(false, 'Failed to create directory: ' .. err)
38+
return
39+
end
40+
41+
local curl_args = {
42+
'curl',
43+
'--fail',
44+
'--location',
45+
'--silent',
46+
'--show-error',
47+
'--output',
48+
output_path,
49+
}
50+
51+
if opts.proxy then
52+
table.insert(curl_args, '--proxy')
53+
table.insert(curl_args, opts.proxy)
54+
end
55+
56+
table.insert(curl_args, url)
57+
vim.system(curl_args, {}, function(result)
58+
if result.code ~= 0 then
59+
callback(false, 'Failed to download: ' .. (result.stderr or 'unknown error'))
60+
return
61+
end
62+
callback(true, nil)
63+
end)
64+
end)
65+
end
66+
67+
local function download_from_github(version, binary_path, opts, callback)
68+
opts = opts or {}
69+
70+
local triple = system.get_triple()
71+
local extension = system.get_lib_extension()
72+
local binary_name = triple .. '.' .. extension
73+
local url = string.format('https://github.com/%s/releases/download/%s/%s', GITHUB_REPO, version, binary_name)
74+
vim.schedule(function()
75+
vim.notify(string.format('Downloading fff.nvim binary for ' .. version), vim.log.levels.INFO)
76+
vim.notify(string.format('Do not open fff until you see a success notification.'), vim.log.levels.WARN)
77+
end)
78+
79+
download_file(url, binary_path, {
80+
proxy = opts.proxy,
81+
extra_curl_args = opts.extra_curl_args,
82+
}, function(success, err)
83+
if not success then
84+
callback(false, err)
85+
return
86+
end
87+
88+
-- Verify the binary can be loaded
89+
local ok, err_msg = pcall(function() package.loadlib(binary_path, 'luaopen_fff_nvim') end)
90+
91+
if not ok then
92+
uv.fs_unlink(binary_path)
93+
callback(false, 'Downloaded binary is not valid: ' .. (err_msg or 'unknown error'))
94+
return
95+
end
96+
97+
vim.schedule(function() vim.notify('fff.nvim binary downloaded successfully!', vim.log.levels.INFO) end)
98+
callback(true, nil)
99+
end)
100+
end
101+
102+
function M.ensure_downloaded(opts, callback)
103+
opts = opts or {}
104+
local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h')
105+
106+
if binary_exists(plugin_dir) and not opts.force then
107+
callback(true, nil)
108+
return
109+
end
110+
111+
local function on_version(target_version)
112+
if not target_version then
113+
callback(false, 'Could not determine target version')
114+
return
115+
end
116+
117+
local binary_path = get_binary_path(plugin_dir)
118+
download_from_github(target_version, binary_path, opts, callback)
119+
end
120+
121+
if opts.version then
122+
on_version(opts.version)
123+
else
124+
get_current_version(plugin_dir, on_version)
125+
end
126+
end
127+
128+
function M.download_binary(callback)
129+
M.ensure_downloaded({ force = true }, function(success, err)
130+
if not success then
131+
if callback then
132+
callback(false, err)
133+
else
134+
error('Failed to download fff.nvim binary: ' .. (err or 'unknown error'))
135+
end
136+
return
137+
end
138+
if callback then callback(true, nil) end
139+
end)
140+
end
141+
142+
function M.build_binary(callback)
143+
local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h')
144+
local has_rustup = vim.fn.executable('rustup') == 1
145+
if not has_rustup then
146+
callback(
147+
false,
148+
'rustup is not found. It is required to build the fff.nvim binary. Install it from https://rustup.rs/'
149+
)
150+
return
151+
end
152+
153+
vim.system({ 'cargo', 'build', '--release' }, { cwd = plugin_dir }, function(result)
154+
if result.code ~= 0 then
155+
callback(false, 'Failed to build rust binary: ' .. (result.stderr or 'unknown error'))
156+
return
157+
end
158+
callback(true, nil)
159+
end)
160+
end
161+
162+
function M.download_or_build_binary()
163+
M.ensure_downloaded({ force = true }, function(download_success, download_error)
164+
if download_success then return end
165+
166+
vim.schedule(
167+
function()
168+
vim.notify(
169+
'Error downloading binary: ' .. (download_error or 'unknown error') .. '\nTrying cargo build --release\n',
170+
vim.log.levels.WARN
171+
)
172+
end
173+
)
174+
175+
M.build_binary(function(build_success, build_error)
176+
if not build_success then
177+
error('Failed to build fff.nvim binary. Build error: ' .. (build_error or 'unknown error'))
178+
else
179+
vim.schedule(function() vim.notify('fff.nvim binary built successfully!', vim.log.levels.INFO) end)
180+
end
181+
end)
182+
end)
183+
end
184+
185+
function M.get_binary_path()
186+
local plugin_dir = vim.fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':h:h')
187+
return get_binary_path(plugin_dir)
188+
end
189+
190+
return M

lua/fff/rust/init.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local download = require('fff.download')
2+
13
--- @return string
24
local function get_lib_extension()
35
if jit.os:lower() == 'mac' or jit.os:lower() == 'osx' then return '.dylib' end
@@ -10,6 +12,7 @@ end
1012
local base_path = debug.getinfo(1).source:match('@?(.*/)')
1113

1214
local paths = {
15+
download.get_binary_path(),
1316
base_path .. '../../../target/release/lib?' .. get_lib_extension(),
1417
base_path .. '../../../target/release/?' .. get_lib_extension(),
1518
}
@@ -24,7 +27,7 @@ package.cpath = package.cpath .. ';' .. table.concat(paths, ';')
2427

2528
local ok, backend = pcall(require, 'fff_nvim')
2629
if not ok then
27-
error('Failed to load fff rust backend. Make sure that it has been built with `cargo build --release`')
30+
error('Failed to load fff rust backend. Make sure that it has been downloaded or built with `cargo build --release`')
2831
end
2932

3033
return backend

0 commit comments

Comments
 (0)