Skip to content

Instantly share code, notes, and snippets.

@yinyin
Created August 4, 2012 02:50
Show Gist options
  • Save yinyin/3253714 to your computer and use it in GitHub Desktop.
Save yinyin/3253714 to your computer and use it in GitHub Desktop.
a specialized {build,install}_scripts distutil command to perform path variable substitution on installing executable scripts
# -*- coding: utf-8 -*-
import os
import re
from distutils.command.build_scripts import build_scripts as _build_scripts
from distutils.command.install_scripts import install_scripts as _install_scripts
_regex_setupvarireplace_check_firstline = re.compile('^#!.*python[0-9.]*([ \t].*)?$')
_regex_setupvarireplace_detect_installpath = re.compile('^(\s*)([A-Za-z0-9_]+)\s*=.+#\s*SETUP-REPLACE:\s*INSTALL-PREFIX\s*$')
_regex_setupvarireplace_detect_librarypath = re.compile('^(\s*)sys\.path\.append\s*\(.+#\s*SETUP-REPLACE:\s*LIBRARY-PATH\s*$')
_regex_setupvarireplace_detect_inline_scan = re.compile('.+#\s*SETUP-REPLACE:\s*INLINE-SCAN\s*$')
def _setup_variable_replace(input_filepath, install_path=None, library_path=None, reverse_op=False, output_filepath=None):
""" replace library path and installation root in specified file
Argument:
input_filepath - path of file to read
install_path=None - path of installation prefix
library_path=None - path of library
reverse_op=False - perform reverse operation
output_filepath=None - path to write result
"""
if output_filepath is None:
output_filepath = input_filepath
outdata = []
fp = open(input_filepath, 'rb')
leading = fp.readline()
if '#!' != leading[0:2]:
fp.close()
return
if (True == reverse_op) and (_regex_setupvarireplace_check_firstline.match(leading) is not None):
if sys.executable is not None:
outdata.append("#!"+sys.executable)
else:
outdata.append("#!/usr/bin/python")
else:
outdata.append(leading.rstrip())
for l in fp:
codeline = None
if codeline is None:
m = _regex_setupvarireplace_detect_installpath.match(l)
if m is not None:
indent_string = m.group(1)
varname = m.group(2)
if not reverse_op:
codeline = "%s%s = '%s'\t# SETUP-REPLACE: INSTALL-PREFIX" % (indent_string, varname, install_path.encode('string_escape'),)
else:
codeline = "%s%s = None\t# SETUP-REPLACE: INSTALL-PREFIX" % (indent_string, varname,)
if codeline is None:
m = _regex_setupvarireplace_detect_librarypath.match(l)
if m is not None:
indent_string = m.group(1)
if not reverse_op:
codeline = "%ssys.path.append('%s')\t# SETUP-REPLACE: LIBRARY-PATH" % (indent_string, library_path.encode('string_escape'),)
else:
codeline = "%ssys.path.append(None)\t# SETUP-REPLACE: LIBRARY-PATH" % (indent_string,)
if codeline is None:
m = _regex_setupvarireplace_detect_inline_scan.match(l)
if m is not None:
codeline = l.rstrip()
if not reverse_op:
codeline = codeline.replace('@LIBRARY_PATH@', library_path.encode('string_escape'))
codeline = codeline.replace('@INSTALL_PATH@', install_path.encode('string_escape'))
else:
codeline = codeline.replace(library_path.encode('string_escape'), '@LIBRARY_PATH@')
codeline = codeline.replace(install_path.encode('string_escape'), '@INSTALL_PATH@') # must reverse INSTALL_PATH in last step since which is common prefix in build process
if codeline is None:
codeline = l.rstrip()
outdata.append(codeline)
fp.close()
fp = open(output_filepath, 'wb')
fp.write("\n".join(outdata))
fp.write("\n")
fp.close()
# ### def _setup_variable_replace
class adv_build_scripts(_build_scripts):
"""
This is a specialization of build_scripts which generates .py script from .py.sample file when .py script not existed.
針對產生執行用指令稿檔案特化的 build_scripts 命令實作,當執行時如果檔案不存在時則找尋 .sample 檔並轉化之
"""
user_options = _build_scripts.user_options + [
('reverse', None, "load .py to .py.sample instead of generating .py",),
]
boolean_options = _build_scripts.boolean_options + ['reverse',]
def initialize_options(self):
_build_scripts.initialize_options(self)
self.build_lib = None
self.reverse = False
# ### def initialize_options
def finalize_options(self):
_build_scripts.finalize_options(self)
self.set_undefined_options('install', ('build_lib', 'build_lib'))
# ### def finalize_options
def run(self):
src_scripts = self.get_source_files()
if not src_scripts:
return
fake_install_path = os.path.dirname(os.path.abspath(__file__))
fake_library_path = os.path.abspath(self.build_lib)
for pyfile in src_scripts:
sampfile = pyfile + ".sample"
if False == self.reverse:
if (not os.path.exists(pyfile)) and os.path.exists(sampfile):
log.info("generating %s -> %s", sampfile, pyfile)
_setup_variable_replace(sampfile, fake_install_path, fake_library_path, output_filepath=pyfile)
else:
if os.path.exists(sampfile) and os.path.exists(pyfile):
log.info("reversing %s -> %s", pyfile, sampfile)
_setup_variable_replace(pyfile, fake_install_path, fake_library_path, reverse_op=True, output_filepath=sampfile)
if False == self.reverse: # not doing actual build_scripts when reversing .py to .py.sample
_build_scripts.run(self)
# ### def run
# ### class adv_build_scripts
class adv_install_scripts(_install_scripts):
"""
This is a specialization of install_script which modify the installed script inplace-ly to replace certain variables such as path of installation and library. (inspired by setup.py of mercurial)
針對所安裝執行用指令稿內容調整的特化 install_scripts 命令實作 (參考自 mercurial 的 setup.py 實作)
"""
def initialize_options(self):
_install_scripts.initialize_options(self)
self.install_lib = None
self.install_prefix = None
# ### def initialize_options
def finalize_options(self):
_install_scripts.finalize_options(self)
self.set_undefined_options('install', ('install_lib', 'install_lib'), ('prefix', 'install_prefix'))
# ### def finalize_options
def run(self):
_install_scripts.run(self)
library_path = os.path.abspath(self.install_lib)
install_path = os.path.abspath(self.install_prefix)
for outfile in self.get_outputs():
log.info("adjusting %s", outfile)
_setup_variable_replace(outfile, install_path, library_path)
# ### def run
# ### class adv_install_scripts
# cmdclass={'install_scripts': adv_install_scripts, 'build_scripts': adv_build_scripts,}
# vim: ts=4 sw=4 ai nowrap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment