msys2-web/app/api.py
2020-09-13 10:50:13 +02:00

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)