forked from theodox/keystone
-
Notifications
You must be signed in to change notification settings - Fork 0
/
keystone.py
183 lines (147 loc) · 6.32 KB
/
keystone.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
"""
Copyright 2018 Steve Theodore
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
import os
import os.path
import base64
import zipfile
import re
import compileall
import argparse
import io
PY_SHIM = '''
import sys
import os
import os.path
import runpy
import maya.cmds
import traceback
try:
melfile = __keystone_file
lastmod = os.path.getmtime(melfile)
zipfolder = maya.cmds.about(pd=True) + "/keystones"
zipname = zipfolder + "/" + os.path.basename(melfile) + ".zip"
print "// starting keystone launcher"
should_update = not os.path.exists(zipname)
if not should_update:
should_update = os.path.getmtime(zipname) < lastmod
if should_update:
if not os.path.exists(zipfolder):
os.makedirs(zipfolder)
print "// created keystone directory"
with open(melfile, 'rb') as binary_blob:
print "// extracting environment from ", melfile
binary_blob.seek(__keystone_offset)
with open(zipname, "wb") as output:
print "// copy to ", zipname
contents = binary_blob.read(40960)
while contents:
output.write(contents)
contents = binary_blob.read(40960)
sys.path.insert(0, zipname)
print "// path inserted"
# restore missing sys.argv on OSX
if not melfile in sys.argv:
args = sys.argv[:]
args.insert(1, melfile)
sys.argv = args
print "// sys.argv: ", sys.argv
if __keystone_main:
print "// launch startup"
runpy.run_path(zipname)
except Exception:
print traceback.format_exc()
maya.cmds.error("Keystone boostrap failed")
'''
MEL_SHIM = '''// keystone
global proc keystone_fileid()
{
string $tokens[];
tokenize(`whatIs keystone_fileid`, " ", $tokens);
python("__keystone_file = r'" + $tokens[4] + "'");
};
keystone_fileid();
python("__keystone_offset = %s; __keystone_main = %i; __keystone_version = 2");
python("import base64; __keystone_cmd = base64.urlsafe_b64decode('%s'); exec __keystone_cmd");
'''
def zip_directory(source, zipfilename, ignore=None, include=None):
compileall.compile_dir(source, maxlevels=24)
archive = zipfile.ZipFile(zipfilename, 'w')
py_re = re.compile(".py$")
if ignore:
ignore = re.compile(ignore)
if include:
include = re.compile(include)
def iter_files():
for root, dirs, files in os.walk(source):
for f in files:
fullpath = os.path.join(root, f).replace("\\", "/")
if any(py_re.findall(fullpath)):
continue
if include is not None and any(include.findall(fullpath)):
yield fullpath
continue
if ignore is None or not ignore.findall(fullpath):
yield fullpath
offset = len(source)
try:
for each_file in iter_files():
archive.write(each_file, each_file[offset:])
finally:
archive.close()
return zipfilename
def generate_mel(melname, folder, ):
try:
zipped = zip_directory(folder, 'keystone_temp.zip')
encoded = base64.urlsafe_b64encode(PY_SHIM)
has_main = os.path.exists(folder + "/__main__.py")
mel_text = MEL_SHIM % ("%s", has_main, encoded)
offset = len(mel_text) + 3 # get pass the
offset += len(str(offset))
mel_text = mel_text % str(offset)
# this slightly unusual write method ensures that
# the offset values are identical on all platforms
with io.open(melname, 'w', newline='\r\n') as output:
output.write(unicode(mel_text))
output.write(u'//')
with open(zipped, 'rb') as zipper:
with open(melname, 'ab') as mel:
mel.write(zipper.read())
finally:
os.remove(zipped)
def python_to_mel(melfile, pythonfile):
"""
compile a single python file to an executable mel file
"""
with open(pythonfile, 'rt') as reader:
py_code = reader.read()
encoded = base64.urlsafe_b64encode(py_code)
py_to_mel = "import base64; _py_code = base64.urlsafe_b64decode('%s'); exec _py_code" % encoded
with open(melfile, 'wt') as writer:
writer.write('python("%s");' % py_to_mel)
desc = '''
Compiles a python project into an executable mel file. If the project folder
contains a __main__.py at the root level, it will be executed when the mel is launched. Any
python modules in the project folder will be added to maya's python path.
'''
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('melfile', help="path to the output mel file")
parser.add_argument('project', help="path to the project folder or script")
parser.add_argument('--script', help='if true, compile a single python file instead of a folder', action='store_true')
args = parser.parse_args()
melfile = os.path.abspath(os.path.normpath(args.melfile))
if not melfile.endswith(".mel"):
melfile += ".mel"
project = os.path.abspath(os.path.normpath(args.project))
if args.script:
if not os.path.exists(project):
raise ValueError("'%s' is not a valid file" % project)
python_to_mel(melfile, project)
else:
if not os.path.isdir(project):
raise ValueError("'%s' is not a directory" % project)
generate_mel(melfile, project)