Viewing file: BuildScripts.py (17.61 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
import os, sys, struct, re from distutils import sysconfig, util from distutils.ccompiler import new_compiler, show_compilers from distutils.core import Command from distutils.dep_util import newer, newer_group
from Structures import Script, ExecutableScript, Executable
STUB_MAIN_SOURCE = os.path.join(os.path.dirname(__file__), 'stubmain.c')
SHELL_SCRIPT_HEAD = """#!%(env_executable)s%(env_args)s%(py_executable)s
# Force Python to ignore the current directory when importing the # module(s) used in the script body. import sys, imp module_info = imp.find_module('Ft', filter(None, sys.path)) imp.load_module('Ft', *module_info)
### SCRIPT BODY ### """
COMMAND_LINE_APP_BODY = """# script generated in %s %%(import_stmt)s %%(classname)s().run() """ % __name__
class BuildScripts(Command):
command_name = 'build_scripts'
description = "\"build\" scripts"
user_options = [ ('build-dir=', 'd', "directory to \"build\" (copy) to"), ('build-temp=', 't', "directory for temporary files (build by-products)"), ('force', 'f', "forcibly build everything (ignore file timestamps"), ('debug', 'g', "compile/link with debugging information"), ('compiler=', 'c', "specify the compiler type"), ]
help_options = [ ('help-compiler', None, "list available compilers", show_compilers), ]
boolean_options = ['force', 'debug']
stub_name = 'stubmain'
def initialize_options(self): self.build_dir = None self.build_temp = None self.force = None self.debug = None self.compiler = None return
def finalize_options(self): undefined_temp = self.build_temp is None
self.set_undefined_options('build', ('build_scripts', 'build_dir'), ('build_temp', 'build_temp'), ('compiler', 'compiler'), ('debug', 'debug'), ('force', 'force'))
if undefined_temp: self.build_temp = os.path.join(self.build_temp, 'scripts')
self.scripts = self.distribution.scripts or []
# Get the linker arguments for building executables if os.name == 'posix': args = sysconfig.get_config_vars('LDFLAGS', 'LINKFORSHARED') self.link_preargs = ' '.join(args).split()
args = sysconfig.get_config_vars('LIBS', 'MODLIBS', 'SYSLIBS', 'LDLAST') self.link_postargs = ' '.join(args).split() else: self.link_preargs = [] self.link_postargs = []
# Get the extension for executables (only if needed for the OS) if os.name == 'nt' or sys.platform.startswith('darwin'): self.exe_extension = sysconfig.get_config_var('EXE') or '' else: self.exe_extension = ''
if os.name == 'nt': if self.debug: self.exe_extension = '_d' + self.exe_extension stub_exe = self.stub_name + self.exe_extension self.win32_stub_filename = os.path.join(self.build_temp, stub_exe) else: self.win32_stub_filename = None return
def run(self): """ Create the proper script for the current platform. """ if not self.scripts: return
# Ensure the destination directory exists self.mkpath(self.build_dir)
# Break out the various types of scripts scripts = [ x for x in self.scripts if isinstance(x, Script) ] executables = [ x for x in self.scripts if isinstance(x, Executable) ] exec_scripts = [ x for x in self.scripts if isinstance(x, ExecutableScript) ]
# Create the compiler for any Windows script, executables or # executable scripts if (os.name == 'nt' and (scripts or exec_scripts)) or executables: self._prep_compiler()
# Create the executable stub for any Windows script or executable # scripts if os.name == 'nt' and (scripts or exec_scripts): self._prep_win32_stub()
for script in scripts: self.build_script(script)
for executable in executables: self.build_executable(executable)
for exec_script in exec_scripts: self.build_executable_script(exec_script)
return
# -- worker functions ---------------------------------------------
def build_script(self, script): """ Builds a CommandLineApp script. On POSIX systems, this is a generated shell script. For Windows, it is a compiled executable with the generated file appended to the end of the stub. """ # Create the script body import_stmt = 'import ' + script.identifier if script.module: import_stmt = 'from ' + script.module + ' ' + import_stmt
repl = {'classname' : script.identifier, 'import_stmt' : import_stmt, }
self.write_script(script, COMMAND_LINE_APP_BODY % repl) return
def build_executable_script(self, exec_script): """ Builds a hand-written shell script. On POSIX systems, this is nothing more than a file copy. For Windows, the executable stub has the contents of the script file appended to the end of the stub. """
# Read in the script body source = util.convert_path(exec_script.source) f = open(source, 'r') script_body = f.read() f.close()
self.write_script(exec_script, script_body, [source]) return
def build_executable(self, executable): """ Builds a compiled executable. For all systems, the executable is created in the same fashion as the Python interpreter executable. """ outfile = self.get_script_filename(executable)
all_sources = self._prep_build(script) sources = [] for source, includes in all_sources: sources.append(source) sources.extend(includes)
if not (self.force or newer_group(sources, outfile, 'newer')): self.announce("skipping '%s' executable (up-to-date)" % executable.name) return else: self.announce("building '%s' executable" % executable.name)
output_dir = os.path.join(self.build_temp, executable.name)
macros = executable.define_macros[:] for undef in executable.undef_macros: macros.append((undef,))
objects = [] for source, includes in all_sources: if not self.force: # Recompile if the includes or source are newer than the # resulting object files. objs = self.compiler.object_filenames([source], 1, output_dir)
# Recompile if any of the inputs are newer than the object inputs = [source] + includes force = 0 for filename in objs: force = force or newer_group(inputs, filename, 'newer') self.compiler.force = force
objs = self.compiler.compile( [source], output_dir=output_dir, macros=macros, include_dirs=executable.include_dirs, debug=self.debug, extra_postargs=executable.extra_compile_args) objects.extend(objs)
# Reset the force flag on the compiler self.compiler.force = self.force
# Now link the object files together into a "shared object" -- # of course, first we have to figure out all the other things # that go into the mix. if os.name == 'nt' and self.debug: executable = executable.name + '_d' else: executable = executable.name
if executable.extra_objects: objects.extend(executable.extra_objects)
# On Windows, non-MSVC compilers need some help finding python # libs. This logic comes from distutils/command/build_ext.py. libraries = executable.libraries if sys.platform == "win32": from distutils.msvccompiler import MSVCCompiler if not isinstance(self.compiler, MSVCCompiler): template = "python%d%d" if self.debug: template = template + "_d" pythonlib = (template % (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) libraries += [pythonlib]
self.compiler.link_executable( objects, executable, libraries=libraries, library_dirs=executable.library_dirs, runtime_library_dirs=executable.runtime_library_dirs, extra_preargs=self.link_preargs, extra_postargs=self.link_postargs + executable.extra_link_args, debug=self.debug, build_temp=self.build_temp)
return
# -- utility functions --------------------------------------------
def write_script(self, script, script_body, extra_sources=None): # Get the destination filename outfile = self.get_script_filename(script)
# Get the list of source dependencies for this script sources = [__file__] if os.name == 'nt': sources.append(self.win32_stub_filename) if extra_sources: sources.extend(extra_sources)
# Determine if the script needs to be built if not (self.force or newer_group(sources, outfile, 'newer')): self.announce("skipping '%s' script (up-to-date)" % script.name) return else: self.announce("building '%s' script" % script.name)
if self.dry_run: # Don't actually create the script return
# prepare args for the bang path at the top of the script ENV_BIN = '/usr/bin/env' if os.name == 'posix': # Some Solaris platforms may not have an 'env' binary. # If /usr/bin/env exists, use '#!/usr/bin/env python' # otherwise, use '#!' + sys.executable env_exec = os.path.isfile(ENV_BIN) and \ os.access(ENV_BIN, os.X_OK) and ENV_BIN or '' env_args = '' py_exec = env_exec and 'python' or sys.executable else: # shouldn't matter on non-POSIX; we'll just use defaults env_exec = ENV_BIN env_args = '' py_exec = 'python'
head_args = { 'env_executable': env_exec, 'env_args': env_exec and (' %s' % env_args) or '', 'py_executable': py_exec }
script = SHELL_SCRIPT_HEAD % head_args + script_body
if os.name == 'posix': # Create the file with execute permissions set fd = os.open(outfile, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0755) os.write(fd, script) os.close(fd) elif os.name == 'nt': # Get the stub executable data f = open(self.win32_stub_filename, 'rb') stub_data = f.read() f.close()
# append the data to the stub executable f = open(outfile, 'wb')
# Write the executable f.write(stub_data)
# Write the script_data struct # struct script_data { # int tag; # int length; # } script_data = struct.pack("ii", 0x1234567A, len(script)) f.write(script_data)
# Write the script string f.write(script)
# Write the script_info struct # struct script_info { # int tag; # int offset; # } script_info = struct.pack("ii", 0x1234567A, len(stub_data)) f.write(script_info)
f.close() else: raise DistutilsPlatformError, \ "don't know how to build scripts on platform %r" % os.name return
def get_script_filename(self, script): """ Convert the name of a script into the name of the file which it will be run from. """ # All Windows scripts are executables if os.name == 'nt' or isinstance(script, Executable): script_name = script.name + self.exe_extension else: script_name = script.name return os.path.join(self.build_dir, script_name)
# -- helper functions ---------------------------------------------
def _prep_compiler(self): # Setup the CCompiler object that we'll use to do all the # compiling and linking self.compiler = new_compiler(compiler=self.compiler, verbose=self.verbose, dry_run=self.dry_run, force=self.force) sysconfig.customize_compiler(self.compiler)
# Make sure Python's include directories (for Python.h, pyconfig.h, # etc.) are in the include search path. py_include = sysconfig.get_python_inc() plat_py_include = sysconfig.get_python_inc(plat_specific=1) include_dirs = [py_include] if plat_py_include != py_include: include_dirs.append(plat_py_include) self.compiler.set_include_dirs(include_dirs)
if os.name == 'posix': # Add the Python archive library ldlibrary = sysconfig.get_config_var('BLDLIBRARY') # MacOSX with frameworks doesn't link against a library if ldlibrary: # Get the location of the library file for d in sysconfig.get_config_vars('LIBDIR', 'LIBP', 'LIBPL'): library = os.path.join(d, ldlibrary) if os.path.exists(library): self.compiler.add_link_object(library) break elif os.name == 'nt': # Add Python's library directory lib_dir = os.path.join(sys.exec_prefix, 'libs') self.compiler.add_library_dir(lib_dir) return
def _prep_win32_stub(self): if self.force or newer(STUB_MAIN_SOURCE, self.win32_stub_filename): # Create the executable stub self.announce("building stub executable")
# Force rebuild of scripts self.force = 1
if os.name == 'nt' and self.debug: executable = self.stub_name + '_d' else: executable = self.stub_name
objects = self.compiler.compile([STUB_MAIN_SOURCE], output_dir=self.build_temp, debug=self.debug)
# On Windows, non-MSVC compilers need some help finding python # libs. This logic comes from distutils/command/build_ext.py. libraries = [] if sys.platform == "win32": from distutils.msvccompiler import MSVCCompiler if not isinstance(self.compiler, MSVCCompiler): template = "python%d%d" if self.debug: template = template + "_d" pythonlib = (template % (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff)) libraries.append(pythonlib)
self.compiler.link_executable(objects, executable, libraries=libraries, output_dir=self.build_temp, debug=self.debug, extra_preargs=self.link_preargs, extra_postargs=self.link_postargs) else: self.announce("skipping stub executable (up-to-date)") return
_include_re = re.compile(r'^\s*#\s*include\s*("|<)(?P<file>[^">]+?)("|>)', re.MULTILINE)
def _find_includes(self, script, filename, includes): source = open(filename).read() basename = os.path.basename(filename)
include_dirs = [os.path.dirname(filename)] include_dirs.extend(script.include_dirs)
match = self._include_re.search(source) while match: filename = util.convert_path(match.group('file')) for base in include_dirs: include = os.path.normpath(os.path.join(base, filename)) if os.path.isfile(include) and include not in includes: includes.append(include) self._find_includes(script, include, includes) break match = self._include_re.search(source, match.end()) return
def _prep_build(self, script): # This should really exist in the CCompiler class, but # that would required overriding all compilers. result = [] for source in script.sources: includes = [] self._find_includes(script, util.convert_path(source), includes) result.append((source, includes)) return result
# -- external interfaces ------------------------------------------
def get_outputs(self): outfiles = [] for script in self.scripts: outfiles.append(self.get_script_filename(script)) return outfiles
def get_source_files(self): filenames = [] for script in self.scripts: if isinstance(script, ExecutableScript): filenames.append(script.source) return filenames
|