147 lines
4.9 KiB
Python
147 lines
4.9 KiB
Python
from functools import cmp_to_key
|
|
|
|
from fastapi import FastAPI, APIRouter, Request, Response
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from typing import Tuple, Dict, List, Set, Any, Iterable
|
|
from .appstate import state, SrcInfoPackage
|
|
from .utils import version_is_newer_than
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def cmp_(a: Any, b: Any) -> int:
|
|
return int((a > b) - (a < b))
|
|
|
|
|
|
def cmp_func(e1: Dict, e2: Dict) -> int:
|
|
# package with fewest deps first
|
|
e1_k = (len(e1["makedepends"]), sorted(e1["provides"]))
|
|
e2_k = (len(e2["makedepends"]), sorted(e2["provides"]))
|
|
e1_p, e1_m = e1["provides"], e1["makedepends"]
|
|
e2_p, e2_m = e2["provides"], e2["makedepends"]
|
|
|
|
e2e1 = e2_m & e1_p
|
|
e1e2 = e1_m & e2_p
|
|
|
|
if e1e2 and e2e1:
|
|
# cyclic dependency!
|
|
return cmp_(e1_k, e2_k)
|
|
elif e2e1:
|
|
return -1
|
|
elif e1e2:
|
|
return 1
|
|
else:
|
|
return cmp_(e1_k, e2_k)
|
|
|
|
|
|
@router.get('/buildqueue')
|
|
async def index(request: Request, response: Response, include_new: bool = True, include_update: bool = True) -> Response:
|
|
srcinfos = []
|
|
|
|
# packages that should be updated
|
|
if include_update:
|
|
for s in state.sources.values():
|
|
for k, p in sorted(s.packages.items()):
|
|
if p.name in state.sourceinfos:
|
|
srcinfo = state.sourceinfos[p.name]
|
|
if not version_is_newer_than(srcinfo.build_version, p.version):
|
|
continue
|
|
srcinfos.append(srcinfo)
|
|
|
|
# packages that are new
|
|
if include_new:
|
|
available: Dict[str, List[SrcInfoPackage]] = {}
|
|
for srcinfo in state.sourceinfos.values():
|
|
available.setdefault(srcinfo.pkgname, []).append(srcinfo)
|
|
for s in state.sources.values():
|
|
for p in s.packages.values():
|
|
available.pop(p.name, None)
|
|
for sis in available.values():
|
|
srcinfos.extend(sis)
|
|
|
|
def build_key(srcinfo: SrcInfoPackage) -> Tuple[str, str]:
|
|
return (srcinfo.repo_url, srcinfo.repo_path)
|
|
|
|
to_build: Dict[Tuple, List[SrcInfoPackage]] = {}
|
|
for srcinfo in srcinfos:
|
|
key = build_key(srcinfo)
|
|
to_build.setdefault(key, []).append(srcinfo)
|
|
|
|
db_makedepends: Dict[str, Set[str]] = {}
|
|
db_depends: Dict[str, Set[str]] = {}
|
|
for s in state.sources.values():
|
|
for p in s.packages.values():
|
|
db_makedepends.setdefault(p.name, set()).update(p.makedepends.keys())
|
|
db_depends.setdefault(p.name, set()).update(p.depends.keys())
|
|
|
|
def get_transitive_depends(packages: Iterable[str]) -> Set[str]:
|
|
todo = set(packages)
|
|
done = set()
|
|
while todo:
|
|
name = todo.pop()
|
|
if name in done:
|
|
continue
|
|
done.add(name)
|
|
# prefer depends from the GIT packages over the DB
|
|
if name in state.sourceinfos:
|
|
si = state.sourceinfos[name]
|
|
todo.update(si.depends.keys())
|
|
elif name in db_makedepends:
|
|
todo.update(db_depends[name])
|
|
return done
|
|
|
|
def get_transitive_makedepends(packages: Iterable[str]) -> Set[str]:
|
|
todo: Set[str] = set()
|
|
for name in packages:
|
|
# prefer depends from the GIT packages over the DB
|
|
if name in state.sourceinfos:
|
|
si = state.sourceinfos[name]
|
|
todo.update(si.depends.keys())
|
|
todo.update(si.makedepends.keys())
|
|
elif name in db_makedepends:
|
|
todo.update(db_depends[name])
|
|
todo.update(db_makedepends[name])
|
|
|
|
return get_transitive_depends(todo)
|
|
|
|
entries = []
|
|
all_provides = {}
|
|
for srcinfos in to_build.values():
|
|
packages = set()
|
|
provides: Set[str] = set()
|
|
for si in srcinfos:
|
|
packages.add(si.pkgname)
|
|
for prov in si.provides:
|
|
provides.add(prov)
|
|
all_provides[prov] = si.pkgname
|
|
|
|
entries.append({
|
|
"repo_url": srcinfos[0].repo_url,
|
|
"repo_path": srcinfos[0].repo_path,
|
|
"version": srcinfos[0].build_version,
|
|
"name": srcinfos[0].pkgbase,
|
|
"packages": packages,
|
|
"provides": provides | packages,
|
|
"makedepends": get_transitive_makedepends(packages),
|
|
})
|
|
|
|
entries.sort(key=cmp_to_key(cmp_func))
|
|
|
|
all_packages: Set[str] = set()
|
|
for e in entries:
|
|
# Replace dependencies on provided names with their providing packages
|
|
makedepends = set(all_provides.get(d, d) for d in e["makedepends"])
|
|
# Only show deps which are known at that point.. so in case of a cycle
|
|
# this will be wrong, but we can't do much about that.
|
|
e["depends"] = sorted(makedepends & all_packages)
|
|
all_packages |= set(e["packages"])
|
|
e["packages"] = sorted(e["packages"])
|
|
del e["makedepends"]
|
|
del e["provides"]
|
|
|
|
return JSONResponse(entries)
|
|
|
|
api = FastAPI(title="MSYS2 Packages API", docs_url="/")
|
|
api.include_router(router)
|