-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathlua2exe.lua
More file actions
196 lines (171 loc) · 5.85 KB
/
Copy pathlua2exe.lua
File metadata and controls
196 lines (171 loc) · 5.85 KB
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
184
185
186
187
188
189
190
191
192
193
194
195
196
local format, pack = string.format, string.pack
local print, loadfile = print, loadfile
local open, stderr, exit = io.open, io.stderr, os.exit
local dirsep, pathsep, pathmark = package.config:match('(.-)\n(.-)\n(.-)\n')
local luapath, cpath, pathgm = package.path, package.cpath, '[^' .. pathsep .. ']+'
local lfs = require('lfs')
local attributes, currentdir, mkdir = lfs.attributes, lfs.currentdir, lfs.mkdir
local haslz4, lz4 = pcall(require, 'minilz4')
local options = {
base = 'lbase.exe',
dest = 'out.exe',
compile = false,
strip = false,
sources = {},
dlls = {}
}
local function printHelpAndExit ()
print(format([[
Pack lua script into a standalone executable.
Syntax: %s [options] scripts...
Options:
-b <base>, --base <base>
Define the base lua VM executable to use. Default: %s
-o <outfile>, --output <outfile>
Define where to put the compiled executable. Default: %s
-s, --strip
Strip debug information in compiled executable. Default: false
scripts...
Define lua scripts to compile into the executable.
The first one is run when launching the executable.
Other scripts are compiled in but not run. They stay available with require().
]], arg and arg[0], options.base, options.dest))
exit(0)
end
local function fail (...)
stderr:write(string.format(...))
exit(1)
end
local function nextarg (args, i)
return args[i], i+1
end
local function parseArgs (args, t)
local i, n, opt = 1, #args
while i<=n do
opt, i = nextarg(args, i)
if opt == '-b' or opt == '--base' then t.base, i = nextarg(args, i)
elseif opt == '-o' or opt == '--output' then t.dest, i = nextarg(args, i)
elseif opt == '-c' or opt == '--compile' then t.compile = true
elseif opt == '-C' or opt == '--no-compile' then t.compile = false
elseif opt == '-s' or opt == '--strip' then t.strip = true
elseif opt == '-g' or opt == '--debug' then t.strip = false
elseif opt == '-h' or opt == '--help' or opt == '-?' then printHelpAndExit()
elseif opt:sub(1,1)=='-' and opt~='-' then warn(format('Unknown option: %s', opt))
else table.insert(t.sources, { name=opt, file=opt })
end end
end
local function checkoptions (options)
if not options.sources or #options.sources<1 then fail('No sources given')
elseif not options.base then fail('No base executable given')
elseif not options.dest then fail('No output file given')
elseif options.strip and not options.compile then fail('Sources must be compiled in order to be striped')
end end
local function readall (fn)
local fp <close>, err = open(fn, 'rb')
return fp and fp:read('a'), err
end
local function dirname (fn)
return fn:match('^(.-)[^' .. dirsep ..']-' .. dirsep .. '?$')
end
local function isdir (fn)
return attributes(fn, 'mode') == 'directory'
end
local function isfile (fn)
return attributes(fn, 'mode') == 'file'
end
local function getoutdir (dest)
local outdir = dirname(dest)
if not outdir or outdir=='' then outdir = currentdir() end
if outdir:sub(-1)~=dirsep then outdir = outdir .. dirsep end
return outdir
end
local function checkinc (sources, pathlist, modname)
if sources[modname] then return true end
for path in pathlist:gmatch(pathgm) do
local fn = path:gsub(pathmark, (modname:gsub('%.', dirsep)))
if isfile(fn) then
local inc = fn:sub(path:find(pathmark), -1)
table.insert(sources, { name=inc, file=fn })
sources[modname] = true
return true
end end
end
local function checkrequire (sources, dlls, modname)
local unused = checkinc(sources, luapath, modname) or checkinc(dlls, cpath, modname)
end
local function checkrequires (sources, dlls)
local i = 1
while sources[i] do
local data = readall(sources[i].file)
for modname in data:gmatch[[require%s*%(?['"]([^'"]+)['"]%)?]] do
checkrequire(sources, dlls, modname)
end
i = i+1
end end
local function copybase (base, dest)
local fp = open(dest, 'wb')
fp:write((readall(base)))
return fp
end
local function packsources (fp, sources, compile, strip)
local read = compile and loadfile or readall
local dump = compile and string.dump or function (x) return x end
for i, source in ipairs(sources) do
local func, err = read(source.file, 'bt')
if not func then
error('Compilation error:\n' .. err)
end
local bin = dump(func, strip)
local out = haslz4 and lz4 and lz4.compress and lz4.compress(bin) or bin
source.offset = fp:seek('cur', 0)
fp:write(string.pack('I4I4I1', #out, #bin, haslz4 and 1 or 0))
fp:write(out)
print(format('Packed %s in %s, size=%d, stored=%d, compressed=%s', source.file, source.name, #bin, #out, haslz4))
end
end
local function writefiletable (fp, sources)
local fileTableOffset = fp:seek('cur', 0)
for i, source in ipairs(sources) do
fp:write(pack('s4I4', source.name, source.offset))
end
fp:write(pack('I4c8', fileTableOffset, 'lua2exe1'))
end
local function getdirs (dir)
local dirs = {}
while dir do
dir = dirname(dir)
if not dir or dir=='' then break end
table.insert(dirs, dir)
end
return dirs
end
local function mkdirs (fn)
local dirs = getdirs(fn)
for i = #dirs, 1, -1 do
local dir = dirs[i]
if not isdir(dir) then
local re, err = mkdir(dir)
end end end
local function copyfile (src, dst)
local da, sa, err = attributes(dst), attributes(src)
if sa and da and sa.modification<=da.modification and sa.size==da.size then return false end -- no need to copy if dst is more recent than src and if they have same size
local data = readall(src)
local fp <close>, err = open(dst, 'wb')
return fp and data and fp:write(data)
end
local function copydlls (dlls, dest)
local outdir = getoutdir(dest)
for i, dll in ipairs(dlls) do
local src, dst = dll.file, outdir..dll.name
mkdirs(dst)
if copyfile(src, dst) then
print(format('Copied %s to %s', src, dst))
end end end
parseArgs(arg or {...}, options)
checkoptions(options)
checkrequires(options.sources, options.dlls)
local fp <close> = copybase(options.base, options.dest)
packsources(fp, options.sources, options.compile, options.strip)
writefiletable(fp, options.sources)
copydlls(options.dlls, options.dest)
print(format('Successfully compiled %s (%d bytes)', options.dest, fp:seek('cur', 0)))