Select Git revision

Anders Blomdell authored
apa2 7.20 KiB
#!/usr/bin/python3
"""
Anders Python Archiver
Copyright (C) 2004-2019 Anders Blomdell <anders.blomdell@control.lth.se>
A small utility program to join a number of python modules
into a single executable python program.
http://www.control.lth.se/user/andersb/software/apa
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""
from __future__ import print_function
import optparse
import os
import re
import string
import sys
def getModuleName(s):
m = re.match("^(.*)/__init__\\.py$", s)
if m:
return m.group(1).replace("/", ".")
m = re.match("^(.*)\\.py$", s)
if m:
return m.group(1).replace("/", ".")
return None
def emitCode(f, module, filename, code):
print("EMIT:", module, file=sys.stderr)
print('"%s" : ( "%s", """%s""")' % (quote(module),
quote(filename),
quote(code)), file=f)
def quote(s):
return s.replace('\\', '\\\\').replace('"', '\\"')
if __name__ == '__main__':
usage = "%prog [options] <main module> <additional modules>*"
optParser = optparse.OptionParser(usage=usage)
optParser.add_option('-d','--documentation',
action='store_true',
help='Show documentation')
optParser.add_option('--interpreter',
default=None,
action='store',
help='Interpreter to use')
optParser.add_option('-o','--output',
action='store',
help='Name of archive')
optParser.add_option('-s','--strip',
action='append',
default=[],
help='Strip this part from filenames')
(options, args) = optParser.parse_args(sys.argv[1:])
if options.documentation:
print(__doc__)
sys.exit(0)
# Read all python files
code = []
processed = set()
for filename in args:
archivename = filename
for strip in options.strip:
m = re.match("^%s/*(.*)" % strip, filename)
if m:
archivename = m.group(1)
break
if archivename in processed:
continue
processed.add(archivename)
name = getModuleName(archivename)
if name:
f = open(filename)
code.append((name, archivename, f.read()))
f.close()
else:
print("Illegal filename '%s'" % filename)
sys.exit(1)
if options.output:
apa = open(options.output, 'w')
else:
apa = None
# Emit code
interpreter = code[0][2].splitlines()[0]
if options.interpreter != None:
print("#!%s" % options.interpreter, file=apa)
elif interpreter.find("python") >= 0:
# Take interpreter version from main file
print(interpreter, file=apa)
else:
# Default interpreter
print("Interpreter defaulting to '#!/usr/bin/python3'", file=sys.stderr)
print("#!/usr/bin/python3", file=apa)
print("\"\"\"", file=apa)
print("Executable archive of '%s'" % code[0][0], file=apa)
print("\nThe following modules are archived:", file=apa)
print(" %-20s %s" % ('__main__', code[0][1]), file=apa)
for (n, f, c) in code[1:]:
print(" %-20s %s" % (n, f), file=apa)
print("""
Archive created by 'apa' (http://www.control.lth.se/user/andersb/software/apa)
To extract as individual files:
* Add a .py suffix to the archive (if needed)
* Start a python interpreter
* Run the following commands:
import <archive name>
<archive name>.extract(<optional destination dir>)
\"\"\"
""", file=apa)
print("code = {", file=apa)
(n, f, c) = code[0]
emitCode(apa, '__main__', f, c)
for (n, f, c) in code[1:]:
print(",", file=apa)
emitCode(apa, n, f, c)
print("}", file=apa)
print("""
class Importer:
\"\"\"Loads modules from local code dictionary
\"\"\"
def __init__(self, code):
self.code = code;
self.path = "<%s>" % str(self.__class__)
if not self.path in sys.path:
sys.path.insert(0, self.path)
def __call__(self, path):
if path == self.path:
return self
return None
def find_module(self, fullname):
try:
self.code[fullname]
return self
except:
return None
def load_module(self, fullname):
mod = sys.modules.setdefault(fullname, types.ModuleType(fullname))
(filename, src) = self.code[fullname]
mod.__file__ = filename
mod.__loader__ = self
mod.__path__ = [ self.path ]
global BAD
BAD = None
try:
code = compile(src, "%s/%s" % (self.path, filename), 'exec')
exec(code, mod.__dict__)
except SystemExit as e:
# Silently propagate exit
raise e
except Exception as e:
# Silently propagate other exceptions as well
BAD = sys.exc_info()
raise e
return mod
def is_package(self, fullname):
(filename, src) = self.code[fullname]
return True
def get_code(self, fullname):
(filename, src) = self.code[fullname]
code = compile(src, "%s/%s" % (self.path, filename), 'exec')
return code
def get_source(self, fullname):
(filename, src) = self.code[fullname]
return src
def get_filename(self, fullname):
(filename, src) = self.code[fullname]
return filename
def extract(dest='.'):
\"\"\"Extracts archived files in dest directory ('.' is default)\"\"\"
import os
import os.path
for (m, (f, c)) in list(code.items()):
filepath = '%s/%s' % (dest, f)
dirpath = os.path.dirname(filepath)
if os.path.exists(filepath):
raise Exception('File already exists: %s' % filepath)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
f = file(filepath, 'w')
f.write(c)
f.close()
os.chmod(filepath, 0o755)
if __name__ == '__main__':
import sys
importer = Importer(code)
sys.path_hooks.append(importer)
import types
try:
# This will start actual execution
importer.load_module("__main__")
except SystemExit as e:
raise e
except:
if BAD:
import traceback
traceback.print_exception(*BAD)
pass
sys.exit(1)
""", file=apa)
if apa:
apa.close()
try:
os.chmod(options.output, 0o755)
except:
pass