package/luarocks: add buildroot addon
authorFrancois Perrad <fperrad@gmail.com>
Wed, 19 Dec 2018 08:17:03 +0000 (09:17 +0100)
committerThomas Petazzoni <thomas.petazzoni@bootlin.com>
Mon, 31 Dec 2018 16:40:58 +0000 (17:40 +0100)
This addon allows to generate a Buildroot package from a Luarocks
package definition.

Signed-off-by: Francois Perrad <francois.perrad@gadz.org>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
docs/manual/adding-packages-luarocks.txt
package/luarocks/0002-Buildroot-addon.patch [new file with mode: 0644]

index ec3d4e7ccdcc9207014c00c8854888e5394483e7..df97cdd5033bc0fed6358a1c968dcee66d924dbc 100644 (file)
@@ -50,6 +50,16 @@ Finally, on line 16, we invoke the +luarocks-package+
 macro that generates all the Makefile rules that actually allows the
 package to be built.
 
+Most of these details can be retrieved from the +rock+ and +rockspec+.
+So, this file and the Config.in file can be generated by running the
+command +luarocks buildroot foo lua-foo+ in the Buildroot
+directory. This command runs a specific Buildroot addon of +luarocks+
+that will automatically generate a Buildroot package. The result must
+still be manually inspected and possibly modified.
+
+* The +package/Config.in+ file has to be updated manually to include the
+  generated Config.in files.
+
 [[luarocks-package-reference]]
 
 ==== +luarocks-package+ reference
