11# SPDX-License-Identifier: Apache-2.0
2- # Copyright 2026 Ague Samuel Amen
2+ # Copyright 2025 Ague Samuel Amen
33#
44# Licensed under the Apache License, Version 2.0 (the "License");
55# you may not use this file except in compliance with the License.
1313# See the License for the specific language governing permissions and
1414# limitations under the License.
1515
16+
1617from __future__ import annotations
1718
1819# Engine-controlled auto builder for PyInstaller
@@ -27,38 +28,82 @@ def AUTO_BUILDER(
2728 Build PyInstaller arguments from the engine-owned mapping.
2829
2930 Mapping conventions supported for an entry value under key "pyinstaller":
30- - str: a single CLI argument (e.g., "--collect-all=numpy")
31- - list[ str]: multiple CLI arguments
32- - dict: expects ' args' or 'flags' -> str | list[str]
33- - True: ignored by default (no generic meaning)
31+ - True: emit [ "--collect-all", {import_name}] for the matched package
32+ - str: a single CLI arg; supports {import_name} placeholder
33+ - list[str]: multiple CLI args; supports {import_name} placeholder
34+ - dict: expects 'args' or 'flags' -> str | list[str]; supports placeholder
3435 """
3536 out : list [str ] = []
36- seen = set ()
37+ seen_items = set ()
38+ seen_collect_all = set ()
3739
3840 for pkg , entry in matched .items ():
3941 if not isinstance (entry , dict ):
4042 continue
4143 val = entry .get ("pyinstaller" )
42- if val is None :
43- continue
44+ import_name = pkg_to_import . get ( pkg , pkg )
45+
4446 args : list [str ] = []
45- if isinstance (val , str ):
46- tmpl_import = pkg_to_import .get (pkg , pkg )
47- args = [val .replace ("{import_name}" , tmpl_import )]
47+ if val is True :
48+ if import_name and import_name not in seen_collect_all :
49+ args = ["--collect-all" , import_name ]
50+ seen_collect_all .add (import_name )
51+ elif isinstance (val , str ):
52+ # Split single string into proper argv tokens (e.g. "--collect-all {import_name}")
53+ s = val .replace ("{import_name}" , import_name )
54+ try :
55+ import shlex as _shlex
56+
57+ args = _shlex .split (s )
58+ except Exception :
59+ args = s .split ()
4860 elif isinstance (val , list ):
49- tmpl_import = pkg_to_import .get (pkg , pkg )
50- args = [str (x ).replace ("{import_name}" , tmpl_import ) for x in val ]
61+ # Flatten list entries, splitting any that contain spaces
62+ tmp : list [str ] = []
63+ for x in val :
64+ s = str (x ).replace ("{import_name}" , import_name )
65+ try :
66+ import shlex as _shlex
67+
68+ parts = _shlex .split (s )
69+ except Exception :
70+ parts = s .split ()
71+ tmp .extend (parts )
72+ args = tmp
5173 elif isinstance (val , dict ):
52- tmpl_import = pkg_to_import .get (pkg , pkg )
5374 a = val .get ("args" ) or val .get ("flags" )
5475 if isinstance (a , list ):
55- args = [str (x ).replace ("{import_name}" , tmpl_import ) for x in a ]
76+ tmp : list [str ] = []
77+ for x in a :
78+ s = str (x ).replace ("{import_name}" , import_name )
79+ try :
80+ import shlex as _shlex
81+
82+ parts = _shlex .split (s )
83+ except Exception :
84+ parts = s .split ()
85+ tmp .extend (parts )
86+ args = tmp
5687 elif isinstance (a , str ):
57- args = [str (a ).replace ("{import_name}" , tmpl_import )]
58- for a in args :
59- if a not in seen :
60- out .append (a )
61- seen .add (a )
88+ s = str (a ).replace ("{import_name}" , import_name )
89+ try :
90+ import shlex as _shlex
91+
92+ args = _shlex .split (s )
93+ except Exception :
94+ args = s .split ()
95+
96+ # de-dup while preserving order
97+ i = 0
98+ while i < len (args ):
99+ item = args [i ]
100+ key = item
101+ if item == "--collect-all" and (i + 1 ) < len (args ):
102+ key = f"--collect-all { args [i + 1 ]} "
103+ if key not in seen_items :
104+ out .append (item )
105+ seen_items .add (key )
106+ i += 1
62107
63108 return out
64109
0 commit comments