2 PROJECT: ReactOS apisets generator
3 LICENSE: MIT (https://spdx.org/licenses/MIT)
4 PURPOSE: Create apiset forwarders based on Wine apisets
5 COPYRIGHT: Copyright 2017,2018 Mark Jansen (mark.jansen@reactos.org)
11 from collections
import defaultdict
15 SCRIPT_DIR
= os
.path
.dirname(os
.path
.realpath(__file__
))
19 IGNORE_OPTIONS
= ('-norelay', '-ret16', '-ret64', '-register', '-private',
20 '-noname', '-ordinal', '-i386', '-arch=', '-stub')
22 # Figure these out later
23 FUNCTION_BLACKLIST
= [
24 # api-ms-win-crt-utility-l1-1-0_stubs.c(6):
25 # error C2169: '_abs64': intrinsic function, cannot be defined
27 '_byteswap_uint64', '_byteswap_ulong', '_byteswap_ushort',
33 '# This file is autogenerated by update.py\n',
38 class InvalidSpecError(Exception):
39 def __init__(self
, message
):
40 Exception.__init
__(self
, message
)
48 Any
= i386 | x86_64 | arm | arm64
67 def __init__(self
, initial
=none
):
71 self
._val |
= sum([Arch
.FROM_STR
[arch
] for arch
in text
.split(',')])
75 return (self
._val
& val
) != 0
79 for value
in Arch
.TO_STR
:
81 arch_str
.append(Arch
.TO_STR
[value
])
82 return ','.join(arch_str
)
85 return bin(self
._val
).count("1")
87 def __add__(self
, other
):
88 return Arch(self
._val | other
._val
) # pylint: disable=W0212
90 def __sub__(self
, other
):
91 return Arch(self
._val
& ~other
._val
) # pylint: disable=W0212
93 def __gt__(self
, other
):
94 return self
._val
> other
._val
# pylint: disable=W0212
96 def __lt__(self
, other
):
97 return self
._val
< other
._val
# pylint: disable=W0212
99 def __eq__(self
, other
):
100 return self
._val
== other
._val
# pylint: disable=W0212
102 def __ne__(self
, other
):
103 return not self
.__eq
__(other
)
106 'ucrtbase': 'msvcrt',
107 'kernelbase': 'kernel32',
111 # These modules cannot be linked against in ROS, so forward it
112 'cfgmgr32': 'setupapi', # Forward everything
113 'wmi': 'advapi32', # Forward everything
116 class SpecEntry(object):
117 def __init__(self
, text
, spec
):
123 self
._forwarder
= None
129 self
.name
= self
._forwarder
[1]
131 def init(self
, text
):
132 tokens
= re
.split(r
'([\s\(\)#;])', text
.strip())
133 tokens
= [token
for token
in tokens
if token
and not token
.isspace()]
135 for comment
in ['#', ';']:
136 if comment
in tokens
:
137 idx
.append(tokens
.index(comment
))
140 tokens
= tokens
[:idx
[0]]
142 raise InvalidSpecError(text
)
143 self
._ord
= tokens
[0]
144 assert self
._ord
== '@' or self
._ord
.isdigit(), text
146 self
.callconv
= tokens
.pop(0)
147 self
.name
= tokens
.pop(0)
148 while self
.name
.startswith(IGNORE_OPTIONS
):
149 if self
.name
.startswith('-arch='):
150 self
.arch
.add(self
.name
[6:])
151 elif self
.name
== '-i386':
152 self
.arch
.add('i386')
153 self
.name
= tokens
.pop(0)
155 self
.arch
= Arch(Arch
.Any
)
156 assert not self
.name
.startswith('-'), text
160 assert ')' in tokens
, text
168 assert len(tokens
) == 1, text
169 self
._forwarder
= tokens
.pop(0).split('.', 2)
170 if len(self
._forwarder
) == 1:
171 self
._forwarder
= ['self', self
._forwarder
[0]]
172 assert len(self
._forwarder
) in (0, 2), self
._forwarder
173 if self
._forwarder
[0] in ALIAS_DLL
:
174 self
._forwarder
[0] = ALIAS_DLL
[self
._forwarder
[0]]
176 def resolve_forwarders(self
, module_lookup
, try_modules
):
178 assert self
._forwarder
[1] == self
.name
, '{}:{}'.format(self
._forwarder
[1], self
.name
)
179 if self
.noname
and self
.name
== '@':
180 return 0 # cannot search for this function
183 for module_name
in try_modules
:
184 assert module_name
in module_lookup
, module_name
185 module
= module_lookup
[module_name
]
186 fwd_arch
= module
.find_arch(self
.name
)
187 callconv
= module
.find_callconv(self
.name
)
190 self
._forwarder
= [module_name
, self
.name
]
191 self
.callconv
= callconv
195 def extra_forwarders(self
, function_lookup
, module_lookup
):
198 if self
.noname
and self
.name
== '@':
199 return 0 # cannot search for this function
200 lst
= function_lookup
.get(self
.name
, None)
202 modules
= list(set([func
.spec
.name
for func
in lst
]))
206 for module
in modules
:
207 mod_arch
= module_lookup
[module
].find_arch(self
.name
)
208 if mod
is None or mod_arch
> arch
:
213 self
._forwarder
= [mod
, self
.name
]
214 mod
= module_lookup
[mod
]
215 self
.arch
= mod
.find_arch(self
.name
)
216 self
.callconv
= mod
.find_callconv(self
.name
)
220 def forwarder_module(self
):
222 return self
._forwarder
[0]
229 def write(self
, spec_file
):
234 opts
= '{} -noname'.format(opts
)
236 assert self
._ord
!= '@'
237 name
= 'Ordinal' + self
._ord
238 if not self
._forwarder
:
239 spec_file
.write('{} stub{} {}{}'.format(self
._ord
, opts
, name
, NL_CHAR
))
240 estimate_size
+= 0x1000
242 assert self
.arch
!= Arch(), self
.name
245 fwd
= '.'.join(self
._forwarder
)
246 name
= self
.name
if not self
.noname
else '@'
248 if self
.callconv
== 'extern':
250 callconv
= 'extern -stub' # HACK
251 fwd
+= ' # the -stub is a HACK to fix VS < 2017 build!'
252 if arch
!= Arch(Arch
.Any
):
253 opts
= '{} -arch={}'.format(opts
, arch
.to_str())
254 spec_file
.write('{ord} {cc}{opts} {name}{args} {fwd}{nl}'.format(ord=self
._ord
,
261 estimate_size
+= 0x100
266 class SpecFile(object):
267 def __init__(self
, fullpath
, name
):
268 self
._path
= fullpath
271 self
._functions
= defaultdict(list)
272 self
._estimate
_size
= 0
275 with
open(self
._path
, 'rb') as specfile
:
276 for line
in specfile
.readlines():
279 entry
= SpecEntry(line
, self
)
280 self
._entries
.append(entry
)
281 self
._functions
[entry
.name
].append(entry
)
282 except InvalidSpecError
:
284 return (sum([entry
.forwarder() for entry
in self
._entries
]), len(self
._entries
))
286 def add_functions(self
, function_lookup
):
287 for entry
in self
._entries
:
288 function_lookup
[entry
.name
].append(entry
)
290 def find(self
, name
):
291 return self
._functions
.get(name
, None)
293 def find_arch(self
, name
):
294 functions
= self
.find(name
)
297 for func
in functions
:
301 def find_callconv(self
, name
):
302 functions
= self
.find(name
)
305 for func
in functions
:
307 callconv
= func
.callconv
308 elif callconv
!= func
.callconv
:
309 assert callconv
!= 'extern', 'Cannot have data/function with same name'
310 callconv
= func
.callconv
313 def resolve_forwarders(self
, module_lookup
):
314 modules
= self
.forwarder_modules()
316 for entry
in self
._entries
:
317 total
+= entry
.resolve_forwarders(module_lookup
, modules
)
318 return (total
, len(self
._entries
))
320 def extra_forwarders(self
, function_lookup
, module_lookup
):
322 for entry
in self
._entries
:
323 total
+= entry
.extra_forwarders(function_lookup
, module_lookup
)
324 return (total
, len(self
._entries
))
326 def forwarder_modules(self
):
327 modules
= defaultdict(int)
328 for entry
in self
._entries
:
329 module
= entry
.forwarder_module()
332 return sorted(modules
, key
=modules
.get
, reverse
=True)
334 def write(self
, spec_file
):
335 written
= set(FUNCTION_BLACKLIST
)
336 self
._estimate
_size
= 0
337 for entry
in self
._entries
:
338 if entry
.name
not in written
:
339 self
._estimate
_size
+= entry
.write(spec_file
)
340 written
.add(entry
.name
)
342 def write_cmake(self
, cmakelists
, baseaddress
):
344 # ntdll and kernel32 are linked against everything, self = internal,
345 # we cannot link cfgmgr32 and wmi?
346 ignore
= ['ntdll', 'kernel32', 'self', 'cfgmgr32', 'wmi']
347 forwarders
= self
.forwarder_modules()
348 fwd_strings
= [x
for x
in forwarders
if not (x
in seen
or x
in ignore
or seen
.add(x
))]
349 fwd_strings
= ' '.join(fwd_strings
)
351 baseaddress
= '0x{:8x}'.format(baseaddress
)
352 cmakelists
.write('add_apiset({} {} {}){}'.format(name
, baseaddress
, fwd_strings
, NL_CHAR
))
353 return self
._estimate
_size
357 def generate_specnames(dll_dir
):
358 win32
= os
.path
.join(dll_dir
, 'win32')
359 for dirname
in os
.listdir(win32
):
360 fullpath
= os
.path
.join(win32
, dirname
, dirname
+ '.spec')
361 if not os
.path
.isfile(fullpath
):
363 fullpath
= os
.path
.join(win32
, dirname
, dirname
.rsplit('.', 1)[0] + '.spec')
364 if not os
.path
.isfile(fullpath
):
368 yield (fullpath
, dirname
)
370 yield (os
.path
.join(dll_dir
, 'ntdll', 'def', 'ntdll.spec'), 'ntdll')
371 yield (os
.path
.join(dll_dir
, 'appcompat', 'apphelp', 'apphelp.spec'), 'apphelp')
372 yield (os
.path
.join(dll_dir
, '..', 'win32ss', 'user', 'user32', 'user32.spec'), 'user32')
373 yield (os
.path
.join(dll_dir
, '..', 'win32ss', 'gdi', 'gdi32', 'gdi32.spec'), 'gdi32')
381 function_lookup
= defaultdict(list)
383 version
= subprocess
.check_output(["git", "describe"], cwd
=wineroot
).strip()
385 print 'Reading Wine apisets for', version
386 wine_apiset_path
= os
.path
.join(wineroot
, 'dlls')
387 for dirname
in os
.listdir(wine_apiset_path
):
388 if not dirname
.startswith('api-'):
390 if not os
.path
.isdir(os
.path
.join(wine_apiset_path
, dirname
)):
392 fullpath
= os
.path
.join(wine_apiset_path
, dirname
, dirname
+ '.spec')
393 spec
= SpecFile(fullpath
, dirname
)
394 wine_apisets
.append(spec
)
396 print 'Parsing Wine apisets,',
398 for apiset
in wine_apisets
:
399 total
= tuple(map(sum, zip(apiset
.parse(), total
)))
400 print 'found', total
[0], '/', total
[1], 'forwarders'
402 print 'Reading ReactOS modules'
403 for fullpath
, dllname
in generate_specnames(os
.path
.dirname(SCRIPT_DIR
)):
404 spec
= SpecFile(fullpath
, dllname
)
405 ros_modules
.append(spec
)
407 print 'Parsing ReactOS modules'
408 for module
in ros_modules
:
410 assert module
.name
not in module_lookup
, module
.name
411 module_lookup
[module
.name
] = module
412 module
.add_functions(function_lookup
)
414 print 'First pass, resolving forwarders,',
416 for apiset
in wine_apisets
:
417 total
= tuple(map(sum, zip(apiset
.resolve_forwarders(module_lookup
), total
)))
418 print 'found', total
[0], '/', total
[1], 'forwarders'
420 print 'Second pass, searching extra forwarders,',
422 for apiset
in wine_apisets
:
423 total
= tuple(map(sum, zip(apiset
.extra_forwarders(function_lookup
, module_lookup
), total
)))
424 print 'found', total
[0], '/', total
[1], 'forwarders'
426 with
open(os
.path
.join(SCRIPT_DIR
, 'CMakeLists.txt.in'), 'rb') as template
:
427 data
= template
.read()
428 data
= data
.replace('%WINE_GIT_VERSION%', version
)
429 # Detect the checkout newline settings
433 print 'Writing apisets'
434 spec_header
= [line
.replace('\n', NL_CHAR
) for line
in SPEC_HEADER
]
435 for apiset
in wine_apisets
:
436 with
open(os
.path
.join(SCRIPT_DIR
, apiset
.name
+ '.spec'), 'wb') as out_spec
:
437 out_spec
.writelines(spec_header
)
438 apiset
.write(out_spec
)
440 print 'Writing CMakeLists.txt'
441 baseaddress
= 0x60000000
442 with
open(os
.path
.join(SCRIPT_DIR
, 'CMakeLists.txt'), 'wb') as cmakelists
:
443 cmakelists
.write(data
)
444 for apiset
in wine_apisets
:
445 baseaddress
+= apiset
.write_cmake(cmakelists
, baseaddress
)
446 baseaddress
+= (0x10000 - baseaddress
) % 0x10000
454 print 'No path specified,'
455 print 'either pass it as argument, or set the environment variable "WINE_SRC_ROOT"'
457 if __name__
== '__main__':
458 main(sys
.argv
[1:] + [os
.environ
.get('WINE_SRC_ROOT')])