Files
navop/script/product-dependency-graph.py

89 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json, subprocess, collections, sys
from PIL import Image, ImageDraw, ImageFont
sys.setrecursionlimit(20000)
APPS = ['navop','navop-shell','navop-data']
meta = json.loads(subprocess.run(['cargo','metadata','--no-deps','--format-version','1'],capture_output=True,text=True).stdout)
ws = {p['name'] for p in meta['packages']}
def edges(product):
out = subprocess.run(['cargo','tree','-p',product,'-e','normal','--prefix','depth'],capture_output=True,text=True).stdout
stack, es = {}, set()
for line in out.splitlines():
if not line.strip(): continue
d=0
while d < len(line) and line[d].isdigit(): d+=1
depth=int(line[:d]) if d else 0
name=line[d:].strip().split(' v')[0].split(' ')[0]
stack[depth]=name
if depth>0 and name in ws and stack.get(depth-1) in ws:
es.add((stack[depth-1],name))
return es
per={p:edges(p) for p in APPS}
membership=collections.defaultdict(set)
for p in APPS:
for n in {x for e in per[p] for x in e}|{p}:
membership[n].add(p)
nodes=set(membership)
union=set().union(*per.values())
preds=collections.defaultdict(set)
for a,b in union:
preds[b].add(a)
state={}
def level(n, seen=()):
if n in state: return state[n]
if n in seen: return 0
if n in APPS: state[n]=0; return 0
ps=preds.get(n,set())
lv=1+max((level(p, seen+(n,)) for p in ps), default=-1) if ps else 0
state[n]=max(lv,0); return state[n]
for n in nodes: level(n)
levels=collections.defaultdict(list)
for n in nodes: levels[state[n]].append(n)
def color(n):
if n in APPS: return (62,74,92), (255,255,255)
m=membership[n]
if len(m)==3: return (203,232,203),(20,20,20)
if m=={'navop','navop-shell'}: return (255,213,160),(20,20,20)
if m=={'navop','navop-data'}: return (186,214,252),(20,20,20)
return (228,228,228),(20,20,20)
fp='/System/Library/Fonts/Supplemental/Arial Unicode.ttf'
F=lambda s: ImageFont.truetype(fp,s)
NW,NH,GX,GY,PAD=152,22,26,10,70
maxlvl=max(len(v) for v in levels.values())
W=PAD*2+maxlvl*(NW+GX)
H=PAD*2+(max(levels)+1)*(NH+GY)+110
img=Image.new('RGB',(W,H),(255,255,255)); d=ImageDraw.Draw(img)
mid={}
order_rank=lambda n:(0 if n in APPS else 1, {'all':0,'shell':1,'data':2,'full':3}[('all' if len(membership[n])==3 else 'shell' if membership[n]=={'navop','navop-shell'} else 'data' if membership[n]=={'navop','navop-data'} else 'full')], n)
for lv,ns in sorted(levels.items()):
ns=sorted(ns,key=order_rank)
tw=len(ns)*(NW+GX)-GX; x0=(W-tw)//2; y=PAD+lv*(NH+GY)
for i,n in enumerate(ns):
x=x0+i*(NW+GX); cx=x+NW/2; cy=y+NH/2
mid[n]=(cx,cy,x,y)
for a,b in union:
if a in mid and b in mid:
ax,ay,_,_=mid[a]; bx,by,_,_=mid[b]
d.line([(ax,ay+NH/2-3),(bx,by-NH/2+2)],fill=(206,206,206),width=1)
for n,(cx,cy,x,y) in mid.items():
fill,fg=color(n)
d.rounded_rectangle([x,y,x+NW,y+NH],radius=4,fill=fill,outline=(110,110,110))
d.text((x+5,y+5),n,font=F(12),fill=fg)
lx,ly=40,H-64
d.text((lx,ly-22),'navop 三产品依赖图(仅工作区内部 crate;node 颜色 = 出现在哪些产品的 cargo tree 中)',font=F(14),fill=(0,0,0))
import math
def swatch(x,y,c): d.rounded_rectangle([x,y,x+24,y+15],radius=3,fill=c,outline=(110,110,110))
legend=[('应用入口',(62,74,92)),('三产品共有',(203,232,203)),('全量 + 终端(shell)',(255,213,160)),('全量 + 数据(data)',(186,214,252)),('仅全量',(228,228,228))]
x=lx
for label,c in legend:
swatch(x,ly,c); d.text((x+32,ly),label,font=F(13),fill=(0,0,0)); x+=270
img.save("docs/design/navop-product-dependency-graph.png")
print('saved',W,H,'nodes',len(nodes),'edges',len(union))