@@ -136,6 +136,9 @@ func BuildPatchedFridaToolsWheels(cfg JobConfig, catalogRoot string, entryDirs [
136136 if len (toolsBuilt ) == 0 {
137137 toolsBuilt , _ = filepath .Glob (filepath .Join (outDir , "*.whl" ))
138138 }
139+ // GitHub #36: older hand-rolled wheels used '.' instead of '+' in the
140+ // local version; pip rejects those filenames. Normalize before copy.
141+ toolsBuilt = sanitizeToolsWheelFilenames (toolsBuilt )
139142 }
140143 }
141144
@@ -577,6 +580,139 @@ func parseVersionFromArchiveName(name string) string {
577580 return name
578581}
579582
583+ // pep440WheelFilenameVersion is the version segment written into a wheel filename.
584+ // PEP 440 local versions MUST keep '+' (e.g. 14.10.4+frida.17.17.0.fridare.kxmwp).
585+ // Replacing '+' with '.' or '_' makes packaging.utils.parse_wheel_filename fail:
586+ //
587+ // Invalid wheel filename (invalid version)
588+ //
589+ // See GitHub suifei/fridare#36.
590+ func pep440WheelFilenameVersion (version string ) string {
591+ v := strings .ReplaceAll (strings .TrimSpace (version ), " " , "" )
592+ if isPEP440Version (v ) {
593+ return v
594+ }
595+ if fixed := repairDottedFridaLocalVersion (v ); isPEP440Version (fixed ) {
596+ return fixed
597+ }
598+ return v
599+ }
600+
601+ func isPEP440Version (v string ) bool {
602+ if v == "" {
603+ return false
604+ }
605+ pub , local , hasLocal := strings .Cut (v , "+" )
606+ if ! isPEP440Release (pub ) {
607+ return false
608+ }
609+ if ! hasLocal {
610+ return true
611+ }
612+ if local == "" {
613+ return false
614+ }
615+ for _ , seg := range strings .Split (local , "." ) {
616+ if ! isAlnum (seg ) {
617+ return false
618+ }
619+ }
620+ return true
621+ }
622+
623+ func isPEP440Release (v string ) bool {
624+ if v == "" {
625+ return false
626+ }
627+ for _ , seg := range strings .Split (v , "." ) {
628+ if seg == "" {
629+ return false
630+ }
631+ for _ , c := range seg {
632+ if c < '0' || c > '9' {
633+ return false
634+ }
635+ }
636+ }
637+ return true
638+ }
639+
640+ func isAlnum (s string ) bool {
641+ if s == "" {
642+ return false
643+ }
644+ for _ , c := range s {
645+ if (c < '0' || c > '9' ) && (c < 'a' || c > 'z' ) && (c < 'A' || c > 'Z' ) {
646+ return false
647+ }
648+ }
649+ return true
650+ }
651+
652+ // repairDottedFridaLocalVersion restores '+' after the public version when a
653+ // previous Fridare build wrote 14.10.4.frida.… or 14.10.4_frida.… .
654+ func repairDottedFridaLocalVersion (v string ) string {
655+ for _ , sep := range []string {".frida." , "_frida." } {
656+ if i := strings .Index (v , sep ); i > 0 {
657+ return v [:i ] + "+frida." + v [i + len (sep ):]
658+ }
659+ }
660+ return v
661+ }
662+
663+ func splitWheelFilename (base string ) (name , ver , py , abi , plat string , ok bool ) {
664+ if ! strings .HasSuffix (strings .ToLower (base ), ".whl" ) || len (base ) < 5 {
665+ return
666+ }
667+ stem := base [:len (base )- 4 ]
668+ parts := strings .Split (stem , "-" )
669+ if len (parts ) < 5 {
670+ return
671+ }
672+ plat = parts [len (parts )- 1 ]
673+ abi = parts [len (parts )- 2 ]
674+ py = parts [len (parts )- 3 ]
675+ name = parts [0 ]
676+ ver = strings .Join (parts [1 :len (parts )- 3 ], "-" )
677+ if name == "" || ver == "" {
678+ return
679+ }
680+ ok = true
681+ return
682+ }
683+
684+ // ensurePEP440ToolsWheelFilename renames a wheel on disk if its version segment
685+ // is not PEP 440 (the historical '.'-local-version bug). pip only inspects the
686+ // outer filename; METADATA inside may already be correct.
687+ func ensurePEP440ToolsWheelFilename (path string ) (string , error ) {
688+ base := filepath .Base (path )
689+ name , ver , py , abi , plat , ok := splitWheelFilename (base )
690+ if ! ok {
691+ return path , nil
692+ }
693+ newVer := pep440WheelFilenameVersion (ver )
694+ if newVer == ver {
695+ return path , nil
696+ }
697+ newPath := filepath .Join (filepath .Dir (path ), fmt .Sprintf ("%s-%s-%s-%s-%s.whl" , name , newVer , py , abi , plat ))
698+ if err := os .Rename (path , newPath ); err != nil {
699+ return path , err
700+ }
701+ return newPath , nil
702+ }
703+
704+ func sanitizeToolsWheelFilenames (paths []string ) []string {
705+ out := make ([]string , 0 , len (paths ))
706+ for _ , p := range paths {
707+ if nw , err := ensurePEP440ToolsWheelFilename (p ); err == nil && nw != "" {
708+ out = append (out , nw )
709+ } else {
710+ out = append (out , p )
711+ }
712+ }
713+ return out
714+ }
715+
580716// pinFridaToolsPackageVersion patches PKG-INFO / setup.py so the rebuilt wheel
581717// reports a local version and pins install_requires to frida==sourceVer.
582718// Returns the PEP 440 local version string (e.g. 14.10.4+frida.17.16.4.fridare.abcde).
@@ -634,11 +770,12 @@ func buildPureFridaToolsWheel(pkgRoot, outDir, version string) (string, error) {
634770 if st , err := os .Stat (pkgDir ); err != nil || ! st .IsDir () {
635771 return "" , fmt .Errorf ("frida_tools package dir missing under %s" , pkgRoot )
636772 }
637- // Normalize version for filename: local versions use + which becomes . in wheel tags sometimes;
638- // PEP 427 allows + in version but filename uses the version as-is with + kept or replaced.
639- // Use underscore-safe: replace + with _
640- fileVer := strings .ReplaceAll (version , "+" , "." )
641- fileVer = strings .ReplaceAll (fileVer , " " , "" )
773+ // Keep PEP 440 '+' in the filename. Do NOT replace with '.' or '_' —
774+ // pip/packaging parse the version as-is (GitHub #36).
775+ fileVer := pep440WheelFilenameVersion (version )
776+ if fileVer == "" {
777+ return "" , fmt .Errorf ("empty frida-tools wheel version" )
778+ }
642779 whlName := fmt .Sprintf ("frida_tools-%s-py3-none-any.whl" , fileVer )
643780 dst := filepath .Join (outDir , whlName )
644781 _ = os .MkdirAll (outDir , 0755 )
@@ -758,7 +895,7 @@ B) frida 原生扩展(按宿主机 OS/架构选 python/host/<id>/,共 6 个
758895 # Linux aarch64
759896 pip install --force-reinstall --no-deps "python/host/linux-arm64/frida-*.whl"
760897
761- # 再装 frida-tools(纯 Python,任意宿主机):
898+ # 再装 frida-tools(纯 Python,任意宿主机)。文件名里的 '+' 是 PEP 440 本地版本,不要改成 '.'。
762899 pip install --force-reinstall --no-deps "%s"
763900
764901验证:
0 commit comments