xref: /OK3568_Linux_fs/buildroot/package/luarocks/buildroot.lua (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1
2--- Module implementing the LuaRocks "buildroot" command.
3local buildroot = {}
4
5local dir = require("luarocks.dir")
6local fs = require("luarocks.fs")
7local util = require("luarocks.util")
8local queries = require("luarocks.queries")
9local search = require("luarocks.search")
10local download = require("luarocks.download")
11local fetch = require("luarocks.fetch")
12
13function buildroot.add_to_parser(parser)
14   local cmd = parser:command("buildroot", [[
15This addon generates Buildroot package files of a rock.
16First argument is the name of a rock, the second argument is optional
17and needed when Buildroot uses another name (usually prefixed by lua-).
18Files are generated with the source content of the rock and more
19especially the rockspec. So, the rock is downloaded and unpacked.
20]], util.see_also())
21      :summary("generate buildroot package files of a rock.")
22
23   cmd:argument("rockname", "the name of a rock to be fetched and unpacked.")
24   cmd:argument("brname", "the name used by Buildroot.")
25      :args("?")
26end
27
28local function brname (name)
29   return name:upper():gsub('-', '_')
30end
31
32local function brlicense (license)
33   if license:match('MIT/X') then
34      return 'MIT'
35   end
36   return license
37end
38
39local function wrap (txt, max)
40   local lines = {}
41   local line = ''
42   for word in txt:gmatch('(%S+)') do
43      if line:len() + word:len() > max - 1 then
44          lines[#lines+1] = line
45          line = ''
46      end
47      if line == '' then
48         line  = word
49      else
50         line = line .. ' ' .. word
51      end
52   end
53   lines[#lines+1] = line
54   return lines
55end
56
57local function has_c_files (rockspec)
58   for _, mod in pairs(rockspec.build.modules or {}) do
59      if type(mod) == 'string' then
60         if mod:match'%.c$' then
61            return true
62         end
63      elseif type(mod) == 'table' then
64         local sources = mod.sources
65         if type(sources) == 'string' and sources:match'%.c$' then
66            return true
67         end
68         for _, src in ipairs(sources or mod) do
69            if src:match'%.c$' then
70               return true
71            end
72         end
73      end
74   end
75   return false
76end
77
78local function get_main_modules (rockspec)
79   local t = {}
80   for name in pairs(rockspec.build.modules or {}) do
81      if not name:match('%.') then
82         t[#t+1] = name
83      end
84   end
85   if #t == 0 then
86      for name in pairs(rockspec.build.modules or {}) do
87         t[#t+1] = name
88      end
89   end
90   if #t == 0 then
91      t[#t+1] = rockspec.package:gsub('%-', '')
92   end
93   table.sort(t)
94   return t
95end
96
97local function get_external_dependencies (rockspec)
98   local t = {}
99   for k in pairs(rockspec.external_dependencies or {}) do
100      k = k:lower()
101      if fs.is_dir('package/' .. k) then
102         t[#t+1] = k
103      else
104         t[#t+1] = 'lib' .. k
105         if not fs.is_dir('package/lib' .. k) then
106            util.printout('unkwown external dependency: ' .. k)
107         end
108      end
109   end
110   table.sort(t)
111   return t
112end
113
114local function get_dependencies (rockspec)
115   local t = {}
116   for i = 1, #rockspec.dependencies do
117      local dep = tostring(rockspec.dependencies[i]):match('^(%S+)')
118      if dep ~= 'lua' then
119         dep = dep:gsub('_', '-')
120         if fs.is_dir('package/lua-' .. dep) then
121            t[#t+1] = 'lua-' .. dep
122         else
123            t[#t+1] = dep
124            if not fs.is_dir('package/' .. dep) then
125               util.printout('unkwown dependency: ' .. dep)
126            end
127         end
128      end
129   end
130   table.sort(t)
131   return t
132end
133
134function get_digest (file)
135   local absname = fs.absolute_name(file)
136   local pipe = io.popen('sha256sum ' .. fs.Q(absname))
137   local line = pipe:read('*l')
138   pipe:close()
139   local computed = line and line:match('(' .. ('%x'):rep(64) .. ')')
140   if computed then
141      return computed
142   else
143      return nil, "Failed to compute SHA256 hash for file " .. absname
144   end
145end
146
147local function generate_config (rockspec, lcname)
148   local ucname = brname(lcname)
149   local only_luajit = rockspec.package:match('^lj')
150   local summary = rockspec.description.summary
151   if not summary then
152      summary = '???'
153   elseif not summary:match('%.%s*$') then
154      summary = summary:gsub('%s*$', '.')
155   end
156   local homepage = rockspec.description.homepage or '???'
157   local external_dependencies = get_external_dependencies(rockspec)
158   local dependencies = get_dependencies(rockspec)
159   local fname = 'package/' .. lcname .. '/Config.in'
160   local f = assert(io.open(fname, 'w'))
161   util.printout('write ' .. fname)
162   f:write('config BR2_PACKAGE_' .. ucname .. '\n')
163   f:write('\tbool "' .. lcname .. '"\n')
164   if only_luajit then
165      f:write('\tdepends on BR2_PACKAGE_LUAJIT\n')
166   end
167   for i = 1, #external_dependencies do
168      f:write('\tselect BR2_PACKAGE_' .. brname(external_dependencies[i]) .. '\n')
169   end
170   for i = 1, #dependencies do
171      f:write('\tselect BR2_PACKAGE_' .. brname(dependencies[i]) .. ' # runtime\n')
172   end
173   f:write('\thelp\n')
174   f:write('\t  ' .. table.concat(wrap(summary, 62), '\n\t  ') .. '\n')
175   f:write('\n\t  ' .. homepage .. '\n')
176   if only_luajit then
177      f:write('\ncomment "' .. lcname .. ' needs LuaJIT"\n')
178      f:write('\tdepends on !BR2_PACKAGE_LUAJIT\n')
179   end
180   f:close()
181end
182
183local function generate_mk (rockspec, lcname, licenses)
184   local function escape (s)
185      return s:gsub('-', '%%-'):gsub('%.', '%%.')
186   end
187
188   local ucname = brname(lcname)
189   local need_name_upstream = false
190   local need_version_upstream = false
191   local name_upstream = rockspec.package
192   local version = rockspec.version
193   local version_upstream = version:match('^([^-]+)-')
194   local revision = version:match('-(%d+)$')
195   local license = rockspec.description.license
196   local subdir = rockspec.source.dir
197   if subdir then
198      local root = subdir:match('^(.-)-' .. escape(version) .. '$')
199      if root then
200         subdir = root .. '-$(' .. ucname .. '_VERSION)'
201      end
202      root = subdir:match('^(.--[Vv])' .. escape(version_upstream) .. '$')
203      if root then
204         need_version_upstream = true
205         subdir = root .. '$(' .. ucname .. '_VERSION_UPSTREAM)'
206      end
207      root = subdir:match('^(.-)-' .. escape(version_upstream) .. '$')
208      if root then
209         if root == lcname then
210            subdir = nil
211         elseif root == name_upstream then
212            subdir = nil
213            need_name_upstream = true
214         else
215            need_version_upstream = true
216            subdir = root .. '-$(' .. ucname .. '_VERSION_UPSTREAM)'
217         end
218      end
219   end
220   local external_dependencies = get_external_dependencies(rockspec)
221   local fname = 'package/' .. lcname .. '/' .. lcname .. '.mk'
222   local f = assert(io.open(fname, 'w'))
223   util.printout('write ' .. fname)
224   f:write('################################################################################\n')
225   f:write('#\n')
226   f:write('# ' .. lcname .. '\n')
227   f:write('#\n')
228   f:write('################################################################################\n')
229   f:write('\n')
230   if need_version_upstream then
231      f:write(ucname .. '_VERSION_UPSTREAM = ' .. version_upstream .. '\n')
232      f:write(ucname .. '_VERSION = $(' .. ucname .. '_VERSION_UPSTREAM)-' .. revision .. '\n')
233   else
234      f:write(ucname .. '_VERSION = ' .. version .. '\n')
235   end
236   if lcname ~= name_upstream:lower() or need_name_upstream then
237      f:write(ucname .. '_NAME_UPSTREAM = ' .. name_upstream .. '\n')
238   end
239   if subdir then
240      f:write(ucname .. '_SUBDIR = ' .. subdir .. '\n')
241   end
242   if license then
243      f:write(ucname .. '_LICENSE = ' .. brlicense(license) .. '\n')
244   end
245   if #licenses == 1 then
246      f:write(ucname .. '_LICENSE_FILES = $(' .. ucname .. '_SUBDIR)/' .. licenses[1] .. '\n')
247   elseif #licenses > 1 then
248      f:write(ucname .. '_LICENSE_FILES =')
249      for i = 1, #licenses do
250         local file = licenses[i]
251         f:write(' \\\n\t$(' .. ucname .. '_SUBDIR)/' .. file)
252      end
253      f:write('\n')
254   end
255   if #external_dependencies > 0 then
256      f:write(ucname .. '_DEPENDENCIES = ' .. table.concat(external_dependencies, ' ') .. '\n')
257   end
258   f:write('\n$(eval $(luarocks-package))\n')
259   f:close()
260end
261
262local function generate_hash (rockspec, lcname, rock_file, licenses, digest)
263   local subdir = rockspec.source.dir
264   local fname = 'package/' .. lcname .. '/' .. lcname .. '.hash'
265   local f = assert(io.open(fname, 'w'))
266   util.printout('write ' .. fname)
267   f:write('# computed by luarocks/buildroot\n')
268   f:write('sha256 ' .. digest[rock_file] .. '  ' .. rock_file .. '\n')
269   for i = 1, #licenses do
270      local file = licenses[i]
271      f:write('sha256 ' .. digest[file] .. '  ' .. subdir .. '/' .. file .. '\n')
272   end
273   f:close()
274end
275
276local function generate_test (rockspec, lcname)
277   local ucname = brname(lcname)
278   local classname = rockspec.package:gsub('%-', ''):gsub('%.', '')
279   classname = classname:sub(1, 1):upper() .. classname:sub(2)
280   local modnames = get_main_modules(rockspec)
281   local fname = 'support/testing/tests/package/test_' .. ucname:lower() .. '.py'
282   local f = assert(io.open(fname, 'w'))
283   util.printout('write ' .. fname)
284   f:write('from tests.package.test_lua import TestLuaBase\n')
285   f:write('\n')
286   f:write('\n')
287   f:write('class TestLua' .. classname .. '(TestLuaBase):\n')
288   f:write('    config = TestLuaBase.config + \\\n')
289   f:write('        """\n')
290   f:write('        BR2_PACKAGE_LUA=y\n')
291   f:write('        BR2_PACKAGE_' .. ucname .. '=y\n')
292   f:write('        """\n')
293   f:write('\n')
294   f:write('    def test_run(self):\n')
295   f:write('        self.login()\n')
296   for i = 1, #modnames do
297      f:write('        self.module_test("' .. modnames[i] .. '")\n')
298   end
299   f:write('\n')
300   f:write('\n')
301   f:write('class TestLuajit' .. classname .. '(TestLuaBase):\n')
302   f:write('    config = TestLuaBase.config + \\\n')
303   f:write('        """\n')
304   f:write('        BR2_PACKAGE_LUAJIT=y\n')
305   f:write('        BR2_PACKAGE_' .. ucname .. '=y\n')
306   f:write('        """\n')
307   f:write('\n')
308   f:write('    def test_run(self):\n')
309   f:write('        self.login()\n')
310   for i = 1, #modnames do
311      f:write('        self.module_test("' .. modnames[i] .. '")\n')
312   end
313   f:close()
314end
315
316--- Driver function for the "buildroot" command.
317-- @return boolean: true if successful
318function buildroot.command(args)
319   local rockname = assert(args.rockname)
320   local fsname = args.brname or rockname
321
322   local query = queries.new(rockname:lower(), nil, nil, false, 'src')
323   local url, err = search.find_suitable_rock(query)
324   if not url then
325      return nil, "Could not find a result named " .. tostring(query) .. ": " .. err
326   end
327   local rock_file = dir.base_name(url)
328
329   local temp_dir, err = fs.make_temp_dir(rockname)
330   if not temp_dir then
331      return nil, "Failed creating temporary dir: " .. err
332   end
333   local ok, err = fs.change_dir(temp_dir)
334   if not ok then return nil, err end
335
336   ok = fs.download(url, rock_file, true)
337   if not ok then
338      return nil, "Failed downloading " .. url
339   end
340
341   local digest = {}
342   digest[rock_file], err = get_digest(rock_file)
343   if not digest[rock_file] then return nil, err end
344   ok, err = fs.unzip(rock_file)
345   if not ok then return nil, err end
346
347   local rockspec_file = rock_file:gsub('%.src%.rock$', '.rockspec')
348   local rockspec, err = fetch.load_rockspec(rockspec_file)
349   if not rockspec then
350      return nil, "Error loading rockspec: " .. err
351   end
352   if rockspec.source.file then
353      ok, err = fs.unpack_archive(rockspec.source.file)
354      if not ok then return nil, err end
355   end
356
357   if rockspec.source.dir ~= '.' then
358      fs.copy(rockspec.local_abs_filename, rockspec.source.dir, 'read')
359   end
360
361   local build_type = rockspec.build.type
362   if build_type ~= 'none' and build_type ~= 'builtin' and build_type ~= 'module' then
363      util.printout('[' .. rockspec.package .. "] build_type '" .. build_type .. "' not supported")
364   end
365
366   local licenses = {}
367   ok, err = fs.change_dir(rockspec.source.dir)
368   if not ok then return nil, err end
369   local files = fs.find()
370   for i = 1, #files do
371      local v = files[i]
372      if v == 'COPYING'
373         or v == 'COPYRIGHT'
374         or v:match('^LICENSE') then
375         licenses[#licenses+1] = v
376         digest[v], err = get_digest(v)
377         if not digest[v] then return nil, err end
378      end
379   end
380   if #licenses == 0 then
381      for i = 1, #files do
382         local v = files[i]
383         if v:match('^docs?/LICENSE')
384            or v:match('^docs?/license')
385            or v:match('^doc/us/license') then
386            licenses[#licenses+1] = v
387            digest[v], err = get_digest(v)
388            if not digest[v] then return nil, err end
389         end
390      end
391   end
392   fs.pop_dir()
393   table.sort(licenses)
394
395   fs.pop_dir()
396   ok, err = fs.make_dir('package/' .. fsname:lower())
397   if not ok then return nil, err end
398
399   generate_config(rockspec, fsname:lower())
400   generate_mk(rockspec, fsname:lower(), licenses)
401   generate_hash(rockspec, fsname:lower(), rock_file, licenses, digest)
402   if has_c_files(rockspec) then
403      ok, err = fs.make_dir('support/testing/tests/package')
404      if not ok then return nil, err end
405      generate_test(rockspec, fsname:lower())
406   end
407
408   return true
409end
410
411return buildroot
412