package execenv import ( "fmt" "strconv" "strings" ) // Wheel 是索引里的一个候选。Kind 为 wheel 或 sdist。 type Wheel struct { Name string Version string Filename string SHA256 string Kind string Requires []string Size int64 } // Result 是一次解析。Packages 只含相对约束新增的包,不含基础层已有的。 type Result struct { Packages []LockedPkg Override []string SDist []string Missing []string Denied []string } func (r Result) OK() bool { return len(r.Override) == 0 && len(r.SDist) == 0 && len(r.Missing) == 0 && len(r.Denied) == 0 } // ResolveMem 用内存索引解析。基础层里已有且版本满足的包不会进入 Packages。 // 版本不满足时记入 Override,不改约束。 func ResolveMem(index []Wheel, reqs []string, constraints []LockedPkg) Result { byName := map[string][]Wheel{} for _, w := range index { byName[normName(w.Name)] = append(byName[normName(w.Name)], w) } pinned := map[string]LockedPkg{} for _, c := range constraints { pinned[normName(c.Name)] = c } chosen := map[string]LockedPkg{} var res Result queue := append([]string{}, reqs...) seen := map[string]bool{} for len(queue) > 0 { req := strings.TrimSpace(queue[0]) queue = queue[1:] if req == "" { continue } name, spec := splitReq(req) key := normName(name) mark := key + "\x00" + spec if seen[mark] { continue } seen[mark] = true if pin, ok := pinned[key]; ok { if spec != "" && !matchSpec(pin.Version, spec) { res.Override = append(res.Override, fmt.Sprintf("%s 需要 %s,基础层固定为 %s", name, spec, pin.Version)) } continue } if prev, ok := chosen[key]; ok { if spec != "" && !matchSpec(prev.Version, spec) { res.Override = append(res.Override, fmt.Sprintf("%s 需要 %s,已选 %s", name, spec, prev.Version)) } continue } best, sdist := pickWheel(byName[key], spec) if sdist && best == nil { res.SDist = append(res.SDist, name) continue } if best == nil { res.Missing = append(res.Missing, name) continue } pkg := LockedPkg{ Name: best.Name, Version: best.Version, Filename: best.Filename, SHA256: best.SHA256, Size: best.Size, } chosen[key] = pkg for _, dep := range best.Requires { queue = append(queue, dep) } } names := make([]string, 0, len(chosen)) for n := range chosen { names = append(names, n) } sortStrings(names) for _, n := range names { res.Packages = append(res.Packages, chosen[n]) } return res } func pickWheel(cands []Wheel, spec string) (best *Wheel, sawSDist bool) { for i := range cands { w := &cands[i] if spec != "" && !matchSpec(w.Version, spec) { continue } if w.Kind == "sdist" { sawSDist = true continue } if best == nil || cmpVer(w.Version, best.Version) > 0 { best = w } } return best, sawSDist } func splitReq(req string) (name, spec string) { for i, r := range req { if r == '=' || r == '<' || r == '>' || r == '!' || r == '~' || r == ' ' { return strings.TrimSpace(req[:i]), strings.TrimSpace(req[i:]) } } return req, "" } func matchSpec(version, spec string) bool { for _, part := range strings.Split(spec, ",") { part = strings.TrimSpace(part) if part == "" || part == "*" { continue } switch { case strings.HasPrefix(part, "=="): if version != strings.TrimPrefix(part, "==") { return false } case strings.HasPrefix(part, "!="): if version == strings.TrimPrefix(part, "!=") { return false } case strings.HasPrefix(part, ">="): if cmpVer(version, strings.TrimPrefix(part, ">=")) < 0 { return false } case strings.HasPrefix(part, "<="): if cmpVer(version, strings.TrimPrefix(part, "<=")) > 0 { return false } case strings.HasPrefix(part, ">"): if cmpVer(version, strings.TrimPrefix(part, ">")) <= 0 { return false } case strings.HasPrefix(part, "<"): if cmpVer(version, strings.TrimPrefix(part, "<")) >= 0 { return false } default: if version != part { return false } } } return true } func cmpVer(a, b string) int { as := splitVer(a) bs := splitVer(b) n := len(as) if len(bs) > n { n = len(bs) } for i := 0; i < n; i++ { ai, bi := 0, 0 if i < len(as) { ai = as[i] } if i < len(bs) { bi = bs[i] } if ai != bi { return ai - bi } } return 0 } func splitVer(v string) []int { v = strings.TrimPrefix(v, "v") if i := strings.IndexAny(v, "abrc"); i > 0 { // 丢掉本地/预发布后缀,比较只看数字段。 head := v[:i] if _, err := strconv.Atoi(string(head[len(head)-1])); err == nil { v = head } } var out []int for _, p := range strings.Split(v, ".") { n := 0 for _, r := range p { if r < '0' || r > '9' { break } n = n*10 + int(r-'0') } out = append(out, n) } return out } func sortStrings(s []string) { for i := 1; i < len(s); i++ { j := i for j > 0 && s[j] < s[j-1] { s[j], s[j-1] = s[j-1], s[j] j-- } } }