├── LICENSE ├── Linux ├── x64 │ ├── unzip │ └── zip └── x86 │ ├── unzip │ └── zip ├── README.md ├── Windows ├── chmod.exe ├── libiconv2.dll ├── libintl3.dll ├── unzip.exe └── zip.exe ├── __bin └── upkg ├── __meta.lua └── init.lua /LICENSE: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | 3 | Pkg: ULUA Package Manager. 4 | 5 | Copyright (C) 2015-2016 Stefano Peluchetti. All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | [ MIT license: http://opensource.org/licenses/MIT ] 26 | 27 | =============================================================================== -------------------------------------------------------------------------------- /Linux/x64/unzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Linux/x64/unzip -------------------------------------------------------------------------------- /Linux/x64/zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Linux/x64/zip -------------------------------------------------------------------------------- /Linux/x86/unzip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Linux/x86/unzip -------------------------------------------------------------------------------- /Linux/x86/zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Linux/x86/zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pkg: ULua Package Manager 2 | ========================= 3 | 4 | A simple but powerful package manager for the [ULua distribution](http://ulua.io). 5 | 6 | ## Features 7 | 8 | - install, remove, list, query, update packages 9 | - proxy (with auth) support 10 | 11 | ```lua 12 | local pkg = require 'pkg' 13 | 14 | pkg.available() -- List all available packages. 15 | pkg.available('?penlight') -- List available packages matching in name or description 'penlight'. 16 | pkg.available('pl') -- Detailed info about Penlight. 17 | 18 | pkg.add('pl') -- Install Penlight and its dependencies. 19 | pkg.remove('pl') -- Remove Penlight and all packages that depend on it. 20 | 21 | pkg.update() -- Update all packages. 22 | ``` 23 | 24 | ## Upkg 25 | 26 | An helper executable named `upkg` is included which replicates the functionality of this module. 27 | 28 | ``` 29 | upkg command [-s] [name] [version] 30 | command : one of "status", "available", "add", "remove", "update" 31 | -s : enable searching (only for "status" and "available" commands) 32 | name : package name 33 | version : package version 34 | ``` 35 | 36 | ## Install 37 | 38 | This module is included in the ULua distribution. 39 | 40 | ## Documentation 41 | 42 | Refer to the [official documentation](http://ulua.io/pkg.html). -------------------------------------------------------------------------------- /Windows/chmod.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Windows/chmod.exe -------------------------------------------------------------------------------- /Windows/libiconv2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Windows/libiconv2.dll -------------------------------------------------------------------------------- /Windows/libintl3.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Windows/libintl3.dll -------------------------------------------------------------------------------- /Windows/unzip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Windows/unzip.exe -------------------------------------------------------------------------------- /Windows/zip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uluadist/lua-pkg/0e88b87ae83a3efa7a52fa60d2392e84d39fe9ed/Windows/zip.exe -------------------------------------------------------------------------------- /__bin/upkg: -------------------------------------------------------------------------------- 1 | local pkg = require 'pkg' 2 | 3 | local commands = { 4 | status = true, 5 | available = true, 6 | add = true, 7 | remove = true, 8 | update = true, 9 | } 10 | 11 | local function err() 12 | print[[ 13 | Universal Lua package manager. Usage: 14 | upkg command [-s] [name] [version] 15 | command : one of "status", "available", "add", "remove", "update" 16 | -s : enable searching (only for "status" and "available" commands) 17 | name : module name 18 | version : module version]] 19 | os.exit(1) 20 | end 21 | 22 | local cmd, search, name, ver = arg[1] 23 | if not cmd or not commands[cmd] then err() end 24 | 25 | search = arg[2] == '-s' 26 | if search then 27 | if cmd ~= 'status' and cmd ~= 'available' then err() end 28 | name, ver = '?'..arg[3], arg[4] 29 | else 30 | name, ver = arg[2], arg[3] 31 | end 32 | 33 | pkg[cmd](name, ver) -------------------------------------------------------------------------------- /__meta.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = "pkg", 3 | version = "1.0.beta10", 4 | require = { 5 | luajit = "2.0", 6 | cURL = "0.3.1", 7 | lfs = "1.6.2", 8 | serpent = "0.27", 9 | }, 10 | license = "MIT ", 11 | homepage = "http://ulua.io/pkg.html", 12 | description = "ULua package manager", 13 | } -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- ULua package manager. 3 | -- 4 | -- Copyright (C) 2015-2016 Stefano Peluchetti. All rights reserved. 5 | -------------------------------------------------------------------------------- 6 | 7 | -- TODO: Bin config.lua for version choice. 8 | -- TODO: Have status(), available(), return info tables. 9 | -- TODO: Optimize require and related functions for speed. 10 | -- TODO: Refactoring toward more readable code. 11 | -- TODO: Add safety against removal of user-developed modules, i.e. when 12 | -- TODO: topath(info.version) is different from the installed version path. 13 | -- TODO: Verify downgrading core packages is safe. 14 | 15 | local coremodule = { luajit = true, pkg = true } 16 | local repoaddr = 'http://pkg.ulua.io' 17 | 18 | -- For paths I need to distinguish version folders: they are so if they are just 19 | -- after the root path and start with a digit. 20 | -- TODO: document! 21 | local modp_root_ver_spec_fmt = '([^%.]*)%.(%d+[^%.]*)%.?(.*)' 22 | local modp_root_spec_fmt = '([^%.]*)%.?(.*)' 23 | 24 | -- TODO: Revise this! I am moving to true semantic versioning and users can 25 | -- TODO: also create their version directories. 26 | local modz_root_ver_fmt = '(.-)~(%d+%.?%d*%.?%d*%.?%a*%d*%-?%d*)%.zip' 27 | local ver_components_fmt = '(%d+)%.?(%d*)%.?(%d*)%.?(%a*)(%d*)%-?(%d*)' 28 | 29 | -- Standard path format: major, minor and patch always present, prerelease 30 | -- optional, all separated by '_', build is optional and separated with '+'. 31 | local function topath(verstr) 32 | return verstr:gsub('%.', '_'):gsub('-', '+') 33 | end 34 | 35 | local function tover(verstr) 36 | return verstr:gsub('_', '.'):gsub('+', '-') 37 | end 38 | 39 | local function modzrootver(s) 40 | local _, _, r, v = s:find(modz_root_ver_fmt) 41 | return r, v 42 | end 43 | 44 | local luabincmd = [[ 45 | @echo off 46 | SETLOCAL 47 | if defined BIT ( 48 | if "%BIT%"=="32" ( 49 | SET LJ_ARCH=x86 50 | ) else ( 51 | if "%BIT%"=="64" ( 52 | SET LJ_ARCH=x64 53 | ) else ( 54 | echo ERROR: BIT=%BIT% is not a valid setting, use 32 or 64 1>&2 && exit /b 1 55 | ) 56 | ) 57 | ) else ( 58 | SET LJ_ARCH=x86 59 | ) 60 | SET LJ_VER_EXT={LJ_VER_EXT_CMD} 61 | SET LUA_ROOT=%~dp0 62 | SET LUA_ROOT=%LUA_ROOT:~0,-1% 63 | SET LUA_ROOT=%LUA_ROOT:\=/% 64 | SET LJ_SYS=Windows 65 | SET LJ_CORE=%LUA_ROOT%/%LJ_VER_EXT%/%LJ_SYS%/%LJ_ARCH% 66 | SET LUA_PATH=%LUA_ROOT%/?/init.lua;%LUA_ROOT%/?.lua;%LJ_CORE%/?/init.lua;%LJ_CORE%/?.lua; 67 | SET LUA_CPATH=%LUA_ROOT%/?.dll;%LUA_ROOT%/loadall.dll; 68 | "%LJ_CORE%/luajit" -l__init %* 69 | ]] 70 | 71 | local luabinsh = [[ 72 | #!/bin/bash 73 | if ! [ -z ${BIT+x} ]; then 74 | if [ "$BIT" == "32" ]; then 75 | LJ_ARCH="x86" 76 | elif [ "$BIT" == "64" ]; then 77 | LJ_ARCH="x64" 78 | else 79 | echo "ERROR: BIT=$BIT is not a valid setting, use 32 or 64" 1>&2 && exit 1 80 | fi 81 | else 82 | LJ_ARCH="x86" 83 | fi 84 | LJ_VER_EXT={LJ_VER_EXT_SH} 85 | LUA_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 86 | if [ "$(uname)" == "Darwin" ]; then 87 | LJ_SYS="OSX" 88 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 89 | LJ_SYS="Linux" 90 | else 91 | echo "ERROR - Unsupported system: ""$(uname -s)" 1>&2 && exit 1 92 | fi 93 | LJ_CORE="$LUA_ROOT""/""$LJ_VER_EXT""/""$LJ_SYS""/""$LJ_ARCH" 94 | LUA_PATH="$LUA_ROOT""/?/init.lua;""$LUA_ROOT""/?.lua;""$LJ_CORE""/?/init.lua;""$LJ_CORE""/?.lua;" 95 | LUA_CPATH="$LUA_ROOT""/?.so;""$LUA_ROOT""/loadall.so;" 96 | LUA_ROOT="$LUA_ROOT" LUA_PATH="$LUA_PATH" LUA_CPATH="$LUA_CPATH" "$LJ_CORE""/"luajit -l__init $@ 97 | ]] 98 | 99 | local pkgbincmd = [[ 100 | @echo off 101 | SETLOCAL 102 | SET BIN_ROOT=%~dp0 103 | SET BIN_ROOT=%BIN_ROOT:~0,-1% 104 | SET BIN_ROOT=%BIN_ROOT:\=/% 105 | SET LUA_ROOT=%BIN_ROOT%/.. 106 | call %LUA_ROOT%/lua.cmd %LUA_ROOT%/{BIN} %* 107 | ]] 108 | 109 | local pkgbinsh = [[ 110 | #!/bin/bash 111 | BIN_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 112 | LUA_ROOT="$BIN_ROOT""/.." 113 | source "$LUA_ROOT"/lua "$LUA_ROOT""/""{BIN}" $@ 114 | ]] 115 | 116 | -------------------------------------------------------------------------------- 117 | -- This is the non-optional part that modifies package.loaders to allow for our 118 | -- modules to be used on any standard LuaJIT installation, relies only on 119 | -- package.loaders[*], package.path and package.cpath. 120 | -- Assumes that no one else has modified package.loaders. 121 | 122 | local ffi = require 'ffi' 123 | 124 | local jos, jarch = jit.os, jit.arch 125 | 126 | local loaded = package.loaded 127 | local searchers = package.loaders 128 | local luasearcher = searchers[2] 129 | local cluasearcher = searchers[3] 130 | 131 | -- Splits a required-name into the root, the eventual version, and the 132 | -- eventual sub-modules chaining. 133 | local function modprootverspec(s) 134 | local _, _, r, v, p = s:find(modp_root_ver_spec_fmt) 135 | if r then 136 | return r, v, p 137 | else 138 | _, _, r, p = s:find(modp_root_spec_fmt) 139 | return r, '', p 140 | end 141 | end 142 | 143 | -- TODO: luasyssearcher(name) ? 144 | 145 | -- Tries to load a CLua module with OS and arch dependent specifications, cannot 146 | -- be done via package.cpath because of splitting between dir and base. 147 | -- TODO: Note that 'name' and 'name.name' correspond to the same file, OK? 148 | local function cluasyssearcher(name) 149 | local root, ver, spec = modprootverspec(name) 150 | local dir = ver ~= '' and root..'.'..ver or root 151 | local base = spec ~= '' and root..'.'..spec or root 152 | base = base:gsub('%.', '_') -- It's OK to substitute '.' with '_' after '-'. 153 | -- Char '-' is necessary due to call to luaopen_*. 154 | return cluasearcher(dir..'.'..jos..'.'..jarch..'.-'..base) 155 | end 156 | 157 | table.insert(searchers, 4, cluasyssearcher) 158 | 159 | -- TODO: Document the side effect. 160 | -- As the clua loader loads the library immediately if found, we have to 161 | -- pre-load the dependencies prior to that or such loading will fail. 162 | local function withinit(searcher) 163 | return function(name) 164 | local rootname, vername = modprootverspec(name) 165 | local initname = rootname..(vername and '.'..vername or '')..'.__init' 166 | local initf = luasearcher(initname) 167 | if type(initf) == 'function' then 168 | initf(name) -- TODO: Document it's called with the same name of module. 169 | end 170 | return searcher(name) 171 | end 172 | end 173 | 174 | for i=2,4 do 175 | searchers[i] = withinit(searchers[i]) 176 | end 177 | 178 | -- Now package.loaders is: 179 | -- [1] = preload 180 | -- [2] = withinit(lua) 181 | -- [3] = withinit(clua) 182 | -- [4] = withinit(cluasys) 183 | -- [5] = cluaroot 184 | -- TODO: Document. 185 | 186 | -- TODO: Cygwin uses 'cyg' instead of 'lib' ? 187 | local function clibpath(modulename, clib) 188 | if jos ~= 'Windows' and clib:sub(1, 3) ~= 'lib' then 189 | clib = 'lib'..clib 190 | end 191 | local cpath = package.cpath 192 | if jos == 'OSX' then 193 | cpath = cpath:gsub('%.so', '.dylib') -- TODO: Check. 194 | end 195 | local path = (modulename..'.'..jos..'.'..jarch..'.'):gsub('%.', '@')..clib 196 | return package.searchpath(path, cpath, '@') 197 | end 198 | 199 | local clib_cache = { } 200 | local ffiload = ffi.load 201 | 202 | -- TODO: Better to cache system libraries as well? 203 | ffi.load = function(name, global) 204 | return clib_cache[name] or ffiload(name, global) 205 | end 206 | 207 | -- For pre-loading of dynamic libraries loaded by module, either via explicit 208 | -- loading via ffi.load() or via implicit loading if the result of ffi.load() 209 | -- or a CLua module depends on dynamic libraries. 210 | local function loadclib(modulename, clib) 211 | if not clib then 212 | -- TODO: Check '%.' or '.' : 213 | local _ 214 | _, _, clib = modulename:find('clib_([^.]*)') 215 | end 216 | local path = clibpath(modulename, clib) 217 | if path then 218 | -- TODO: Better to load with global? 219 | clib_cache[clib] = ffiload(path) 220 | end 221 | end 222 | 223 | local rootpath = os.getenv('LUA_ROOT') 224 | 225 | if not rootpath then 226 | -- Stop here, only pkg.loadclib is returned. 227 | return { 228 | loadclib = loadclib, 229 | } 230 | end 231 | 232 | local hostpath = rootpath and rootpath..'/host' 233 | 234 | -------------------------------------------------------------------------------- 235 | -- Optional part: if used then packages must be managed via pkg module only. 236 | 237 | -- Loaded lazily: 238 | local lfs, curl, serpent 239 | 240 | local function T(x, n, t, req) 241 | if (req or type(x) ~= 'nil') and type(x) ~= t then 242 | error('argument #'..n..' must be of type "'..t..'", "'..type(x)..'" passed') 243 | end 244 | end 245 | 246 | local function finalize(f, onerr) 247 | return function(...) 248 | local ok, err = xpcall(f, debug.traceback, ...) 249 | if not ok then 250 | onerr() 251 | error(err) 252 | end 253 | end 254 | end 255 | 256 | local function iow(opt, ...) 257 | if not opt.silent then 258 | io.write(...) 259 | end 260 | end 261 | 262 | local function copy(t) 263 | if type(t) ~= 'table' then return t end 264 | local o = { } 265 | for k,v in pairs(t) do 266 | o[k] = copy(v) 267 | end 268 | return o 269 | end 270 | 271 | local function filter(x, f) 272 | local y, j = { }, 0 273 | for i=1,#x do 274 | if f(x[i]) then 275 | j = j + 1; y[j] = x[i] 276 | end 277 | end 278 | return y 279 | end 280 | 281 | local function optdefaults(opt) 282 | opt = opt or { } 283 | local o = copy(opt) 284 | local ok, defopt = pcall(dofile, hostpath..'/config.lua') 285 | if not ok then 286 | error('error in "host/config.lua": '..defopt) 287 | end 288 | for k,v in pairs(defopt) do 289 | if type(o[k]) == 'nil' then 290 | o[k] = v 291 | end 292 | end 293 | return o 294 | end 295 | 296 | local function maxlen(x, c) 297 | local len = 0 298 | for i=1,#x do len = math.max(len, c and #x[i][c] or #x[i]) end 299 | return len 300 | end 301 | 302 | local function fill(s, upto, with) 303 | with = with or ' ' 304 | return s..(with):rep(upto - #s) 305 | end 306 | 307 | local function confirm(opt) 308 | if opt and opt.noconfirm then 309 | return true 310 | end 311 | local answer 312 | repeat 313 | io.write('Confirm (y/n)? ') 314 | io.flush() 315 | answer = io.read() 316 | until answer == 'y' or answer == 'n' 317 | return answer == 'y' 318 | end 319 | 320 | local function dlprogress(pre, post, opt) 321 | pre = pre or '' 322 | post = post or '' 323 | local count = { 0 } 324 | return function(tot, now) 325 | if tot > 0 then 326 | count[1] = count[1] + 1 327 | local perc = string.format('%3d', now/tot*100) 328 | local totk = string.format('%d', tot/1000) 329 | iow(opt, pre, perc, '% of ', totk, 'KB', post, '\r') 330 | io.flush() 331 | end 332 | end, count 333 | end 334 | 335 | local buf_mt = { 336 | write = function(self, s) -- Never invoked as multi-arguments. 337 | self._i = self._i + 1 338 | self._l = self._l + #s 339 | self._b[self._i] = s 340 | end, 341 | to = function(self, f) 342 | local b = self._b 343 | for i=1,#b do 344 | f:write(b[i]) 345 | end 346 | end, 347 | len = function(self) 348 | return self._l 349 | end, 350 | __tostring = function(self) 351 | return table.concat(self._b, '') 352 | end, 353 | } 354 | buf_mt.__index = buf_mt 355 | 356 | local function dlbuf() 357 | return setmetatable({ _i = 0, _l = 0, _b = { } }, buf_mt) 358 | end 359 | 360 | -- TODO: Check failures: 361 | local function download(addr, what, out, opt) 362 | curl = curl or require 'cURL' 363 | opt = optdefaults(opt) 364 | local ce = curl.easy() 365 | ce:setopt_failonerror(true) 366 | if opt and opt.proxy then 367 | ce:setopt_proxy(opt.proxy) 368 | end 369 | if opt and opt.proxyauth then 370 | ce:setopt_proxyuserpwd(opt.proxyauth) 371 | end 372 | if type(out) == 'table' then 373 | ce:setopt_noprogress(false) 374 | local len = maxlen(what) 375 | iow(opt, 'Downloading:\n') 376 | for i=1,#what do 377 | ce:setopt_url(addr..what[i]) 378 | local buf = dlbuf() 379 | local bar, cbar = dlprogress('+ '..fill(what[i], len)..' | ', nil, opt) 380 | ce:setopt_writefunction(buf) 381 | ce:setopt_progressfunction(bar) 382 | ce:perform() 383 | if cbar[1] == 0 then 384 | -- Make sure that progress bar is called at least once. 385 | bar(buf:len(), buf:len()) 386 | end 387 | iow(opt, '\n') io.flush() 388 | local f = assert(io.open(out[i], 'wb')) 389 | buf:to(f) 390 | assert(f:close()) 391 | end 392 | else 393 | ce:setopt_url(addr..what) 394 | local buf = dlbuf() 395 | ce:setopt_writefunction(buf) 396 | ce:perform() 397 | return tostring(buf) 398 | end 399 | end 400 | 401 | local function esc(s) 402 | return '"'..s..'"' 403 | end 404 | 405 | local nullpath = jos == 'Windows' and 'nul' or '/dev/null' 406 | local pkgpath = rootpath..'/'..(...):gsub('%.', '/')..'/' 407 | local unzipcmd = esc('unzip') 408 | if jos == 'Windows' then 409 | unzipcmd = esc(pkgpath..jos..'/unzip') 410 | elseif jos == 'Linux' then 411 | unzipcmd = esc(pkgpath..jos..'/'..jarch..'/unzip') 412 | end 413 | local chmodcmd = esc((jos == 'Windows' and pkgpath..jos..'/' or '')..'chmod') 414 | 415 | local function unzip(inpath, outpath) 416 | local cmd = unzipcmd..' -qq '..esc(inpath)..' -d '..esc(outpath) 417 | local result = os.execute(jos == 'Windows' and esc(cmd) or cmd) 418 | if not (result == 0 or result == true) then -- Support for LUA52COMPAT. 419 | error('failed to execute: '..cmd) 420 | end 421 | end 422 | 423 | local function setexecflag(fname) 424 | -- Useless to capture eventual failures here. 425 | -- As users might be different use a+x, not (u)+x. 426 | local cmd = chmodcmd..' a+x '..esc(fname)..' > '..nullpath..' 2>&1' 427 | os.execute(jos == 'Windows' and esc(cmd) or cmd) 428 | end 429 | 430 | -- Do its best to remove everything in a path, no error thrown. 431 | local function emptydir(path) 432 | lfs = lfs or require 'host.init.__lfs' 433 | if lfs.attributes(path) and lfs.attributes(path).mode == 'directory' then 434 | for file in lfs.dir(path) do 435 | if file ~= '.' and file ~= '..' then 436 | local f = path..'/'..file 437 | local attr = lfs.attributes(f) 438 | if attr and attr.mode == 'directory' then -- Attr might be nil. 439 | emptydir(f) -- Recurse. 440 | lfs.rmdir(f) 441 | else 442 | os.remove(f) 443 | end 444 | end 445 | end 446 | end 447 | end 448 | 449 | -------------------------------------------------------------------------------- 450 | local kaorder = { 451 | head = 1, 452 | alpha = 2, 453 | beta = 3, 454 | work = 4, 455 | rc = 5, 456 | [''] = 6, -- Stable. 457 | patch = 7, -- Stable. 458 | } 459 | 460 | local function specval(n, p, ka, kd, rd) 461 | return math.max(1, 462 | n ~= '' and 2 or 0, 463 | p ~= '' and 3 or 0, 464 | ka ~= '' and 4 or 0, 465 | kd ~= '' and 5 or 0, 466 | rd ~= '' and 6 or 0) 467 | end 468 | 469 | -- Split string into its version components. 470 | local function versplit(s) 471 | local f, l, m, n, p, ka, kd, rd = s:find(ver_components_fmt) 472 | local mn = tonumber(m) 473 | local nn = tonumber(n) or 0 474 | local pn = tonumber(p) or 0 475 | local kan = ka and kaorder[ka:lower()] 476 | local kdn = tonumber(kd) or 0 477 | local rdn = tonumber(rd) or 0 478 | if f ~= 1 or l ~= #s or s:sub(l, l) == '.' or s:find('%.%.') or s:find('%.%-') 479 | or not kan then 480 | error('"'..s..'" is not a valid module version') 481 | end 482 | return mn, nn, pn, kan, kdn, rdn, specval(n, p, ka, kd, rd) 483 | end 484 | 485 | -- Return true if versions are equal up to the specification level. 486 | local function vereqspec(l, r, spec) 487 | if not l or not r then return true end 488 | local lm, ln, lp, lka, lkd, lrd, lspec = versplit(l) 489 | local rm, rn, rp, rka, rkd, rrd, rspec = versplit(r) 490 | spec = spec or math.min(lspec, rspec) 491 | if spec >= 1 and lm ~= rm then return false end 492 | if spec >= 2 and ln ~= rn then return false end 493 | if spec >= 3 and lp ~= rp then return false end 494 | if spec >= 4 and lka ~= rka then return false end 495 | if spec >= 5 and lkd ~= rkd then return false end 496 | if spec >= 6 and lrd ~= rrd then return false end 497 | return true 498 | end 499 | 500 | -- Return true if l <= r. 501 | local function vercomp(l, r, strict) 502 | local lm, ln, lp, lka, lkd, lrd = versplit(l) 503 | local rm, rn, rp, rka, rkd, rrd = versplit(r) 504 | if lm ~= rm then 505 | return lm < rm 506 | elseif ln ~= rn then 507 | return ln < rn 508 | elseif lp ~= rp then 509 | return lp < rp 510 | elseif lka ~= rka then 511 | return lka < rka 512 | elseif lkd ~= rkd then 513 | return lkd < rkd 514 | else 515 | if strict then 516 | return lrd < rrd 517 | else 518 | return lrd <= rrd 519 | end 520 | end 521 | end 522 | 523 | local function verlt(l, r) 524 | return vercomp(l, r, true) 525 | end 526 | 527 | local function verle(l, r) 528 | return vercomp(l, r, false) 529 | end 530 | 531 | local function vinfosorter(x, y) 532 | return not verle(x.version, y.version) 533 | end 534 | 535 | local function isstable(x) 536 | local _,_,_,ka = versplit(x.version) 537 | return ka == kaorder[''] or ka == kaorder['patch'] 538 | end 539 | 540 | local function firstok(version, vinfo, spec) 541 | for i=1,#vinfo do 542 | local ver = vinfo[i].version 543 | -- Ver must be equal up to spec to version, and better or equal in the rest. 544 | if vereqspec(version, ver, spec) and verle(version, ver) then 545 | return vinfo[i] 546 | end 547 | end 548 | end 549 | 550 | local function infobest(repo, name, version, spec) 551 | spec = spec or 1 552 | local vinfo = repo[name] or { } 553 | table.sort(vinfo, vinfosorter) 554 | local svinfo = filter(vinfo, isstable) 555 | -- If no version indicated return latest stable or, if not available, latest 556 | -- unstable. 557 | if not version then 558 | return svinfo[1] or vinfo[1] 559 | else 560 | return firstok(version, svinfo, spec) or firstok(version, vinfo, spec) 561 | end 562 | end 563 | 564 | local function hasmod(repo, name) 565 | if not repo[name] then 566 | error('cannot find module "'..name..'"') 567 | end 568 | end 569 | 570 | local function infobestchk(repo, name, ver, spec) 571 | hasmod(repo, name) 572 | local info = infobest(repo, name, ver, spec) 573 | if not info then 574 | error('cannot find matching version of module "'..name..'"') 575 | end 576 | return info 577 | end 578 | 579 | local function infoinsert(repo, name, info) 580 | repo[name] = repo[name] or { } 581 | table.insert(repo[name], info) 582 | table.sort(repo[name], vinfosorter) 583 | end 584 | 585 | -------------------------------------------------------------------------------- 586 | -- Load the meta-data given that it can be found. 587 | local function getmeta(fullname) 588 | local f = luasearcher(fullname..'.__meta') 589 | return type(f) == 'function' and f(fullname..'.__meta') 590 | end 591 | 592 | local function okdeprepo(repo) 593 | local o = { } 594 | repeat 595 | local rn = 0 596 | for hname,hvinfo in pairs(repo) do 597 | for i=#hvinfo,1,-1 do 598 | local torem = false 599 | for reqname,reqver in pairs(hvinfo[i].require or { }) do 600 | if not infobest(repo, reqname, reqver) then 601 | torem = torem or { } 602 | torem[#torem + 1] = reqname..'~'..reqver 603 | end 604 | end 605 | if torem then 606 | o[#o + 1] = { hname, hvinfo[i], torem } 607 | rn = rn + 1 608 | table.remove(hvinfo, i); if #hvinfo == 0 then repo[hname] = nil end 609 | end 610 | end 611 | end 612 | until rn == 0 613 | return o 614 | end 615 | 616 | local function checkrepo(repo, onmissing) 617 | onmissing = onmissing or error 618 | local miss = okdeprepo(repo) 619 | if #miss > 0 then 620 | for i=1,#miss do 621 | local mod = miss[i][1]..'~'..miss[i][2].version 622 | local dep = table.concat(miss[i][3], ', ') 623 | onmissing(mod..' has unmet dependencies: '..dep) 624 | end 625 | end 626 | return miss 627 | end 628 | 629 | local syncedhostrepo 630 | 631 | local function writebin(pkgbin, ext, relpath, bin) 632 | local cmd = pkgbin:gsub('{BIN}', relpath..'/'..bin) 633 | local no_lua_ext_bin = bin:gsub('%.lua$', '') 634 | local fname = rootpath..'/bin/'..no_lua_ext_bin..ext 635 | local f = assert(io.open(fname, 'w'..(ext == '' and 'b' or ''))) 636 | f:write(cmd) 637 | assert(f:close()) 638 | setexecflag(fname) 639 | end 640 | 641 | -- Discover all name/version available modules and update the bin directory. 642 | local function updatehostrepo() 643 | lfs = lfs or require 'host.init.__lfs' 644 | syncedhostrepo = { } 645 | for mod in lfs.dir(rootpath) do 646 | if mod ~= "." and mod ~= ".." then 647 | local rootpathmod = rootpath..'/'..mod 648 | if lfs.attributes(rootpathmod).mode == 'directory' then 649 | for ver in lfs.dir(rootpathmod) do 650 | if ver ~= "." and ver ~= ".." then 651 | local meta = getmeta(mod..'.'..ver) 652 | if meta then -- This is a module. 653 | -- Notice that ver is not used at all for building the repo! 654 | meta.version_dir = ver 655 | infoinsert(syncedhostrepo, mod, meta) 656 | end 657 | end 658 | end 659 | end 660 | end 661 | end 662 | 663 | -- TODO: Make version to be called configurable. 664 | if not lfs.attributes(rootpath..'/bin') then 665 | lfs.mkdir(rootpath..'/bin') 666 | end 667 | emptydir(rootpath..'/bin') 668 | for name in pairs(syncedhostrepo) do 669 | local info = infobestchk(syncedhostrepo, name) 670 | local relpath = name..'/'..info.version_dir..'/__bin' 671 | if lfs.attributes(rootpath..'/'..relpath) then 672 | for bin in lfs.dir(rootpath..'/'..relpath) do 673 | if bin ~= '.' and bin ~= ".." then 674 | writebin(pkgbincmd, '.cmd', relpath, bin) 675 | writebin(pkgbinsh, '', relpath, bin) 676 | end 677 | end 678 | end 679 | end 680 | 681 | serpent = serpent or require 'serpent' -- Now can be found. 682 | local repocode = 'return '..serpent.block(syncedhostrepo, { comment = false }) 683 | local freponew = assert(io.open(hostpath..'/tmp/__repo.lua', 'w')) 684 | freponew:write(repocode) 685 | assert(freponew:close()) 686 | os.remove(hostpath..'/tmp/__repo.old') -- Delete if present. 687 | os.rename(hostpath..'/init/__repo.lua', hostpath..'/tmp/__repo.old') 688 | assert(os.rename(hostpath..'/tmp/__repo.lua', hostpath..'/init/__repo.lua')) 689 | end 690 | 691 | local function loadhostrepo() 692 | local ok, repo = pcall(function() 693 | return dofile(hostpath..'/init/__repo.lua') 694 | end) 695 | if not ok then -- Attempt recovery. 696 | updatehostrepo() -- Requires patched _G.require. 697 | else 698 | syncedhostrepo = repo 699 | end 700 | end 701 | 702 | local function webrepo(opt) 703 | opt = optdefaults(opt) 704 | local addr = opt.repository or repoaddr 705 | local repo = download(addr, '/repo', nil, opt) 706 | repo = assert(loadstring(repo))() 707 | return repo 708 | end 709 | 710 | local function lexlt(x, y) 711 | return x[1] < y[1] 712 | end 713 | 714 | local function rtostr(r) 715 | local a = { } 716 | for nam,vinfo in pairs(r) do 717 | table.sort(vinfo, vinfosorter) 718 | local ver = { } 719 | for i=1,#vinfo do ver[i] = vinfo[i].version end 720 | local des = vinfo[1].description or '' 721 | if #des > 80 then 722 | des = des:sub(1, 78)..'..' 723 | end 724 | a[#a + 1] = { nam, des, table.concat(ver, ', ') } 725 | end 726 | table.sort(a, lexlt) 727 | local namlen = maxlen(a, 1) 728 | local deslen = maxlen(a, 2) 729 | for i=1,#a do 730 | local nam, des, ver = unpack(a[i]) 731 | a[i] = '+ '..fill(nam, namlen)..' | '..fill(des, deslen)..' | '..ver 732 | end 733 | return table.concat(a, '\n') 734 | end 735 | 736 | local function infopkg(repo, name, ver) 737 | local info = infobestchk(repo, name, ver) 738 | local req = { } 739 | for reqn, reqv in pairs(info.require or { }) do 740 | req[#req + 1] = { reqn, reqv } 741 | end 742 | table.sort(req, lexlt) 743 | for i=1,#req do 744 | req[i] = table.concat(req[i], '~') 745 | end 746 | req = table.concat(req, ', ') 747 | io.write('Module information:\n') 748 | io.write('name : ', name, '\n') 749 | io.write('version : ', info.version, '\n') 750 | io.write('require : ', req, '\n') 751 | io.write('description : ', info.description or '', '\n') 752 | io.write('homepage : ', info.homepage or '', '\n') 753 | io.write('license : ', info.license or '', '\n') 754 | end 755 | 756 | local toupdate, performupdate 757 | 758 | local updated_pkg_msg = [[ 759 | The package "pkg" needs to be updated before continuing with this operation. 760 | LuaJIT *will exit* to finalize such update. 761 | ]] 762 | 763 | local function updatepkgmod(opt) 764 | local hostr, webr = copy(syncedhostrepo), webrepo(opt) 765 | local pkghost = infobestchk(hostr, 'pkg') 766 | local pkgrepo = infobestchk(webr, 'pkg') 767 | if verlt(pkghost.version, pkgrepo.version) then 768 | io.write(updated_pkg_msg) 769 | local addr, remr = { }, { } 770 | toupdate('pkg', hostr, webr, addr, remr) 771 | local updatedpkg = performupdate(opt, addr, remr) 772 | if updatedpkg then 773 | io.write('Exiting\n') 774 | os.exit() 775 | else 776 | error('Operation canceled') 777 | end 778 | end 779 | -- end 780 | return hostr, webr 781 | end 782 | 783 | local function search(repo, name) 784 | name = name:lower() 785 | local match = { } 786 | for n,v in pairs(repo) do 787 | -- TODO: I'm checking only the last one. 788 | local desc = v[1].description or '' 789 | if n:lower():find(name, 1, true) or desc:lower():find(name, 1, true) then 790 | match[n] = v 791 | end 792 | end 793 | return match 794 | end 795 | 796 | local function status(name, ver) 797 | T(name, 1, 'string') T(ver, 2, 'string') 798 | name = name or '?' 799 | local hostr = syncedhostrepo 800 | if name == '?' then 801 | io.write('Installed modules:\n', rtostr(hostr), '\n') 802 | elseif name:sub(1, 1) == '?' then 803 | io.write('Installed modules:\n', rtostr(search(hostr, name:sub(2))), '\n') 804 | else 805 | infopkg(hostr, name, ver) 806 | end 807 | end 808 | 809 | local function available(name, ver, opt) 810 | T(name, 1, 'string') T(ver, 2, 'string') T(opt, 3, 'table') 811 | name = name or '?' 812 | opt = optdefaults(opt) 813 | local _, webr = updatepkgmod(opt) 814 | if name == '?' then 815 | io.write('Available modules:\n', rtostr(webr), '\n') 816 | elseif name:sub(1, 1) == '?' then 817 | io.write('Available modules:\n', rtostr(search(webr, name:sub(2))), '\n') 818 | else 819 | infopkg(webr, name, ver) 820 | end 821 | end 822 | 823 | -------------------------------------------------------------------------------- 824 | local function findinit(name) 825 | local errs = { } 826 | for _, searcher in ipairs(searchers) do 827 | local f = searcher(name) 828 | if type(f) == 'function' then 829 | return f 830 | elseif type(f) == 'string' then 831 | errs[#errs + 1] = f 832 | end 833 | end 834 | error("module '"..name.."' not found"..table.concat(errs)) 835 | end 836 | 837 | local sentinel = function() end 838 | 839 | -- Pre-loading of dynamic libraries loaded by module, either via explicit 840 | -- loading via ffi.load() or via implicit loading if the result of ffi.load() 841 | -- or a CLua module depends on dynamic libraries. 842 | -- TODO: Do not unload the module in package.loaded, document! 843 | local function requirefull(name, plainname) 844 | plainname = plainname or name 845 | local p = loaded[name] 846 | if p then 847 | if p == sentinel then 848 | error("loop or previous error loading module '"..name..'"') 849 | end 850 | return p 851 | end 852 | local init = findinit(name) 853 | loaded[name] = sentinel 854 | -- Load the module. 855 | local res = init(name) 856 | -- Module() or others in init(name) might set loaded[name] or, 857 | -- in the case of versioned modules, loaded[plainname]. 858 | if res then 859 | loaded[name] = res 860 | elseif loaded[plainname] then 861 | -- No problem with module() here: contains a check to avoid conflicts. 862 | loaded[name] = loaded[plainname] 863 | end 864 | if loaded[name] == sentinel then 865 | loaded[name] = true 866 | end 867 | return loaded[name] 868 | end 869 | 870 | local reqstack = { } 871 | 872 | -- Cannot simply modify package.loaders as for versioned modules package.loaded 873 | -- must *not* be set equal to the (unversioned) module name. 874 | -- TODO: Optimize for speed. 875 | local function requirever(name, ver, opt) 876 | T(name, 1, 'string', true) T(ver, 2, 'string') T(opt, 3, 'table') 877 | -- 0: Give priority to versioned modules as module() or others might set 878 | -- loaded[name] breaking the 'load correct version' paradigm. 879 | local hostr = syncedhostrepo or { } -- Can be null during recovery. 880 | local rootname, _, specname = modprootverspec(name) 881 | if hostr[rootname] then -- Requiring a versioned module. 882 | opt = optdefaults(opt) 883 | if not ver then 884 | -- 1V: check if calling from a module which has meta info, if so set 885 | -- version. 886 | local rootfrom, verfrom = modprootverspec(reqstack[#reqstack - 1] or '') 887 | if rootfrom then -- The require call come from a module. 888 | local meta = getmeta(rootfrom..(verfrom and '.'..verfrom or '')) 889 | if meta then -- And the module has versioning info. 890 | if rootname ~= rootfrom then 891 | if not (meta.require and meta.require[rootname]) then 892 | if rootname ~= 'pkg' then 893 | iow(opt, 'WARN: module "'..rootfrom..'" is missing version ' 894 | ..'info for dependency "'..rootname..'"\n') 895 | end 896 | else 897 | ver = meta.require[rootname] 898 | end 899 | else 900 | ver = meta.version 901 | end 902 | end 903 | end 904 | end 905 | -- 2V: check best matching module. 906 | local info = infobestchk(hostr, rootname, ver) 907 | -- 3V: return versioned module. 908 | local matchver = info.version_dir 909 | local fullname = rootname..'.'..matchver 910 | if specname ~= '' then 911 | fullname = fullname..'.'..specname 912 | end 913 | return requirefull(fullname, name) 914 | else 915 | -- 1NV: simply return require as usual. 916 | return requirefull(name) 917 | end 918 | end 919 | 920 | -- TODO: Only require is traced: dofile() and loadfile() are not, document! 921 | _G.require = function(name, ver, opt) 922 | reqstack[#reqstack + 1] = name 923 | local ok, mod = xpcall(requirever, debug.traceback, name, ver, opt) 924 | reqstack[#reqstack] = nil 925 | if not ok then 926 | error(mod) -- TODO: Avoid double stack printing. 927 | end 928 | return mod 929 | end 930 | 931 | -------------------------------------------------------------------------------- 932 | local function updateinit1(fn, fv) 933 | local f = assert(io.open(hostpath..'/init/__'..fn..'.lua', 'w')) 934 | f:write('return require "'..fn..'.'..fv..'"\n') 935 | assert(f:close()) 936 | end 937 | 938 | -- Update LuaJIT executable scripts. 939 | local function updateinit(addr, remr) 940 | updatehostrepo() 941 | local hostr = syncedhostrepo 942 | addr = addr or { } 943 | remr = remr or { } 944 | if addr.pkg or remr.pkg then 945 | updateinit1('pkg', infobestchk(hostr, 'pkg').version_dir) 946 | end 947 | if addr.lfs or remr.lfs then 948 | updateinit1('lfs', infobestchk(hostr, 'lfs').version_dir) 949 | end 950 | if addr.luajit or remr.luajit then 951 | local lua_supported = infobestchk(hostr, 'luajit', '2.1.head', 2) 952 | local lua_supported_version_dir = 'luajit/'..lua_supported.version_dir 953 | local vermap = { 954 | LJ_VER_EXT_CMD = lua_supported_version_dir, 955 | LJ_VER_EXT_SH = '"'..lua_supported_version_dir..'"', 956 | } 957 | local fcmd = assert(io.open(rootpath..'/lua.cmd', 'w')) 958 | fcmd:write((luabincmd:gsub('{(.-)}', vermap))) 959 | assert(fcmd:close()) 960 | local fsh = assert(io.open(rootpath..'/lua', 'wb')) 961 | fsh:write((luabinsh:gsub('{(.-)}', vermap))) 962 | assert(fsh:close()) 963 | setexecflag(rootpath..'/lua') 964 | end 965 | end 966 | 967 | local function filenames_add(repo) 968 | local fns, fvs = { }, { } 969 | for name,vinfo in pairs(repo) do 970 | for i=1,#vinfo do 971 | table.insert(fns, name) 972 | table.insert(fvs, vinfo[i].version) 973 | end 974 | end 975 | return fns, fvs 976 | end 977 | 978 | local function filenames_remove(repo) 979 | local fns, fvs = { }, { } 980 | for name,vinfo in pairs(repo) do 981 | for i=1,#vinfo do 982 | table.insert(fns, name) 983 | table.insert(fvs, vinfo[i].version_dir) 984 | end 985 | end 986 | return fns, fvs 987 | end 988 | 989 | local function pkgsdownload(fns, fvs, opt) 990 | local todown, tofiles = { }, { } 991 | for i=1,#fns do 992 | local pkgfile = hostpath..'/pkg'..'/'..fns[i]..'~'..fvs[i]..'.zip' 993 | local f = io.open(pkgfile) 994 | if not f then 995 | todown[#todown + 1] = '/pkg/'..fns[i]..'/'..fvs[i] 996 | tofiles[#tofiles + 1] = pkgfile 997 | else 998 | f:close() 999 | end 1000 | end 1001 | if #todown > 0 then 1002 | local addr = opt.repository or repoaddr 1003 | download(addr, todown, tofiles, opt) 1004 | end 1005 | end 1006 | 1007 | local function pkgsunzip(fns, fvs) 1008 | local pkgdir = hostpath..'/pkg' 1009 | for i=1,#fns do 1010 | unzip(pkgdir..'/'..fns[i]..'~'..fvs[i]..'.zip', hostpath..'/tmp') 1011 | end 1012 | end 1013 | 1014 | local function pkgsinstall(fns, fvs) 1015 | lfs = lfs or require 'host.init.__lfs' 1016 | for i=1,#fns do 1017 | local fn, fv = fns[i], fvs[i] 1018 | if not lfs.attributes(rootpath..'/'..fn) then 1019 | assert(lfs.mkdir(rootpath..'/'..fn)) 1020 | end 1021 | local targetpath = rootpath..'/'..fn..'/'..topath(fv) 1022 | if lfs.attributes(targetpath) then -- Should never happen. 1023 | error('path "'..targetpath..'" already exists') 1024 | end 1025 | assert(os.rename(hostpath..'/tmp/'..fn, targetpath)) 1026 | end 1027 | end 1028 | 1029 | local function pkgsremove(fns, fvs) 1030 | lfs = lfs or require 'host.init.__lfs' 1031 | for i=1,#fns do 1032 | local fn, fv = fns[i], fvs[i] 1033 | local targetpath = rootpath..'/'..fn..'/'..fv 1034 | if not lfs.attributes(targetpath) then -- Should never happen. 1035 | error('path "'..targetpath..'" does not exist') 1036 | end 1037 | local backuppath = hostpath..'/tmp/'..fn..'_'..fv 1038 | assert(os.rename(targetpath, backuppath)) 1039 | lfs.rmdir(rootpath..'/'..fn) 1040 | end 1041 | end 1042 | 1043 | -- Modify hostr so that it includes the new modules. 1044 | local function toadd(name, version, hostr, webr, addr) 1045 | -- If suitable version already installed (any if version not present) or to be 1046 | -- installed => stop: 1047 | if infobest(hostr, name, version) 1048 | or infobest(addr, name, version) then 1049 | return 1050 | end 1051 | -- If no suitable version available (any if version not present) => error: 1052 | local info = infobestchk(webr, name, version) 1053 | -- Add module. 1054 | infoinsert(addr, name, info) 1055 | infoinsert(hostr, name, info) 1056 | -- Repeat for all required modules as well. 1057 | for reqname, reqver in pairs(info.require or { }) do 1058 | toadd(reqname, reqver, hostr, webr, addr) 1059 | end 1060 | end 1061 | 1062 | local performadd = finalize(function(addr, opt) 1063 | emptydir(hostpath..'/tmp') 1064 | local fns, fvs = filenames_add(addr) 1065 | pkgsdownload(fns, fvs, opt) 1066 | pkgsunzip(fns, fvs) 1067 | pkgsinstall(fns, fvs, opt) 1068 | updateinit(addr, nil) 1069 | end, updatehostrepo) 1070 | 1071 | local function add(name, version, opt) 1072 | T(name, 1, 'string', true) T(version, 2, 'string') T(opt, 3, 'table') 1073 | opt = optdefaults(opt) 1074 | local hostr, webr = updatepkgmod(opt) 1075 | local addr = { } 1076 | toadd(name, version, hostr, webr, addr) 1077 | if next(addr) then 1078 | iow(opt, 'Installing matching module and its requirements:\n') 1079 | iow(opt, rtostr(addr), '\n') 1080 | if confirm(opt) then 1081 | performadd(addr, opt) 1082 | iow(opt, 'Done\n') 1083 | end 1084 | else 1085 | iow(opt, 'Module already installed\n') 1086 | end 1087 | end 1088 | 1089 | local performremove = finalize(function(remr, opt) 1090 | emptydir(hostpath..'/tmp') 1091 | local fns, fvs = filenames_remove(remr) 1092 | pkgsremove(fns, fvs, opt) 1093 | updateinit(nil, remr) 1094 | end, updatehostrepo) 1095 | 1096 | local function remove(name, version, opt) 1097 | T(name, 1, 'string', true) T(version, 2, 'string') T(opt, 3, 'table') 1098 | opt = optdefaults(opt) 1099 | local hostr = copy(syncedhostrepo) 1100 | hasmod(hostr, name) 1101 | -- Remove all matching modules. 1102 | local remr = { } 1103 | local vinfo = hostr[name] 1104 | for i=#vinfo,1,-1 do 1105 | if vereqspec(version, vinfo[i].version) then 1106 | infoinsert(remr, name, vinfo[i]) 1107 | table.remove(vinfo, i); if #vinfo == 0 then hostr[name] = nil end 1108 | end 1109 | end 1110 | if not remr[name] then 1111 | error('no matching version of module "'..name..'" is installed') 1112 | end 1113 | -- And all the modules whose dependencies are now not satisfied, and so on... 1114 | local tormr = okdeprepo(hostr) 1115 | for i=1,#tormr do 1116 | infoinsert(remr, tormr[i][1], tormr[i][2]) 1117 | end 1118 | -- Check no one of the core modules will be removed. 1119 | for cname,_ in pairs(coremodule) do 1120 | if not hostr[cname] then 1121 | error('operation results in the removal of core module "'..cname..'"') 1122 | end 1123 | end 1124 | iow(opt, 'Removing matching modules and modules that depend on them:\n') 1125 | iow(opt, rtostr(remr), '\n') 1126 | if confirm(opt) then 1127 | performremove(remr, opt) 1128 | iow(opt, 'Done\n') 1129 | end 1130 | end 1131 | 1132 | toupdate = function(name, hostr, webr, addr, remr) 1133 | hasmod(hostr, name) 1134 | if not webr[name] then 1135 | return 1136 | end 1137 | local vinfo, tmpr = hostr[name], { } 1138 | for i=#vinfo,1,-1 do 1139 | local candidate = infobest(webr, name, vinfo[i].version) 1140 | if candidate and candidate.version ~= vinfo[i].version 1141 | and (isstable(candidate) or not isstable(vinfo[i])) then 1142 | -- Updated module is guaranteed to satisfy any dependency that the 1143 | -- obsoleted one did, hence we can just remove it without checks. 1144 | infoinsert(remr, name, vinfo[i]) 1145 | -- No need to check hostr[name] empty, call to toadd() later. 1146 | table.remove(vinfo, i) 1147 | -- Cannot add in this pass the new module via toadd() as it would modify 1148 | -- vinfo that I am traversing. 1149 | table.insert(tmpr, candidate) 1150 | end 1151 | end 1152 | for i=1,#tmpr do 1153 | -- Takes care of new dependencies and no module is added twice. 1154 | toadd(name, tmpr[i].version, hostr, webr, addr) 1155 | end 1156 | end 1157 | 1158 | performupdate = function(opt, addr, remr) 1159 | if next(addr) then 1160 | iow(opt, 'Installing updated modules and their requirements:\n') 1161 | iow(opt, rtostr(addr), '\n') 1162 | iow(opt, 'Removing obsoleted modules:\n') 1163 | iow(opt, rtostr(remr), '\n') 1164 | if confirm(opt) then 1165 | performadd(addr, opt) 1166 | performremove(remr, opt) 1167 | iow(opt, 'Done\n') 1168 | return true 1169 | end 1170 | else 1171 | iow(opt, 'No module to update\n') 1172 | end 1173 | return false 1174 | end 1175 | 1176 | local function update(opt) 1177 | T(opt, 1, 'table') 1178 | opt = optdefaults(opt) 1179 | local hostr, webr = updatepkgmod(opt) 1180 | local addr, remr = { }, { } 1181 | for hname, _ in pairs(hostr) do 1182 | toupdate(hname, hostr, webr, addr, remr) 1183 | end 1184 | performupdate(opt, addr, remr) 1185 | end 1186 | 1187 | -------------------------------------------------------------------------------- 1188 | 1189 | loadhostrepo() 1190 | 1191 | return { 1192 | loadclib = loadclib, 1193 | 1194 | available = available, 1195 | status = status, 1196 | add = add, 1197 | remove = remove, 1198 | update = update, 1199 | 1200 | util = { 1201 | verlt = verlt, 1202 | verle = verle, 1203 | versplit = versplit, 1204 | emptydir = emptydir, 1205 | download = download, 1206 | modzrootver = modzrootver, 1207 | topath = topath, 1208 | tover = tover, 1209 | rtostr = rtostr, 1210 | infoinsert = infoinsert, 1211 | infobest = infobest, 1212 | okdeprepo = okdeprepo, 1213 | checkrepo = checkrepo, 1214 | confirm = confirm, 1215 | } 1216 | } 1217 | --------------------------------------------------------------------------------