local M = {} local abuild = require('aports.abuild') local pkg = require('aports.pkg') local function split_subpkgs(str, linguas, pkgname) local t = {} if not str then return nil end for e in string.gmatch(str, "%S+") do t[#t + 1] = string.gsub(e, ":.*", "") end for _, v in pairs(linguas) do t[#t + 1] = ("%s-lang-%s"):format(pkgname, v) end return t end local function split_deps(str) local t = {} if not str then return nil end for e in string.gmatch(str, "%S+") do t[#t + 1] = string.gsub(e, "[=<>~].*", "") end return t end local function split(str) local t = {} if not str then return nil end for e in string.gmatch(str, "%S+") do t[#t + 1] = e end return t end local function split_key(str) local t = {} for _, key in pairs(split(str)) do t[key] = true end return t end local function split_apkbuild(line) if not line then return nil end local dir, pkgname, pkgver, pkgrel, pkgdesc, arch, license, options, depends, makedepends, checkdepends, subpackages, linguas, source, url = string.match(line, string.rep("([^\\]*)", 14, "\\")) linguas = split(linguas) return { dir = dir, pkgname = pkgname, pkgver = pkgver, pkgrel = pkgrel, pkgdesc = pkgdesc, license = license, depends = split_deps(depends), makedepends = split_deps(makedepends), checkdepends = split_deps(checkdepends), linguas = linguas, subpackages = split_subpkgs(subpackages, linguas, pkgname), source = split(source), url = url, arch = split_key(arch), options = split_key(options) } end -- parse the APKBUILDs and return an iterator local function apkbuilds_open(aportsdir, repos) local str = "" if not repos then return nil end --expand repos for _, repo in pairs(repos) do str = ("%s %s/%s/*/APKBUILD"):format(str, aportsdir, repo) end local obj = {} --luacheck: ignore 631 (line is too long) obj.handle = io.popen(". "..abuild.functions..";"..[[ for i in ]]..str..[[; do pkgname= pkgver= pkgrel= pkgdesc= arch= license= options= depends= makedepends= makedepends_build= makedepends_host= checkdepends= subpackages= linguas= source= url= dir="${i%/APKBUILD}"; [ -n "$dir" ] || exit 1; cd "$dir"; . ./APKBUILD; echo $dir\\$pkgname\\$pkgver\\$pkgrel\\$pkgdesc\\$arch\\$license\\$options\\$depends\\$makedepends $makedepends_host $makedepends_build\\$checkdepends\\$subpackages\\$linguas\\$source\\$url ; done; ]]) obj.read = function(self) return function() return split_apkbuild(self.handle:read("*line")) end end obj.close = function(self) return self.handle:close() end return obj end local function init_apkdb(aportsdir, repos, repodest) local pkgdb = {} local revdeps = {} local apkbuilds = apkbuilds_open(aportsdir, repos) for a in apkbuilds:read() do -- io.write(a.pkgname.." "..a.pkgver.."\t"..a.dir.."\n") if pkgdb[a.pkgname] == nil then pkgdb[a.pkgname] = {} end pkg.init(a, repodest) table.insert(pkgdb[a.pkgname], a) -- add subpackages to package db for _, v in pairs(a.subpackages) do if pkgdb[v] == nil then pkgdb[v] = {} end table.insert(pkgdb[v], a) end -- add to reverse dependencies for dep in a:each_dependency() do if revdeps[dep] == nil then revdeps[dep] = {} end table.insert(revdeps[dep], a) end end if not apkbuilds:close() then return nil end return pkgdb, revdeps end local Aports = {} function Aports:recursive_dependencies(pkgname) local visited={} local apkdb = self.apks return coroutine.wrap(function() local function recurs(pn) if not pn or visited[pn] or not apkdb[pn] then return nil end visited[pn] = true for _, p in pairs(apkdb[pn]) do for dep in p:each_dependency() do if recurs(dep) then return true end end end coroutine.yield(pn) end return recurs(pkgname) end) end function Aports:target_packages(pkgname) return coroutine.wrap(function() for _, v in pairs(self.apks[pkgname]) do coroutine.yield(pkgname.."-"..v.pkgver.."-r"..v.pkgrel..".apk") end end) end function Aports:each_name() return coroutine.wrap(function() for k, v in pairs(self.apks) do coroutine.yield(k, v) end end) end function Aports:each_reverse_dependency(pkg) --luacheck: ignore 431 return coroutine.wrap(function() for k, v in pairs(self.revdeps[pkg] or {}) do coroutine.yield(k, v) end end) end function Aports:each_known_dependency(pkg) --luacheck: ignore 431 return coroutine.wrap(function() for dep in pkg:each_dependency() do if self.apks[dep] then coroutine.yield(dep) end end end) end function Aports:each_pkg_with_name(name) if not self.apks[name] then io.stderr:write("WARNING: "..name..": not provided by any known APKBUILD\n") return function() return nil end end return coroutine.wrap(function() for index, pkg in pairs(self.apks[name]) do --luacheck: ignore 431 coroutine.yield(pkg, index) end end) end function Aports:each() return coroutine.wrap(function() for name, pkglist in self:each_name() do for _, pkg in pairs(pkglist) do --luacheck: ignore 431 coroutine.yield(pkg, name) end end end) end function Aports:each_aport() return coroutine.wrap(function() for pkg, name in self:each() do --luacheck: ignore 431 if name == pkg.pkgname then coroutine.yield(pkg) end end end) end function Aports:each_need_build() return coroutine.wrap(function() for aport in self:each_aport() do if aport:relevant() and not aport:all_apks_exists() then coroutine.yield(aport) end end end) end function Aports:each_in_build_order(namelist) local pkgs = {} for _, name in pairs(namelist) do for pkg in self:each_pkg_with_name(name) do --luacheck: ignore 431 pkgs[pkg.dir] = true end end return coroutine.wrap(function() for _, name in pairs(namelist) do for dep in self:recursive_dependencies(name) do for pkg in self:each_pkg_with_name(dep) do --luacheck: ignore 431 if pkgs[pkg.dir] then coroutine.yield(pkg) pkgs[pkg.dir] = nil end end end end end) end function Aports:git_describe() local cmd = ("git --git-dir %s/.git describe"):format(self.aportsdir) local f = io.popen(cmd) if not f then return nil end local result = f:read("*line") f:read("*a") f:close() return result end function Aports:known_deps_exists(pkg) --luacheck: ignore 431 for name in self:each_known_dependency(pkg) do for dep in self:each_pkg_with_name(name) do if dep.pkgname ~= pkg.pkgname and dep:relevant() and not dep:all_apks_exists() then return nil end end end return true end function M.new(aportsdir, repos, repodest) local h = Aports h.aportsdir = aportsdir if type(repos) == "table" then h.repos = repos else h.repos = { repos } end h.apks, h.revdeps = init_apkdb(aportsdir, h.repos, repodest) if h.apks == nil then return nil, h.revdeps end return h end return M