neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

bump_deps.lua (6546B)


      1 #!/usr/bin/env -S nvim -l
      2 
      3 -- Usage:
      4 --    ./scripts/bump_deps.lua -h
      5 
      6 assert(vim.fn.executable('gh') == 1)
      7 assert(vim.fn.executable('sed') == 1)
      8 
      9 local required_branch_prefix = 'bump-'
     10 local commit_prefix = 'build(deps): '
     11 
     12 local repos = {
     13  'luajit/luajit',
     14  'libuv/libuv',
     15  'luvit/luv',
     16  'neovim/unibilium',
     17  'juliastrings/utf8proc',
     18  'tree-sitter/tree-sitter',
     19  'tree-sitter/tree-sitter-c',
     20  'tree-sitter-grammars/tree-sitter-lua',
     21  'tree-sitter-grammars/tree-sitter-vim',
     22  'neovim/tree-sitter-vimdoc',
     23  'tree-sitter-grammars/tree-sitter-query',
     24  'tree-sitter-grammars/tree-sitter-markdown',
     25  'bytecodealliance/wasmtime',
     26  'uncrustify/uncrustify',
     27 }
     28 
     29 local zig_mode = {
     30  luajit = false,
     31  uncrustify = false,
     32  wasmtime = false,
     33  unibilium = 'nested',
     34  utf8proc = 'nested',
     35  libuv = 'nested',
     36 }
     37 
     38 local dependency_table = {} --- @type table<string, string>
     39 for _, repo in pairs(repos) do
     40  dependency_table[vim.fs.basename(repo)] = repo
     41 end
     42 
     43 local function die(msg)
     44  print(msg)
     45  vim.cmd('cquit 1')
     46 end
     47 
     48 -- Executes and returns the output of `cmd`, or nil on failure.
     49 -- if die_on_fail is true, process dies with die_msg on failure
     50 local function _run(cmd, die_on_fail, die_msg)
     51  local rv = vim.system(cmd):wait()
     52  if rv.code ~= 0 then
     53    if die_on_fail then
     54      die(die_msg)
     55    end
     56    return nil
     57  end
     58  return vim.trim(rv.stdout)
     59 end
     60 
     61 -- Run a command, return nil on failure
     62 local function run(cmd)
     63  return _run(cmd, false, '')
     64 end
     65 
     66 -- Run a command, die on failure with err_msg
     67 local function run_die(cmd, err_msg)
     68  -- print(vim.inspect(table.concat(cmd, ' ')))
     69  return _run(cmd, true, err_msg)
     70 end
     71 
     72 local nvim_src_dir = run({ 'git', 'rev-parse', '--show-toplevel' })
     73 local deps_file = nvim_src_dir .. '/' .. 'cmake.deps/deps.txt'
     74 
     75 --- @param repo string
     76 --- @param ref string
     77 local function get_archive_info(repo, ref)
     78  local temp_dir = os.getenv('TMPDIR') or os.getenv('TEMP') or '/tmp'
     79 
     80  local archive_name = ref .. '.tar.gz'
     81  local archive_path = temp_dir .. '/' .. archive_name
     82  local archive_url = 'https://github.com/' .. repo .. '/archive/' .. archive_name
     83 
     84  run_die(
     85    { 'curl', '-sfL', archive_url, '-o', archive_path },
     86    'Failed to download archive from GitHub'
     87  )
     88 
     89  local shacmd = (
     90    vim.fn.executable('sha256sum') == 1 and { 'sha256sum', archive_path }
     91    or { 'shasum', '-a', '256', archive_path }
     92  )
     93  local archive_sha = run(shacmd):gmatch('%w+')()
     94  return { url = archive_url, sha = archive_sha }
     95 end
     96 
     97 local function get_gh_commit_sha(repo, ref)
     98  local full_repo = string.format('https://github.com/%s.git', repo)
     99  local tag_exists = run_die({ 'git', 'ls-remote', full_repo, 'refs/tags/' .. ref }) ~= ''
    100  -- We'd rather use the git tag over commit sha if possible
    101  if tag_exists then
    102    return ref
    103  end
    104 
    105  local sha = assert(
    106    run_die(
    107      { 'gh', 'api', 'repos/' .. repo .. '/commits/' .. ref, '--jq', '.sha' },
    108      'Failed to get commit hash from GitHub. Not a valid ref?'
    109    )
    110  )
    111  return sha
    112 end
    113 
    114 local function update_deps_file(symbol, kind, value)
    115  run_die({
    116    'sed',
    117    '-i',
    118    '-e',
    119    's/' .. symbol .. '_' .. kind .. '.*$' .. '/' .. symbol .. '_' .. kind .. ' ' .. value .. '/',
    120    deps_file,
    121  }, 'Failed to write ' .. deps_file)
    122 end
    123 
    124 local function ref(name, _ref)
    125  local repo = dependency_table[name]
    126  local symbol = string.gsub(name, 'tree%-sitter', 'treesitter'):gsub('%-', '_')
    127  local symbol_upper = symbol:upper()
    128 
    129  run_die(
    130    { 'git', 'diff', '--quiet', 'HEAD', '--', deps_file },
    131    deps_file .. ' has uncommitted changes'
    132  )
    133 
    134  _ref = get_gh_commit_sha(repo, _ref)
    135 
    136  local archive = get_archive_info(repo, _ref)
    137  local comment = string.sub(_ref, 1, 9)
    138 
    139  local checked_out_branch = assert(run({ 'git', 'rev-parse', '--abbrev-ref', 'HEAD' }))
    140  if not checked_out_branch:match('^' .. required_branch_prefix) then
    141    print(
    142      "Current branch '"
    143        .. checked_out_branch
    144        .. "' doesn't seem to start with "
    145        .. required_branch_prefix
    146    )
    147    print('Checking out to bump-' .. name)
    148    run_die({ 'git', 'checkout', '-b', 'bump-' .. name }, 'git failed to create branch')
    149  end
    150 
    151  print('Updating ' .. name .. ' to ' .. archive.url .. '\n')
    152  update_deps_file(symbol_upper, 'URL', archive.url:gsub('/', '\\/'))
    153  update_deps_file(symbol_upper, 'SHA256', archive.sha)
    154  run_die({ 'git', 'add', deps_file })
    155 
    156  local zig = zig_mode[symbol]
    157  if zig ~= false then
    158    if zig == 'nested' then
    159      -- don't care about un-cding, "git commit" doesn't care and then we die
    160      vim.fn.chdir('deps/' .. symbol)
    161    end
    162    -- note: get_gh_commit_sha is likely superfluous with zig. but use the same resolved hash, for consistency.
    163    run_die({
    164      'zig',
    165      'fetch',
    166      '--save=' .. symbol,
    167      'git+https://github.com/' .. repo .. '#' .. _ref,
    168    })
    169    run_die({ 'git', 'add', 'build.zig.zon' })
    170  end
    171 
    172  run_die({
    173    'git',
    174    'commit',
    175    '-m',
    176    commit_prefix .. 'bump ' .. name .. ' to ' .. comment,
    177  }, 'git failed to commit')
    178 end
    179 
    180 local function usage()
    181  local this_script = tostring(vim.fs.basename(_G.arg[0]))
    182  local script_exe = './' .. this_script
    183  local help = ([=[
    184    Bump Nvim dependencies
    185 
    186    Usage:  %s [options]
    187        Bump to HEAD, tagged version or commit:
    188            %s luv --head
    189            %s luv --ref 1.43.0-0
    190            %s luv --ref abc123
    191 
    192    Options:
    193        -h, --help            show this message and exit.
    194        --list                list all dependencies
    195 
    196    Dependency Options:
    197        --ref <ref>           bump to a specific commit or tag.
    198        --head                bump to a current head.
    199  ]=]):format(script_exe, script_exe, script_exe, script_exe)
    200  print(help)
    201 end
    202 
    203 local function list_deps()
    204  local l = 'Dependencies:\n'
    205  for k in vim.spairs(dependency_table) do
    206    l = string.format('%s\n%s%s', l, string.rep(' ', 2), k)
    207  end
    208  print(l)
    209 end
    210 
    211 do
    212  local args = {}
    213  local i = 1
    214  while i <= #_G.arg do
    215    if _G.arg[i] == '-h' or _G.arg[i] == '--help' then
    216      args.h = true
    217    elseif _G.arg[i] == '--list' then
    218      args.list = true
    219    elseif _G.arg[i] == '--ref' then
    220      args.ref = _G.arg[i + 1]
    221      i = i + 1
    222    elseif _G.arg[i] == '--head' then
    223      args.ref = 'HEAD'
    224    elseif vim.startswith(_G.arg[i], '--') then
    225      die(string.format('Invalid argument %s\n', _G.arg[i]))
    226    else
    227      args.dep = _G.arg[i]
    228    end
    229    i = i + 1
    230  end
    231 
    232  if args.h then
    233    usage()
    234  elseif args.list then
    235    list_deps()
    236  elseif args.ref then
    237    ref(args.dep, args.ref)
    238  else
    239    die('missing required arg\n')
    240  end
    241 end