1#!/bin/sh
2SH=--[[                                             # -*- mode: lua; -*-
3## Slingshot rockspec generator.
4##
5## This file is distributed with Slingshot, and licensed under the
6## terms of the MIT license reproduced below.
7
8## ====================================================================
9## Copyright (C) 2013-2015 Gary V. Vaughan
10##
11## Permission is hereby granted, free of charge, to any person
12## obtaining a copy of this software and associated documentation
13## files (the "Software"), to deal in the Software without restriction,
14## including without limitation the rights to use, copy, modify, merge,
15## publish, distribute, sublicense, and/or sell copies of the Software,
16## and to permit persons to whom the Software is furnished to do so,
17## subject to the following conditions:
18##
19## The above copyright notice and this permission notice shall be
20## included in  all copies or substantial portions of the Software.
21##
22## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE-
25## MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
26## FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27## CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29## ====================================================================
30
31
32_lua_version_re='"Lua 5."[123]*'
33_lua_binaries='lua lua5.3 lua53 lua5.2 lua52 luajit lua5.1 lua51'
34
35export LUA
36export LUA_INIT
37export LUA_INIT_5_2
38export LUA_INIT_5_3
39export LUA_PATH
40export LUA_CPATH
41
42# Be Bourne compatible
43if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
44  emulate sh
45  NULLCMD=:
46  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
47  # is contrary to our usage.  Disable this feature.
48  alias -g '${1+"$@"}'='"$@"'
49  setopt NO_GLOB_SUBST
50else
51  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
52fi
53
54# If LUA is not set, search PATH for something suitable.
55test -n "$LUA" || {
56  # Check that the supplied binary is executable and returns a compatible
57  # Lua version number.
58  func_vercheck ()
59  {
60    test -x "$1" && {
61      eval 'case `'$1' -e "print (_VERSION)" 2>/dev/null` in
62        '"$_lua_version_re"') LUA='$1' ;;
63      esac'
64    }
65  }
66
67  progname=`echo "$0" |${SED-sed} 's|.*/||'`
68
69  save_IFS="$IFS"
70  LUA=
71  for x in $_lua_binaries; do
72    IFS=:
73    for dir in $PATH; do
74      IFS="$save_IFS"
75      func_vercheck "$dir/$x"
76      test -n "$LUA" && break
77    done
78    IFS="$save_IFS"
79    test -n "$LUA" && break
80    e="${e+$e\n}$progname: command not found on PATH: $x"
81  done
82}
83
84test -n "$LUA" || {
85  printf "${e+$e\n}$progname: retry after 'export LUA=/path/to/lua'\n" >&2
86  exit 1
87}
88
89LUA_INIT=
90LUA_INIT_5_2=
91LUA_INIT_5_3=
92
93# Reexecute using the interpreter suppiled in LUA, or found above.
94exec "$LUA" "$0" "$@"
95]]SH
96
97
98--[[ ============== ]]--
99--[[ Parse options. ]]--
100--[[ ============== ]]--
101
102local usage = 'Usage: mkrockspecs [OPTIONS] PACKAGE VERSION [REVISION] [FILE]\n'
103
104prog = {
105  name = arg[0] and arg[0]:gsub (".*/", "") or "mkrockspecs",
106
107  opts = {},
108}
109
110-- Print an argument processing error message, and return non-zero exit
111-- status.
112local function opterr (msg)
113  io.stderr:write (usage)
114  io.stderr:write (prog.name .. ": error: " .. msg .. ".\n")
115  io.stderr:write (prog.name .. ": Try '" .. prog.name .. " --help' for help,\n")
116  os.exit (2)
117end
118
119local function setopt (optname, arglist, i)
120  local opt = arglist[i]
121  if i + 1 > #arglist then
122    opterr ("option '" .. opt .. "' requires an argument")
123  end
124  prog.opts[optname] = arglist[i + 1]
125  return i + 1
126end
127
128local function die (msg)
129  msg:gsub ("([^\n]+)\n?",
130            function ()
131              io.stderr:write (prog.name .. ": error: " .. msg.. "\n")
132	    end)
133  os.exit (1)
134end
135
136prog["--help"] = function ()
137  print (usage .. [[
138
139Convert a YAML configuration file into a full rockspec.
140
141If FILE is provided, load it as the base configuration, otherwise if
142there is a 'rockspec.conf' in the current directory use that, or else
143wait for input on stdin.  If FILE is '-', force reading base config-
144uration from stdin.
145
146PACKAGE and VERSION are the package name and version number as defined
147by 'configure.ac' or similar. REVISION is only required for a revised
148rockspec if the default "-1" revision was released with errors.
149
150  -b, --branch=BRANCH    make git rockspec use BRANCH
151  -m, --module-dir=ROOT  directory of lua-files for builtin build type
152  -r, --repository=REPO  set the repository name (default=PACKAGE)
153      --help             print this help, then exit
154      --version          print version number, then exit
155
156Report bugs to http://github.com/gvvaughan/slingshot/issues.]])
157    os.exit (0)
158end
159
160prog["--version"] = function ()
161  print [[mkrockspecs (slingshot) 8.0.0
162Written by Gary V. Vaughan <gary@gnu.org>, 2013
163
164Copyright (C) 2013, Gary V. Vaughan
165Slingshot comes with ABSOLUTELY NO WARRANTY.
166See source files for individual license conditions.]]
167  os.exit (0)
168end
169
170prog["-b"] = function (argl, i) return setopt ("branch", argl, i) end
171prog["--branch"] = prog["-b"]
172
173prog["-m"] = function (argl, i) return setopt ("module_root", argl, i) end
174prog["--module-dir"] = prog["-m"]
175
176prog["-r"] = function (argl, i) return setopt ("repository", argl, i) end
177prog["--repository"] = prog["-r"]
178
179local nonopts
180local i = 0
181while i < #arg do
182  i = i + 1
183  local opt = arg[i]
184
185  -- Collect remaining arguments not nonopts to save back into _G.arg later.
186  if type (nonopts) == "table" then
187    table.insert (nonopts, opt)
188
189  -- Run prog.option handler.
190  elseif opt:sub (1,1) == "-" and type (prog[opt]) == "function" then
191    i = prog[opt] (arg, i)
192
193  -- End of option arguments.
194  elseif opt == "--" then
195    nonopts = {}
196
197  -- Diagnose unknown command line options.
198  elseif opt:sub (1, 1) == "-" then
199    opterr ("unrecognized option '" .. opt .. "'")
200
201  -- First non-option argument marks the end of options.
202  else
203    nonopts = { opt }
204  end
205end
206
207-- put non-option args back into global arg table.
208nonopts = nonopts or {}
209nonopts[0] = arg[0]
210_G.arg = nonopts
211
212if select ("#", ...) < 2 then
213  opterr ("only " .. select ("#", ...) .. " arguments provided")
214end
215
216local package  = arg[1]
217local version  = arg[2]
218local revision = arg[3] or "1"
219local conf     = arg[4] or "rockspec.conf"
220
221-- Unless set explicity, assume the repo is named after the package.
222if prog.opts.repository == nil then
223  prog.opts.repository = package
224end
225
226
227--[[ ================= ]]--
228--[[ Helper functions. ]]--
229--[[ ================= ]]--
230
231local ok, posix = pcall (require, "posix")
232
233files = {}
234
235if ok then
236  -- faster version if luaposix is available
237  function tree (root)
238    for f in posix.files (root) do
239      local path = root .. "/" .. f
240      if f:match ("%.lua$") then
241        table.insert (files, path)
242      elseif f == "." or f == ".." then
243        -- don't go into a loop
244      elseif posix.stat (path, "type") == "directory" then
245        tree (path)
246      end
247    end
248  end
249else
250  -- fallback version that executes ls in subshells
251  function tree (root)
252    local p = io.popen ("ls -1 " .. root .. " 2>/dev/null")
253
254    if p ~= nil then
255      local f = p:read "*l"
256      while f ~= nil do
257        if f:match ("%.lua$") then
258          table.insert (files, root .. "/" .. f)
259        else
260          tree (root .. "/" .. f)
261        end
262        f = p:read "*l"
263      end
264    end
265  end
266end
267
268local function escape_pattern (s)
269  return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0"))
270end
271
272local function loadmap (root)
273  local map = {}
274  tree (root)
275  for _, f in ipairs (files) do
276    local m = f:match ("^" .. escape_pattern (root) .. "/(.*)%.lua")
277    map [m:gsub ("/", "."):gsub ("%.init$", "")] = f:gsub ("^%./", "")
278  end
279  return map
280end
281
282
283--[[ =================== ]]--
284--[[ Load configuration. ]]--
285--[[ =================== ]]--
286
287local yaml = require "lyaml"
288
289-- Slurp io.input ().
290local function slurp ()
291  h = io.input ()
292  if h then
293    local s = h:read "*a"
294    h:close ()
295    return s
296  end
297end
298
299if conf == "-" then
300  io.input (io.stdin)
301else
302  local h = io.open (conf)
303  if h then
304    io.input (conf)
305    h:close ()
306  else
307    io.input (io.stdin)
308  end
309end
310
311local spec = yaml.load (slurp ())
312local default = { source = {} }
313
314-- url needn't be given if it is identical to homepage.
315local url
316if spec.source ~= nil then
317  url = spec.source.url
318elseif spec.description ~= nil then
319  url = spec.description.homepage
320else
321  die (conf .. ": could not find source.url or description.homepage")
322end
323url = url:gsub ("^[a-z]*://", ""):gsub ("%.git$", "")
324
325-- Interpolate default values.
326default.package = package
327default.version = version .. "-" .. revision
328
329configure_flags = ""
330if type (spec.external_dependencies) == "table" then
331  CPPFLAGS, LDFLAGS = "", ""
332  for name, vars in pairs (spec.external_dependencies) do
333    if vars.library then
334      CPPFLAGS = CPPFLAGS .. " -I$(" .. name .. "_INCDIR)"
335      LDFLAGS  = LDFLAGS  .. " -L$(" .. name .. "_LIBDIR)"
336    end
337  end
338
339  if string.len (CPPFLAGS) > 0 then
340    configure_flags = configure_flags ..
341        "CPPFLAGS='" .. CPPFLAGS:gsub ("^%s", "") .. "'" ..
342        " LDFLAGS='" ..  LDFLAGS:gsub ("^%s", "") .. "'" ..
343        " "
344  end
345end
346
347-- If we have a module root, use the luarocks "builtin" type.
348if version ~= "scm" and version ~= "git" then
349  if prog.opts.module_root ~= nil then
350    default.build = {
351      type = "builtin",
352      modules = loadmap (prog.opts.module_root),
353    }
354  elseif spec.build ~= nil and spec.build.modules ~= nil then
355    default.build = { type = "builtin" }
356  end
357end
358
359default.build = default.build or {
360  type = "command",
361  build_command = "./configure " ..
362    "LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' " .. configure_flags ..
363    "--prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' " ..
364    "--datarootdir='$(PREFIX)' && make clean all",
365  install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'",
366  copy_directories = {},
367}
368
369-- Additional spec-type dependent values.
370spec.source = spec.source or {}
371spec.build  = spec.build  or {}
372if version ~= "scm" and version ~= "git" then
373  spec.source.url = "http://" .. url .. "/archive/release-v" .. version .. ".zip"
374  spec.source.dir = prog.opts.repository .. "-release-v" .. version
375else
376  spec.source.url = "git://" .. url .. ".git"
377  spec.source.branch = prog.opts.branch
378  spec.build.modules = nil
379  default.build.build_command =  "LUA='$(LUA)' ./bootstrap && " .. default.build.build_command
380end
381
382
383-- Recursive merge, settings from spec take precedence.  Elements of src
384-- overwrite equivalent keys in dest.
385local function merge (dest, src)
386  for k, v in pairs (src) do
387    if type (v) == "table" then
388      dest[k] = merge (dest[k] or {}, src[k])
389    else
390      dest[k] = src[k]
391    end
392  end
393  return dest
394end
395
396spec = merge (default, spec)
397
398
399--[[ ======= ]]--
400--[[ Output. ]]--
401--[[ ======= ]]--
402
403-- Recursively format X, with pretty printing.
404local function format (x, indent)
405  indent = indent or ""
406  if type (x) == "table" then
407    if next (x) == nil then
408      return "{}"
409    else
410      local s = "{\n"
411
412      -- Collect and sort non-numeric keys first.
413      keys = {}
414      for k in pairs (x) do
415        if type (k) ~= "number" then table.insert (keys, k) end
416      end
417      table.sort (keys, function (a, b) return tostring (a) < tostring (b) end)
418
419      -- Display non-numeric key pairs in sort order.
420      for _, k in ipairs (keys) do
421        s = s .. indent
422        if k:match ("[^_%w]") then
423          -- wrap keys with non-%w chars in square brackets
424          s = s .. '["' .. k .. '"]'
425        else
426          s = s .. k
427        end
428        s = s .. " = " .. format (x[k], indent .. "  ") .. ",\n"
429      end
430
431      -- And numeric key pairs last.
432      for i, v in ipairs (x) do
433        s = s .. indent .. format (v, indent .. "  ") .. ",\n"
434      end
435      return s..indent:sub (1, -3).."}"
436    end
437  elseif type (x) == "string" then
438    return string.format ("%q", x)
439  else
440    return tostring (x)
441  end
442end
443
444-- Use the standard order for known keys.
445for _, k in ipairs {
446  "package",
447  "version",
448  "description",
449  "source",
450  "dependencies",
451  "external_dependencies",
452  "build",
453} do
454  print (k .. " = " .. format (spec[k], "  "))
455  spec[k] = nil
456end
457
458-- Output anything left in the table at the end.
459for i, v in pairs (spec) do
460  print (i .. " = " .. format (v, "  "))
461end
462
463os.exit (0)
464