From: Michael Schroeder Date: Mon, 25 Mar 2024 11:38:34 +0000 (+0100) Subject: Add lua bindings X-Git-Tag: 0.7.29~20 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0d6e498aa0a6da0a923dc7b1745c0869c76b14d2;p=thirdparty%2Flibsolv.git Add lua bindings --- diff --git a/CMakeLists.txt b/CMakeLists.txt index f899c49a..116767dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ OPTION (ENABLE_PERL "Build the perl bindings?" OFF) OPTION (ENABLE_PYTHON "Build the python bindings?" OFF) OPTION (ENABLE_RUBY "Build the ruby bindings?" OFF) OPTION (ENABLE_TCL "Build the Tcl bindings?" OFF) +OPTION (ENABLE_LUA "Build the lua bindings?" OFF) OPTION (USE_VENDORDIRS "Install the bindings in vendor directories?" OFF) @@ -326,6 +327,7 @@ SET (PACKAGE "libsolv") SET (VERSION "${LIBSOLV_MAJOR}.${LIBSOLV_MINOR}.${LIBSOLV_PATCH}") ADD_DEFINITIONS (-D_FILE_OFFSET_BITS=64) +#ADD_DEFINITIONS (-D_TIME_BITS=64) CONFIGURE_FILE (src/solvversion.h.in src/solvversion.h) SET (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Package dependency solver library") diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 737cee42..4889a4c6 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -20,3 +20,6 @@ ENDIF (ENABLE_RUBY) IF (ENABLE_TCL) ADD_SUBDIRECTORY (tcl) ENDIF (ENABLE_TCL) +IF (ENABLE_LUA) + ADD_SUBDIRECTORY (lua) +ENDIF (ENABLE_LUA) diff --git a/bindings/lua/CMakeLists.txt b/bindings/lua/CMakeLists.txt new file mode 100644 index 00000000..b42df214 --- /dev/null +++ b/bindings/lua/CMakeLists.txt @@ -0,0 +1,21 @@ +FIND_PACKAGE (Lua) + +ADD_CUSTOM_COMMAND ( + OUTPUT solv_lua.c + COMMAND ${SWIG_EXECUTABLE} ${SWIG_FLAGS} -lua -I${CMAKE_SOURCE_DIR}/src -o solv_lua.c ${CMAKE_SOURCE_DIR}/bindings/solv.i + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_SOURCE_DIR}/bindings/solv.i +) + +INCLUDE_DIRECTORIES (${LUA_INCLUDE_DIR}) +IF (NOT LUA_INSTALL_DIR) + SET(LUA_INSTALL_DIR ${CMAKE_INSTALL_FULL_LIBDIR}/lua/${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}) +ENDIF (NOT LUA_INSTALL_DIR) + +MESSAGE (STATUS "Lua installation dir: ${LUA_INSTALL_DIR}") + +ADD_LIBRARY (bindings_lua SHARED solv_lua.c) +SET_TARGET_PROPERTIES (bindings_lua PROPERTIES PREFIX "" OUTPUT_NAME "solv" INSTALL_NAME_DIR "${LUA_INSTALL_DIR}") +TARGET_LINK_LIBRARIES (bindings_lua libsolvext libsolv ${LUA_LIBRARY} ${SYSTEM_LIBRARIES}) +INSTALL (TARGETS bindings_lua LIBRARY DESTINATION ${LUA_INSTALL_DIR}) + diff --git a/bindings/solv.i b/bindings/solv.i index 9d888e4d..1cfea38e 100644 --- a/bindings/solv.i +++ b/bindings/solv.i @@ -16,6 +16,54 @@ %} #endif +/** + ** lua object stashing + **/ +#if defined(SWIGLUA) +%{ +SWIGINTERN +void prep_stashed_lua_var(lua_State* L, char *name, void *ptr) +{ + lua_getglobal(L, "solv"); + if (lua_getfield(L, -1, "_stash") == LUA_TNIL) { + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "_stash"); + } + lua_remove(L, -2); + lua_pushfstring(L, "%s:%p", name, ptr); +} + +SWIGINTERN +void set_stashed_lua_var(lua_State* L, int idx, char *name, void *ptr) +{ + lua_pushvalue(L, idx); + prep_stashed_lua_var(L, name, ptr); + lua_pushvalue(L, -3); + lua_settable(L, -3); + lua_pop(L, 2); +} + +SWIGINTERN +void get_stashed_lua_var(lua_State* L, char *name, void *ptr) +{ + prep_stashed_lua_var(L, name, ptr); + lua_gettable(L, -2); + lua_remove(L, -2); +} + +SWIGINTERN +void clr_stashed_lua_var(lua_State* L, char *name, void *ptr) +{ + prep_stashed_lua_var(L, name, ptr); + lua_pushnil(L); + lua_settable(L, -3); + lua_pop(L, 1); +} +%} +#endif + /** ** binaryblob handling **/ @@ -27,6 +75,24 @@ typedef struct { } BinaryBlob; %} +#if defined(SWIGLUA) +%typemap(in,noblock=1) (const unsigned char *str, size_t len) (char *buf = 0, size_t size = 0) { + if (!lua_isstring(L, $input)) SWIG_fail_arg($symname, $input, "const char *"); + buf = (char *)lua_tolstring(L, $input, &size); + $1 = (unsigned char *)buf; + $2 = size; +} +%typemap(out,noblock=1) BinaryBlob { + if ($1.data) { + lua_pushlstring(L, $1.data, $1.len); + } else { + lua_pushnil(L); + } + SWIG_arg++; +} + +#else + %typemap(in,noblock=1,fragment="SWIG_AsCharPtrAndSize") (const unsigned char *str, size_t len) (int res, char *buf = 0, size_t size = 0, int alloc = 0) { #if defined(SWIGTCL) { @@ -86,6 +152,8 @@ typedef struct { #endif } +#endif + /** ** Queue handling **/ @@ -389,6 +457,101 @@ typedef struct { #endif /* SWIGTCL */ +#if defined(SWIGLUA) +%typemap(out) Queue { + int i; + lua_newtable(L); + for (i = 0; i < $1.count; i++) { + lua_pushnumber(L, $1.elements[i]); + lua_rawseti(L, -2, i + 1); + } + queue_free(&$1); + SWIG_arg = 1; +} + +%define Queue2Array(type, step, con) %{ + int i; + int cnt = $1.count / step; + Id *idp = $1.elements; + + lua_newtable(L); + for (i = 0; i < cnt; i++, idp += step) + { + Id id = *idp; +#define result resultx + type result = con; + $typemap(out, type) + lua_rawseti(L, -2, i+1); +#undef result + } + queue_free(&$1); + SWIG_arg = 1; +%} +%enddef + +%define Array2Queue(asval_meth,typestr) %{ { + int i; + luaL_checktype(L, -1, LUA_TTABLE); + for (i = 1; i; i++) { + lua_rawgeti(L, -1, i); + if (lua_type(L, -1) == LUA_TNIL) + i = -1; + else + { + int v; + int e = asval_meth(L, -1, &v); + if (!SWIG_IsOK(e)) { + lua_pop(L, 1); + SWIG_Lua_pusherrstring(L,"list in argument $argnum must contain only " typestr); + SWIG_fail; + } + queue_push(&$1, v); + } + lua_pop(L, 1); + } +} +%} +%enddef + +%define ObjArray2Queue(type, obj2queue) %{ { + int i; + luaL_checktype(L, -1, LUA_TTABLE); + for (i = 1; i; i++) { + lua_rawgeti(L, -1, i); + if (lua_type(L, -1) == LUA_TNIL) + i = -1; + else + { + type obj; + int e = SWIG_ConvertPtr(L, -1, (void **)&obj, $descriptor(type), 0 | 0); + if (!SWIG_IsOK(e)) + { + lua_pop(L, 1); + SWIG_Lua_pusherrstring(L,"list in argument $argnum must contain only "`type`); + SWIG_fail; + } + obj2queue; + } + lua_pop(L, 1); + } +} +%} +%enddef + +%{ + +SWIGINTERN int +SWIG_AsVal_int(lua_State* L, int idx, int *val) { + int ecode = lua_isnumber(L, idx) ? SWIG_OK : SWIG_TypeError; + if (ecode == SWIG_OK) + *val = (int)lua_tonumber(L, idx); + return ecode; +} + +%} + +#endif /* SWIGLUA */ + %typemap(in) Queue Array2Queue(SWIG_AsVal_int, "integers") %typemap(in) Queue solvejobs ObjArray2Queue(Job *, queue_push2(&$1, obj->how, obj->what)) %typemap(in) Queue solvables ObjArray2Queue(XSolvable *, queue_push(&$1, obj->id)) @@ -544,6 +707,15 @@ typedef Tcl_Obj *AppObjectPtr; %typemap(out) AppObjectPtr { Tcl_SetObjResult(interp, $1 ? $1 : Tcl_NewObj()); } +#elif defined(SWIGLUA) +typedef void *AppObjectPtr; +%typemap(in) AppObjectPtr { + $1 = (void *)L; +} +%typemap(out) AppObjectPtr { + get_stashed_lua_var(L, "appdata", $1); + SWIG_arg++; +} #else #warning AppObjectPtr not defined for this language! #endif @@ -566,15 +738,23 @@ SWIGINTERN int SWIG_AsValSolvFpPtr(VALUE obj, FILE **val) { #elif defined(SWIGTCL) SWIG_AsValSolvFpPtr SWIG_TCL_DECL_ARGS_2(void *obj, FILE **val) { +#elif defined(SWIGLUA) +SWIG_AsValSolvFpPtr(lua_State *L, int idx, FILE **val) { #else SWIG_AsValSolvFpPtr(void *obj, FILE **val) { #endif static swig_type_info* desc = 0; void *vptr = 0; +#ifdef SWIGPYTHON int ecode; +#endif if (!desc) desc = SWIG_TypeQuery("SolvFp *"); +#if defined(SWIGLUA) + if ((SWIG_ConvertPtr(L, idx, &vptr, desc, 0)) == SWIG_OK) { +#else if ((SWIG_ConvertPtr(obj, &vptr, desc, 0)) == SWIG_OK) { +#endif if (val) *val = vptr ? ((SolvFp *)vptr)->fp : 0; return SWIG_OK; @@ -605,6 +785,8 @@ SWIGINTERN int SWIG_AsValDepId(VALUE obj, int *val) { #elif defined(SWIGTCL) SWIG_AsValDepId SWIG_TCL_DECL_ARGS_2(void *obj, int *val) { +#elif defined(SWIGLUA) +SWIG_AsValDepId(lua_State *L, int idx, int *val) { #else SWIG_AsValDepId(void *obj, int *val) { #endif @@ -614,12 +796,18 @@ SWIG_AsValDepId(void *obj, int *val) { if (!desc) desc = SWIG_TypeQuery("Dep *"); #ifdef SWIGTCL ecode = SWIG_AsVal_int SWIG_TCL_CALL_ARGS_2(obj, val); +#elif defined(SWIGLUA) + ecode = SWIG_AsVal_int(L, idx, val); #else ecode = SWIG_AsVal_int(obj, val); #endif if (SWIG_IsOK(ecode)) return ecode; +#if defined(SWIGLUA) + if ((SWIG_ConvertPtr(L, idx, &vptr, desc, 0)) == SWIG_OK) { +#else if ((SWIG_ConvertPtr(obj, &vptr, desc, 0)) == SWIG_OK) { +#endif if (val) *val = vptr ? ((Dep *)vptr)->id : 0; return SWIG_OK; @@ -645,12 +833,17 @@ SWIG_AsValDepId(void *obj, int *val) { SWIG_ConvertPtr(ST(0), &argp1,SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0 ); #elif defined(SWIGTCL) SWIG_ConvertPtr(objv[1], &argp1, SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0); +#elif defined(SWIGLUA) + SWIG_ConvertPtr(L, 1, (void **)&arg1, SWIGTYPE_p_Pool, SWIG_POINTER_DISOWN | 0); #else #warning disown_helper not implemented for this language, this is likely going to leak memory #endif #ifdef SWIGTCL Tcl_SetObjResult(interp, SWIG_From_int((int)(0))); +#elif defined(SWIGLUA) + $result = 0; + lua_pushnumber(L, $result); SWIG_arg++; #else $result = SWIG_From_int((int)(0)); #endif @@ -689,6 +882,22 @@ SWIG_AsValDepId(void *obj, int *val) { **/ %include "typemaps.i" +#if defined(SWIGLUA) +%runtime "swigerrors.swg"; +%include "typemaps/swigmacros.swg" +%include "typemaps/valtypes.swg" +%include "typemaps/inoutlist.swg" + +%rename(__call) *::__next__; +%rename(__tostring) *::__str__; +%rename(__index) *::__getitem__; +%rename(__eq) *::__eq__; +%rename(__ne) *::__ne__; + +%typemap(in) void *ign1 {}; +%typemap(in) void *ign2 {}; +%typemap(in,checkfn="lua_isfunction") int lua_function_idx { $1 = $input; }; +#endif #if defined(SWIGTCL) %rename("==") *::__eq__; @@ -698,12 +907,45 @@ SWIG_AsValDepId(void *obj, int *val) { %typemap(in,numinputs=0,noblock=1) XRule **OUTPUT ($*1_ltype temp) { $1 = &temp; } +#if defined(SWIGLUA) +%typemap(argout,noblock=1) XRule **OUTPUT { + SWIG_NewPointerObj(L, (void *)(*$1), SWIGTYPE_p_XRule, SWIG_POINTER_OWN); SWIG_arg++; +} +#else %typemap(argout,noblock=1) XRule **OUTPUT { %append_output(SWIG_NewPointerObj((void*)(*$1), SWIGTYPE_p_XRule, SWIG_POINTER_OWN | %newpointer_flags)); } +#endif +#if defined(SWIGLUA) +%typemap(in,noblock=1,fragment="SWIG_AsValSolvFpPtr") FILE * { + { + FILE *val; + int ecode = SWIG_AsValSolvFpPtr(L, $input, &val); + if (!SWIG_IsOK(ecode)) SWIG_fail; + $1 = val; + } +} +%typemap(typecheck,precedence=%checkcode(POINTER),fragment="SWIG_AsValSolvFpPtr") FILE * { + int res = SWIG_AsValSolvFpPtr(L, $input, NULL); + $1 = SWIG_CheckState(res); +} +%typemap(in,noblock=1,fragment="SWIG_AsValDepId") DepId { + { + int val; + int ecode = SWIG_AsValDepId(L, $input, &val); + if (!SWIG_IsOK(ecode)) SWIG_fail; + $1 = val; + } +} +%typemap(typecheck,precedence=%checkcode(INT32),fragment="SWIG_AsValDepId") DepId { + int res = SWIG_AsValDepId(L, $input, NULL); + $1 = SWIG_CheckState(res); +} +#else %typemaps_asval(%checkcode(POINTER), SWIG_AsValSolvFpPtr, "SWIG_AsValSolvFpPtr", FILE*); %typemaps_asval(%checkcode(INT32), SWIG_AsValDepId, "SWIG_AsValDepId", DepId); +#endif /** @@ -1058,6 +1300,27 @@ SWIGINTERN void *appdata_get_helper(void **appdatap) { %} +#elif defined(SWIGLUA) + +%{ +SWIGINTERN void appdata_disown_helper(void *appdata) { +} +SWIGINTERN void appdata_clr_helper(void **appdatap) { + if (*appdatap) { + void *appdata = *appdatap; + clr_stashed_lua_var((lua_State*)appdata, "appdata", (void *)appdatap); + *appdatap = 0; + } +} +SWIGINTERN void appdata_set_helper(void **appdatap, void *appdata) { + *appdatap = appdata; + set_stashed_lua_var((lua_State*)appdata, -1, "appdata", (void *)appdatap); +} +SWIGINTERN void *appdata_get_helper(void **appdatap) { + return (void *)appdatap; +} +%} + #else #warning appdata helpers not implemented for this language #endif @@ -1852,6 +2115,34 @@ returnself(matchsolvable) pool_setloadcallback($self, loadcallback, callback_var); } } +#elif defined(SWIGLUA) + %{ + SWIGINTERN int loadcallback(Pool *pool, Repodata *data, void *d) { + lua_State* L = d; + get_stashed_lua_var(L, "loadcallback", pool); + XRepodata *xd = new_XRepodata(data->repo, data->repodataid); + SWIG_NewPointerObj(L,SWIG_as_voidptr(xd), SWIGTYPE_p_XRepodata, 0); + int res = lua_pcall(L, 1, 1, 0); + res = res == LUA_OK ? lua_toboolean(L, -1) : 0; + lua_pop(L, 1); + return res; + } + %} + void clr_loadcallback() { + if ($self->loadcallback == loadcallback) { + lua_State* L = $self->loadcallbackdata; + clr_stashed_lua_var(L, "loadcallback", $self); + pool_setloadcallback($self, 0, 0); + } + } + void set_loadcallback(int lua_function_idx, lua_State* L) { + clr_stashed_lua_var(L, "loadcallback", $self); + if (!lua_isnil(L, lua_function_idx)) { + set_stashed_lua_var(L, lua_function_idx, "loadcallback", $self); + pool_setloadcallback($self, loadcallback, L); + } + } + #else #warning loadcallback not implemented for this language #endif @@ -2616,7 +2907,11 @@ returnself(matchsolvable) perliter(solv::Dataiterator) #endif %newobject __next__; +#ifdef SWIGLUA + Datamatch *__next__(void *ign1=0, void *ign2=0) { +#else Datamatch *__next__() { +#endif Dataiterator *ndi; if (!dataiterator_step($self)) { return 0; @@ -2865,7 +3160,11 @@ returnself(matchsolvable) perliter(solv::Pool_solvable_iterator) #endif %newobject __next__; +#ifdef SWIGLUA + XSolvable *__next__(void *ign1=0, void *ign2=0) { +#else XSolvable *__next__() { +#endif Pool *pool = $self->pool; if ($self->id >= pool->nsolvables) return 0; @@ -2919,7 +3218,11 @@ returnself(matchsolvable) #ifdef SWIGPERL perliter(solv::Pool_repo_iterator) #endif +#ifdef SWIGLUA + Repo *__next__(void *ign1=0, void *ign2=0) { +#else Repo *__next__() { +#endif Pool *pool = $self->pool; if ($self->id >= pool->nrepos) return 0; @@ -2975,7 +3278,11 @@ returnself(matchsolvable) perliter(solv::Repo_solvable_iterator) #endif %newobject __next__; +#ifdef SWIGLUA + XSolvable *__next__(void *ign1=0, void *ign2=0) { +#else XSolvable *__next__() { +#endif Repo *repo = $self->repo; Pool *pool = repo->pool; if (repo->start > 0 && $self->id < repo->start) diff --git a/examples/luasolv b/examples/luasolv new file mode 100644 index 00000000..0e1dc588 --- /dev/null +++ b/examples/luasolv @@ -0,0 +1,590 @@ +#!/usr/bin/lua + +releasever = nil +solvcachedir = '/var/cache/solv' + +posix = require("posix") +posix.sys.stat = require("posix.sys.stat") +require("solv") + +-- helpers + +function parse_ini_file(fn) + local sections = {} + for line in io.open(fn, 'r'):lines() do + local match = line:match('^%[([^%[%]]+)%]$') + if match then + section = match + sections[section] = sections[section] or {} + else + local param, value = line:match('^([%w|_]+)%s-=%s-(.+)$') + if param then sections[section][param] = value end + end + end + return sections +end + +function die(str) + io.stdout:write(str.."\n") + os.exit(1) +end + +function isdir(path) + local st = posix.sys.stat.stat(path) + return st and posix.S_ISDIR(st.st_mode) ~= 0 +end + +function lsdir(path) + local content = posix.dirent.dir(path) or {} + table.sort(content) + return content +end + +function load_stub(repodata) + local repo = repodata.repo.appdata + if repo then + return repo:load_ext(repodata) + end + return false +end + +-- generic repo implementation + +Repo = {} + +function Repo.calc_cookie_filename(filename) + chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256) + chksum:add("1.1") + chksum:add_stat(filename) + return chksum:raw() +end + +function Repo.calc_cookie_fp(fp) + chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256) + chksum:add("1.1") + chksum:add_fp(fp) + return chksum:raw() +end + +function Repo.calc_cookie_ext(f, cookie) + chksum = solv.Chksum(solv.REPOKEY_TYPE_SHA256) + chksum:add("1.1") + chksum:add(cookie) + chksum:add_fstat(f:fileno()) + return chksum:raw() +end + +function Repo:download(file, uncompress, chksum, markincomplete) + if not self.baseurl then + io.stdout:write(self.alias..": no baseurl\n") + return nil + end + local url = self.baseurl:gsub('/$', '')..'/'..file + local tmpfile = io.tmpfile() + local fd = posix.stdio.fileno(tmpfile) + local status, reason = posix.spawn({'curl', '-f', '-s', '-L', '-o', '/dev/fd/'..fd, '--', url}) + if posix.unistd.lseek(fd, 0, posix.unistd.SEEK_END) == 0 and (status == 0 or not chksum) then + tmpfile:close() + return nil + end + posix.unistd.lseek(fd, 0, posix.unistd.SEEK_SET); + if status ~= 0 then + print(file..": download error "..status.."\n"); + tmpfile:close() + return nil + end + if chksum then + local fchksum = solv.Chksum(chksum.type); + fchksum:add_fd(fd) + if fchksum ~= chksum then + print(file..": checksum error") + if markincomplete then self.incomplete = 1 end + return nil + end + end + local ret + if uncompress then + ret = solv.xfopen_fd(file, fd); + else + ret = solv.xfopen_fd(nil, fd); + end + tmpfile:close() + return ret +end + +function Repo:load(pool) + self.handle = pool:add_repo(self.alias) + self.handle.appdata = self + self.handle.priority = 99 - self.priority + if self:usecachedrepo() then + io.stdout:write("repo '"..self.alias.."': cached\n") + return true + end + return false +end + +function Repo:cachepath(ext) + local path = self.alias:gsub('^%.', '_'); + if ext then + path = path .. '_' .. ext ..'.solvx' + else + path = path ..'.solv' + end + return '/var/cache/solv/' .. path:gsub('/', '_'); +end + +function Repo:usecachedrepo(ext, mark) + local cookie + if ext then + cookie = self.extcookie + else + cookie = self.cookie + end + local repopath = self:cachepath(ext) + local f = io.open(repopath, 'rb') + if not f then + return false + end + f:seek('end', -32) + local fcookie = f:read(32) + if not fcookie or fcookie:len() ~= 32 then + f:close() + return false + end + if cookie and cookie ~= fcookie then + f:close() + return false + end + local fextcookie + if self.type ~= 'system' and not ext then + f:seek('end', -64) + fextcookie = f:read(32) + if not fcookie or fcookie:len() ~= 32 then + f:close() + return false + end + end + f:seek('set') + posix.unistd.lseek(posix.stdio.fileno(f), 0, posix.unistd.SEEK_SET) + local ff = solv.xfopen_fd('', posix.stdio.fileno(f)) + f:close() + local flags = 0 + if ext then + flags = flags | solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES + if ext ~= 'DL' then flags = flags | solv.Repo.REPO_LOCALPOOL end + end + if not self.handle:add_solv(ff, flags) then + ff:close() + return false + end + if self.type ~= 'system' and not ext then + self.cookie = fcookie + self.extcookie = fextcookie + end + if mark then + posix.utime(repopath) + end + return true +end + +function Repo:writecachedrepo(ext, repodata) + if self.incomplete then return end + if not isdir(solvcachedir) then + posix.sys.stat.mkdir(solvcachedir, 7 * 64 + 5 * 8 + 5) + end + local fd, tmpname = posix.stdlib.mkstemp(solvcachedir..'/.newsolv-XXXXXX') + if not fd then + return + end + local ff = solv.xfopen_fd('', fd) + if not repodata then + self.handle:write(ff) + elseif ext then + repodata:write(ff) + else + self.handle:write_first_repodata(ff) + end + ff:flush() + if self.type ~= 'system' and not ext then + self.extcookie = self.extcookie or Repo.calc_cookie_ext(ff, self.cookie) + ff:write(self.extcookie) + end + if not ext then + ff:write(self.cookie) + else + ff:write(self.extcookie) + end + ff:close() + os.rename(tmpname, self:cachepath(ext)) +end + +function Repo:add_ext_keys(ext, repodata, handle) + if ext == 'DL' then + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOSITORY_DELTAINFO) + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_FLEXARRAY) + elseif ext == 'DU' then + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_DISKUSAGE) + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRNUMNUMARRAY) + elseif ext == 'FL' then + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.SOLVABLE_FILELIST) + repodata:add_idarray(handle, solv.REPOSITORY_KEYS, solv.REPOKEY_TYPE_DIRSTRARRAY) + end +end + +-- rpmmd repo implementation + +Repo_rpmmd = {} +setmetatable(Repo_rpmmd, {__index = Repo }) + +function Repo_rpmmd:find(what) + local di = self.handle:Dataiterator_meta(solv.REPOSITORY_REPOMD_TYPE, what, solv.Dataiterator.SEARCH_STRING) + di:prepend_keyname(solv.REPOSITORY_REPOMD) + for d in di do + local dp = d:parentpos() + local filename = dp:lookup_str(solv.REPOSITORY_REPOMD_LOCATION) + if filename then + local chksum = dp:lookup_checksum(solv.REPOSITORY_REPOMD_CHECKSUM) + if not chksum then + print("no "..filename.." file checksum!\n") + return + end + return filename, chksum + end + end +end + +function Repo_rpmmd:add_ext(repodata, what, ext) + local filename, filechksum = self:find(what) + if not filename and what == 'deltainfo' then + filename, filechksum = self:find('prestodelta') + end + if filename then + local handle = repodata:new_handle() + repodata:set_poolstr(handle, solv.REPOSITORY_REPOMD_TYPE, what) + repodata:set_str(handle, solv.REPOSITORY_REPOMD_LOCATION, filename) + repodata:set_checksum(handle, solv.REPOSITORY_REPOMD_CHECKSUM, filechksum) + self:add_ext_keys(ext, repodata, handle) + repodata:add_flexarray(solv.SOLVID_META, solv.REPOSITORY_EXTERNAL, handle) + end +end + +function Repo_rpmmd:add_exts() + repodata = self.handle:add_repodata(0) + repodata:extend_to_repo() + self:add_ext(repodata, 'deltainfo', 'DL') + self:add_ext(repodata, 'filelists', 'FL') + repodata:internalize() +end + +function Repo_rpmmd:load(pool) + if Repo.load(self, pool) then return true end + io.stdout:write("rpmmd repo '"..self.alias.."': ") + local f = self:download("repodata/repomd.xml"); + if not f then + print("no repomd.xml file, skipped"); + self.handle:free(true) + self.handle = nil + return false + end + self.cookie = self.calc_cookie_fp(f) + if self:usecachedrepo(nil, True) then + print("cached") + return true + end + self.handle:add_repomdxml(f, 0) + f:close() + print("fetching") + local filename, filechksum + filename, filechksum = self:find('primary') + if filename then + f = self:download(filename, true, filechksum, true) + if f then + self.handle:add_rpmmd(f, nil, 0) + f:close() + end + if self.incomplete then return false end + end + filename, filechksum = self:find('updateinfo') + if filename then + f = self:download(filename, true, filechksum, true) + if f then + self.handle:add_updateinfoxml(f, 0) + f:close() + end + end + self:add_exts() + self:writecachedrepo() + self.handle:create_stubs() + return true +end + +function Repo_rpmmd:load_ext(repodata) + local repomdtype = repodata:lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_TYPE) + local ext + if repomdtype == 'filelists' then + ext = 'FL' + elseif repomdtype == 'deltainfo' then + ext = 'DL' + else + return false + end + io.stdout:write("["..self.alias..":"..ext..": ") + if self:usecachedrepo(ext) then + io.stdout:write("cached]\n") + return true + end + io.stdout:write("fetching]\n") + local filename = repodata:lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_LOCATION) + local filechksum = repodata:lookup_str(solv.SOLVID_META, solv.REPOSITORY_REPOMD_CHECKSUM) + local f = self:download(filename, true, filechksum) + if not f then +print("download failed") + return false + end + if ext == 'FL' then + self.handle:add_rpmmd(f, 'FL', solv.Repo.REPO_USE_LOADING|solv.Repo.REPO_EXTEND_SOLVABLES|solv.Repo.REPO_LOCALPOOL) + elseif ext == 'DL' then + self.handle:add_deltainfoxml(f, solv.Repo.REPO_USE_LOADING) + end + self:writecachedrepo(ext, repodata) + return true +end + +-- susetags repo implementation + +Repo_susetags = {} +setmetatable(Repo_susetags, {__index = Repo }) + +-- unknown repo implementation + +Repo_unknown = {} +setmetatable(Repo_unknown, {__index = Repo }) + +function Repo_unknown:load() + print("unsupported repo '"..self.alias.."': skipped") + return false +end + +-- system repo implementation + +Repo_system = {} +setmetatable(Repo_system, {__index = Repo }) + +function Repo_system:load(pool) + self.handle = pool:add_repo(self.alias) + self.handle.appdata = self + pool.installed = self.handle + io.stdout:write("rpm database: ") + self.cookie = Repo.calc_cookie_filename("/var/lib/rpm/Packages") + if self:usecachedrepo() then + print("cached") + return true + end + io.stdout:write("reading\n") + local oldf = solv.xfopen(self:cachepath()) + self.handle:add_rpmdb_reffp(oldf, solv.Repo.REPO_REUSE_REPODATA) + self:writecachedrepo() +end + +-- main + +cmd = arg[1] +if not cmd then die("Usage: luasolv COMMAND [ARGS]") end +table.remove(arg, 1) + +cmdabbrev = { ['ls']='list'; ['in']='install'; ['rm']='erase'; ['ve']='verify'; ['se']='search' } +cmd = cmdabbrev[cmd] or cmd + +cmdactions = { ['install']=solv.Job.SOLVER_INSTALL; ['erase']=solv.Job.SOLVER_ERASE; ['up']=solv.Job.SOLVER_UPDATE; ['dup']=solv.Job.SOLVER_DISTUPGRADE; ['verify']=solv.Job.SOLVER_VERIFY; ['list']=0; ['info']=0 } + +repos = {} +reposdir = {} +if isdir('/etc/zypp/repos.d') then + table.insert(reposdir, '/etc/zypp/repos.d') +elseif isdir('/etc/yum/repos.d') then + table.insert(reposdir, '/etc/yum/repos.d') +end +for _, repodir in ipairs(reposdir) do + for _, e in ipairs(lsdir(repodir)) do + if e:sub(-5) == '.repo' then + sections = parse_ini_file(repodir..'/'..e) + for alias, section in pairs(sections) do + repo = { ['alias']=alias; ['enabled']=false; ['priority']=99; ['autorefresh']=true; ['type']='rpm-md'; ['metadata_expire']=900 } + if section.name then repo.name = section.name end + if section.type then repo.type = section.type end + if section.baseurl then repo.baseurl = section.baseurl end + if section.metadata_expire then repo.metadata_expire = tonumber(section.metadata_expire) end + if section.enabled then repo.enabled = tonumber(section.enabled) ~= 0 end + if section.autorefresh then repo.autorefresh = tonumber(section.autorefresh) ~= 0 end + if section.gpgcheck then repo.gpgcheck = tonumber(section.gpgcheck) ~= 0 end + if section.priority then repo.priority = tonumber(section.priority) end + if repo.baseurl and releasever then repo.baseurl = repo.baseurl:gsub('$releasever', releasever) end + if repo.type == 'rpm-md' then + setmetatable(repo , {__index = Repo_rpmmd }) + elseif repo['type'] == 'yast2' then + setmetatable(repo , {__index = Repo_susetags }) + else + setmetatable(repo , {__index = Repo_unknown }) + end + table.insert(repos, repo) + end + end + end +end + +local pool = solv.Pool() +pool:setarch() +pool:set_loadcallback(load_stub) + +sysrepo = { ['alias']='@System', ['type']='system' } +setmetatable(sysrepo , {__index = Repo_system }) +sysrepo:load(pool) +for _, repo in ipairs(repos) do + if repo.enabled then + repo:load(pool) + end +end + +if cmd == 'search' then + pool:createwhatprovides() + sel = pool:Selection() + di = pool:Dataiterator(solv.SOLVABLE_NAME, arg[1], solv.Dataiterator.SEARCH_SUBSTRING | solv.Dataiterator.SEARCH_NOCASE) + for d in di do + sel:add_raw(solv.Job.SOLVER_SOLVABLE, d.solvid) + end + for _, s in ipairs(sel:solvables()) do + print(' - '..tostring(s)..' ['..s.repo.name..']: '..s:lookup_str(solv.SOLVABLE_SUMMARY)) + end + os.exit(0) +end + +if not cmdactions[cmd] then die("unknown command '"..cmd.."'") end + +pool:addfileprovides() +pool:createwhatprovides() + +jobs = {} +for _, a in ipairs(arg) do + flags = solv.Selection.SELECTION_NAME | solv.Selection.SELECTION_PROVIDES | solv.Selection.SELECTION_GLOB + flags = flags | solv.Selection.SELECTION_CANON | solv.Selection.SELECTION_DOTARCH | solv.Selection.SELECTION_REL + if a:sub(1, 1) == '/' then + flags = flags | solv.Selection.SELECTION_FILELIST + if cmd == 'erase' then + flags = flags | solv.Selection.SELECTION_INSTALLED_ONLY + end + end + local sel = pool:select(a, flags) + if sel:isempty() then + sel = pool:select(a, flags | solv.Selection.SELECTION_NOCASE) + if not sel:isempty() then + print("[ignoring case for '"..a.."']") + end + end + if (sel.flags & solv.Selection.SELECTION_FILELIST) ~= 0 then + print("[using file list match for '"..a.."']") + end + if (sel.flags & solv.Selection.SELECTION_PROVIDES) ~= 0 then + print("[using capability match for '"..a.."']") + end + for _, j in ipairs(sel:jobs(cmdactions[cmd])) do + table.insert(jobs, j) + end +end + +if #jobs == 0 and (cmd == 'up' or cmd == 'dup' or cmd == 'verify') then + for _, j in ipairs(pool:Selection_all():jobs(cmdactions[cmd])) do + table.insert(jobs, j) + end +end + +if #jobs == 0 then die("no package matched.") end + +if cmd == 'list' or cmd == 'info' then + for _, job in ipairs(jobs) do + for _, s in ipairs(job:solvables()) do + if cmd == 'info' then + local str + print('Name: '..tostring(s)) + print('Repo: '..s.repo.name) + print('Summary: '..s:lookup_str(solv.SOLVABLE_SUMMARY)) + str = s:lookup_str(solv.SOLVABLE_URL) + if str then print('Url: '..str) end + str = s:lookup_str(solv.SOLVABLE_LICENSE) + if str then print('License: '..str) end + print("Description\n"..s:lookup_str(solv.SOLVABLE_DESCRIPTION)) + print('') + else + print(' - '..tostring(s)..' ['..s.repo.name..']') + print(' '..s:lookup_str(solv.SOLVABLE_SUMMARY)) + end + end + end + os.exit(0) +end + +local solver = pool:Solver() +if cmd == 'erase' then + solver:set_flag(solv.Solver.SOLVER_FLAG_ALLOW_UNINSTALL, 1) +end + +while true do + local problems = solver:solve(jobs) + if #problems == 0 then break end + for _, problem in ipairs(problems) do + print(("Problem %d/%d:"):format(problem.id, #problems)) + print(problem) + local solutions = problem:solutions() + for _, solution in ipairs(solutions) do + print((" Solution %d:"):format(solution.id)) + local elements = solution:elements(true) + for _, element in ipairs(elements) do + print(" - ".. element:str()) + end + print('') + end + end + os.exit(1) +end + +local trans = solver:transaction() +if trans:isempty() then + print("Nothing to do.") + os.exit(0) +end +print('') +print("Transaction summary:") +print('') +for _, cl in ipairs(trans:classify(solv.Transaction.SOLVER_TRANSACTION_SHOW_OBSOLETES | solv.Transaction.SOLVER_TRANSACTION_OBSOLETE_IS_UPGRADE)) do + if cl.type == solv.Transaction.SOLVER_TRANSACTION_ERASE then + print(("%d erased packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_INSTALL then + print(("%d installed packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_REINSTALLED then + print(("%d reinstalled packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED then + print(("%d downgraded packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_CHANGED then + print(("%d changed packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED then + print(("%d upgraded packages:"):format(cl.count)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_VENDORCHANGE then + print(("%d vendor changes from '%s' to '%s':"):format(cl.count, cl.fromstr, cl.tostr)) + elseif cl.type == solv.Transaction.SOLVER_TRANSACTION_ARCHCHANGE then + print(("%d arch changes from '%s' to '%s':"):format(cl.count, cl.fromstr, cl.tostr)) + else + cl = nil + end + if cl then + for _, p in ipairs(cl:solvables()) do + if cl.type == solv.Transaction.SOLVER_TRANSACTION_UPGRADED or cl.type == solv.Transaction.SOLVER_TRANSACTION_DOWNGRADED then + print((" - %s -> %s"):format(p, trans:othersolvable(p))) + else + print((" - %s"):format(p)) + end + end + print('') + end +end +print(("install size change: %d K"):format(trans:calc_installsizechange())) +print('') +