package execenv import ( "archive/zip" "fmt" "io" "os" "path/filepath" "strings" ) // UnpackWheel 把 wheel 解进 site-packages。wheel 是 zip;这里不执行任何入口脚本。 func UnpackWheel(wheelPath, sitePackages string) error { zr, err := zip.OpenReader(wheelPath) if err != nil { return fmt.Errorf("execenv: open wheel: %w", err) } defer zr.Close() if err := os.MkdirAll(sitePackages, 0o755); err != nil { return err } root := filepath.Clean(sitePackages) for _, f := range zr.File { name := filepath.Clean(f.Name) if name == "." || strings.HasPrefix(name, "..") || filepath.IsAbs(name) { return fmt.Errorf("execenv: wheel path escapes: %s", f.Name) } // 安装器脚本不落地,避免把「解包」变成「执行」。 if strings.Contains(name, ".data/scripts/") { continue } dest := filepath.Join(root, name) if !strings.HasPrefix(dest, root+string(os.PathSeparator)) && dest != root { return fmt.Errorf("execenv: wheel path escapes: %s", f.Name) } if f.FileInfo().IsDir() { if err := os.MkdirAll(dest, 0o755); err != nil { return err } continue } if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { return err } rc, err := f.Open() if err != nil { return err } out, err := os.OpenFile(dest, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) if err != nil { rc.Close() return err } _, cerr := io.Copy(out, rc) out.Close() rc.Close() if cerr != nil { return cerr } } return nil } // BuildLayer 把已经在 wheelhouse 里的 wheel 解进新层。目录已存在则直接复用。 func (r *Root) BuildLayer(l Lock) (string, error) { id, err := r.WriteLock(l) if err != nil { return "", err } dest := r.LayerDir(id) if st, err := os.Stat(dest); err == nil && st.IsDir() { return id, nil } for _, pkg := range l.Packages { wheel := filepath.Join(r.Wheelhouse(), pkg.SHA256+".whl") if _, err := os.Stat(wheel); err != nil { alt := filepath.Join(r.Wheelhouse(), pkg.Filename) if _, err2 := os.Stat(alt); err2 != nil { return "", fmt.Errorf("execenv: wheel missing for %s", pkg.Name) } wheel = alt } sum, err := fileSHA256(wheel) if err != nil { return "", err } if pkg.SHA256 != "" && !strings.EqualFold(sum, pkg.SHA256) { return "", fmt.Errorf("execenv: hash mismatch for %s: got %s want %s", pkg.Name, sum, pkg.SHA256) } if err := UnpackWheel(wheel, dest); err != nil { _ = os.RemoveAll(filepath.Dir(dest)) return "", err } } if err := os.MkdirAll(dest, 0o755); err != nil { return "", err } return id, nil } func fileSHA256(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := sha256New() if _, err := io.Copy(h, f); err != nil { return "", err } return h.Sum(), nil }