From a9d5912b4dd1575d6882575861b32c0b433dde65 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 25 Sep 2013 02:08:42 +0900 Subject: [PATCH 1/5] Prototype implementation of dependency resolution (#2) --- README.md | 29 +++++++++++ plug.vim | 152 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index cc64944..6934935 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Somewhere between [Pathogen](https://github.com/tpope/vim-pathogen) and - Parallel installation/update (requires [+ruby](http://junegunn.kr/2013/09/installing-vim-with-ruby-support/)) - Smallest possible feature set +- Dependency resolution using `Plugfile` (experimental) ### Cons. @@ -64,6 +65,34 @@ plugins with `plug#begin(path)` call. (Default number of threads = `g:plug_threads` or 16) +### Dependency resolution + +If a Vim plugin specifies its dependent plugins in `Plugfile` in its root +directory, vim-plug will automatically source it recursively during the +installation. + +A `Plugfile` should contain a set of `Plug` commands for the dependent plugins. + +I've created two dummy repositories with Plugfiles as an example to this scheme. + +- [junegunn/dummy1](https://github.com/junegunn/dummy1) + - `Plug 'junegunn/vim-scroll-position'` + - `Plug 'junegunn/dummy2'` +- [junegunn/dummy2](https://github.com/junegunn/dummy2) + - `Plug 'junegunn/Zenburn'` + - `Plug 'junegunn/jellybeans.vim'` + - `Plug 'junegunn/dummy1'` + - (Circular dependencies are ignored) + +If you put `Plug 'junegunn/dummy1'` in your configuration file, and run +`:PlugInstall`, + +1. vim-plug first installs dummy1 +2. And sees if the repository has Plugfile +3. Plugfile is loaded and vim-plug discovers dependent plugins +4. Dependent plugins are then installed as well, and their Plugfiles are + examined and their dependencies are resolved recursively. + ### Articles - [Writing my own Vim plugin manager](http://junegunn.kr/2013/09/writing-my-own-vim-plugin-manager) diff --git a/plug.vim b/plug.vim index 2830309..8f7c8d9 100644 --- a/plug.vim +++ b/plug.vim @@ -51,6 +51,7 @@ endif let g:loaded_plug = 1 let s:plug_source = 'https://raw.github.com/junegunn/vim-plug/master/plug.vim' +let s:plug_file = 'Plugfile' let s:plug_win = 0 let s:is_win = has('win32') || has('win64') let s:me = expand(':p') @@ -74,7 +75,7 @@ function! plug#begin(...) let g:plug_home = home let g:plugs = {} - command! -nargs=+ Plug call s:add() + command! -nargs=+ Plug call s:add(1, ) command! -nargs=* PlugInstall call s:install() command! -nargs=* PlugUpdate call s:update() command! -nargs=0 -bang PlugClean call s:clean('' == '!') @@ -83,6 +84,11 @@ function! plug#begin(...) endfunction function! plug#end() + let keys = keys(g:plugs) + while !empty(keys) + let keys = keys(s:extend(keys)) + endwhile + set nocompatible filetype off for plug in values(g:plugs) @@ -97,9 +103,10 @@ function! plug#end() endfunction function! s:add(...) - if a:0 == 1 - let [plugin, branch] = [a:1, 'master'] - elseif a:0 == 2 + let force = a:1 + if a:0 == 2 + let [plugin, branch] = [a:2, 'master'] + elseif a:0 == 3 let [plugin, branch] = a:000 else echoerr "Invalid number of arguments (1..2)" @@ -116,6 +123,8 @@ function! s:add(...) endif let name = substitute(split(plugin, '/')[-1], '\.git$', '', '') + if !force && has_key(g:plugs, name) | return | endif + let dir = fnamemodify(join([g:plug_home, name], '/'), ':p') let spec = { 'dir': dir, 'uri': uri, 'branch': branch } @@ -229,47 +238,79 @@ function! s:update_impl(pull, args) call s:finish() endfunction -function! s:update_serial(pull) - let st = reltime() - let base = g:plug_home - let cnt = 0 - let total = len(g:plugs) +function! s:extend(names) + let prev = copy(g:plugs) + try + command! -nargs=+ Plug call s:add(0, ) + for name in a:names + let spec = g:plugs[name] + let plugfile = spec.dir . '/'. s:plug_file + if filereadable(plugfile) + execute "source ". plugfile + endif + endfor + finally + command! -nargs=+ Plug call s:add(1, ) + endtry + return filter(copy(g:plugs), '!has_key(prev, v:key)') +endfunction - for [name, spec] in items(g:plugs) - let cnt += 1 - let d = shellescape(spec.dir) - if isdirectory(spec.dir) - execute 'cd '.spec.dir - if s:git_valid(spec, 0) - let result = a:pull ? - \ s:system( - \ printf('git checkout -q %s && git pull origin %s 2>&1', - \ spec.branch, spec.branch)) : 'Already installed' - let error = a:pull ? v:shell_error != 0 : 0 +function! s:update_progress(cnt, total) + call setline(1, "Updating plugins (".a:cnt."/".a:total.")") + call s:progress_bar(2, a:cnt, a:total) + normal! 2G + redraw +endfunction + +function! s:update_serial(pull) + let st = reltime() + let base = g:plug_home + let todo = copy(g:plugs) + let total = len(todo) + let done = {} + + while !empty(todo) + for [name, spec] in items(todo) + let done[name] = 1 + let d = shellescape(spec.dir) + if isdirectory(spec.dir) + execute 'cd '.spec.dir + if s:git_valid(spec, 0) + let result = a:pull ? + \ s:system( + \ printf('git checkout -q %s && git pull origin %s 2>&1', + \ spec.branch, spec.branch)) : 'Already installed' + let error = a:pull ? v:shell_error != 0 : 0 + else + let result = "PlugClean required. Invalid remote." + let error = 1 + endif else - let result = "PlugClean required. Invalid remote." - let error = 1 + if !isdirectory(base) + call mkdir(base, 'p') + endif + execute 'cd '.base + let result = s:system( + \ printf('git clone --recursive %s -b %s %s 2>&1', + \ shellescape(spec.uri), shellescape(spec.branch), d)) + let error = v:shell_error != 0 endif + cd - + if error + let result = '(x) ' . result + endif + call append(3, '- ' . name . ': ' . result) + call s:update_progress(len(done), total) + endfor + + if !empty(s:extend(keys(todo))) + let todo = filter(copy(g:plugs), '!has_key(done, v:key)') + let total += len(todo) + call s:update_progress(len(done), total) else - if !isdirectory(base) - call mkdir(base, 'p') - endif - execute 'cd '.base - let result = s:system( - \ printf('git clone --recursive %s -b %s %s 2>&1', - \ shellescape(spec.uri), shellescape(spec.branch), d)) - let error = v:shell_error != 0 + break endif - cd - - if error - let result = '(x) ' . result - endif - call setline(1, "Updating plugins (".cnt."/".total.")") - call s:progress_bar(2, cnt, total) - call append(3, '- ' . name . ': ' . result) - normal! 2G - redraw - endfor + endwhile call setline(1, "Updated. Elapsed time: " . split(reltimestr(reltime(st)))[0] . ' sec.') endfunction @@ -282,22 +323,32 @@ function! s:update_parallel(pull, threads) cd = VIM::evaluate('s:is_win').to_i == 1 ? 'cd /d' : 'cd' pull = VIM::evaluate('a:pull').to_i == 1 base = VIM::evaluate('g:plug_home') - all = VIM::evaluate('g:plugs') - total = all.length - cnt = 0 + all = VIM::evaluate('copy(g:plugs)') + done = {} skip = 'Already installed' mtx = Mutex.new take1 = proc { mtx.synchronize { all.shift } } - log = proc { |name, result, ok| - mtx.synchronize { + logh = proc { + cnt, tot = done.length, VIM::evaluate('len(g:plugs)') + $curbuf[1] = "Updating plugins (#{cnt}/#{tot})" + $curbuf[2] = '[' + ('=' * cnt).ljust(tot) + ']' + VIM::command('normal! 2G') + VIM::command('redraw') + } + log = proc { |name, result, ok| + mtx.synchronize do + done[name] = true result = '(x) ' + result unless ok result = "- #{name}: #{result}" - $curbuf[1] = "Updating plugins (#{cnt += 1}/#{total})" - $curbuf[2] = '[' + ('=' * cnt).ljust(total) + ']' $curbuf.append 3, result - VIM::command('normal! 2G') - VIM::command('redraw') - } + logh.call + end + } + refill = proc { |name| + mtx.synchronize do + all.merge! VIM::evaluate("s:extend(['#{name}'])") + logh.call + end } VIM::evaluate('a:threads').to_i.times.map { |i| Thread.new(i) do |ii| @@ -323,6 +374,7 @@ function! s:update_parallel(pull, threads) end result = result.lines.to_a.last.strip log.call name, result, ok + refill.call name end end }.each(&:join) From 214b4fb6facea4a58623dd35a9a13c80dbfff33b Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 25 Sep 2013 12:02:15 +0900 Subject: [PATCH 2/5] Prevent serialization due to early termination of threads --- plug.vim | 56 +++++++++++++++++++++++++++----------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/plug.vim b/plug.vim index 8f7c8d9..c10ae60 100644 --- a/plug.vim +++ b/plug.vim @@ -344,40 +344,38 @@ function! s:update_parallel(pull, threads) logh.call end } - refill = proc { |name| - mtx.synchronize do - all.merge! VIM::evaluate("s:extend(['#{name}'])") - logh.call - end - } - VIM::evaluate('a:threads').to_i.times.map { |i| - Thread.new(i) do |ii| - while pair = take1.call - name = pair.first - dir, uri, branch = pair.last.values_at *%w[dir uri branch] - ok, result = - if File.directory? dir - current_uri = `#{cd} #{dir} && git config remote.origin.url`.chomp - if $? == 0 && current_uri == uri - if pull - [true, `#{cd} #{dir} && git checkout -q #{branch} && git pull origin #{branch} 2>&1`] + until all.empty? + names = all.keys + [names.length, VIM::evaluate('a:threads').to_i].min.times.map { |i| + Thread.new(i) do + while pair = take1.call + name = pair.first + dir, uri, branch = pair.last.values_at *%w[dir uri branch] + ok, result = + if File.directory? dir + current_uri = `#{cd} #{dir} && git config remote.origin.url`.chomp + if $? == 0 && current_uri == uri + if pull + [true, `#{cd} #{dir} && git checkout -q #{branch} && git pull origin #{branch} 2>&1`] + else + [true, skip] + end else - [true, skip] + [false, "PlugClean required. Invalid remote."] end else - [false, "PlugClean required. Invalid remote."] + FileUtils.mkdir_p(base) + r = `#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{dir} 2>&1` + [$? == 0, r] end - else - FileUtils.mkdir_p(base) - r = `#{cd} #{base} && git clone --recursive #{uri} -b #{branch} #{dir} 2>&1` - [$? == 0, r] - end - result = result.lines.to_a.last.strip - log.call name, result, ok - refill.call name + result = result.lines.to_a.last.strip + log.call name, result, ok + end end - end - }.each(&:join) + }.each(&:join) + all.merge! VIM::evaluate("s:extend(#{names.inspect})") + logh.call + end $curbuf[1] = "Updated. Elapsed time: #{"%.6f" % (Time.now - st)} sec." EOF endfunction From 2cf0c4fda509688f6fc9c940fb0539c01dda0c05 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 25 Sep 2013 12:30:02 +0900 Subject: [PATCH 3/5] Spawn multiple threads unless g:plug_threads is set to 1 --- README.md | 16 +++++++--------- plug.vim | 20 ++++++++------------ 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6934935..a60c1ca 100644 --- a/README.md +++ b/README.md @@ -73,16 +73,14 @@ installation. A `Plugfile` should contain a set of `Plug` commands for the dependent plugins. -I've created two dummy repositories with Plugfiles as an example to this scheme. +I've created three dummy repositories with Plugfiles as an example to this +scheme. -- [junegunn/dummy1](https://github.com/junegunn/dummy1) - - `Plug 'junegunn/vim-scroll-position'` - - `Plug 'junegunn/dummy2'` -- [junegunn/dummy2](https://github.com/junegunn/dummy2) - - `Plug 'junegunn/Zenburn'` - - `Plug 'junegunn/jellybeans.vim'` - - `Plug 'junegunn/dummy1'` - - (Circular dependencies are ignored) +- [junegunn/dummy1](https://github.com/junegunn/dummy1/blob/master/Plugfile) + - Plugfile includes `Plug 'junegunn/dummy2'` +- [junegunn/dummy2](https://github.com/junegunn/dummy2/blob/master/Plugfile) + - Plugfile includes `Plug 'junegunn/dummy3'` +- [junegunn/dummy3](https://github.com/junegunn/dummy3/blob/master/Plugfile) If you put `Plug 'junegunn/dummy1'` in your configuration file, and run `:PlugInstall`, diff --git a/plug.vim b/plug.vim index c10ae60..b494f43 100644 --- a/plug.vim +++ b/plug.vim @@ -217,12 +217,7 @@ function! s:finish() endfunction function! s:update_impl(pull, args) - if has('ruby') && get(g:, 'plug_parallel', 1) - let threads = min( - \ [len(g:plugs), len(a:args) > 0 ? a:args[0] : get(g:, 'plug_threads', 16)]) - else - let threads = 1 - endif + let threads = len(a:args) > 0 ? a:args[0] : get(g:, 'plug_threads', 16) call s:prepare() call append(0, a:pull ? 'Updating plugins' : 'Installing plugins') @@ -230,7 +225,7 @@ function! s:update_impl(pull, args) normal! 2G redraw - if threads > 1 + if has('ruby') && threads > 1 call s:update_parallel(a:pull, threads) else call s:update_serial(a:pull) @@ -255,8 +250,9 @@ function! s:extend(names) return filter(copy(g:plugs), '!has_key(prev, v:key)') endfunction -function! s:update_progress(cnt, total) - call setline(1, "Updating plugins (".a:cnt."/".a:total.")") +function! s:update_progress(pull, cnt, total) + call setline(1, (a:pull ? 'Updating' : 'Installing'). + \ " plugins (".a:cnt."/".a:total.")") call s:progress_bar(2, a:cnt, a:total) normal! 2G redraw @@ -300,13 +296,13 @@ function! s:update_serial(pull) let result = '(x) ' . result endif call append(3, '- ' . name . ': ' . result) - call s:update_progress(len(done), total) + call s:update_progress(a:pull, len(done), total) endfor if !empty(s:extend(keys(todo))) let todo = filter(copy(g:plugs), '!has_key(done, v:key)') let total += len(todo) - call s:update_progress(len(done), total) + call s:update_progress(a:pull, len(done), total) else break endif @@ -330,7 +326,7 @@ function! s:update_parallel(pull, threads) take1 = proc { mtx.synchronize { all.shift } } logh = proc { cnt, tot = done.length, VIM::evaluate('len(g:plugs)') - $curbuf[1] = "Updating plugins (#{cnt}/#{tot})" + $curbuf[1] = "#{pull ? 'Updating' : 'Installing'} plugins (#{cnt}/#{tot})" $curbuf[2] = '[' + ('=' * cnt).ljust(tot) + ']' VIM::command('normal! 2G') VIM::command('redraw') From 8ee3a0b36df623cf303c4e145ac8b27aefb4b23d Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 25 Sep 2013 12:41:49 +0900 Subject: [PATCH 4/5] Fix branch argument --- plug.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plug.vim b/plug.vim index b494f43..2e51d36 100644 --- a/plug.vim +++ b/plug.vim @@ -107,7 +107,7 @@ function! s:add(...) if a:0 == 2 let [plugin, branch] = [a:2, 'master'] elseif a:0 == 3 - let [plugin, branch] = a:000 + let [plugin, branch] = [a:2, a:3] else echoerr "Invalid number of arguments (1..2)" return From d05f75f782f6cf56b6103f68c1065ae18a7a41c3 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Wed, 25 Sep 2013 12:45:14 +0900 Subject: [PATCH 5/5] Proper handling of invalid branch in parallel installer --- plug.vim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plug.vim b/plug.vim index 2e51d36..c51cd01 100644 --- a/plug.vim +++ b/plug.vim @@ -274,7 +274,7 @@ function! s:update_serial(pull) if s:git_valid(spec, 0) let result = a:pull ? \ s:system( - \ printf('git checkout -q %s && git pull origin %s 2>&1', + \ printf('git checkout -q %s 2>&1 && git pull origin %s 2>&1', \ spec.branch, spec.branch)) : 'Already installed' let error = a:pull ? v:shell_error != 0 : 0 else @@ -352,7 +352,8 @@ function! s:update_parallel(pull, threads) current_uri = `#{cd} #{dir} && git config remote.origin.url`.chomp if $? == 0 && current_uri == uri if pull - [true, `#{cd} #{dir} && git checkout -q #{branch} && git pull origin #{branch} 2>&1`] + output = `#{cd} #{dir} && git checkout -q #{branch} 2>&1 && git pull origin #{branch} 2>&1` + [$? == 0, output] else [true, skip] end