"""Extension management for Windows. | |
Under Windows it is unlikely the .obj files are of use, as special compiler options | |
are needed (primarily to toggle the behavior of "public" symbols. | |
I dont consider it worth parsing the MSVC makefiles for compiler options. Even if | |
we get it just right, a specific freeze application may have specific compiler | |
options anyway (eg, to enable or disable specific functionality) | |
So my basic strategy is: | |
* Have some Windows INI files which "describe" one or more extension modules. | |
(Freeze comes with a default one for all known modules - but you can specify | |
your own). | |
* This description can include: | |
- The MSVC .dsp file for the extension. The .c source file names | |
are extraced from there. | |
- Specific compiler/linker options | |
- Flag to indicate if Unicode compilation is expected. | |
At the moment the name and location of this INI file is hardcoded, | |
but an obvious enhancement would be to provide command line options. | |
""" | |
import os, sys | |
try: | |
import win32api | |
except ImportError: | |
win32api = None # User has already been warned | |
class CExtension: | |
"""An abstraction of an extension implemented in C/C++ | |
""" | |
def __init__(self, name, sourceFiles): | |
self.name = name | |
# A list of strings defining additional compiler options. | |
self.sourceFiles = sourceFiles | |
# A list of special compiler options to be applied to | |
# all source modules in this extension. | |
self.compilerOptions = [] | |
# A list of .lib files the final .EXE will need. | |
self.linkerLibs = [] | |
def GetSourceFiles(self): | |
return self.sourceFiles | |
def AddCompilerOption(self, option): | |
self.compilerOptions.append(option) | |
def GetCompilerOptions(self): | |
return self.compilerOptions | |
def AddLinkerLib(self, lib): | |
self.linkerLibs.append(lib) | |
def GetLinkerLibs(self): | |
return self.linkerLibs | |
def checkextensions(unknown, extra_inis, prefix): | |
# Create a table of frozen extensions | |
defaultMapName = os.path.join( os.path.split(sys.argv[0])[0], "extensions_win32.ini") | |
if not os.path.isfile(defaultMapName): | |
sys.stderr.write("WARNING: %s can not be found - standard extensions may not be found\n" % defaultMapName) | |
else: | |
# must go on end, so other inis can override. | |
extra_inis.append(defaultMapName) | |
ret = [] | |
for mod in unknown: | |
for ini in extra_inis: | |
# print "Looking for", mod, "in", win32api.GetFullPathName(ini),"...", | |
defn = get_extension_defn( mod, ini, prefix ) | |
if defn is not None: | |
# print "Yay - found it!" | |
ret.append( defn ) | |
break | |
# print "Nope!" | |
else: # For not broken! | |
sys.stderr.write("No definition of module %s in any specified map file.\n" % (mod)) | |
return ret | |
def get_extension_defn(moduleName, mapFileName, prefix): | |
if win32api is None: return None | |
os.environ['PYTHONPREFIX'] = prefix | |
dsp = win32api.GetProfileVal(moduleName, "dsp", "", mapFileName) | |
if dsp=="": | |
return None | |
# We allow environment variables in the file name | |
dsp = win32api.ExpandEnvironmentStrings(dsp) | |
# If the path to the .DSP file is not absolute, assume it is relative | |
# to the description file. | |
if not os.path.isabs(dsp): | |
dsp = os.path.join( os.path.split(mapFileName)[0], dsp) | |
# Parse it to extract the source files. | |
sourceFiles = parse_dsp(dsp) | |
if sourceFiles is None: | |
return None | |
module = CExtension(moduleName, sourceFiles) | |
# Put the path to the DSP into the environment so entries can reference it. | |
os.environ['dsp_path'] = os.path.split(dsp)[0] | |
os.environ['ini_path'] = os.path.split(mapFileName)[0] | |
cl_options = win32api.GetProfileVal(moduleName, "cl", "", mapFileName) | |
if cl_options: | |
module.AddCompilerOption(win32api.ExpandEnvironmentStrings(cl_options)) | |
exclude = win32api.GetProfileVal(moduleName, "exclude", "", mapFileName) | |
exclude = exclude.split() | |
if win32api.GetProfileVal(moduleName, "Unicode", 0, mapFileName): | |
module.AddCompilerOption('/D UNICODE /D _UNICODE') | |
libs = win32api.GetProfileVal(moduleName, "libs", "", mapFileName).split() | |
for lib in libs: | |
module.AddLinkerLib(win32api.ExpandEnvironmentStrings(lib)) | |
for exc in exclude: | |
if exc in module.sourceFiles: | |
modules.sourceFiles.remove(exc) | |
return module | |
# Given an MSVC DSP file, locate C source files it uses | |
# returns a list of source files. | |
def parse_dsp(dsp): | |
# print "Processing", dsp | |
# For now, only support | |
ret = [] | |
dsp_path, dsp_name = os.path.split(dsp) | |
try: | |
lines = open(dsp, "r").readlines() | |
except IOError, msg: | |
sys.stderr.write("%s: %s\n" % (dsp, msg)) | |
return None | |
for line in lines: | |
fields = line.strip().split("=", 2) | |
if fields[0]=="SOURCE": | |
if os.path.splitext(fields[1])[1].lower() in ['.cpp', '.c']: | |
ret.append( win32api.GetFullPathName(os.path.join(dsp_path, fields[1] ) ) ) | |
return ret | |
def write_extension_table(fname, modules): | |
fp = open(fname, "w") | |
try: | |
fp.write (ext_src_header) | |
# Write fn protos | |
for module in modules: | |
# bit of a hack for .pyd's as part of packages. | |
name = module.name.split('.')[-1] | |
fp.write('extern void init%s(void);\n' % (name) ) | |
# Write the table | |
fp.write (ext_tab_header) | |
for module in modules: | |
name = module.name.split('.')[-1] | |
fp.write('\t{"%s", init%s},\n' % (name, name) ) | |
fp.write (ext_tab_footer) | |
fp.write(ext_src_footer) | |
finally: | |
fp.close() | |
ext_src_header = """\ | |
#include "Python.h" | |
""" | |
ext_tab_header = """\ | |
static struct _inittab extensions[] = { | |
""" | |
ext_tab_footer = """\ | |
/* Sentinel */ | |
{0, 0} | |
}; | |
""" | |
ext_src_footer = """\ | |
extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab); | |
int PyInitFrozenExtensions() | |
{ | |
return PyImport_ExtendInittab(extensions); | |
} | |
""" |