diff --git a/package/luarocks/0002-Buildroot-addon.patch b/package/luarocks/0002-Buildroot-addon.patch
new file mode 100644 (file)
index 0000000..3d7ea11
--- /dev/null
@@ -0,0 +1,361 @@
+From c250ac86b8633a2d53c4d10a642f2304e2b2d148 Mon Sep 17 00:00:00 2001
+From: Francois Perrad <francois.perrad@gadz.org>
+Date: Sat, 27 Oct 2018 17:15:50 +0200
+Subject: [PATCH] Add a Buildroot addon
+
+This addon allows to generate a Buildroot package from a Luarocks one.
+
+Signed-off-by: Francois Perrad <francois.perrad@gadz.org>
+---
+ src/bin/luarocks                 |   1 +
+ src/luarocks/addon/buildroot.lua | 326 +++++++++++++++++++++++++++++++
+ 2 files changed, 327 insertions(+)
+ create mode 100644 src/luarocks/addon/buildroot.lua
+
+diff --git a/src/bin/luarocks b/src/bin/luarocks
+index d982530..53c7b0b 100755
+--- a/src/bin/luarocks
++++ b/src/bin/luarocks
+@@ -31,6 +31,7 @@ local commands = {
+    config = "luarocks.cmd.config",
+    which = "luarocks.cmd.which",
+    test = "luarocks.cmd.test",
++   buildroot = "luarocks.addon.buildroot",
+ }
+ cmd.run_command(description, commands, "luarocks.cmd.external", ...)
+diff --git a/src/luarocks/addon/buildroot.lua b/src/luarocks/addon/buildroot.lua
+new file mode 100644
+index 0000000..606b94c
+--- /dev/null
++++ b/src/luarocks/addon/buildroot.lua
+@@ -0,0 +1,326 @@
++
++--- Module implementing the LuaRocks "buildroot" command.
++local buildroot = {}
++
++local dir = require("luarocks.dir")
++local fs = require("luarocks.fs")
++local util = require("luarocks.util")
++local queries = require("luarocks.queries")
++local search = require("luarocks.search")
++local download = require("luarocks.download")
++local fetch = require("luarocks.fetch")
++
++buildroot.help_summary = "generate buildroot package files of a rock."
++buildroot.help_arguments = "rockname [brname]"
++buildroot.help = [[
++This addon generates Buildroot package files of a rock.
++First argument is the name of a rock, the second argument is optional
++and needed when Buildroot uses another name (usually prefixed by lua-).
++Files are generated with the source content of the rock and more
++especially the rockspec. So, the rock is downloaded and unpacked.
++]]
++
++local function brname (name)
++   return name:upper():gsub('-', '_')
++end
++
++local function brlicense (license)
++   if license:match('MIT/X') then
++      return 'MIT'
++   end
++   return license
++end
++
++local function wrap (txt, max)
++   local lines = {}
++   local line = ''
++   for word in txt:gmatch('(%S+)') do
++      if line:len() + word:len() > max - 1 then
++          lines[#lines+1] = line
++          line = ''
++      end
++      if line == '' then
++         line  = word
++      else
++         line = line .. ' ' .. word
++      end
++   end
++   lines[#lines+1] = line
++   return lines
++end
++
++local function get_external_dependencies (rockspec)
++   local t = {}
++   for k in pairs(rockspec.external_dependencies or {}) do
++      k = k:lower()
++      if fs.is_dir('package/' .. k) then
++         t[#t+1] = k
++      else
++         t[#t+1] = 'lib' .. k
++         if not fs.is_dir('package/lib' .. k) then
++            util.printout('unkwown external dependency: ' .. k)
++         end
++      end
++   end
++   table.sort(t)
++   return t
++end
++
++local function get_dependencies (rockspec)
++   local t = {}
++   for i = 1, #rockspec.dependencies do
++      local dep = tostring(rockspec.dependencies[i]):match('^(%S+)')
++      if dep ~= 'lua' then
++         dep = dep:gsub('_', '-')
++         if fs.is_dir('package/lua-' .. dep) then
++            t[#t+1] = 'lua-' .. dep
++         else
++            t[#t+1] = dep
++            if not fs.is_dir('package/' .. dep) then
++               util.printout('unkwown dependency: ' .. dep)
++            end
++         end
++      end
++   end
++   table.sort(t)
++   return t
++end
++
++function get_digest (file)
++   local absname = fs.absolute_name(file)
++   local pipe = io.popen('sha256sum ' .. fs.Q(absname))
++   local line = pipe:read('*l')
++   pipe:close()
++   local computed = line and line:match('(' .. ('%x'):rep(64) .. ')')
++   if computed then
++      return computed
++   else
++      return nil, "Failed to compute SHA256 hash for file " .. absname
++   end
++end
++
++local function generate_config (rockspec, lcname)
++   local ucname = brname(lcname)
++   local only_luajit = rockspec.package:match('^lj')
++   local summary = rockspec.description.summary
++   if not summary then
++      summary = '???'
++   elseif not summary:match('%.%s*$') then
++      summary = summary:gsub('%s*$', '.')
++   end
++   local homepage = rockspec.description.homepage or '???'
++   local external_dependencies = get_external_dependencies(rockspec)
++   local dependencies = get_dependencies(rockspec)
++   local fname = 'package/' .. lcname .. '/Config.in'
++   local f = assert(io.open(fname, 'w'))
++   util.printout('write ' .. fname)
++   f:write('config BR2_PACKAGE_' .. ucname .. '\n')
++   f:write('\tbool "' .. lcname .. '"\n')
++   if only_luajit then
++      f:write('\tdepends on BR2_PACKAGE_LUAJIT\n')
++   end
++   for i = 1, #external_dependencies do
++      f:write('\tselect BR2_PACKAGE_' .. brname(external_dependencies[i]) .. '\n')
++   end
++   for i = 1, #dependencies do
++      f:write('\tselect BR2_PACKAGE_' .. brname(dependencies[i]) .. ' # runtime\n')
++   end
++   f:write('\thelp\n')
++   f:write('\t  ' .. table.concat(wrap(summary, 62), '\n\t  ') .. '\n')
++   f:write('\n\t  ' .. homepage .. '\n')
++   if only_luajit then
++      f:write('\ncomment "' .. lcname .. ' needs LuaJIT"\n')
++      f:write('\tdepends on !BR2_PACKAGE_LUAJIT\n')
++   end
++   f:close()
++end
++
++local function generate_mk (rockspec, lcname, licenses)
++   local function escape (s)
++      return s:gsub('-', '%%-'):gsub('%.', '%%.')
++   end
++
++   local ucname = brname(lcname)
++   local need_name_upstream = false
++   local need_version_upstream = false
++   local name_upstream = rockspec.package
++   local version = rockspec.version
++   local version_upstream = version:match('^([^-]+)-')
++   local revision = version:match('-(%d+)$')
++   local license = rockspec.description.license
++   local subdir = rockspec.source.dir
++   if subdir then
++      local root = subdir:match('^(.-)-' .. escape(version) .. '$')
++      if root then
++         subdir = root .. '-$(' .. ucname .. '_VERSION)'
++      end
++      root = subdir:match('^(.--[Vv])' .. escape(version_upstream) .. '$')
++      if root then
++         need_version_upstream = true
++         subdir = root .. '$(' .. ucname .. '_VERSION_UPSTREAM)'
++      end
++      root = subdir:match('^(.-)-' .. escape(version_upstream) .. '$')
++      if root then
++         if root == lcname then
++            subdir = nil
++         elseif root == name_upstream then
++            subdir = nil
++            need_name_upstream = true
++         else
++            need_version_upstream = true
++            subdir = root .. '-$(' .. ucname .. '_VERSION_UPSTREAM)'
++         end
++      end
++   end
++   local external_dependencies = get_external_dependencies(rockspec)
++   local fname = 'package/' .. lcname .. '/' .. lcname .. '.mk'
++   local f = assert(io.open(fname, 'w'))
++   util.printout('write ' .. fname)
++   f:write('################################################################################\n')
++   f:write('#\n')
++   f:write('# ' .. lcname .. '\n')
++   f:write('#\n')
++   f:write('################################################################################\n')
++   f:write('\n')
++   if need_version_upstream then
++      f:write(ucname .. '_VERSION_UPSTREAM = ' .. version_upstream .. '\n')
++      f:write(ucname .. '_VERSION = $(' .. ucname .. '_VERSION_UPSTREAM)-' .. revision .. '\n')
++   else
++      f:write(ucname .. '_VERSION = ' .. version .. '\n')
++   end
++   if lcname ~= name_upstream:lower() or need_name_upstream then
++      f:write(ucname .. '_NAME_UPSTREAM = ' .. name_upstream .. '\n')
++   end
++   if subdir then
++      f:write(ucname .. '_SUBDIR = ' .. subdir .. '\n')
++   end
++   if license then
++      f:write(ucname .. '_LICENSE = ' .. brlicense(license) .. '\n')
++   end
++   if #licenses == 1 then
++      f:write(ucname .. '_LICENSE_FILES = $(' .. ucname .. '_SUBDIR)/' .. licenses[1] .. '\n')
++   elseif #licenses > 1 then
++      f:write(ucname .. '_LICENSE_FILES =')
++      for i = 1, #licenses do
++         local file = licenses[i]
++         f:write(' \\\n    $(' .. ucname .. '_SUBDIR)/' .. file)
++      end
++      f:write('\n')
++   end
++   if #external_dependencies > 0 then
++      f:write(ucname .. '_DEPENDENCIES = ' .. table.concat(external_dependencies, ' ') .. '\n')
++   end
++   f:write('\n$(eval $(luarocks-package))\n')
++   f:close()
++end
++
++local function generate_hash (rockspec, lcname, rock_file, licenses, digest)
++   local subdir = rockspec.source.dir
++   local fname = 'package/' .. lcname .. '/' .. lcname .. '.hash'
++   local f = assert(io.open(fname, 'w'))
++   util.printout('write ' .. fname)
++   f:write('# computed by luarocks/buildroot\n')
++   f:write('sha256 ' .. digest[rock_file] .. '  ' .. rock_file .. '\n')
++   for i = 1, #licenses do
++      local file = licenses[i]
++      f:write('sha256 ' .. digest[file] .. '  ' .. subdir .. '/' .. file .. '\n')
++   end
++   f:close()
++end
++
++--- Driver function for the "buildroot" command.
++-- @param rockname string: the name of a rock to be fetched and unpacked.
++-- @param brname string: the name used by Buildroot (optional)
++-- @return boolean: true if successful
++function buildroot.command(flags, rockname, fsname)
++   if type(rockname) ~= 'string' then
++      return nil, "Argument missing. "..util.see_help('buildroot')
++   end
++   fsname = fsname or rockname
++   assert(type(fsname) == 'string')
++
++   local query = queries.new(rockname:lower(), nil, false, 'src')
++   local url, err = search.find_suitable_rock(query)
++   if not url then
++      return nil, "Could not find a result named " .. tostring(query) .. ": " .. err
++   end
++   local rock_file = dir.base_name(url)
++
++   local temp_dir, err = fs.make_temp_dir(rockname)
++   if not temp_dir then
++      return nil, "Failed creating temporary dir: " .. err
++   end
++   local ok, err = fs.change_dir(temp_dir)
++   if not ok then return nil, err end
++
++   ok = fs.download(url, rock_file, true)
++   if not ok then
++      return nil, "Failed downloading " .. url
++   end
++
++   local digest = {}
++   digest[rock_file], err = get_digest(rock_file)
++   if not digest[rock_file] then return nil, err end
++   ok, err = fs.unzip(rock_file)
++   if not ok then return nil, err end
++
++   local rockspec_file = rock_file:gsub('%.src%.rock$', '.rockspec')
++   local rockspec, err = fetch.load_rockspec(rockspec_file)
++   if not rockspec then
++      return nil, "Error loading rockspec: " .. err
++   end
++   if rockspec.source.file then
++      ok, err = fs.unpack_archive(rockspec.source.file)
++      if not ok then return nil, err end
++   end
++
++   if rockspec.source.dir ~= '.' then
++      fs.copy(rockspec.local_abs_filename, rockspec.source.dir, 'read')
++   end
++
++   local build_type = rockspec.build.type
++   if build_type ~= 'none' and build_type ~= 'builtin' and build_type ~= 'module' then
++      util.printout('[' .. rockspec.package .. "] build_type '" .. build_type .. "' not supported")
++   end
++
++   local licenses = {}
++   ok, err = fs.change_dir(rockspec.source.dir)
++   if not ok then return nil, err end
++   local files = fs.find()
++   for i = 1, #files do
++      local v = files[i]
++      if v == 'COPYING'
++         or v == 'COPYRIGHT'
++         or v:match('^LICENSE') then
++         licenses[#licenses+1] = v
++         digest[v], err = get_digest(v)
++         if not digest[v] then return nil, err end
++      end
++   end
++   if #licenses == 0 then
++      for i = 1, #files do
++         local v = files[i]
++         if v:match('^doc/LICENSE')
++            or v:match('^doc/license')
++            or v:match('^doc/us/license') then
++            licenses[#licenses+1] = v
++            digest[v], err = get_digest(v)
++            if not digest[v] then return nil, err end
++         end
++      end
++   end
++   fs.pop_dir()
++   table.sort(licenses)
++
++   fs.pop_dir()
++   ok, err = fs.make_dir('package/' .. fsname:lower())
++   if not ok then return nil, err end
++
++   generate_config(rockspec, fsname:lower())
++   generate_mk(rockspec, fsname:lower(), licenses)
++   generate_hash(rockspec, fsname:lower(), rock_file, licenses, digest)
++
++   return true
++end
++
++return buildroot
+-- 
+2.17.1
+