aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYING32
-rw-r--r--Makefile123
-rwxr-xr-xconfigure362
-rw-r--r--install.bat312
-rwxr-xr-xmakedist68
-rwxr-xr-xsrc/bin/luarocks19
-rwxr-xr-xsrc/bin/luarocks-admin14
-rw-r--r--src/luarocks/build.lua276
-rw-r--r--src/luarocks/build/builtin.lua152
-rw-r--r--src/luarocks/build/cmake.lua54
-rw-r--r--src/luarocks/build/command.lua30
-rw-r--r--src/luarocks/build/make.lua84
-rw-r--r--src/luarocks/cfg.lua242
-rw-r--r--src/luarocks/command_line.lua133
-rw-r--r--src/luarocks/deps.lua618
-rw-r--r--src/luarocks/fetch.lua284
-rw-r--r--src/luarocks/fetch/cvs.lua43
-rw-r--r--src/luarocks/fetch/git.lua53
-rw-r--r--src/luarocks/fetch/sscm.lua40
-rw-r--r--src/luarocks/fs.lua43
-rw-r--r--src/luarocks/fs/unix.lua519
-rw-r--r--src/luarocks/fs/win32.lua369
-rw-r--r--src/luarocks/help.lua69
-rw-r--r--src/luarocks/install.lua115
-rw-r--r--src/luarocks/list.lua35
-rw-r--r--src/luarocks/make.lua54
-rw-r--r--src/luarocks/make_manifest.lua32
-rw-r--r--src/luarocks/manif.lua439
-rw-r--r--src/luarocks/pack.lua116
-rw-r--r--src/luarocks/path.lua224
-rw-r--r--src/luarocks/persist.lua91
-rw-r--r--src/luarocks/remove.lua163
-rw-r--r--src/luarocks/rep.lua187
-rw-r--r--src/luarocks/require.lua263
-rw-r--r--src/luarocks/search.lua394
-rw-r--r--src/luarocks/type_check.lua233
-rw-r--r--src/luarocks/unpack.lua148
-rw-r--r--src/luarocks/util.lua180
-rwxr-xr-xtest/run_tests.sh76
-rw-r--r--test/test_deps.lua67
-rwxr-xr-xtest/test_require.lua25
41 files changed, 6781 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 00000000..adec4655
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,32 @@
1LuaRocks is free software: it can be used for both academic and commercial
2purposes at absolutely no cost. There are no royalties or GNU-like "copyleft"
3restrictions. LuaRocks qualifies as Open Source software. Its licenses are
4compatible with the GPL. LuaRocks is not in the public domain and the Kepler
5Project keeps its copyright. The legal details are below.
6
7The spirit of the license is that you are free to use LuaRocks for any purpose
8at no cost without having to ask us. The only requirement is that if you do
9use LuaRocks, then you should give us credit by including the appropriate
10copyright notice somewhere in your product or its documentation.
11
12------------------------------------------------------------------------------
13
14Copyright © 2007 Kepler Project.
15
16Permission is hereby granted, free of charge, to any person obtaining a copy
17of this software and associated documentation files (the "Software"), to deal
18in the Software without restriction, including without limitation the rights
19to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20copies of the Software, and to permit persons to whom the Software is
21furnished to do so, subject to the following conditions:
22
23The above copyright notice and this permission notice shall be included in all
24copies or substantial portions of the Software.
25
26THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..5241eb7a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,123 @@
1# $Id: Makefile,v 1.30 2008/08/18 14:07:35 hisham Exp $
2
3include config.unix
4
5DESTDIR =
6PREFIX ?= /usr/local
7BINDIR ?= $(PREFIX)/bin
8LUADIR ?= $(PREFIX)/share/lua/5.1/
9LUA_DIR ?= /usr/local
10LUA_BINDIR ?= $(LUA_DIR)/bin
11
12BIN_FILES = luarocks luarocks-admin
13LUAROCKS_FILES = build/cmake.lua build/command.lua build.lua build/make.lua \
14command_line.lua cfg.lua deps.lua fetch.lua fs.lua fs/unix.lua \
15fs/win32.lua help.lua install.lua list.lua persist.lua \
16make_manifest.lua pack.lua path.lua rep.lua require.lua search.lua \
17type_check.lua util.lua remove.lua build/builtin.lua make.lua manif.lua unpack.lua \
18fetch/cvs.lua fetch/sscm.lua fetch/git.lua
19
20CONFIG_FILE = $(SYSCONFDIR)/config.lua
21
22all:
23 for f in $(BIN_FILES) ;\
24 do \
25 sed "1d" src/bin/$$f >> src/bin/$$f.bak ;\
26 echo "#!$(LUA_BINDIR)/lua$(LUA_SUFFIX)" > src/bin/$$f ;\
27 echo "package.path = [[$(LUADIR)/?.lua;$(LUADIR)/?/init.lua;]]..package.path" >> src/bin/$$f ;\
28 cat src/bin/$$f.bak >> src/bin/$$f ;\
29 rm src/bin/$$f.bak ;\
30 done
31 cp src/luarocks/cfg.lua src/luarocks/cfg.lua.bak
32 rm src/luarocks/cfg.lua
33 if [ -n "$(PREFIX)" ] ;\
34 then \
35 echo "local LUAROCKS_PREFIX=[[$(PREFIX)]]" >> src/luarocks/cfg.lua ;\
36 fi
37 if [ -n "$(LUA_INCDIR)" ] ;\
38 then \
39 echo "local LUA_INCDIR=[[$(LUA_INCDIR)]]" >> src/luarocks/cfg.lua ;\
40 fi
41 if [ -n "$(LUA_LIBDIR)" ] ;\
42 then \
43 echo "local LUA_LIBDIR=[[$(LUA_LIBDIR)]]" >> src/luarocks/cfg.lua ;\
44 fi
45 if [ -n "$(LUA_BINDIR)" ] ;\
46 then \
47 echo "local LUA_BINDIR=[[$(LUA_BINDIR)]]" >> src/luarocks/cfg.lua ;\
48 fi
49 if [ -n "$(LUA_SUFFIX)" ] ;\
50 then \
51 echo "local LUA_INTERPRETER=[[lua$(LUA_SUFFIX)]]" >> src/luarocks/cfg.lua ;\
52 fi
53 if [ -n "$(SYSCONFDIR)" ] ;\
54 then \
55 echo "local LUAROCKS_SYSCONFIG=[[$(SYSCONFDIR)/config.lua]]" >> src/luarocks/cfg.lua ;\
56 fi
57 if [ -n "$(ROCKS_TREE)" ] ;\
58 then \
59 echo "local LUAROCKS_ROCKS_TREE=[[$(ROCKS_TREE)]]" >> src/luarocks/cfg.lua ;\
60 fi
61 if [ -n "$(FORCE_CONFIG)" ] ;\
62 then \
63 echo "local LUAROCKS_FORCE_CONFIG=true" >> src/luarocks/cfg.lua ;\
64 fi
65 echo "local LUAROCKS_UNAME_S=[[$(LUAROCKS_UNAME_S)]]" >> src/luarocks/cfg.lua
66 echo "local LUAROCKS_UNAME_M=[[$(LUAROCKS_UNAME_M)]]" >> src/luarocks/cfg.lua
67 echo "local LUAROCKS_DOWNLOADER=[[$(LUAROCKS_DOWNLOADER)]]" >> src/luarocks/cfg.lua
68 echo "local LUAROCKS_MD5CHECKER=[[$(LUAROCKS_MD5CHECKER)]]" >> src/luarocks/cfg.lua
69 cat src/luarocks/cfg.lua.bak >> src/luarocks/cfg.lua
70 rm src/luarocks/cfg.lua.bak
71 @echo
72 @echo "Done. Type 'make install' to install into $(PREFIX)."
73 @echo
74
75luadoc:
76 rm -rf doc/luadoc
77 mkdir -p doc/luadoc
78 cd src && luadoc -d ../doc/luadoc --nofiles luarocks/*.lua
79
80check_makefile:
81 echo $(BIN_FILES) | tr " " "\n" | sort > makefile_list.txt
82 ( cd src/bin && ls -d * ) | grep -v "CVS" | sort > luarocks_dir.txt
83 echo $(LUAROCKS_FILES) | tr " " "\n" | sort >> makefile_list.txt
84 ( cd src/luarocks && ls -d *.lua ) | sort >> luarocks_dir.txt
85 diff makefile_list.txt luarocks_dir.txt
86 rm makefile_list.txt luarocks_dir.txt
87 @echo
88 @echo "Makefile is sane."
89 @echo
90
91clean:
92 for f in $(BIN_FILES) ;\
93 do \
94 sed -i.bak "s,^#!.*lua.*,#!/usr/bin/env lua,;/^package.path/d" src/bin/$$f ;\
95 rm src/bin/$$f.bak ;\
96 done
97 sed -i.bak "/^local LUA/d" src/luarocks/cfg.lua
98 rm src/luarocks/cfg.lua.bak
99
100install:
101 mkdir -p "$(DESTDIR)$(BINDIR)"
102 cd src/bin && cp $(BIN_FILES) "$(DESTDIR)$(BINDIR)"
103 mkdir -p "$(DESTDIR)$(LUADIR)/luarocks"
104 cd src/luarocks && for f in $(LUAROCKS_FILES); do d="$(DESTDIR)$(LUADIR)/luarocks"/`dirname "$$f"`; mkdir -p "$$d"; cp "$$f" "$$d"; done
105 mkdir -p "$(DESTDIR)$(ROCKS_TREE)"
106 if [ ! -f "$(DESTDIR)$(CONFIG_FILE)" ] ;\
107 then \
108 mkdir -p `dirname "$(DESTDIR)$(CONFIG_FILE)"` ;\
109 echo 'rocks_servers = {' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
110 echo ' [[http://luarocks.luaforge.net/rocks]]' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
111 echo '}' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
112 echo 'rocks_trees = {' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
113 if [ ! -n "$(FORCE_CONFIG)" ] ;\
114 then \
115 echo ' home..[[/.luarocks]],' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
116 fi ;\
117 echo ' [[$(ROCKS_TREE)]]' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
118 echo '}' >> "$(DESTDIR)$(CONFIG_FILE)" ;\
119 if [ -n "$(SCRIPTS_DIR)" ] ;\
120 then \
121 echo "scripts_dir = [[$(SCRIPTS_DIR)]]" >> "$(DESTDIR)$(CONFIG_FILE)" ;\
122 fi ;\
123 fi
diff --git a/configure b/configure
new file mode 100755
index 00000000..268b7241
--- /dev/null
+++ b/configure
@@ -0,0 +1,362 @@
1#!/bin/sh
2
3# A basic configure script for LuaRocks.
4# Not doing any fancy shell stuff here to keep good compatibility.
5
6# Defaults
7
8PREFIX="/usr/local"
9SYSCONFDIR="$PREFIX/etc/luarocks"
10ROCKS_TREE="$PREFIX/lib/luarocks"
11SCRIPTS_DIR=""
12LUA_SUFFIX=""
13LUA_DIR="/usr"
14LUA_BINDIR="/usr/bin"
15LUA_INCDIR="/usr/include"
16LUA_LIBDIR="/usr/lib"
17
18# ----------------------------------------------------------------------------
19# FUNCTION DEFINITIONS
20# ----------------------------------------------------------------------------
21
22# Help
23
24show_help() {
25cat <<EOF
26Configure LuaRocks.
27
28--help This help.
29--prefix=DIR Prefix where LuaRocks should be installed.
30 Default is $PREFIX
31--sysconfdir=DIR Location where the config file should be installed.
32 Default is \$PREFIX/etc/luarocks
33--rocks-tree=FILE Root of the local tree of installed rocks.
34 Default is \$PREFIX/lib/luarocks
35--scripts-dir=DIR Where to install scripts installed by rocks.
36 Default is \$ROCKS_TREE/bin. Use this if you
37 want to modify it so that rocks install scripts
38 somewhere else (possibly somewhere in your \$PATH),
39 but if you use a system path such as /usr/bin,
40 beware of clashes between files installed by
41 LuaRocks and by your system's package manager.
42 Example: /usr/local/bin
43--lua-suffix=SUFFIX Versioning suffix to use in Lua filenames.
44 Default is "$LUA_SUFFIX" (lua$LUA_SUFFIX...)
45--with-lua=PREFIX Use Lua from given prefix.
46 Default is $LUA_DIR
47--with-lua-include=DIR You can also specify Lua's includes dir.
48 Default is \$LUA_DIR/include
49--with-lua-lib=DIR You can also specify Lua's libraries dir.
50 Default is \$LUA_DIR/lib
51--with-downloader=TOOL Which tool to use as a downloader.
52 Valid options are: wget, curl.
53 Default is to auto-detect.
54--with-md5-checker=TOOL Which tool to use as a downloader.
55 Valid options are: md5sum, openssl
56 Default is to auto-detect.
57--force-config Use a single config location. Do not use the
58 \$LUAROCKS_CONFIG variable or the user's home
59 directory. Useful to avoid conflicts when LuaRocks
60 is embedded within an application.
61EOF
62}
63
64# Helper functions
65
66find_program() {
67 path="$PATH"
68 item="`echo "$path" | sed 's/\([^:]*\):.*/\1/'`"
69 path="`echo "$path" | sed -n 's/[^:]*::*\(.*\)/\1/p'`"
70 found="no"
71 while [ -n "$item" ]
72 do
73 if [ -f "$item/$1" ]
74 then
75 found="yes"
76 break
77 fi
78 item="`echo "$path" | sed 's/\([^:]*\):.*/\1/'`"
79 path="`echo "$path" | sed -n 's/[^:]*::*\(.*\)/\1/p'`"
80 done
81 if [ "$found" = "yes" ]
82 then
83 echo "$item"
84 else
85 echo ""
86 fi
87}
88
89find_helper() {
90 explanation="$1"
91 shift
92 tried="$*"
93 while [ -n "$1" ]
94 do
95 found=`find_program "$1"`
96 if [ -n "$found" ]
97 then
98 echo "$1 found at $found"
99 HELPER=$1
100 return
101 fi
102 shift
103 done
104 echo "Could not find a $explanation. Tried: $tried."
105 echo "Make sure one of them is installed and available in your PATH."
106 exit 1
107}
108
109case `echo -n x` in
110-n*) echo_n_flag='';;
111*) echo_n_flag='-n';;
112esac
113
114echo_n() {
115 echo $echo_n_flag "$*"
116}
117
118die() {
119 echo "$*"
120 exit 1
121}
122
123# ----------------------------------------------------------------------------
124# MAIN PROGRAM
125# ----------------------------------------------------------------------------
126
127# Parse options
128
129while [ -n "$1" ]
130do
131 value="`echo $1 | sed 's/[^=]*.\(.*\)/\1/'`"
132 key="`echo $1 | sed 's/=.*//'`"
133 if `echo "$value" | grep "~" >/dev/null 2>/dev/null`
134 then
135 echo
136 echo '*WARNING*: the "~" sign is not expanded in flags.'
137 echo 'If you mean the home directory, use $HOME instead.'
138 echo
139 fi
140 case "$key" in
141 --help)
142 show_help
143 exit 0
144 ;;
145 --prefix)
146 [ -n "$value" ] || die "Missing value in flag $key."
147 PREFIX="$value"
148 PREFIX_SET=yes
149 ;;
150 --sysconfdir)
151 [ -n "$value" ] || die "Missing value in flag $key."
152 SYSCONFDIR="$value"
153 SYSCONFDIR_SET=yes
154 ;;
155 --rocks-tree)
156 [ -n "$value" ] || die "Missing value in flag $key."
157 ROCKS_TREE="$value"
158 ROCKS_TREE_SET=yes
159 ;;
160 --scripts-dir)
161 [ -n "$value" ] || die "Missing value in flag $key."
162 SCRIPTS_DIR="$value"
163 SCRIPTS_DIR_SET=yes
164 ;;
165 --force-config)
166 FORCE_CONFIG=yes
167 ;;
168 --lua-suffix)
169 [ -n "$value" ] || die "Missing value in flag $key."
170 LUA_SUFFIX="$value"
171 LUA_SUFFIX_SET=yes
172 ;;
173 --with-lua)
174 [ -n "$value" ] || die "Missing value in flag $key."
175 LUA_DIR="$value"
176 LUA_DIR_SET=yes
177 ;;
178 --with-lua-include)
179 [ -n "$value" ] || die "Missing value in flag $key."
180 LUA_INCDIR="$value"
181 LUA_INCDIR_SET=yes
182 ;;
183 --with-lua-lib)
184 [ -n "$value" ] || die "Missing value in flag $key."
185 LUA_LIBDIR="$value"
186 LUA_LIBDIR_SET=yes
187 ;;
188 --with-downloader)
189 [ -n "$value" ] || die "Missing value in flag $key."
190 case "$value" in
191 wget|curl) LUAROCKS_DOWNLOADER="$value" ;;
192 *) echo "Invalid option: $value. See --help." ; exit 1 ;;
193 esac
194 LUAROCKS_DOWNLOADER_SET=yes
195 ;;
196 --with-md5-checker)
197 [ -n "$value" ] || die "Missing value in flag $key."
198 case "$value" in
199 md5sum|openssl|md5) LUAROCKS_MD5CHECKER="$value" ;;
200 *) echo "Invalid option: $value. See --help." ; exit 1 ;;
201 esac
202 LUAROCKS_MD5CHECKER_SET=yes
203 ;;
204 *)
205 echo "Error: Unknown flag: $1"
206 exit 1
207 ;;
208 esac
209 shift
210done
211
212
213if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ]
214then
215 if [ "$PREFIX" = "/usr" ]
216 then SYSCONFDIR=/etc/luarocks
217 else SYSCONFDIR=$PREFIX/etc/luarocks
218 fi
219fi
220
221
222if [ "$PREFIX_SET" = "yes" -a ! "$ROCKS_TREE_SET" = "yes" ]
223then
224 ROCKS_TREE=$PREFIX/lib/luarocks
225fi
226
227if [ "$LUA_SUFFIX_SET" != "yes" ]
228then
229 for suffix in "" "5.1" "51" ""
230 do
231 LUA_SUFFIX="$suffix"
232 if [ "$LUA_DIR_SET" = "yes" ]
233 then
234 if [ -f "$LUA_DIR/bin/lua$suffix" ]
235 then
236 find_lua="$LUA_DIR"
237 fi
238 else
239 find_lua=`find_program lua$suffix`
240 fi
241 if [ -n "$find_lua" ]
242 then
243 echo "Lua interpreter found: $find_lua/lua$suffix..."
244 break
245 fi
246 done
247fi
248
249if [ "$LUA_DIR_SET" != "yes" ]
250then
251 echo_n "Looking for Lua... "
252 if [ ! -n "$find_lua" ]
253 then
254 find_lua=`find_program lua$LUA_SUFFIX`
255 fi
256
257 if [ -n "$find_lua" ]
258 then
259 LUA_DIR=`dirname $find_lua`
260 LUA_BINDIR="$find_lua"
261 echo "lua$LUA_SUFFIX found in \$PATH: $find_lua"
262 else
263 echo "lua$LUA_SUFFIX not found in \$PATH."
264 echo "You may want to use the flags --with-lua and/or --lua-suffix. See --help."
265 exit 1
266 fi
267fi
268
269if [ "$LUA_INCDIR_SET" != "yes" ]
270then
271 LUA_INCDIR="$LUA_DIR/include"
272fi
273
274if [ "$LUA_LIBDIR_SET" != "yes" ]
275then
276 LUA_LIBDIR="$LUA_DIR/lib"
277fi
278
279if [ "$LUA_DIR_SET" = "yes" ]
280then
281 LUA_BINDIR="$LUA_DIR/bin"
282fi
283
284echo_n "Checking Lua includes... "
285lua_h="$LUA_INCDIR/lua.h"
286if [ -f "$lua_h" ]
287then
288 echo "lua.h found in $lua_h"
289else
290 echo "lua.h not found (looked in $lua_h)"
291 echo "You may want to use the flag --with-lua-include. See --help."
292 exit 1
293fi
294
295if [ "$LUAROCKS_DOWNLOADER_SET" != "yes" ]
296then
297 find_helper "downloader helper program" wget curl fetch
298 LUAROCKS_DOWNLOADER=$HELPER
299fi
300
301if [ "$LUAROCKS_MD5CHECKER_SET" != "yes" ]
302then
303 find_helper "MD5 checksum calculator" md5sum openssl md5
304 LUAROCKS_MD5CHECKER=$HELPER
305fi
306
307echo_n "Configuring for system... "
308if uname -s
309then
310 LUAROCKS_UNAME_S=`uname -s`
311else
312 echo "Could not determine operating system. 'uname -s' failed."
313 exit 1
314fi
315echo_n "Configuring for architecture... "
316if uname -m
317then
318 LUAROCKS_UNAME_M=`uname -m`
319else
320 echo "Could not determine processor architecture. 'uname -m' failed."
321 exit 1
322fi
323
324if [ -f config.unix ]; then
325 rm -f config.unix
326fi
327
328# Write config
329
330echo "Writing configuration..."
331echo
332
333cat <<EOF > config.unix
334# This file was automatically generated by the configure script.
335# Run "./configure --help" for details.
336
337PREFIX=$PREFIX
338SYSCONFDIR=$SYSCONFDIR
339ROCKS_TREE=$ROCKS_TREE
340SCRIPTS_DIR=$SCRIPTS_DIR
341LUA_SUFFIX=$LUA_SUFFIX
342LUA_DIR=$LUA_DIR
343LUA_INCDIR=$LUA_INCDIR
344LUA_LIBDIR=$LUA_LIBDIR
345LUA_BINDIR=$LUA_BINDIR
346FORCE_CONFIG=$FORCE_CONFIG
347LUAROCKS_UNAME_M=$LUAROCKS_UNAME_M
348LUAROCKS_UNAME_S=$LUAROCKS_UNAME_S
349LUAROCKS_DOWNLOADER=$LUAROCKS_DOWNLOADER
350LUAROCKS_MD5CHECKER=$LUAROCKS_MD5CHECKER
351
352EOF
353
354echo "Installation prefix: $PREFIX"
355echo "LuaRocks configuration directory: $SYSCONFDIR"
356echo "Using Lua from: $LUA_DIR"
357
358make clean > /dev/null 2> /dev/null
359
360echo
361echo "Done. You can now run 'make' to build."
362echo
diff --git a/install.bat b/install.bat
new file mode 100644
index 00000000..141fe0f8
--- /dev/null
+++ b/install.bat
@@ -0,0 +1,312 @@
1@ECHO OFF
2
3REM Boy, it feels like 1994 all over again.
4
5SETLOCAL
6
7SET PREFIX=C:\LuaRocks
8SET VERSION=1.0
9SET SYSCONFDIR=C:\LuaRocks
10SET ROCKS_TREE=C:\LuaRocks
11SET SCRIPTS_DIR=
12SET FORCE=OFF
13SET INSTALL_LUA=OFF
14SET LUA_INTERPRETER=
15SET LUA_PREFIX=
16SET LUA_BINDIR=
17SET LUA_INCDIR=
18SET LUA_LIBDIR=
19SET FORCE_CONFIG=
20SET MKDIR=.\bin\mkdir -p
21
22REM ***********************************************************
23REM Option parser
24REM ***********************************************************
25
26:PARSE_LOOP
27IF [%1]==[] GOTO DONE_PARSING
28IF [%1]==[/?] (
29 ECHO Installs LuaRocks.
30 ECHO.
31 ECHO /P [dir] Where to install.
32 ECHO Default is %PREFIX%
33 ECHO /CONFIG [dir] Location where the config file should be installed.
34 ECHO Default is %SYSCONFDIR%
35 ECHO /TREE [dir] Root of the local tree of installed rocks.
36 ECHO Default is %ROCKS_TREE%
37 ECHO /SCRIPTS [dir] Where to install scripts installed by rocks.
38 ECHO Default is TREE/bin.
39 ECHO.
40 ECHO /L Install LuaRocks' own copy of Lua even if detected.
41 ECHO /LUA [dir] Location where Lua is installed - e.g. c:\lua\5.1\
42 ECHO /INC [dir] Location of Lua includes - e.g. c:\lua\5.1\include
43 ECHO /LIB [dir] Location of Lua libraries -e.g. c:\lua\5.1\lib
44 ECHO /BIN [dir] Location of Lua executables - e.g. c:\lua\5.1\bin
45 ECHO.
46 ECHO /FORCECONFIG Use a single config location. Do not use the
47 ECHO LUAROCKS_CONFIG variable or the user's home directory.
48 ECHO Useful to avoid conflicts when LuaRocks
49 ECHO is embedded within an application.
50 ECHO.
51 ECHO /F Remove installation directory if it already exists.
52 ECHO.
53 GOTO QUIT
54)
55IF /I [%1]==[/P] (
56 SET PREFIX=%2
57 SHIFT /1
58 SHIFT /1
59 GOTO PARSE_LOOP
60)
61IF /I [%1]==[/CONFIG] (
62 SET SYSCONFDIR=%2
63 SHIFT /1
64 SHIFT /1
65 GOTO PARSE_LOOP
66)
67IF /I [%1]==[/TREE] (
68 SET ROCKS_TREE=%2
69 SHIFT /1
70 SHIFT /1
71 GOTO PARSE_LOOP
72)
73IF /I [%1]==[/SCRIPTS] (
74 SET SCRIPTS_DIR=%2
75 SHIFT /1
76 SHIFT /1
77 GOTO PARSE_LOOP
78)
79IF /I [%1]==[/L] (
80 SET INSTALL_LUA=ON
81 SHIFT /1
82 GOTO PARSE_LOOP
83)
84IF /I [%1]==[/LUA] (
85 SET LUA_PREFIX=%2
86 SHIFT /1
87 SHIFT /1
88 GOTO PARSE_LOOP
89)
90IF /I [%1]==[/LIB] (
91 SET LUA_LIBDIR=%2
92 SHIFT /1
93 SHIFT /1
94 GOTO PARSE_LOOP
95)
96IF /I [%1]==[/INC] (
97 SET LUA_INCDIR=%2
98 SHIFT /1
99 SHIFT /1
100 GOTO PARSE_LOOP
101)
102IF /I [%1]==[/BIN] (
103 SET LUA_BINDIR=%2
104 SHIFT /1
105 SHIFT /1
106 GOTO PARSE_LOOP
107)
108IF /I [%1]==[/FORCECONFIG] (
109 SET FORCE_CONFIG=ON
110 SHIFT /1
111 GOTO PARSE_LOOP
112)
113IF /I [%1]==[/F] (
114 SET FORCE=ON
115 SHIFT /1
116 GOTO PARSE_LOOP
117)
118ECHO Unrecognized option: %1
119GOTO ERROR
120:DONE_PARSING
121
122SET FULL_PREFIX=%PREFIX%\%VERSION%
123
124SET BINDIR=%FULL_PREFIX%
125SET LIBDIR=%FULL_PREFIX%
126SET LUADIR=%FULL_PREFIX%\lua
127SET INCDIR=%FULL_PREFIX%\include
128
129REM ***********************************************************
130REM Detect Lua
131REM ***********************************************************
132
133IF [%INSTALL_LUA%]==[ON] GOTO USE_OWN_LUA
134
135FOR %%L IN (%LUA_PREFIX% c:\lua\5.1.2 c:\lua c:\kepler\1.1) DO (
136 SET CURR=%%L
137 IF EXIST "%%L" (
138 IF NOT [%LUA_BINDIR%]==[] (
139 IF EXIST %LUA_BINDIR%\lua5.1.exe (
140 SET LUA_INTERPRETER=%LUA_BINDIR%\lua5.1.exe
141 GOTO INTERPRETER_IS_SET
142 )
143 IF EXIST %LUA_BINDIR%\lua.exe (
144 SET LUA_INTERPRETER=%LUA_BINDIR%\lua.exe
145 GOTO INTERPRETER_IS_SET
146 )
147 ECHO Lua executable lua.exe or lua5.1.exe not found in %LUA_BINDIR%
148 GOTO ERROR
149 )
150 SET CURR=%%L
151 FOR %%E IN (\ \bin\) DO (
152 IF EXIST "%%L%%E\lua5.1.exe" (
153 SET LUA_INTERPRETER=%%L%%E\lua5.1.exe
154 SET LUA_BINDIR=%%L%%E
155 GOTO INTERPRETER_IS_SET
156 )
157 IF EXIST "%%L%%E\lua.exe" (
158 SET LUA_INTERPRETER=%%L%%E\lua.exe
159 SET LUA_BINDIR=%%L%%E
160 GOTO INTERPRETER_IS_SET
161 )
162 )
163 GOTO TRY_NEXT_LUA_DIR
164 :INTERPRETER_IS_SET
165 IF NOT "%LUA_LIBDIR%"=="" (
166 IF EXIST %LUA_LIBDIR%\lua5.1.lib GOTO LIBDIR_IS_SET
167 ECHO lua5.1.lib not found in %LUA_LIBDIR%
168 GOTO ERROR
169 )
170 FOR %%E IN (\ \lib\ \bin\) DO (
171 IF EXIST "%CURR%%%E\lua5.1.lib" (
172 SET LUA_LIBDIR=%CURR%%%E
173 GOTO LIBDIR_IS_SET
174 )
175 )
176 GOTO TRY_NEXT_LUA_DIR
177 :LIBDIR_IS_SET
178 IF NOT [%LUA_INCDIR%]==[] (
179 IF EXIST %LUA_INCDIR%\lua.h GOTO INCDIR_IS_SET
180 ECHO lua.h not found in %LUA_INCDIR%
181 GOTO ERROR
182 )
183 FOR %%E IN (\ \include\) DO (
184 IF EXIST "%CURR%%%E\lua.h" (
185 SET LUA_INCDIR=%CURR%%%E
186 GOTO INCDIR_IS_SET
187 )
188 )
189 GOTO TRY_NEXT_LUA_DIR
190 :INCDIR_IS_SET
191 %LUA_INTERPRETER% -v 2>NUL
192 IF NOT ERRORLEVEL 1 (
193 GOTO LUA_IS_SET
194 )
195 )
196:TRY_NEXT_LUA_DIR
197 REM wtf
198)
199ECHO Could not find Lua. Will install its own copy.
200ECHO See /? for options for specifying the location of Lua.
201:USE_OWN_LUA
202SET INSTALL_LUA=ON
203SET LUA_INTERPRETER=lua5.1
204SET LUA_BINDIR=%BINDIR%
205SET LUA_LIBDIR=%LIBDIR%
206SET LUA_INCDIR=%INCDIR%
207:LUA_IS_SET
208ECHO.
209ECHO Will configure LuaRocks with the following paths:
210ECHO Lua interpreter: %LUA_INTERPRETER%
211ECHO Lua binaries: %LUA_BINDIR%
212ECHO Lua libraries: %LUA_LIBDIR%
213ECHO Lua includes: %LUA_INCDIR%
214ECHO.
215
216REM ***********************************************************
217REM Install LuaRocks files
218REM ***********************************************************
219
220IF [%FORCE%]==[ON] (
221 ECHO Removing %FULL_PREFIX%...
222 RD /S /Q "%FULL_PREFIX%"
223)
224
225IF NOT EXIST "%FULL_PREFIX%" GOTO NOT_EXIST_PREFIX
226 ECHO %FULL_PREFIX% exists. Use /F to force removal and reinstallation.
227 GOTO ERROR
228:NOT_EXIST_PREFIX
229
230ECHO Installing LuaRocks in %FULL_PREFIX%...
231IF NOT EXIST "%BINDIR%" %MKDIR% "%BINDIR%"
232IF ERRORLEVEL 1 GOTO ERROR
233IF [%INSTALL_LUA%]==[ON] (
234 IF NOT EXIST "%LUA_BINDIR%" %MKDIR% "%LUA_BINDIR%"
235 IF NOT EXIST "%LUA_INCDIR%" %MKDIR% "%LUA_INCDIR%"
236 COPY lua5.1\bin\*.* "%LUA_BINDIR%" >NUL
237 COPY lua5.1\include\*.* "%LUA_INCDIR%" >NUL
238)
239COPY bin\*.* "%BINDIR%" >NUL
240IF ERRORLEVEL 1 GOTO ERROR
241COPY src\bin\*.* "%BINDIR%" >NUL
242IF ERRORLEVEL 1 GOTO ERROR
243FOR %%C IN (luarocks luarocks-admin) DO (
244 RENAME "%BINDIR%\%%C" %%C.lua
245 IF ERRORLEVEL 1 GOTO ERROR
246 DEL /F /Q "%BINDIR%\%%C.bat" 2>NUL
247 ECHO @ECHO OFF>> "%BINDIR%\%%C.bat"
248 ECHO SETLOCAL>> "%BINDIR%\%%C.bat"
249 ECHO SET LUA_PATH=%LUADIR%\?.lua;%LUADIR%\?\init.lua;%%LUA_PATH%%>> "%BINDIR%\%%C.bat"
250 ECHO SET PATH=%BINDIR%\;%%PATH%%>> "%BINDIR%\%%C.bat"
251 ECHO "%LUA_INTERPRETER%" "%BINDIR%\%%C.lua" %%*>> "%BINDIR%\%%C.bat"
252 ECHO ENDLOCAL>> "%BINDIR%\%%C.bat"
253)
254IF NOT EXIST "%LUADIR%\luarocks" %MKDIR% "%LUADIR%\luarocks"
255IF ERRORLEVEL 1 GOTO ERROR
256XCOPY /S src\luarocks\*.* "%LUADIR%\luarocks" >NUL
257IF ERRORLEVEL 1 GOTO ERROR
258
259RENAME "%LUADIR%\luarocks\cfg.lua" "cfg.lua.bak"
260ECHO local LUA_INCDIR=[[%LUA_INCDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
261ECHO local LUA_LIBDIR=[[%LUA_LIBDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
262ECHO local LUA_BINDIR=[[%LUA_BINDIR%]]>> "%LUADIR%\luarocks\cfg.lua"
263ECHO local LUA_INTERPRETER=[[%LUA_INTERPRETER%]]>> "%LUADIR%\luarocks\cfg.lua"
264ECHO local LUAROCKS_UNAME_S=[[WindowsNT]]>> "%LUADIR%\luarocks\cfg.lua"
265ECHO local LUAROCKS_UNAME_M=[[x86]]>> "%LUADIR%\luarocks\cfg.lua"
266ECHO local LUAROCKS_SYSCONFIG=[[%SYSCONFDIR%/config.lua]]>> "%LUADIR%\luarocks\cfg.lua"
267ECHO local LUAROCKS_ROCKS_TREE=[[%ROCKS_TREE%]]>> "%LUADIR%\luarocks\cfg.lua"
268ECHO local LUAROCKS_PREFIX=[[%PREFIX%]]>> "%LUADIR%\luarocks\cfg.lua"
269IF NOT [%FORCE_CONFIG%]==[] ECHO local LUAROCKS_FORCE_CONFIG=true>> "%LUADIR%\luarocks\cfg.lua"
270TYPE "%LUADIR%\luarocks\cfg.lua.bak">> "%LUADIR%\luarocks\cfg.lua"
271
272DEL /F /Q "%LUADIR%\luarocks\cfg.lua.bak"
273
274SET CONFIG_FILE=%SYSCONFDIR%\config.lua
275
276IF NOT EXIST "%SYSCONFDIR%" %MKDIR% "%SYSCONFDIR%"
277IF NOT EXIST "%CONFIG_FILE%" (
278 ECHO rocks_servers = {>> "%CONFIG_FILE%"
279 ECHO [[http://luarocks.luaforge.net/rocks]]>> "%CONFIG_FILE%"
280 ECHO }>> "%CONFIG_FILE%"
281 ECHO rocks_trees = {>> "%CONFIG_FILE%"
282 IF [%FORCE_CONFIG%]==[] ECHO home..[[/luarocks]],>> "%CONFIG_FILE%"
283 ECHO [[%ROCKS_TREE%]]>> "%CONFIG_FILE%"
284 ECHO }>> "%CONFIG_FILE%"
285 IF NOT [%SCRIPTS_DIR%]==[] ECHO scripts_dir=[[%SCRIPTS_DIR%]]>> "%CONFIG_FILE%"
286)
287
288IF [%SCRIPTS_DIR%]==[] (
289 %MKDIR% "%ROCKS_TREE%"\bin >NUL
290 COPY lua5.1\bin\*.dll "%ROCKS_TREE%"\bin >NUL
291)
292IF NOT [%SCRIPTS_DIR%]==[] (
293 %MKDIR% "%SCRIPTS_DIR%" >NUL
294 COPY lua5.1\bin\*.dll "%SCRIPTS_DIR%" >NUL
295)
296
297IF NOT EXIST "%ROCKS_TREE%" %MKDIR% "%ROCKS_TREE%"
298IF NOT EXIST "%APPDATA%/luarocks" %MKDIR% "%APPDATA%/luarocks"
299
300REM ***********************************************************
301REM Exit handlers
302REM ***********************************************************
303
304ECHO LuaRocks is installed!
305:QUIT
306ENDLOCAL
307EXIT /B 0
308
309:ERROR
310ECHO Failed installing LuaRocks.
311ENDLOCAL
312EXIT /B 1
diff --git a/makedist b/makedist
new file mode 100755
index 00000000..3e78040a
--- /dev/null
+++ b/makedist
@@ -0,0 +1,68 @@
1#!/bin/sh
2
3cvslist() {
4 if [ "$1" ]
5 then local prefix="$1/"
6 fi
7 cvs list $1 | grep -v "^?" | while read line
8 do
9 local path="$prefix$line"
10 echo "$path"
11 if [ -d "$path" ]
12 then
13 cvslist "$path"
14 fi
15 done
16}
17
18if ! [ "$1" ]
19then
20 echo "usage: $0 <version>"
21 exit 1
22fi
23
24cvs list > /dev/null 2> /dev/null
25if [ $? != 0 ]
26then
27 echo "Your version of CVS may be too old. At least 1.12 is needed."
28 exit 1
29fi
30
31make clean
32
33out="luarocks-$1"
34rm -rf "$out"
35mkdir "$out"
36list=`cvslist`
37rm -f missing_ref
38echo "$list" | while read i
39do
40 if [ -f "$i" ]
41 then
42 dir=`dirname $i`
43 mkdir -p "$out/$dir"
44 cp "$i" "$out/$dir"
45 if echo "$i" | grep -q "^src/"
46 then
47 grep -qw `basename "$i"` Makefile || {
48 echo "Missing ref in makefile: $i"
49 touch missing_ref
50 exit 1
51 }
52 fi
53 fi
54done
55if [ -e missing_ref ]
56then
57 rm -f missing_ref
58 exit 1
59fi
60rm -f "$out-win32.zip" "$out.tar.gz"
61rm "$out/makedist"
62rm "$out/install.bat"
63tar czvpf "$out.tar.gz" "$out"
64cp install.bat "$out"
65cp -a win32/bin "$out"
66cp -a win32/lua5.1 "$out"
67zip -r "$out-win32.zip" "$out"
68rm -rf "$out"
diff --git a/src/bin/luarocks b/src/bin/luarocks
new file mode 100755
index 00000000..8812ecc8
--- /dev/null
+++ b/src/bin/luarocks
@@ -0,0 +1,19 @@
1#!/usr/bin/env lua
2
3local command_line = require("luarocks.command_line")
4
5program_name = "luarocks"
6program_description = "LuaRocks main command-line interface"
7
8commands = {}
9commands.help = require("luarocks.help")
10commands.pack = require("luarocks.pack")
11commands.unpack = require("luarocks.unpack")
12commands.build = require("luarocks.build")
13commands.install = require("luarocks.install")
14commands.search = require("luarocks.search")
15commands.list = require("luarocks.list")
16commands.remove = require("luarocks.remove")
17commands.make = require("luarocks.make")
18
19command_line.run_command(...)
diff --git a/src/bin/luarocks-admin b/src/bin/luarocks-admin
new file mode 100755
index 00000000..826597f6
--- /dev/null
+++ b/src/bin/luarocks-admin
@@ -0,0 +1,14 @@
1#!/usr/bin/env lua
2
3local command_line = require("luarocks.command_line")
4
5program_name = "luarocks-admin"
6program_description = "LuaRocks repository administration interface"
7
8commands = {
9}
10
11commands.help = require("luarocks.help")
12commands.make_manifest = require("luarocks.make_manifest")
13
14command_line.run_command(...)
diff --git a/src/luarocks/build.lua b/src/luarocks/build.lua
new file mode 100644
index 00000000..f0f7225d
--- /dev/null
+++ b/src/luarocks/build.lua
@@ -0,0 +1,276 @@
1
2--- Module implementing the LuaRocks "build" command.
3-- Builds a rock, compiling its C parts if any.
4module("luarocks.build", package.seeall)
5
6local path = require("luarocks.path")
7local util = require("luarocks.util")
8local rep = require("luarocks.rep")
9local fetch = require("luarocks.fetch")
10local fs = require("luarocks.fs")
11local deps = require("luarocks.deps")
12local manif = require("luarocks.manif")
13
14help_summary = "Build/compile a rock."
15help_arguments = "{<rockspec>|<rock>|<name> [<version>]}"
16help = [[
17Build a rock, compiling its C parts if any.
18Argument may be a rockspec file, a source rock file
19or the name of a rock to be fetched from a repository.
20]]
21
22--- Install files to a given location.
23-- Takes a table where the array part is a list of filenames to be copied.
24-- In the hash part, other keys are identifiers in Lua module format,
25-- to indicate which subdirectory the file should be copied to. For example,
26-- install_files({["foo.bar"] = "src/bar.lua"}, "boo") will copy src/bar.lua
27-- to boo/foo.
28-- @param files table or nil: A table containing a list of files to copy in
29-- the format described above. If nil is passed, this function is a no-op.
30-- Directories should be delimited by forward slashes as in internet URLs.
31-- @param location string: The base directory files should be copied to.
32-- @return boolean or (nil, string): True if succeeded or
33-- nil and an error message.
34local function install_files(files, location)
35 assert(type(files) == "table" or not files)
36 assert(type(location) == "string")
37 if files then
38 for k, file in pairs(files) do
39 local dest = location
40 if type(k) == "string" then
41 dest = fs.make_path(location, path.module_to_path(k))
42 end
43 fs.make_dir(dest)
44 local ok = fs.copy(fs.make_path(file), dest)
45 if not ok then
46 return nil, "Failed copying "..file
47 end
48 end
49 end
50 return true
51end
52
53--- Write to the current directory the contents of a table,
54-- where each key is a file name and its value is the file content.
55-- @param files table: The table of files to be written.
56local function extract_from_rockspec(files)
57 for name, content in pairs(files) do
58 local fd = io.open(fs.make_path(fs.current_dir(), name), "w+")
59 fd:write(content)
60 fd:close()
61 end
62end
63
64--- Applies patches inlined in the build.patches section
65-- and extracts files inlined in the build.extra_files section
66-- of a rockspec.
67-- @param rockspec table: A rockspec table.
68-- @return boolean or (nil, string): True if succeeded or
69-- nil and an error message.
70function apply_patches(rockspec)
71 assert(type(rockspec) == "table")
72
73 local build = rockspec.build
74 if build.extra_files then
75 extract_from_rockspec(build.extra_files)
76 end
77 if build.patches then
78 extract_from_rockspec(build.patches)
79 for patch, _ in util.sortedpairs(build.patches) do
80 print("Applying patch "..patch.."...")
81 local ok, err = fs.patch(tostring(patch))
82 if not ok then
83 return nil, "Failed applying patch "..patch
84 end
85 end
86 end
87 return true
88end
89
90--- Build and install a rock given a rockspec.
91-- @param rockspec_file string: local or remote filename of a rockspec.
92-- @param need_to_fetch boolean: true if sources need to be fetched,
93-- false if the rockspec was obtained from inside a source rock.
94-- @return boolean or (nil, string): True if succeeded or
95-- nil and an error message.
96function build_rockspec(rockspec_file, need_to_fetch, minimal_mode)
97 assert(type(rockspec_file) == "string")
98 assert(type(need_to_fetch) == "boolean")
99
100 local rockspec, err = fetch.load_rockspec(rockspec_file)
101 if err then
102 return nil, err
103 elseif not rockspec.build then
104 return nil, "Rockspec error: build table not specified"
105 elseif not rockspec.build.type then
106 return nil, "Rockspec error: build type not specified"
107 end
108
109 local ok, err = deps.fulfill_dependencies(rockspec)
110 if err then
111 return nil, err
112 end
113 ok, err = deps.check_external_deps(rockspec, "build")
114 if err then
115 return nil, err
116 end
117
118 local name, version = rockspec.name, rockspec.version
119 if rep.is_installed(name, version) then
120 rep.delete_version(name, version)
121 end
122
123 if not minimal_mode then
124 local _, dir
125 if need_to_fetch then
126 ok, dir = fetch.fetch_sources(rockspec, true)
127 if not ok then
128 return nil, dir
129 end
130 fs.change_dir(dir)
131 elseif rockspec.source.file then
132 local ok, err = fs.unpack_archive(rockspec.source.file)
133 if not ok then
134 return nil, err
135 end
136 end
137 fs.change_dir(rockspec.source.dir)
138 end
139
140 local dirs = {
141 lua = path.lua_dir(name, version),
142 lib = path.lib_dir(name, version),
143 conf = path.conf_dir(name, version),
144 bin = path.bin_dir(name, version),
145 }
146
147 for _, dir in pairs(dirs) do
148 fs.make_dir(dir)
149 end
150 local rollback = util.schedule_function(function()
151 fs.delete(path.install_dir(name, version))
152 fs.remove_dir_if_empty(path.versions_dir(name))
153 end)
154
155 local build = rockspec.build
156
157 if not minimal_mode then
158 ok, err = apply_patches(rockspec)
159 if err then
160 return nil, err
161 end
162 end
163
164 if build.type ~= "none" then
165
166 -- Temporary compatibility
167 if build.type == "module" then build.type = "builtin" end
168
169 ok, build_type = pcall(require, "luarocks.build." .. build.type)
170 if not ok or not type(build_type) == "table" then
171 return nil, "Failed initializing build back-end for build type '"..build.type.."'"
172 end
173
174 ok, err = build_type.run(rockspec)
175 if not ok then
176 return nil, "Build error: " .. err
177 end
178 end
179
180 if build.install then
181 for id, dir in pairs(dirs) do
182 ok, err = install_files(build.install[id], dir)
183 if not ok then
184 return nil, err
185 end
186 end
187 end
188
189 local copy_directories = build.copy_directories or {"doc"}
190
191 for _, dir in pairs(copy_directories) do
192 if fs.is_dir(dir) then
193 local dest = fs.make_path(path.install_dir(name, version), dir)
194 fs.make_dir(dest)
195 fs.copy_contents(dir, dest)
196 end
197 end
198
199 for _, dir in pairs(dirs) do
200 fs.remove_dir_if_empty(dir)
201 end
202
203 fs.pop_dir()
204
205 fs.copy(rockspec.local_filename, path.rockspec_file(name, version))
206 if need_to_fetch then
207 fs.pop_dir()
208 end
209
210 ok, err = rep.install_bins(name, version)
211 if not ok then return nil, err end
212
213 ok, err = rep.run_hook(rockspec, "post_install")
214 if err then
215 return nil, err
216 end
217
218 ok, err = manif.update_manifest(name, version)
219 if err then
220 return nil, err
221 end
222 util.remove_scheduled_function(rollback)
223 return true
224end
225
226--- Build and install a rock.
227-- @param rock_file string: local or remote filename of a rock.
228-- @param need_to_fetch boolean: true if sources need to be fetched,
229-- false if the rockspec was obtained from inside a source rock.
230-- @return boolean or (nil, string): True if build was successful,
231-- or false and an error message.
232local function build_rock(rock_file, need_to_fetch)
233 assert(type(rock_file) == "string")
234 assert(type(need_to_fetch) == "boolean")
235
236 local dir, err = fetch.fetch_and_unpack_rock(rock_file)
237 if not dir then
238 return nil, err
239 end
240 local rockspec_file = path.rockspec_name_from_rock(rock_file)
241 fs.change_dir(dir)
242 local ok, err = build_rockspec(rockspec_file, need_to_fetch)
243 fs.pop_dir()
244 return ok, err
245end
246
247--- Driver function for "build" command.
248-- @param name string: A local or remote rockspec or rock file.
249-- If a package name is given, forwards the request to "search" and,
250-- if returned a result, installs the matching rock.
251-- @param version string: When passing a package name, a version number may
252-- also be given.
253-- @return boolean or (nil, string): True if build was successful; nil and an
254-- error message otherwise.
255function run(...)
256 local flags, name, version = util.parse_flags(...)
257 if type(name) ~= "string" then
258 return nil, "Argument missing, see help."
259 end
260 assert(type(version) == "string" or not version)
261
262 if name:match("%.rockspec$") then
263 return build_rockspec(name, true)
264 elseif name:match("%.src%.rock$") then
265 return build_rock(name, false)
266 elseif name:match("%.all%.rock$") then
267 local install = require("luarocks.install")
268 return install.install_binary_rock(name)
269 elseif name:match("%.rock$") then
270 return build_rock(name, true)
271 elseif not name:match(fs.dir_separator) then
272 local search = require("luarocks.search")
273 return search.act_on_src_or_rockspec(run, name, version)
274 end
275 return nil, "Don't know what to do with "..name
276end
diff --git a/src/luarocks/build/builtin.lua b/src/luarocks/build/builtin.lua
new file mode 100644
index 00000000..c93edaa2
--- /dev/null
+++ b/src/luarocks/build/builtin.lua
@@ -0,0 +1,152 @@
1
2--- A builtin build system: back-end to provide a portable way of building C-based Lua modules.
3module("luarocks.build.builtin", package.seeall)
4
5local fs = require("luarocks.fs")
6local path = require("luarocks.path")
7local util = require("luarocks.util")
8local cfg = require("luarocks.cfg")
9
10--- Check if platform was detected
11-- @param query string: The platform name to check.
12-- @return boolean: true if LuaRocks is currently running on queried platform.
13local function is_platform(query)
14 assert(type(query) == "string")
15
16 for _, platform in ipairs(cfg.platforms) do
17 if platform == query then
18 return true
19 end
20 end
21end
22
23--- Run a command displaying its execution on standard output.
24-- @return boolean: true if command succeeds (status code 0), false
25-- otherwise.
26local function execute(...)
27 io.stdout:write(table.concat({...}, " ").."\n")
28 return fs.execute(...)
29end
30
31--- Driver function for the builtin build back-end.
32-- @param rockspec table: the loaded rockspec.
33-- @return boolean or (nil, string): true if no errors ocurred,
34-- nil and an error message otherwise.
35function run(rockspec)
36 assert(type(rockspec) == "table")
37 local compile_object, compile_library
38
39 local build = rockspec.build
40 local variables = rockspec.variables
41
42 local function add_flags(extras, flag, flags)
43 if flags then
44 if type(flags) ~= "table" then
45 flags = { tostring(flags) }
46 end
47 util.variable_substitutions(flags, variables)
48 for _, v in ipairs(flags) do
49 table.insert(extras, flag:format(v))
50 end
51 end
52 end
53
54 if is_platform("win32") then
55 compile_object = function(object, source, defines, incdirs)
56 local extras = {}
57 add_flags(extras, "-D%s", defines)
58 add_flags(extras, "-I%s", incdirs)
59 return execute(variables.CC.." "..variables.CFLAGS, "-c", "-Fo"..object, "-I"..variables.LUA_INCDIR, source, unpack(extras))
60 end
61 compile_library = function(library, objects, libraries, libdirs, name)
62 local extras = { unpack(objects) }
63 add_flags(extras, "-libpath:%s", libdirs)
64 add_flags(extras, "%s.lib", libraries)
65 local basename = fs.base_name(library):gsub(".[^.]*$", "")
66 local deffile = basename .. ".def"
67 local def = io.open(fs.make_path(fs.current_dir(), deffile), "w+")
68 def:write("EXPORTS\n")
69 def:write("luaopen_"..name:gsub("%.", "_").."\n")
70 def:close()
71 local ok = execute(variables.LD, "-dll", "-def:"..deffile, "-out:"..library, fs.make_path(variables.LUA_LIBDIR, "lua5.1.lib"), unpack(extras))
72 local manifestfile = basename..".dll.manifest"
73 if ok and fs.exists(manifestfile) then
74 ok = execute(variables.MT, "-manifest", manifestfile, "-outputresource:"..basename..".dll;2")
75 end
76 return ok
77 end
78 else
79 compile_object = function(object, source, defines, incdirs)
80 local extras = {}
81 add_flags(extras, "-D%s", defines)
82 add_flags(extras, "-I%s", incdirs)
83 return execute(variables.CC.." "..variables.CFLAGS, "-I"..variables.LUA_INCDIR, "-c", source, "-o", object, unpack(extras))
84 end
85 compile_library = function (library, objects, libraries, libdirs)
86 local extras = { unpack(objects) }
87 add_flags(extras, "-L%s", libdirs)
88 add_flags(extras, "-l%s", libraries)
89 if is_platform("cygwin") then
90 add_flags(extras, "-l%s", {"lua"})
91 end
92 return execute(variables.LD.." "..variables.LIBFLAG, "-o", library, unpack(extras))
93 end
94 end
95
96 local ok = true
97 local built_modules = {}
98 local luadir = path.lua_dir(rockspec.name, rockspec.version)
99 local libdir = path.lib_dir(rockspec.name, rockspec.version)
100 local docdir = path.doc_dir(rockspec.name, rockspec.version)
101 for name, info in pairs(build.modules) do
102 local moddir = path.module_to_path(name)
103 if type(info) == "string" then
104 local ext = info:match(".([^.]+)$")
105 if ext == "lua" then
106 local dest = fs.make_path(luadir, moddir)
107 built_modules[info] = dest
108 else
109 info = {info}
110 end
111 end
112 if type(info) == "table" then
113 local objects = {}
114 local sources = info.sources
115 if info[1] then sources = info end
116 if type(sources) == "string" then sources = {sources} end
117 for _, source in ipairs(sources) do
118 local object = source:gsub(".[^.]*$", "."..cfg.obj_extension)
119 if not object then
120 object = source.."."..cfg.obj_extension
121 end
122 ok = compile_object(object, source, info.defines, info.incdirs)
123 if not ok then break end
124 table.insert(objects, object)
125 end
126 if not ok then break end
127 local module_name = fs.make_path(moddir, name:match("([^.]*)$").."."..cfg.lib_extension):gsub("//", "/")
128 if moddir ~= "" then
129 fs.make_dir(moddir)
130 end
131 local dest = fs.make_path(libdir, moddir)
132 built_modules[module_name] = dest
133 ok = compile_library(module_name, objects, info.libraries, info.libdirs, name)
134 if not ok then break end
135 end
136 end
137 for name, dest in pairs(built_modules) do
138 fs.make_dir(dest)
139 ok = fs.copy(name, dest)
140 if not ok then break end
141 end
142 if ok then
143 if fs.is_dir("lua") then
144 fs.copy_contents("lua", luadir)
145 end
146 end
147 if ok then
148 return true
149 else
150 return nil, "Build error"
151 end
152end
diff --git a/src/luarocks/build/cmake.lua b/src/luarocks/build/cmake.lua
new file mode 100644
index 00000000..7fd1fcc8
--- /dev/null
+++ b/src/luarocks/build/cmake.lua
@@ -0,0 +1,54 @@
1
2--- Build back-end for CMake-based modules.
3module("luarocks.build.cmake", package.seeall)
4
5local fs = require("luarocks.fs")
6local util = require("luarocks.util")
7local cfg = require("luarocks.cfg")
8
9--- Driver function for the "cmake" build back-end.
10-- @param rockspec table: the loaded rockspec.
11-- @return boolean or (nil, string): true if no errors ocurred,
12-- nil and an error message otherwise.
13function run(rockspec)
14 assert(type(rockspec) == "table")
15 local build = rockspec.build
16 local variables = build.variables or {}
17
18 -- Pass Env variables
19 build.variables.CMAKE_MODULE_PATH=os.getenv("CMAKE_MODULE_PATH")
20 build.variables.CMAKE_LIBRARY_PATH=os.getenv("CMAKE_LIBRARY_PATH")
21 build.variables.CMAKE_INCLUDE_PATH=os.getenv("CMAKE_INCLUDE_PATH")
22
23 util.variable_substitutions(variables, rockspec.variables)
24
25 -- If inline cmake is present create CMakeLists.txt from it.
26 if type(build.cmake) == "string" then
27 local cmake = assert(io.open(fs.current_dir().."/CMakeLists.txt", "w"))
28 cmake:write(build.cmake)
29 cmake:close()
30 end
31
32
33 -- Execute cmake with variables.
34 local args = ""
35 if cfg.cmake_generator then
36 args = args .. ' -G"'..cfg.cmake_generator.. '"'
37 end
38 for k,v in pairs(variables) do
39 args = args .. ' -D' ..k.. '="' ..v.. '"'
40 end
41
42 if not fs.execute("cmake . " ..args) then
43 return nil, "Failed cmake."
44 end
45
46 if not fs.execute("make -fMakefile") then
47 return nil, "Failed building."
48 end
49
50 if not fs.execute("make -fMakefile install") then
51 return nil, "Failed installing."
52 end
53 return true
54end
diff --git a/src/luarocks/build/command.lua b/src/luarocks/build/command.lua
new file mode 100644
index 00000000..72d3de7f
--- /dev/null
+++ b/src/luarocks/build/command.lua
@@ -0,0 +1,30 @@
1
2--- Build back-end for raw listing of commands in rockspec files.
3module("luarocks.build.command", package.seeall)
4
5local fs = require("luarocks.fs")
6local util = require("luarocks.util")
7
8--- Driver function for the "command" build back-end.
9-- @param rockspec table: the loaded rockspec.
10-- @return boolean or (nil, string): true if no errors ocurred,
11-- nil and an error message otherwise.
12function run(rockspec)
13 assert(type(rockspec) == "table")
14
15 local build = rockspec.build
16
17 util.variable_substitutions(build, rockspec.variables)
18
19 if build.build_command then
20 if not fs.execute(build.build_command) then
21 return nil, "Failed building."
22 end
23 end
24 if build.install_command then
25 if not fs.execute(build.install_command) then
26 return nil, "Failed installing."
27 end
28 end
29 return true
30end
diff --git a/src/luarocks/build/make.lua b/src/luarocks/build/make.lua
new file mode 100644
index 00000000..b3e553a9
--- /dev/null
+++ b/src/luarocks/build/make.lua
@@ -0,0 +1,84 @@
1
2--- Build back-end for using Makefile-based packages.
3module("luarocks.build.make", package.seeall)
4
5local fs = require("luarocks.fs")
6local util = require("luarocks.util")
7local cfg = require("luarocks.cfg")
8
9--- Call "make" with given target and variables
10-- @param pass boolean: If true, run make; if false, do nothing.
11-- @param target string: The make target; an empty string indicates
12-- the default target.
13-- @param variables table: A table containing string-string key-value
14-- pairs representing variable assignments to be passed to make.
15-- @return boolean: false if any errors occurred, true otherwise.
16local function make_pass(pass, target, variables)
17 assert(type(pass) == "boolean")
18 assert(type(target) == "string")
19 assert(type(variables) == "table")
20
21 local assignments = {}
22 for k,v in pairs(variables) do
23 table.insert(assignments, k.."="..v)
24 end
25 if pass then
26 return fs.execute(cfg.make.." "..target, unpack(assignments))
27 else
28 return true
29 end
30end
31
32--- Driver function for the "make" build back-end.
33-- @param rockspec table: the loaded rockspec.
34-- @return boolean or (nil, string): true if no errors ocurred,
35-- nil and an error message otherwise.
36function run(rockspec)
37 assert(type(rockspec) == "table")
38
39 local build = rockspec.build
40
41 if build.build_pass == nil then build.build_pass = true end
42 if build.install_pass == nil then build.install_pass = true end
43 build.build_variables = build.build_variables or {}
44 build.install_variables = build.install_variables or {}
45 build.build_target = build.build_target or ""
46 build.install_target = build.install_target or "install"
47 local makefile = build.makefile or cfg.makefile
48 if makefile then
49 -- Assumes all make's accept -f. True for POSIX make, GNU make and Microsoft nmake.
50 build.build_target = "-f "..makefile.." "..build.build_target
51 build.install_target = "-f "..makefile.." "..build.install_target
52 end
53
54 if build.variables then
55 for var, val in pairs(build.variables) do
56 build.build_variables[var] = val
57 build.install_variables[var] = val
58 end
59 end
60
61 util.variable_substitutions(build.build_variables, rockspec.variables)
62 util.variable_substitutions(build.install_variables, rockspec.variables)
63
64 local auto_variables = { "CC" }
65
66 for _, variable in pairs(auto_variables) do
67 if not build.build_variables[variable] then
68 build.build_variables[variable] = rockspec.variables[variable]
69 end
70 if not build.install_variables[variable] then
71 build.install_variables[variable] = rockspec.variables[variable]
72 end
73 end
74
75 local ok = make_pass(build.build_pass, build.build_target, build.build_variables)
76 if not ok then
77 return nil, "Failed building."
78 end
79 ok = make_pass(build.install_pass, build.install_target, build.install_variables)
80 if not ok then
81 return nil, "Failed installing."
82 end
83 return true
84end
diff --git a/src/luarocks/cfg.lua b/src/luarocks/cfg.lua
new file mode 100644
index 00000000..3c4c5aed
--- /dev/null
+++ b/src/luarocks/cfg.lua
@@ -0,0 +1,242 @@
1
2local rawset, next, table, pairs, print, require, io, os, setmetatable =
3 rawset, next, table, pairs, print, require, io, os, setmetatable
4
5--- Configuration for LuaRocks.
6-- Tries to load the user's configuration file and
7-- defines defaults for unset values. See the
8-- <a href="http://luarocks.org/en/Config_file_format">config
9-- file format documentation</a> for details.
10module("luarocks.cfg")
11
12local persist = require("luarocks.persist")
13
14local detected = {}
15local system,proc
16
17-- A proper installation of LuaRocks will hardcode the system
18-- and proc values with LUAROCKS_UNAME_S and LUAROCKS_UNAME_M,
19-- so that this detection does not run every time. When it is
20-- performed, we use the Unix way to identify the system,
21-- even on Windows (assuming UnxUtils or Cygwin).
22system = LUAROCKS_UNAME_S or io.popen("uname -s"):read("*l")
23proc = LUAROCKS_UNAME_M or io.popen("uname -m"):read("*l")
24if proc:match("i[%d]86") then
25 proc = "x86"
26elseif proc:match("amd64") or proc:match("x86_64") then
27 proc = "x86_64"
28elseif proc:match("Power Macintosh") then
29 proc = "powerpc"
30end
31
32if system == "FreeBSD" then
33 detected.unix = true
34 detected.freebsd = true
35elseif system == "Darwin" then
36 detected.unix = true
37 detected.macosx = true
38elseif system == "Linux" then
39 detected.unix = true
40 detected.linux = true
41elseif system and system:match("^CYGWIN") then
42 detected.unix = true
43 detected.cygwin = true
44elseif system and system:match("^Windows") then
45 detected.windows = true
46else
47 detected.unix = true
48 -- Fall back to Unix in unknown systems.
49end
50
51local sys_config_file, home_config_file, home_tree
52if detected.windows then
53 home = os.getenv("APPDATA") or "c:"
54 sys_config_file = "c:/luarocks/config.lua"
55 home_config_file = home.."/luarocks/config.lua"
56 home_tree = home.."/luarocks/"
57else
58 home = os.getenv("HOME") or ""
59 sys_config_file = "/etc/luarocks/config.lua"
60 home_config_file = home.."/.luarocks/config.lua"
61 home_tree = home.."/.luarocks/"
62end
63
64variables = {}
65rocks_trees = {}
66
67persist.load_into_table(LUAROCKS_SYSCONFIG or sys_config_file, _M)
68
69if not LUAROCKS_FORCE_CONFIG then
70 home_config_file = os.getenv("LUAROCKS_CONFIG") or home_config_file
71 local home_overrides = persist.load_into_table(home_config_file, { home = home })
72 if home_overrides then
73 local util = require("luarocks.util")
74 util.deep_merge(_M, home_overrides)
75 end
76end
77
78local defaults = {
79 arch = "unknown",
80 lib_extension = "unknown",
81 obj_extension = "unknown",
82 rocks_servers = {
83 "http://luarocks.luaforge.net/rocks"
84 },
85 lua_extension = "lua",
86 lua_interpreter = LUA_INTERPRETER or "lua",
87 downloader = LUAROCKS_DOWNLOADER or "wget",
88 md5checker = LUAROCKS_MD5CHECKER or "md5sum",
89 variables = {}
90}
91
92defaults.external_deps_subdirs = {
93 bin = "bin",
94 lib = "lib",
95 include = "include"
96}
97defaults.runtime_external_deps_subdirs = defaults.external_deps_subdirs
98
99if detected.windows then
100 home_config_file = home_config_file:gsub("\\","/")
101 defaults.lib_extension = "dll"
102 defaults.obj_extension = "obj"
103 local rootdir = LUAROCKS_ROCKS_TREE or home_tree
104 defaults.root_dir = rootdir
105 defaults.rocks_dir = rootdir.."/rocks/"
106 defaults.scripts_dir = rootdir.."/bin/"
107 defaults.external_deps_dirs = { "c:/external/" }
108 defaults.variables.LUA_BINDIR = LUA_BINDIR and LUA_BINDIR:gsub("\\", "/") or "c:/lua5.1/bin"
109 defaults.variables.LUA_INCDIR = LUA_INCDIR and LUA_INCDIR:gsub("\\", "/") or "c:/lua5.1/include"
110 defaults.variables.LUA_LIBDIR = LUA_LIBDIR and LUA_LIBDIR:gsub("\\", "/") or "c:/lua5.1/lib"
111 defaults.arch = "win32-"..proc
112 defaults.platforms = {"win32", "windows" }
113 defaults.cmake_generator = "MinGW Makefiles"
114 -- TODO: Split Windows flavors between mingw and msvc
115 -- defaults.make = "make"
116 -- defaults.makefile = "Makefile"
117 defaults.make = "nmake"
118 defaults.makefile = "Makefile.win"
119 defaults.variables.CC = "cl"
120 defaults.variables.LD = "link"
121 defaults.variables.MT = "mt"
122 defaults.variables.CFLAGS = "/MD /O2"
123 defaults.variables.LIBFLAG = "/dll"
124 defaults.external_deps_patterns = {
125 bin = { "?.exe", "?.bat" },
126 lib = { "?.lib", "?.dll" },
127 include = { "?.h" }
128 }
129 defaults.runtime_external_deps_patterns = {
130 bin = { "?.exe", "?.bat" },
131 lib = { "?.dll" },
132 include = { "?.h" }
133 }
134end
135
136if detected.unix then
137 defaults.lib_extension = "so"
138 defaults.obj_extension = "o"
139 local rootdir = LUAROCKS_ROCKS_TREE or home_tree
140 defaults.root_dir = rootdir
141 defaults.rocks_dir = rootdir.."/rocks/"
142 defaults.scripts_dir = rootdir.."/bin/"
143 defaults.external_deps_dirs = { "/usr/local", "/usr" }
144 defaults.variables.LUA_BINDIR = LUA_BINDIR or "/usr/local/bin"
145 defaults.variables.LUA_INCDIR = LUA_INCDIR or "/usr/local/include"
146 defaults.variables.LUA_LIBDIR = LUA_LIBDIR or "/usr/local/lib"
147 defaults.variables.CFLAGS = "-O2"
148 defaults.cmake_generator = "Unix Makefiles"
149 defaults.make = "make"
150 defaults.platforms = { "unix" }
151 defaults.variables.CC = "cc"
152 defaults.variables.LD = "ld"
153 defaults.variables.LIBFLAG = "-shared"
154 defaults.external_deps_patterns = {
155 bin = { "?" },
156 lib = { "lib?.a", "lib?.so" },
157 include = { "?.h" }
158 }
159 defaults.runtime_external_deps_patterns = {
160 bin = { "?" },
161 lib = { "lib?.so" },
162 include = { "?.h" }
163 }
164end
165
166if detected.cygwin then
167 defaults.lib_extension = "so" -- can be overridden in the config file for mingw builds
168 defaults.arch = "cygwin-"..proc
169 defaults.platforms = {"unix", "cygwin"}
170 defaults.cmake_generator = "Unix Makefiles"
171 defaults.variables.CC = "echo -llua | xargs gcc"
172 defaults.variables.LD = "echo -llua | xargs gcc"
173 defaults.variables.LIBFLAG = "-shared"
174end
175
176defaults.external_lib_extension = defaults.lib_extension
177
178if detected.macosx then
179 defaults.external_lib_extension = "dylib"
180 defaults.arch = "macosx-"..proc
181 defaults.platforms = {"unix", "macosx"}
182 defaults.variables.CC = "export MACOSX_DEPLOYMENT_TARGET=10.3; gcc"
183 defaults.variables.LD = "export MACOSX_DEPLOYMENT_TARGET=10.3; gcc"
184 defaults.variables.LIBFLAG = "-bundle -undefined dynamic_lookup -all_load"
185end
186
187if detected.linux then
188 defaults.arch = "linux-"..proc
189 defaults.platforms = {"unix", "linux"}
190 defaults.variables.CC = "gcc"
191 defaults.variables.LD = "gcc"
192 defaults.variables.LIBFLAG = "-shared"
193end
194
195if detected.freebsd then
196 defaults.arch = "freebsd-"..proc
197 defaults.make = "gmake"
198 defaults.platforms = {"unix", "freebsd"}
199 defaults.variables.CC = "gcc"
200 defaults.variables.LD = "gcc"
201 defaults.variables.LIBFLAG = "-shared"
202end
203
204if proc == "x86_64" and not defaults.variables.CFLAGS:match("-fPIC") then
205 defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC"
206end
207
208-- Expose some more values detected by LuaRocks for use by rockspec authors.
209defaults.variables.LUA = defaults.lua_interpreter
210defaults.variables.LIB_EXTENSION = defaults.lib_extension
211defaults.variables.OBJ_EXTENSION = defaults.obj_extension
212defaults.variables.LUAROCKS_PREFIX = LUAROCKS_PREFIX
213
214local cfg_mt = {
215 __index = function(t, k)
216 local default = defaults[k]
217 if default then
218 rawset(t, k, default)
219 end
220 return default
221 end
222}
223
224if not _M.variables then
225 _M.variables = {}
226end
227for k,v in pairs(defaults.variables) do
228 if not _M.variables[k] then
229 _M.variables[k] = v
230 end
231end
232
233setmetatable(_M, cfg_mt)
234
235if not next(rocks_trees) then
236 if home_tree then
237 table.insert(rocks_trees, home_tree)
238 end
239 if LUAROCKS_ROCKS_TREE then
240 table.insert(rocks_trees, LUAROCKS_ROCKS_TREE)
241 end
242end
diff --git a/src/luarocks/command_line.lua b/src/luarocks/command_line.lua
new file mode 100644
index 00000000..b3020284
--- /dev/null
+++ b/src/luarocks/command_line.lua
@@ -0,0 +1,133 @@
1
2program_version = "1.0"
3
4--- Functions for command-line scripts.
5module("luarocks.command_line", package.seeall)
6
7local util = require("luarocks.util")
8local cfg = require("luarocks.cfg")
9local fs = require("luarocks.fs")
10
11--- Display an error message and exit.
12-- @param message string: The error message.
13local function die(message)
14 assert(type(message) == "string")
15
16 local ok, err = pcall(util.run_scheduled_functions)
17 if not ok then
18 print("\nLuaRocks "..program_version.." internal bug (please report at luarocks-developers@lists.luaforge.net):\n"..err)
19 end
20 print("\nError: "..message)
21 os.exit(1)
22end
23
24--- Main command-line processor.
25-- Parses input arguments and calls the appropriate driver function
26-- to execute the action requested on the command-line, forwarding
27-- to it any additional arguments passed by the user.
28-- Uses the global table "commands", which contains
29-- the loaded modules representing commands.
30-- @param ... string: Arguments given on the command-line.
31function run_command(...)
32 local args = {...}
33 local cmdline_vars = {}
34 for i = #args, 1, -1 do
35 local arg = args[i]
36 if arg:match("^[^-][^=]*=") then
37 local var, val = arg:match("^([A-Z_][A-Z0-9_]*)=(.*)")
38 if val then
39 cmdline_vars[var] = val
40 table.remove(args, i)
41 else
42 die("Invalid assignment: "..arg)
43 end
44 end
45 end
46 local nonflags = { util.parse_flags(unpack(args)) }
47 local flags = table.remove(nonflags, 1)
48
49 if flags["to"] then
50 if flags["to"] == true then
51 die("Argument error: use --to=<path>")
52 end
53 local root_dir = fs.absolute_name(flags["to"])
54 cfg.root_dir = root_dir
55 cfg.rocks_dir = root_dir.."/rocks"
56 cfg.scripts_dir = root_dir.."/bin"
57 else
58 local trees = cfg.rocks_trees
59 for i = #trees, 1, -1 do
60 local tree = trees[i]
61 if fs.make_dir(tree) and fs.is_writable(tree) then
62 cfg.root_dir = tree
63 cfg.rocks_dir = tree.."/rocks"
64 cfg.scripts_dir = rawget(cfg, "scripts_dir") or tree.."/bin"
65 break
66 end
67 end
68 end
69
70 cfg.root_dir = cfg.root_dir:gsub("/+$", "")
71 cfg.rocks_dir = cfg.rocks_dir:gsub("/+$", "")
72 cfg.scripts_dir = cfg.scripts_dir:gsub("/+$", "")
73
74 cfg.variables.ROCKS_TREE = cfg.root_dir
75 cfg.variables.SCRIPTS_DIR = cfg.scripts_dir
76
77 if flags["from"] then
78 if flags["from"] == true then
79 die("Argument error: use --from=<url>")
80 end
81 local protocol, path = fs.split_url(flags["from"])
82 table.insert(cfg.rocks_servers, 1, protocol.."://"..path)
83 end
84
85 if flags["only-from"] then
86 if flags["only-from"] == true then
87 die("Argument error: use --only-from=<url>")
88 end
89 cfg.rocks_servers = { flags["only-from"] }
90 end
91
92 local command
93
94 if flags["version"] then
95 print(program_name.." "..program_version)
96 print(program_description)
97 print()
98 os.exit(0)
99 elseif flags["help"] or #nonflags == 0 then
100 command = "help"
101 args = nonflags
102 else
103 command = nonflags[1]
104 for i, arg in ipairs(args) do
105 if arg == command then
106 table.remove(args, i)
107 break
108 end
109 end
110 end
111
112 if command ~= "help" then
113 for k, v in pairs(cmdline_vars) do
114 cfg.variables[k] = v
115 end
116 end
117
118 command = command:gsub("-", "_")
119 if commands[command] then
120 local xp, ok, err = xpcall(function() return commands[command].run(unpack(args)) end, function(err)
121 die(debug.traceback("LuaRocks "..program_version
122 .." bug (please report at luarocks-developers@lists.luaforge.net).\n"
123 ..err, 2))
124 end)
125 if xp and (not ok) then
126 die(err)
127 end
128 else
129 die("Unknown command: "..command)
130 end
131
132 util.run_scheduled_functions()
133end
diff --git a/src/luarocks/deps.lua b/src/luarocks/deps.lua
new file mode 100644
index 00000000..d5a64e52
--- /dev/null
+++ b/src/luarocks/deps.lua
@@ -0,0 +1,618 @@
1
2--- Dependency handling functions.
3-- Dependencies are represented in LuaRocks through strings with
4-- a package name followed by a comma-separated list of constraints.
5-- Each constraint consists of an operator and a version number.
6-- In this string format, version numbers are represented as
7-- naturally as possible, like they are used by upstream projects
8-- (e.g. "2.0beta3"). Internally, LuaRocks converts them to a purely
9-- numeric representation, allowing comparison following some
10-- "common sense" heuristics. The precise specification of the
11-- comparison criteria is the source code of this module, but the
12-- test/test_deps.lua file included with LuaRocks provides some
13-- insights on what these criteria are.
14module("luarocks.deps", package.seeall)
15
16local rep = require("luarocks.rep")
17local search = require("luarocks.search")
18local install = require("luarocks.install")
19local cfg = require("luarocks.cfg")
20local manif = require("luarocks.manif")
21local fs = require("luarocks.fs")
22local fetch = require("luarocks.fetch")
23local path = require("luarocks.path")
24
25local operators = {
26 ["=="] = "==",
27 ["~="] = "~=",
28 [">"] = ">",
29 ["<"] = "<",
30 [">="] = ">=",
31 ["<="] = "<=",
32 ["~>"] = "~>",
33 -- plus some convenience translations
34 [""] = "==",
35 ["="] = "==",
36 ["!="] = "~="
37}
38
39local deltas = {
40 scm = 1000,
41 cvs = 1000,
42 rc = -1000,
43 pre = -10000,
44 beta = -100000,
45 alpha = -1000000
46}
47
48local version_mt = {
49 --- Equality comparison for versions.
50 -- All version numbers must be equal.
51 -- If both versions have revision numbers, they must be equal;
52 -- otherwise the revision number is ignored.
53 -- @param v1 table: version table to compare.
54 -- @param v2 table: version table to compare.
55 -- @return boolean: true if they are considered equivalent.
56 __eq = function(v1, v2)
57 if #v1 ~= #v2 then
58 return false
59 end
60 for i = 1, #v1 do
61 if v1[i] ~= v2[i] then
62 return false
63 end
64 end
65 if v1.revision and v2.revision then
66 return (v1.revision == v2.revision)
67 end
68 return true
69 end,
70 --- Size comparison for versions.
71 -- All version numbers are compared.
72 -- If both versions have revision numbers, they are compared;
73 -- otherwise the revision number is ignored.
74 -- @param v1 table: version table to compare.
75 -- @param v2 table: version table to compare.
76 -- @return boolean: true if v1 is considered lower than v2.
77 __lt = function(v1, v2)
78 for i = 1, math.max(#v1, #v2) do
79 local v1i, v2i = v1[i] or 0, v2[i] or 0
80 if v1i ~= v2i then
81 return (v1i < v2i)
82 end
83 end
84 if v1.revision and v2.revision then
85 return (v1.revision < v2.revision)
86 end
87 return false
88 end
89}
90
91local version_cache = {}
92setmetatable(version_cache, {
93 __mode = "kv"
94})
95
96--- Parse a version string, converting to table format.
97-- A version table contains all components of the version string
98-- converted to numeric format, stored in the array part of the table.
99-- If the version contains a revision, it is stored numerically
100-- in the 'revision' field. The original string representation of
101-- the string is preserved in the 'string' field.
102-- Returned version tables use a metatable
103-- allowing later comparison through relational operators.
104-- @param vstring string: A version number in string format.
105-- @return table or nil: A version table or nil
106-- if the input string contains invalid characters.
107function parse_version(vstring)
108 if not vstring then return nil end
109 assert(type(vstring) == "string")
110
111 local cached = version_cache[vstring]
112 if cached then
113 return cached
114 end
115
116 local version = {}
117 local i = 1
118
119 local function add_token(number)
120 version[i] = version[i] and version[i] + number/100000 or number
121 i = i + 1
122 end
123
124 -- trim leading and trailing spaces
125 vstring = vstring:match("^%s*(.*)%s*$")
126 version.string = vstring
127 -- store revision separately if any
128 local main, revision = vstring:match("(.*)%-(%d+)$")
129 if revision then
130 vstring = main
131 version.revision = tonumber(revision)
132 end
133 while #vstring > 0 do
134 -- extract a number
135 local token, rest = vstring:match("^(%d+)[%.%-%_]*(.*)")
136 if token then
137 add_token(tonumber(token))
138 else
139 -- extract a word
140 token, rest = vstring:match("^(%a+)[%.%-%_]*(.*)")
141 if not token then
142 return nil
143 end
144 local last = #version
145 version[i] = deltas[token] or (token:byte() / 1000)
146 end
147 vstring = rest
148 end
149 setmetatable(version, version_mt)
150 version_cache[vstring] = version
151 return version
152end
153
154--- Utility function to compare version numbers given as strings.
155-- @param a string: one version.
156-- @param b string: another version.
157-- @return boolean: True if a > b.
158function compare_versions(a, b)
159 return parse_version(a) > parse_version(b)
160end
161
162--- Consumes a constraint from a string, converting it to table format.
163-- For example, a string ">= 1.0, > 2.0" is converted to a table in the
164-- format {op = ">=", version={1,0}} and the rest, "> 2.0", is returned
165-- back to the caller.
166-- @param input string: A list of constraints in string format.
167-- @return (table, string) or nil: A table representing the same
168-- constraints and the string with the unused input, or nil if the
169-- input string is invalid.
170local function parse_constraint(input)
171 assert(type(input) == "string")
172
173 local no_upgrade, op, version, rest = input:match("^(@?)([<>=~!]*)%s*([%w%.%_%-]+)[%s,]*(.*)")
174 op = operators[op]
175 version = parse_version(version)
176 if not op or not version then return nil end
177 return { op = op, version = version, no_upgrade = no_upgrade=="@" and true or nil }, rest
178end
179
180--- Convert a list of constraints from string to table format.
181-- For example, a string ">= 1.0, < 2.0" is converted to a table in the format
182-- {{op = ">=", version={1,0}}, {op = "<", version={2,0}}}.
183-- Version tables use a metatable allowing later comparison through
184-- relational operators.
185-- @param input string: A list of constraints in string format.
186-- @return table or nil: A table representing the same constraints,
187-- or nil if the input string is invalid.
188function parse_constraints(input)
189 assert(type(input) == "string")
190
191 local constraints, constraint = {}, nil
192 while #input > 0 do
193 constraint, input = parse_constraint(input)
194 if constraint then
195 table.insert(constraints, constraint)
196 else
197 return nil
198 end
199 end
200 return constraints
201end
202
203--- Convert a dependency from string to table format.
204-- For example, a string "foo >= 1.0, < 2.0"
205-- is converted to a table in the format
206-- {name = "foo", constraints = {{op = ">=", version={1,0}},
207-- {op = "<", version={2,0}}}}. Version tables use a metatable
208-- allowing later comparison through relational operators.
209-- @param dep string: A dependency in string format
210-- as entered in rockspec files.
211-- @return table or nil: A table representing the same dependency relation,
212-- or nil if the input string is invalid.
213function parse_dep(dep)
214 assert(type(dep) == "string")
215
216 local name, rest = dep:match("^%s*(%a[%w%-]*%w)%s*(.*)")
217 if not name then return nil end
218 local constraints = parse_constraints(rest)
219 if not constraints then return nil end
220 return { name = name, constraints = constraints }
221end
222
223--- Convert a version table to a string.
224-- @param v table: The version table
225-- @param internal boolean or nil: Whether to display versions in their
226-- internal representation format or how they were specified.
227-- @return string: The dependency information pretty-printed as a string.
228function show_version(v, internal)
229 assert(type(v) == "table")
230 assert(type(internal) == "boolean" or not internal)
231
232 return (internal
233 and table.concat(v, ":")..(v.revision and tostring(v.revision) or "")
234 or v.string)
235end
236
237--- Convert a dependency in table format to a string.
238-- @param dep table: The dependency in table format
239-- @param internal boolean or nil: Whether to display versions in their
240-- internal representation format or how they were specified.
241-- @return string: The dependency information pretty-printed as a string.
242function show_dep(dep, internal)
243 assert(type(dep) == "table")
244 assert(type(internal) == "boolean" or not internal)
245
246 local pretty = {}
247 for _, c in ipairs(dep.constraints) do
248 table.insert(pretty, c.op .. " " .. show_version(c.version, internal))
249 end
250 return dep.name.." "..table.concat(pretty, ", ")
251end
252
253--- A more lenient check for equivalence between versions.
254-- This returns true if the requested components of a version
255-- match and ignore the ones that were not given. For example,
256-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match.
257-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2"
258-- doesn't.
259-- @param version string or table: Version to be tested; may be
260-- in string format or already parsed into a table.
261-- @param requested string or table: Version requested; may be
262-- in string format or already parsed into a table.
263-- @return boolean: True if the tested version matches the requested
264-- version, false otherwise.
265local function partial_match(version, requested)
266 assert(type(version) == "string" or type(version) == "table")
267 assert(type(requested) == "string" or type(version) == "table")
268
269 if type(version) ~= "table" then version = parse_version(version) end
270 if type(requested) ~= "table" then requested = parse_version(requested) end
271 if not version or not requested then return false end
272
273 for i, ri in ipairs(requested) do
274 local vi = version[i] or 0
275 if ri ~= vi then return false end
276 end
277 if requested.revision then
278 return requested.revision == version.revision
279 end
280 return true
281end
282
283--- Check if a version satisfies a set of constraints.
284-- @param version table: A version in table format
285-- @param constraints table: An array of constraints in table format.
286-- @return boolean: True if version satisfies all constraints,
287-- false otherwise.
288function match_constraints(version, constraints)
289 assert(type(version) == "table")
290 assert(type(constraints) == "table")
291 local ok = true
292 setmetatable(version, version_mt)
293 for _, constr in pairs(constraints) do
294 local constr_version = constr.version
295 setmetatable(constr.version, version_mt)
296 if constr.op == "==" then ok = version == constr_version
297 elseif constr.op == "~=" then ok = version ~= constr_version
298 elseif constr.op == ">" then ok = version > constr_version
299 elseif constr.op == "<" then ok = version < constr_version
300 elseif constr.op == ">=" then ok = version >= constr_version
301 elseif constr.op == "<=" then ok = version <= constr_version
302 elseif constr.op == "~>" then ok = partial_match(version, constr_version)
303 end
304 if not ok then break end
305 end
306 return ok
307end
308
309--- Attempt to match a dependency to an installed rock.
310-- @param dep table: A dependency parsed in table format.
311-- @param blacklist table: Versions that can't be accepted. Table where keys
312-- are program versions and values are 'true'.
313-- @return table or nil: A table containing fields 'name' and 'version'
314-- representing an installed rock which matches the given dependency,
315-- or nil if it could not be matched.
316local function match_dep(dep, blacklist)
317 assert(type(dep) == "table")
318
319 local versions
320 if dep.name == "lua" then
321 versions = { "5.1" }
322 else
323 versions = manif.get_versions(dep.name)
324 end
325 if not versions then
326 return nil
327 end
328 if blacklist then
329 local i = 1
330 while versions[i] do
331 if blacklist[versions[i]] then
332 table.remove(versions, i)
333 else
334 i = i + 1
335 end
336 end
337 end
338 local candidates = {}
339 for _, vstring in ipairs(versions) do
340 local version = parse_version(vstring)
341 if match_constraints(version, dep.constraints) then
342 table.insert(candidates, version)
343 end
344 end
345 if #candidates == 0 then
346 return nil
347 else
348 table.sort(candidates)
349 return {
350 name = dep.name,
351 version = candidates[#candidates].string
352 }
353 end
354end
355
356--- Attempt to match dependencies of a rockspec to installed rocks.
357-- @param rockspec table: The rockspec loaded as a table.
358-- @param blacklist table or nil: Program versions to not use as valid matches.
359-- Table where keys are program names and values are tables where keys
360-- are program versions and values are 'true'.
361-- @return table, table: A table where keys are dependencies parsed
362-- in table format and values are tables containing fields 'name' and
363-- version' representing matches, and a table of missing dependencies
364-- parsed as tables.
365function match_deps(rockspec, blacklist)
366 assert(type(rockspec) == "table")
367 assert(type(blacklist) == "table" or not blacklist)
368 local matched, missing, no_upgrade = {}, {}, {}
369
370 for _, dep in ipairs(rockspec.dependencies) do
371 local found = match_dep(dep, blacklist and blacklist[dep.name] or nil)
372 if found then
373 if dep.name ~= "lua" then
374 matched[dep] = found
375 end
376 else
377 if dep.constraints[1] and dep.constraints[1].no_upgrade then
378 no_upgrade[dep.name] = dep
379 else
380 missing[dep.name] = dep
381 end
382 end
383 end
384 return matched, missing, no_upgrade
385end
386
387--- Return a set of values of a table.
388-- @param tbl table: The input table.
389-- @return table: The array of keys.
390local function values_set(tbl)
391 local set = {}
392 for _, v in pairs(tbl) do
393 set[v] = true
394 end
395 return set
396end
397
398--- Check dependencies of a rock and attempt to install any missing ones.
399-- Packages are installed using the LuaRocks "install" command.
400-- Aborts the program if a dependency could not be fulfilled.
401-- @param rockspec table: A rockspec in table format.
402-- @return boolean or (nil, string): True if no errors occurred, or
403-- nil and an error message if any test failed.
404function fulfill_dependencies(rockspec)
405
406 if rockspec.supported_platforms then
407 if not platforms_set then
408 platforms_set = values_set(cfg.platforms)
409 end
410 local supported = nil
411 for _, plat in pairs(rockspec.supported_platforms) do
412 local neg, plat = plat:match("^(!?)(.*)")
413 if neg == "!" then
414 if platforms_set[plat] then
415 return nil, "This rockspec for "..rockspec.package.." does not support "..plat.." platforms."
416 end
417 else
418 if platforms_set[plat] then
419 supported = true
420 else
421 if supported == nil then
422 supported = false
423 end
424 end
425 end
426 end
427 if supported == false then
428 local plats = table.concat(cfg.platforms, ", ")
429 return nil, "This rockspec for "..rockspec.package.." does not support "..plats.." platforms."
430 end
431 end
432
433 local matched, missing, no_upgrade = match_deps(rockspec)
434
435 if next(no_upgrade) then
436 print("Missing dependencies for "..rockspec.name.." "..rockspec.version..":")
437 for _, dep in pairs(no_upgrade) do
438 print(show_dep(dep))
439 end
440 if next(missing) then
441 for _, dep in pairs(missing) do
442 print(show_dep(dep))
443 end
444 end
445 print()
446 for _, dep in pairs(no_upgrade) do
447 print("This version of "..rockspec.name.." is designed for use with")
448 print(show_dep(dep)..", but is configured to avoid upgrading it")
449 print("automatically. Please upgrade "..dep.name.." with")
450 print(" luarocks install "..dep.name)
451 print("or choose an older version of "..rockspec.name.." with")
452 print(" luarocks search "..rockspec.name)
453 end
454 return nil, "Failed matching dependencies."
455 end
456
457 if next(missing) then
458 print()
459 print("Missing dependencies for "..rockspec.name..":")
460 for _, dep in pairs(missing) do
461 print(show_dep(dep))
462 end
463 print()
464
465 for _, dep in pairs(missing) do
466 -- Double-check in case dependency was filled during recursion.
467 if not match_dep(dep) then
468 local rock = search.find_suitable_rock(dep)
469 if not rock then
470 return nil, "Could not find a rock to satisfy dependency: "..show_dep(dep)
471 end
472 local ok, err = install.run(rock)
473 if not ok then
474 return nil, "Failed installing dependency: "..rock.." - "..err
475 end
476 end
477 end
478 end
479 return true
480end
481
482--- Set up path-related variables for external dependencies.
483-- For each key in the external_dependencies table in the
484-- rockspec file, four variables are created: <key>_DIR, <key>_BINDIR,
485-- <key>_INCDIR and <key>_LIBDIR. These are not overwritten
486-- if already set (e.g. by the LuaRocks config file or through the
487-- command-line). Values in the external_dependencies table
488-- are tables that may contain a "header" or a "library" field,
489-- with filenames to be tested for existence.
490-- @param rockspec table: The rockspec table.
491-- @param mode string: if "build" is given, checks all files;
492-- if "install" is given, do not scan for headers.
493-- @return boolean or (nil, string): True if no errors occurred, or
494-- nil and an error message if any test failed.
495function check_external_deps(rockspec, mode)
496 assert(type(rockspec) == "table")
497
498 local vars = rockspec.variables
499 local patterns = cfg.external_deps_patterns
500 local subdirs = cfg.external_deps_subdirs
501 if mode == "install" then
502 patterns = cfg.runtime_external_deps_patterns
503 subdirs = cfg.runtime_external_deps_subdirs
504 end
505 local dirs = {
506 BINDIR = { subdir = subdirs.bin, testfile = "program", pattern = patterns.bin },
507 INCDIR = { subdir = subdirs.include, testfile = "header", pattern = patterns.include },
508 LIBDIR = { subdir = subdirs.lib, testfile = "library", pattern = patterns.lib }
509 }
510 if mode == "install" then
511 dirs.INCDIR = nil
512 end
513 if rockspec.external_dependencies then
514 for name, files in pairs(rockspec.external_dependencies) do
515 local ok = true
516 local failed_file = nil
517 for _, extdir in ipairs(cfg.external_deps_dirs) do
518 ok = true
519 local prefix = vars[name.."_DIR"]
520 if not prefix then
521 prefix = extdir
522 end
523 for dirname, dirdata in pairs(dirs) do
524 dirdata.dir = vars[name.."_"..dirname] or fs.make_path(prefix, dirdata.subdir)
525 local file = files[dirdata.testfile]
526 if file then
527 local files = {}
528 if not file:match("%.") then
529 for _, pattern in ipairs(dirdata.pattern) do
530 table.insert(files, pattern:gsub("?", file))
531 end
532 else
533 table.insert(files, file)
534 end
535 local found = false
536 failed_file = nil
537 for _, f in pairs(files) do
538 if f:match("%.so$") or f:match("%.dylib$") or f:match("%.dll$") then
539 f = f:gsub("%.[^.]+$", "."..cfg.external_lib_extension)
540 end
541 local testfile = fs.make_path(dirdata.dir, f)
542 if fs.exists(testfile) then
543 found = true
544 break
545 else
546 if failed_file then
547 failed_file = failed_file .. ", or " .. f
548 else
549 failed_file = f
550 end
551 end
552 end
553 if not found then
554 ok = false
555 break
556 end
557 end
558 end
559 if ok then
560 for dirname, dirdata in pairs(dirs) do
561 vars[name.."_"..dirname] = dirdata.dir
562 end
563 vars[name.."_DIR"] = prefix
564 break
565 end
566 end
567 if not ok then
568 return nil, "Could not find expected file "..failed_file.." for "..name.." -- you may have to install "..name.." in your system and/or set the "..name.."_DIR variable"
569 end
570 end
571 end
572 return true
573end
574
575--- Recursively scan dependencies, to build a transitive closure of all
576-- dependent packages.
577-- @param results table: The results table being built.
578-- @param name string: Package name.
579-- @param version string: Package version.
580-- @return (table, table): The results and a table of missing dependencies.
581function scan_deps(results, missing, manifest, name, version)
582 assert(type(results) == "table")
583 assert(type(missing) == "table")
584 assert(type(name) == "string")
585 assert(type(version) == "string")
586
587 local err
588 if results[name] then
589 return results, missing
590 end
591 if not manifest.dependencies then manifest.dependencies = {} end
592 local dependencies = manifest.dependencies
593 if not dependencies[name] then dependencies[name] = {} end
594 local dependencies_name = dependencies[name]
595 local deplist = dependencies_name[version]
596 local rockspec, err
597 if not deplist then
598 rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version))
599 if err then
600 missing[name.." "..version] = true
601 return results, missing
602 end
603 dependencies_name[version] = rockspec.dependencies
604 else
605 rockspec = { dependencies = deplist }
606 end
607 local matched, failures = match_deps(rockspec)
608 for _, match in pairs(matched) do
609 results, missing = scan_deps(results, missing, manifest, match.name, match.version)
610 end
611 if next(failures) then
612 for _, failure in pairs(failures) do
613 missing[show_dep(failure)] = true
614 end
615 end
616 results[name] = version
617 return results, missing
618end
diff --git a/src/luarocks/fetch.lua b/src/luarocks/fetch.lua
new file mode 100644
index 00000000..b61fab28
--- /dev/null
+++ b/src/luarocks/fetch.lua
@@ -0,0 +1,284 @@
1
2--- Functions related to fetching and loading local and remote files.
3module("luarocks.fetch", package.seeall)
4
5local fs = require("luarocks.fs")
6local type_check = require("luarocks.type_check")
7local path = require("luarocks.path")
8local deps = require("luarocks.deps")
9local persist = require("luarocks.persist")
10local util = require("luarocks.util")
11
12--- Fetch a local or remote file.
13-- Make a remote or local URL/pathname local, fetching the file if necessary.
14-- Other "fetch" and "load" functions use this function to obtain files.
15-- If a local pathname is given, it is returned as a result.
16-- @param url string: a local pathname or a remote URL.
17-- @param filename string or nil: this function attempts to detect the
18-- resulting local filename of the remote file as the basename of the URL;
19-- if that is not correct (due to a redirection, for example), the local
20-- filename can be given explicitly as this second argument.
21-- @return string or (nil, string): the absolute local pathname for the
22-- fetched file, or nil and a message in case of errors.
23function fetch_url(url, filename)
24 assert(type(url) == "string")
25 assert(type(filename) == "string" or not filename)
26
27 local protocol, pathname = fs.split_url(url)
28 if protocol == "file" then
29 return fs.absolute_name(pathname)
30 elseif protocol == "http" or protocol == "ftp" or protocol == "https" then
31 local ok = fs.download(url)
32 if not ok then
33 return nil, "Failed downloading "..url
34 end
35 return fs.make_path(fs.current_dir(), filename or fs.base_name(url))
36 else
37 return nil, "Unsupported protocol "..protocol
38 end
39end
40
41--- For remote URLs, create a temporary directory and download URL inside it.
42-- This temporary directory will be deleted on program termination.
43-- For local URLs, just return the local pathname and its directory.
44-- @param url string: URL to be downloaded
45-- @param tmpname string: name pattern to use for avoiding conflicts
46-- when creating temporary directory.
47-- @param filename string or nil: local filename of URL to be downloaded,
48-- in case it can't be inferred from the URL.
49-- @return (string, string) or (nil, string): absolute local pathname of
50-- the fetched file and temporary directory name; or nil and an error message.
51function fetch_url_at_temp_dir(url, tmpname, filename)
52 assert(type(url) == "string")
53 assert(type(tmpname) == "string")
54 assert(type(filename) == "string" or not filename)
55 filename = filename or fs.base_name(url)
56
57 local protocol, pathname = fs.split_url(url)
58 if protocol == "file" then
59 return pathname, fs.dir_name(pathname)
60 else
61 local dir = fs.make_temp_dir(tmpname)
62 if not dir then
63 return nil, "Failed creating temporary directory."
64 end
65 util.schedule_function(fs.delete, dir)
66 fs.change_dir(dir)
67 local file, err = fetch_url(url, filename)
68 if not file then
69 return nil, "Error fetching file: "..err
70 end
71 fs.pop_dir()
72 return file, dir
73 end
74end
75
76--- Obtain a rock and unpack it.
77-- If a directory is not given, a temporary directory will be created,
78-- which will be deleted on program termination.
79-- @param rock_file string: URL or filename of the rock.
80-- @param dest string or nil: if given, directory will be used as
81-- a permanent destination.
82-- @return string or (nil, string): the directory containing the contents
83-- of the unpacked rock.
84function fetch_and_unpack_rock(rock_file, dest)
85 assert(type(rock_file) == "string")
86 assert(type(dest) == "string" or not dest)
87
88 local name = fs.base_name(rock_file):match("(.*)%.[^.]*%.rock")
89
90 local rock_file, err = fetch_url_at_temp_dir(rock_file,"luarocks-rock-"..name)
91 if not rock_file then
92 return nil, "Could not fetch rock file: " .. err
93 end
94
95 rock_file = fs.absolute_name(rock_file)
96 local dir
97 if dest then
98 dir = dest
99 fs.make_dir(dir)
100 else
101 dir = fs.make_temp_dir(name)
102 end
103 if not dest then
104 util.schedule_function(fs.delete, dir)
105 end
106 fs.change_dir(dir)
107 local ok = fs.unzip(rock_file)
108 if not ok then
109 return nil, "Failed unpacking rock file: " .. rock_file
110 end
111 fs.pop_dir()
112 return dir
113end
114
115--- Back-end function that actually loads the local rockspec.
116-- Performs some validation and postprocessing of the rockspec contents.
117-- @param file string: The local filename of the rockspec file.
118-- @return table or (nil, string): A table representing the rockspec
119-- or nil followed by an error message.
120function load_local_rockspec(filename)
121 assert(type(filename) == "string")
122
123 local rockspec, err = persist.load_into_table(filename)
124 if not rockspec then
125 return nil, "Could not load rockspec file "..filename.." ("..err..")"
126 end
127
128 local ok, err = type_check.type_check_rockspec(rockspec)
129 if not ok then
130 return nil, filename..": "..err
131 end
132
133 if rockspec.rockspec_format then
134 if deps.compare_versions(rockspec.rockspec_format, type_check.rockspec_format) then
135 return nil, "Rockspec format "..rockspec.rockspec_format.." is not supported, please upgrade LuaRocks."
136 end
137 end
138
139 util.platform_overrides(rockspec.build)
140 util.platform_overrides(rockspec.dependencies)
141 util.platform_overrides(rockspec.external_dependencies)
142 util.platform_overrides(rockspec.source)
143 util.platform_overrides(rockspec.hooks)
144
145 local basename = fs.base_name(filename)
146 rockspec.name = basename:match("(.*)-[^-]*-[0-9]*")
147 if not rockspec.name then
148 return nil, "Expected filename in format 'name-version-revision.rockspec'."
149 end
150
151 local protocol, pathname = fs.split_url(rockspec.source.url)
152 if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
153 rockspec.source.file = rockspec.source.file or fs.base_name(rockspec.source.url)
154 end
155 rockspec.source.protocol, rockspec.source.pathname = protocol, pathname
156
157 -- Temporary compatibility
158 if not rockspec.source.module then
159 rockspec.source.module = rockspec.source.cvs_module
160 rockspec.source.tag = rockspec.source.cvs_tag
161 end
162
163 local name_version = rockspec.package:lower() .. "-" .. rockspec.version
164 if basename ~= name_version .. ".rockspec" then
165 return nil, "Inconsistency between rockspec filename ("..basename..") and its contents ("..name_version..".rockspec)."
166 end
167
168 rockspec.local_filename = filename
169 local filebase = rockspec.source.file or rockspec.source.url
170 local base = fs.base_name(filebase)
171 base = base:gsub("%.[^.]*$", ""):gsub("%.tar$", "")
172 rockspec.source.dir = rockspec.source.dir
173 or rockspec.source.module
174 or ((filebase:match(".lua$") or filebase:match(".c$")) and ".")
175 or base
176 if rockspec.dependencies then
177 for i = 1, #rockspec.dependencies do
178 local parsed = deps.parse_dep(rockspec.dependencies[i])
179 if not parsed then
180 return nil, "Parse error processing dependency '"..rockspec.dependencies[i].."'"
181 end
182 rockspec.dependencies[i] = parsed
183 end
184 else
185 rockspec.dependencies = {}
186 end
187 local ok, err = path.configure_paths(rockspec)
188 if err then
189 return nil, "Error verifying paths: "..err
190 end
191
192 return rockspec
193end
194
195--- Load a local or remote rockspec into a table.
196-- This is the entry point for the LuaRocks tools.
197-- Only the LuaRocks runtime loader should use
198-- load_local_rockspec directly.
199-- @param filename string: Local or remote filename of a rockspec.
200-- @return table or (nil, string): A table representing the rockspec
201-- or nil followed by an error message.
202function load_rockspec(filename)
203 assert(type(filename) == "string")
204
205 local name = fs.base_name(filename):match("(.*)%.rockspec")
206 if not name then
207 return nil, "Filename '"..filename.."' does not look like a rockspec."
208 end
209
210 local filename, err = fetch_url_at_temp_dir(filename,"luarocks-rockspec-"..name)
211 if not filename then
212 return nil, err
213 end
214
215 return load_local_rockspec(filename)
216end
217
218--- Download sources for building a rock using the basic URL downloader.
219-- @param rockspec table: The rockspec table
220-- @param extract boolean: Whether to extract the sources from
221-- the fetched source tarball or not.
222-- @param dest_dir string or nil: If set, will extract to the given directory.
223-- @return (string, string) or (nil, string): The absolute pathname of
224-- the fetched source tarball and the temporary directory created to
225-- store it; or nil and an error message.
226function get_sources(rockspec, extract, dest_dir)
227 assert(type(rockspec) == "table")
228 assert(type(extract) == "boolean")
229 assert(type(dest_dir) == "string" or not dest_dir)
230
231 local url = rockspec.source.url
232 local name = rockspec.name.."-"..rockspec.version
233 local filename = rockspec.source.file
234 local source_file, dir, err
235 if dest_dir then
236 fs.change_dir(dest_dir)
237 source_file, err = fetch_url(url, filename)
238 fs.pop_dir()
239 dir = dest_dir
240 else
241 source_file, dir = fetch_url_at_temp_dir(url, "luarocks-source-"..name, filename)
242 end
243 if not source_file then
244 return nil, err or dir
245 end
246 if rockspec.source.md5 then
247 if not fs.check_md5(source_file, rockspec.source.md5) then
248 return nil, "MD5 check for "..filename.." has failed."
249 end
250 end
251 if extract then
252 fs.change_dir(dir)
253 fs.unpack_archive(rockspec.source.file)
254 fs.pop_dir()
255 end
256 return source_file, dir
257end
258
259--- Download sources for building a rock, calling the appropriate protocol method.
260-- @param rockspec table: The rockspec table
261-- @param extract boolean: When downloading compressed formats, whether to extract
262-- the sources from the fetched archive or not.
263-- @param dest_dir string or nil: If set, will extract to the given directory.
264-- @return (string, string) or (nil, string): The absolute pathname of
265-- the fetched source tarball and the temporary directory created to
266-- store it; or nil and an error message.
267function fetch_sources(rockspec, extract, dest_dir)
268 assert(type(rockspec) == "table")
269 assert(type(extract) == "boolean")
270 assert(type(dest_dir) == "string" or not dest_dir)
271
272 local protocol = rockspec.source.protocol
273 local proto
274 if protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" then
275 proto = require("luarocks.fetch")
276 else
277 ok, proto = pcall(require, "luarocks.fetch."..protocol)
278 if not ok then
279 return nil, "Unknown protocol "..protocol
280 end
281 end
282
283 return proto.get_sources(rockspec, extract, dest_dir)
284end
diff --git a/src/luarocks/fetch/cvs.lua b/src/luarocks/fetch/cvs.lua
new file mode 100644
index 00000000..291388cb
--- /dev/null
+++ b/src/luarocks/fetch/cvs.lua
@@ -0,0 +1,43 @@
1
2--- Fetch back-end for retrieving sources from CVS.
3module("luarocks.fetch.cvs", package.seeall)
4
5local fs = require("luarocks.fs")
6local util = require("luarocks.util")
7
8--- Download sources for building a rock, using CVS.
9-- @param rockspec table: The rockspec table
10-- @param extract boolean: Unused in this module (required for API purposes.)
11-- @param dest_dir string or nil: If set, will extract to the given directory.
12-- @return (string, string) or (nil, string): The absolute pathname of
13-- the fetched source tarball and the temporary directory created to
14-- store it; or nil and an error message.
15function get_sources(rockspec, extract, dest_dir)
16 assert(type(rockspec) == "table")
17 assert(type(dest_dir) == "string" or not dest_dir)
18
19 local name_version = rockspec.name .. "-" .. rockspec.version
20 local module = rockspec.source.module or fs.base_name(rockspec.source.url)
21 local command = {"cvs", "-d"..rockspec.source.pathname, "export", module}
22 if rockspec.source.tag then
23 table.insert(command, 4, "-r")
24 table.insert(command, 5, rockspec.source.tag)
25 end
26 local dir
27 if not dest_dir then
28 dir = fs.make_temp_dir(name_version)
29 if not dir then
30 return nil, "Failed creating temporary directory."
31 end
32 util.schedule_function(fs.delete, dir)
33 else
34 dir = dest_dir
35 end
36 fs.change_dir(dir)
37 if not fs.execute(unpack(command)) then
38 return nil, "Failed fetching files from CVS."
39 end
40 fs.pop_dir()
41 return module, dir
42end
43
diff --git a/src/luarocks/fetch/git.lua b/src/luarocks/fetch/git.lua
new file mode 100644
index 00000000..d0d204a9
--- /dev/null
+++ b/src/luarocks/fetch/git.lua
@@ -0,0 +1,53 @@
1
2--- Fetch back-end for retrieving sources from GIT.
3module("luarocks.fetch.git", package.seeall)
4
5local fs = require("luarocks.fs")
6local util = require("luarocks.util")
7
8--- Download sources for building a rock, using git.
9-- @param rockspec table: The rockspec table
10-- @param extract boolean: Unused in this module (required for API purposes.)
11-- @param dest_dir string or nil: If set, will extract to the given directory.
12-- @return (string, string) or (nil, string): The absolute pathname of
13-- the fetched source tarball and the temporary directory created to
14-- store it; or nil and an error message.
15function get_sources(rockspec, extract, dest_dir)
16 assert(type(rockspec) == "table")
17 assert(type(dest_dir) == "string" or not dest_dir)
18
19 local name_version = rockspec.name .. "-" .. rockspec.version
20 local module = fs.base_name(rockspec.source.url)
21 -- Strip off .git from base name if present
22 module = module:gsub("%.git$", "")
23 local command = {"git", "clone", rockspec.source.url, module}
24 local checkout_command
25 local tag_or_branch = rockspec.source.tag or rockspec.source.branch
26 if tag_or_branch then
27 checkout_command = {"git", "checkout", tag_or_branch}
28 end
29 local dir
30 if not dest_dir then
31 dir = fs.make_temp_dir(name_version)
32 if not dir then
33 return nil, "Failed creating temporary directory."
34 end
35 util.schedule_function(fs.delete, dir)
36 else
37 dir = dest_dir
38 end
39 fs.change_dir(dir)
40 if not fs.execute(unpack(command)) then
41 return nil, "Failed fetching files from GIT while cloning."
42 end
43 if checkout_command then
44 fs.change_dir(module)
45 if not fs.execute(unpack(checkout_command)) then
46 return nil, "Failed fetching files from GIT while getting tag/branch."
47 end
48 fs.pop_dir()
49 end
50 fs.pop_dir()
51 return module, dir
52end
53
diff --git a/src/luarocks/fetch/sscm.lua b/src/luarocks/fetch/sscm.lua
new file mode 100644
index 00000000..a2c70b96
--- /dev/null
+++ b/src/luarocks/fetch/sscm.lua
@@ -0,0 +1,40 @@
1
2--- Fetch back-end for retrieving sources from Surround SCM Server
3module("luarocks.fetch.sscm", package.seeall)
4
5local fs = require("luarocks.fs")
6
7--- Download sources via Surround SCM Server for building a rock.
8-- @param rockspec table: The rockspec table
9-- @param extract boolean: Unused in this module (required for API purposes.)
10-- @param dest_dir string or nil: If set, will extract to the given directory.
11-- @return (string, string) or (nil, string): The absolute pathname of
12-- the fetched source tarball and the temporary directory created to
13-- store it; or nil and an error message.
14function get_sources(rockspec, extract, dest_dir)
15 assert(type(rockspec) == "table")
16 assert(type(dest_dir) == "string" or not dest_dir)
17
18 local module = rockspec.source.module or fs.base_name(rockspec.source.url)
19 local branch, repository = string.match(rockspec.source.pathname, "^([^/]*)/(.*)")
20 if not branch or not repository then
21 return nil, "Error retrieving branch and repository from rockspec."
22 end
23 -- Search for working directory.
24 local working_dir
25 local tmp = io.popen(string.format([[sscm property "/" -d -b%s -p%s]], branch, repository))
26 for line in tmp:lines() do
27 --%c because a chr(13) comes in the end.
28 working_dir = string.match(line, "Working directory:[%s]*(.*)%c$")
29 if working_dir then break end
30 end
31 tmp:close()
32 if not working_dir then
33 return nil, "Error retrieving working directory from SSCM."
34 end
35 if not fs.execute([["sscm"]], "get", "*", "-e" , "-r", "-b"..branch, "-p"..repository, "-tmodify", "-wreplace") then
36 return nil, "Failed fetching files from SSCM."
37 end
38 -- FIXME: This function does not honor the dest_dir parameter.
39 return module, working_dir
40end
diff --git a/src/luarocks/fs.lua b/src/luarocks/fs.lua
new file mode 100644
index 00000000..7027167a
--- /dev/null
+++ b/src/luarocks/fs.lua
@@ -0,0 +1,43 @@
1
2local rawset = rawset
3
4--- Proxy module for filesystem and platform abstractions.
5-- All code using "fs" code should require "luarocks.fs",
6-- and not the various platform-specific implementations.
7-- However, see the documentation of the implementation
8-- for the API reference.
9module("luarocks.fs", package.seeall)
10
11local cfg = require("luarocks.cfg")
12
13local fs_impl = nil
14for _, platform in ipairs(cfg.platforms) do
15 local ok, result = pcall(require, "luarocks.fs."..platform)
16 if ok then
17 fs_impl = result
18 if fs_impl then
19 break
20 end
21 end
22end
23
24local fs_unix = require("luarocks.fs.unix")
25
26local fs_mt = {
27 __index = function(t, k)
28 local impl = fs_impl and fs_impl[k]
29 if not impl then
30 impl = fs_unix[k]
31 end
32 rawset(t, k, impl)
33 return impl
34 end
35}
36
37setmetatable(luarocks.fs, fs_mt)
38
39fs_unix.init_fs_functions(luarocks.fs)
40if fs_impl then
41 fs_impl.init_fs_functions(luarocks.fs)
42end
43
diff --git a/src/luarocks/fs/unix.lua b/src/luarocks/fs/unix.lua
new file mode 100644
index 00000000..2a849329
--- /dev/null
+++ b/src/luarocks/fs/unix.lua
@@ -0,0 +1,519 @@
1
2local assert, type, table, io, package, math, os, ipairs =
3 assert, type, table, io, package, math, os, ipairs
4
5--- Unix implementation of filesystem and platform abstractions.
6module("luarocks.fs.unix", package.seeall)
7
8local cfg = require("luarocks.cfg")
9
10math.randomseed(os.time())
11
12dir_stack = {}
13
14local fs_absolute_name,
15 fs_base_name,
16 fs_copy,
17 fs_current_dir,
18 fs_dir_stack,
19 fs_execute,
20 fs_execute_string,
21 fs_is_dir,
22 fs_make_dir,
23 fs_make_path,
24 fs_exists,
25 fs_find,
26 fs_Q
27
28function init_fs_functions(impl)
29 fs_absolute_name = impl.absolute_name
30 fs_base_name = impl.base_name
31 fs_copy = impl.copy
32 fs_current_dir = impl.current_dir
33 fs_dir_stack = impl.dir_stack
34 fs_execute = impl.execute
35 fs_execute_string = impl.execute_string
36 fs_is_dir = impl.is_dir
37 fs_make_dir = impl.make_dir
38 fs_make_path = impl.make_path
39 fs_exists = impl.exists
40 fs_find = impl.find
41 fs_Q = impl.Q
42end
43
44dir_separator = "/"
45
46--- Quote argument for shell processing.
47-- Adds single quotes and escapes.
48-- @param arg string: Unquoted argument.
49-- @return string: Quoted argument.
50function Q(arg)
51 assert(type(arg) == "string")
52
53 return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
54end
55
56--- Obtain current directory.
57-- Uses the module's internal dir stack.
58-- @return string: the absolute pathname of the current directory.
59function current_dir()
60 local current = os.getenv("PWD")
61 if not current then
62 local pipe = io.popen("pwd")
63 current = pipe:read("*l")
64 pipe:close()
65 end
66 for _, dir in ipairs(fs_dir_stack) do
67 current = fs_absolute_name(dir, current)
68 end
69 return current
70end
71
72--- Run the given command.
73-- The command is executed in the current directory in the dir stack.
74-- @param cmd string: No quoting/escaping is applied to the command.
75-- @return boolean: true if command succeeds (status code 0), false
76-- otherwise.
77function execute_string(cmd)
78 if os.execute("cd " .. fs_Q(fs_current_dir()) .. " && " .. cmd) == 0 then
79 return true
80 else
81 return false
82 end
83end
84
85--- Run the given command, quoting its arguments.
86-- The command is executed in the current directory in the dir stack.
87-- @param command string: The command to be executed. No quoting/escaping
88-- is applied.
89-- @param ... Strings containing additional arguments, which are quoted.
90-- @return boolean: true if command succeeds (status code 0), false
91-- otherwise.
92function execute(command, ...)
93 assert(type(command) == "string")
94
95 for _, arg in ipairs({...}) do
96 assert(type(arg) == "string")
97 command = command .. " " .. fs_Q(arg)
98 end
99 return fs_execute_string(command)
100end
101
102--- Change the current directory.
103-- Uses the module's internal dir stack. This does not have exact
104-- semantics of chdir, as it does not handle errors the same way,
105-- but works well for our purposes for now.
106-- @param dir string: The directory to switch to.
107function change_dir(dir)
108 assert(type(dir) == "string")
109 table.insert(fs_dir_stack, dir)
110end
111
112--- Change directory to root.
113-- Allows leaving a directory (e.g. for deleting it) in
114-- a crossplatform way.
115function change_dir_to_root()
116 table.insert(fs_dir_stack, "/")
117end
118
119--- Change working directory to the previous in the dir stack.
120function pop_dir()
121 table.remove(fs_dir_stack)
122end
123
124--- Create a directory if it does not already exist.
125-- If any of the higher levels in the path name does not exist
126-- too, they are created as well.
127-- @param dir string: pathname of directory to create.
128-- @return boolean: true on success, false on failure.
129function make_dir(dir)
130 assert(dir)
131 return fs_execute("mkdir -p", dir)
132end
133
134--- Remove a directory if it is empty.
135-- Does not return errors (for example, if directory is not empty or
136-- if already does not exist)
137-- @param dir string: pathname of directory to remove.
138function remove_dir_if_empty(dir)
139 assert(dir)
140 fs_execute_string("rmdir "..fs_Q(dir).." 1> /dev/null 2> /dev/null")
141end
142
143--- Copy a file.
144-- @param src string: Pathname of source
145-- @param dest string: Pathname of destination
146-- @return boolean or (boolean, string): true on success, false on failure,
147-- plus an error message.
148function copy(src, dest)
149 assert(src and dest)
150 if fs_execute("cp", src, dest) then
151 return true
152 else
153 return false, "Failed copying "..src.." to "..dest
154 end
155end
156
157--- Recursively copy the contents of a directory.
158-- @param src string: Pathname of source
159-- @param dest string: Pathname of destination
160-- @return boolean or (boolean, string): true on success, false on failure,
161-- plus an error message.
162function copy_contents(src, dest)
163 assert(src and dest)
164 if fs_execute_string("cp -a "..fs_Q(src).."/* "..fs_Q(dest).." 1> /dev/null 2>/dev/null") then
165 return true
166 else
167 return false, "Failed copying "..src.." to "..dest
168 end
169end
170
171--- Delete a file or a directory and all its contents.
172-- For safety, this only accepts absolute paths.
173-- @param arg string: Pathname of source
174-- @return boolean: true on success, false on failure.
175function delete(arg)
176 assert(arg)
177 assert(arg:sub(1,1) == "/")
178 return fs_execute_string("rm -rf " .. fs_Q(arg) .. " 1> /dev/null 2>/dev/null")
179end
180
181--- List the contents of a directory.
182-- @param at string or nil: directory to list (will be the current
183-- directory if none is given).
184-- @return table: an array of strings with the filenames representing
185-- the contents of a directory.
186function dir(at)
187 assert(type(at) == "string" or not at)
188 if not at then
189 at = fs_current_dir()
190 end
191 if not fs_is_dir(at) then
192 return {}
193 end
194 local result = {}
195 local pipe = io.popen("cd "..fs_Q(at).." && ls")
196 for file in pipe:lines() do
197 table.insert(result, file)
198 end
199 pipe:close()
200 return result
201end
202
203--- Recursively scan the contents of a directory.
204-- @param at string or nil: directory to scan (will be the current
205-- directory if none is given).
206-- @return table: an array of strings with the filenames representing
207-- the contents of a directory.
208function find(at)
209 assert(type(at) == "string" or not at)
210 if not at then
211 at = fs_current_dir()
212 end
213 if not fs_is_dir(at) then
214 return {}
215 end
216 local result = {}
217 local pipe = io.popen("cd "..fs_Q(at).." && find * 2>/dev/null")
218 for file in pipe:lines() do
219 table.insert(result, file)
220 end
221 pipe:close()
222 return result
223end
224
225--- Compress files in a .zip archive.
226-- @param zipfile string: pathname of .zip archive to be created.
227-- @param ... Filenames to be stored in the archive are given as
228-- additional arguments.
229-- @return boolean: true on success, false on failure.
230function zip(zipfile, ...)
231 return fs_execute("zip -r", zipfile, ...)
232end
233
234--- Uncompress files from a .zip archive.
235-- @param zipfile string: pathname of .zip archive to be extracted.
236-- @return boolean: true on success, false on failure.
237function unzip(zipfile)
238 assert(zipfile)
239 return fs_execute("unzip", zipfile)
240end
241
242--- Test for existance of a file.
243-- @param file string: filename to test
244-- @return boolean: true if file exists, false otherwise.
245function exists(file)
246 assert(file)
247 return fs_execute("test -r", file)
248end
249
250--- Test is file/dir is writable.
251-- @param file string: filename to test
252-- @return boolean: true if file exists, false otherwise.
253function is_writable(file)
254 assert(file)
255 return fs_execute("test -w", file)
256end
257
258--- Test is pathname is a directory.
259-- @param file string: pathname to test
260-- @return boolean: true if it is a directory, false otherwise.
261function is_dir(file)
262 assert(file)
263 return fs_execute("test -d", file)
264end
265
266--- Download a remote file.
267-- @param url string: URL to be fetched.
268-- @param filename string or nil: this function attempts to detect the
269-- resulting local filename of the remote file as the basename of the URL;
270-- if that is not correct (due to a redirection, for example), the local
271-- filename can be given explicitly as this second argument.
272-- @return boolean: true on success, false on failure.
273function download(url, filename)
274 assert(type(url) == "string")
275 assert(type(filename) == "string" or not filename)
276
277 if cfg.downloader == "wget" then
278 if filename then
279 return fs_execute("wget --quiet --continue --output-document ", filename, url)
280 else
281 return fs_execute("wget --quiet --continue ", url)
282 end
283 elseif cfg.downloader == "curl" then
284 filename = filename or fs_base_name(url)
285 return fs_execute_string("curl "..fs_Q(url).." 2> /dev/null 1> "..fs_Q(filename))
286 end
287end
288
289--- Strip the path off a path+filename.
290-- @param pathname string: A path+name, such as "/a/b/c".
291-- @return string: The filename without its path, such as "c".
292function base_name(pathname)
293 assert(type(pathname) == "string")
294
295 local base = pathname:match(".*/([^/]*)")
296 return base or pathname
297end
298
299--- Strip the name off a path+filename.
300-- @param pathname string: A path+name, such as "/a/b/c".
301-- @return string: The filename without its path, such as "/a/b/".
302-- For entries such as "/a/b/", "/a/" is returned. If there are
303-- no directory separators in input, "" is returned.
304function dir_name(pathname)
305 assert(type(pathname) == "string")
306
307 local dir = pathname:gsub("/*$", ""):match("(.*/)[^/]*")
308 return dir or ""
309end
310
311--- Create a temporary directory.
312-- @param name string: name pattern to use for avoiding conflicts
313-- when creating temporary directory.
314-- @return string or nil: name of temporary directory or nil on failure.
315function make_temp_dir(name)
316 assert(type(name) == "string")
317
318 local temp_dir = (os.getenv("TMP") or "/tmp") .. "/" .. name .. "-" .. tostring(math.floor(math.random() * 10000))
319 if fs_make_dir(temp_dir) then
320 return temp_dir
321 else
322 return nil
323 end
324end
325
326--- Apply a patch.
327-- @param patchname string: The filename of the patch.
328function patch(patchname)
329 return fs_execute("patch -p1 -f -i ", patchname)
330end
331
332--- Unpack an archive.
333-- Extract the contents of an archive, detecting its format by
334-- filename extension.
335-- @param archive string: Filename of archive.
336-- @return boolean or (boolean, string): true on success, false and an error message on failure.
337function unpack_archive(archive)
338 assert(type(archive) == "string")
339
340 local ok
341 if archive:match("%.tar%.gz$") or archive:match("%.tgz$") then
342 -- ok = fs_execute("tar zxvpf ", archive)
343 ok = fs_execute_string("gunzip -c "..archive.."|tar -xf -")
344 elseif archive:match("%.tar%.bz2$") then
345 -- ok = fs_execute("tar jxvpf ", archive)
346 ok = fs_execute_string("bunzip2 -c "..archive.."|tar -xf -")
347 elseif archive:match("%.zip$") then
348 ok = fs_execute("unzip ", archive)
349 elseif archive:match("%.lua$") or archive:match("%.c$") then
350 -- Ignore .lua and .c files; they don't need to be extracted.
351 return true
352 else
353 local ext = archive:match(".*(%..*)")
354 return false, "Unrecognized filename extension "..(ext or "")
355 end
356 if not ok then
357 return false, "Failed extracting "..archive
358 end
359 return true
360end
361
362--- Check the MD5 checksum for a file.
363-- @param file string: The file to be checked.
364-- @param md5sum string: The string with the expected MD5 checksum.
365-- @return boolean: true if the MD5 checksum for 'file' equals 'md5sum', false if not
366-- or if it could not perform the check for any reason.
367function check_md5(file, md5sum)
368 file = fs_absolute_name(file)
369 local computed
370 if cfg.md5checker == "md5sum" then
371 local pipe = io.popen("md5sum "..file)
372 computed = pipe:read("*l"):gsub("[^%x]+", "")
373 pipe:close()
374 if computed then
375 computed = computed:sub(1,32)
376 end
377 elseif cfg.md5checker == "openssl" then
378 local pipe = io.popen("openssl md5 "..file)
379 computed = pipe:read("*l")
380 pipe:close()
381 if computed then
382 computed = computed:sub(-32)
383 end
384 elseif cfg.md5checker == "md5" then
385 local pipe = io.popen("md5 "..file)
386 computed = pipe:read("*l")
387 pipe:close()
388 if computed then
389 computed = computed:sub(-32)
390 end
391 end
392 if not computed then
393 return false
394 end
395 if computed:match("^"..md5sum) then
396 return true
397 else
398 return false
399 end
400end
401
402--- Return an absolute pathname from a potentially relative one.
403-- @param pathname string: pathname to convert.
404-- @param relative_to string or nil: path to prepend when making
405-- pathname absolute, or the current dir in the dir stack if
406-- not given.
407-- @return string: The pathname converted to absolute.
408function absolute_name(pathname, relative_to)
409 assert(type(pathname) == "string")
410 assert(type(relative_to) == "string" or not relative_to)
411
412 relative_to = relative_to or fs_current_dir()
413 if pathname:sub(1,1) == "/" then
414 return pathname
415 else
416 return relative_to .. "/" .. pathname
417 end
418end
419
420--- Describe a path in a cross-platform way.
421-- Use this function to avoid platform-specific directory
422-- separators in other modules. If the first item contains a
423-- protocol descriptor (e.g. "http:"), paths are always constituted
424-- with forward slashes.
425-- @param ... strings representing directories
426-- @return string: a string with a platform-specific representation
427-- of the path.
428function make_path(...)
429 local items = {...}
430 local i = 1
431 while items[i] do
432 if items[i] == "" then
433 table.remove(items, i)
434 else
435 i = i + 1
436 end
437 end
438 return table.concat(items, "/")
439end
440
441--- Split protocol and path from an URL or local pathname.
442-- URLs should be in the "protocol://path" format.
443-- For local pathnames, "file" is returned as the protocol.
444-- @param url string: an URL or a local pathname.
445-- @return string, string: the protocol, and the absolute pathname without the protocol.
446function split_url(url)
447 assert(type(url) == "string")
448
449 local protocol, pathname = url:match("^([^:]*)://(.*)")
450 if not protocol then
451 protocol = "file"
452 pathname = url
453 end
454 if protocol == "file" then
455 pathname = fs_absolute_name(pathname)
456 end
457 return protocol, pathname
458end
459
460--- Create a wrapper to make a script executable from the command-line.
461-- @param file string: Pathname of script to be made executable.
462-- @param dest string: Directory where to put the wrapper.
463-- @return boolean or (nil, string): True if succeeded, or nil and
464-- an error message.
465function wrap_script(file, dest)
466 assert(type(file) == "string")
467 assert(type(dest) == "string")
468
469 local base = fs_base_name(file)
470 local wrapname = dest.."/"..base
471 local wrapper = io.open(wrapname, "w")
472 if not wrapper then
473 return nil, "Could not open "..wrapname.." for writing."
474 end
475 wrapper:write("#!/bin/sh\n\n")
476 wrapper:write('LUA_PATH="'..package.path..';$LUA_PATH"\n')
477 wrapper:write('LUA_CPATH="'..package.cpath..';$LUA_CPATH"\n')
478 wrapper:write('export LUA_PATH LUA_CPATH\n')
479 wrapper:write('exec "'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" "$@"\n')
480 wrapper:close()
481 if fs_execute("chmod +x",wrapname) then
482 return true
483 else
484 return nil, "Could not make "..wrapname.." executable."
485 end
486end
487
488--- Check if a file (typically inside path.bin_dir) is an actual binary
489-- or a Lua wrapper.
490-- @param filename string: the file name with full path.
491-- @return boolean: returns true if file is an actual binary
492-- (or if it couldn't check) or false if it is a Lua wrapper.
493local function is_actual_binary(filename)
494 local file = io.open(filename)
495 if file then
496 local found = false
497 if file:read():match("#!/bin/sh") then
498 local line = file:read()
499 line = file:read()
500 if not(line and line:match("LUA_PATH")) then
501 found = true
502 end
503 end
504 file:close()
505 if found then
506 return false
507 else
508 return true
509 end
510 else
511 return true
512 end
513 return false
514end
515
516function copy_binary(filename, dest)
517 return fs_copy(filename, dest)
518end
519
diff --git a/src/luarocks/fs/win32.lua b/src/luarocks/fs/win32.lua
new file mode 100644
index 00000000..5702a3aa
--- /dev/null
+++ b/src/luarocks/fs/win32.lua
@@ -0,0 +1,369 @@
1--- Windows implementation of filesystem and platform abstractions.
2-- Download http://unxutils.sourceforge.net/ for Windows GNU utilities
3-- used by this module.
4module("luarocks.fs.win32", package.seeall)
5
6local cfg = require("luarocks.cfg")
7
8local fs_base_name,
9 fs_copy,
10 fs_current_dir,
11 fs_execute,
12 fs_execute_string,
13 fs_is_dir,
14 fs_make_path,
15 fs_Q
16
17function init_fs_functions(impl)
18 fs_base_name = impl.base_name
19 fs_copy = impl.copy
20 fs_current_dir = impl.current_dir
21 fs_execute = impl.execute
22 fs_execute_string = impl.execute_string
23 fs_is_dir = impl.is_dir
24 fs_make_path = impl.make_path
25 fs_Q = impl.Q
26end
27
28--- Quote argument for shell processing. Fixes paths on Windows.
29-- Adds single quotes and escapes.
30-- @param arg string: Unquoted argument.
31-- @return string: Quoted argument.
32function Q(arg)
33 assert(type(arg) == "string")
34 -- Quote DIR for Windows
35 if arg:match("^[\.a-zA-Z]?:?[\\/]") then
36 return '"' .. arg:gsub("/", "\\"):gsub('"', '\\"') .. '"'
37 end
38 -- URLs and anything else
39 return '"' .. arg:gsub('"', '\\"') .. '"'
40end
41
42local function command_at(dir, cmd)
43 local drive = dir:match("^([A-Za-z]:)")
44 cmd = "cd " .. fs_Q(dir) .. " & " .. cmd
45 if drive then
46 cmd = drive .. " & " .. cmd
47 end
48 return cmd
49end
50
51--- Run the given command.
52-- The command is executed in the current directory in the dir stack.
53-- @param cmd string: No quoting/escaping is applied to the command.
54-- @return boolean: true if command succeeds (status code 0), false
55-- otherwise.
56function execute_string(cmd)
57 if os.execute(command_at(fs_current_dir(), cmd)) == 0 then
58 return true
59 else
60 return false
61 end
62end
63
64--- Test for existance of a file.
65-- @param file string: filename to test
66-- @return boolean: true if file exists, false otherwise.
67function exists(file)
68 assert(file)
69 return fs_execute("if not exist " .. fs_Q(file) ..
70 " invalidcommandname 2>NUL 1>NUL")
71end
72
73--- Test is pathname is a directory.
74-- @param file string: pathname to test
75-- @return boolean: true if it is a directory, false otherwise.
76function is_dir(file)
77 assert(file)
78 return fs_execute("chdir /D " .. fs_Q(file) .. " 2>NUL 1>NUL")
79end
80
81--- Test is file/dir is writable.
82-- @param file string: filename to test
83-- @return boolean: true if file exists, false otherwise.
84function is_writable(file)
85 assert(file)
86 local result
87 if is_dir(file) then
88 local file2 = file .. '/.tmpluarockstestwritable'
89 local fh = io.open(file2, 'w')
90 result = fh ~= nil
91 if fh then fh:close() end
92 os.remove(file2)
93 else
94 local fh = io.open(file, 'r+')
95 result = fh ~= nil
96 if fh then fh:close() end
97 end
98 return result
99end
100
101
102--- Create a directory if it does not already exist.
103-- If any of the higher levels in the path name does not exist
104-- too, they are created as well.
105-- @param dir string: pathname of directory to create.
106-- @return boolean: true on success, false on failure.
107function make_dir(dir)
108 assert(dir)
109 fs_execute("mkdir "..fs_Q(dir).." 1> NUL 2> NUL")
110 return 1
111end
112
113--- Remove a directory if it is empty.
114-- Does not return errors (for example, if directory is not empty or
115-- if already does not exist)
116-- @param dir string: pathname of directory to remove.
117function remove_dir_if_empty(dir)
118 assert(dir)
119 fs_execute_string("rmdir "..fs_Q(dir).." 1> NUL 2> NUL")
120end
121
122--- Copy a file.
123-- @param src string: Pathname of source
124-- @param dest string: Pathname of destination
125-- @return boolean or (boolean, string): true on success, false on failure,
126-- plus an error message.
127function copy(src, dest)
128 assert(src and dest)
129 if dest:match("[/\\]$") then dest = dest:sub(1, -2) end
130 if fs_execute("cp", src, dest) then
131 return true
132 else
133 return false, "Failed copying "..src.." to "..dest
134 end
135end
136
137--- Recursively copy the contents of a directory.
138-- @param src string: Pathname of source
139-- @param dest string: Pathname of destination
140-- @return boolean or (boolean, string): true on success, false on failure,
141-- plus an error message.
142function copy_contents(src, dest)
143 assert(src and dest)
144 if fs_execute_string("cp -a "..src.."\\*.* "..fs_Q(dest).." 1> NUL 2> NUL") then
145 return true
146 else
147 return false, "Failed copying "..src.." to "..dest
148 end
149end
150
151--- Delete a file or a directory and all its contents.
152-- For safety, this only accepts absolute paths.
153-- @param arg string: Pathname of source
154-- @return boolean: true on success, false on failure.
155function delete(arg)
156 assert(arg)
157 assert(arg:match("^[\a-zA-Z]?:?[\\/]"))
158 fs_execute("chmod a+rw -R ", arg)
159 return fs_execute_string("rm -rf " .. fs_Q(arg) .. " 1> NUL 2> NUL")
160end
161
162--- List the contents of a directory.
163-- @param at string or nil: directory to list (will be the current
164-- directory if none is given).
165-- @return table: an array of strings with the filenames representing
166-- the contents of a directory.
167function dir(at)
168 assert(type(at) == "string" or not at)
169 if not at then
170 at = fs_current_dir()
171 end
172 if not fs_is_dir(at) then
173 return {}
174 end
175 local result = {}
176 local pipe = io.popen(command_at(at, "ls"))
177 for file in pipe:lines() do
178 table.insert(result, file)
179 end
180 pipe:close()
181
182 return result
183end
184
185--- Recursively scan the contents of a directory.
186-- @param at string or nil: directory to scan (will be the current
187-- directory if none is given).
188-- @return table: an array of strings with the filenames representing
189-- the contents of a directory. Paths are returned with forward slashes.
190function find(at)
191 assert(type(at) == "string" or not at)
192 if not at then
193 at = fs_current_dir()
194 end
195 if not fs_is_dir(at) then
196 return {}
197 end
198 local result = {}
199 local pipe = io.popen(command_at(at, "find 2> NUL"))
200 for file in pipe:lines() do
201 -- Windows find is a bit different
202 if file:sub(1,2)==".\\" then file=file:sub(3) end
203 if file ~= "." then
204 table.insert(result, (file:gsub("\\", "/")))
205 end
206 end
207 return result
208end
209
210
211--- Download a remote file.
212-- @param url string: URL to be fetched.
213-- @param filename string or nil: this function attempts to detect the
214-- resulting local filename of the remote file as the basename of the URL;
215-- if that is not correct (due to a redirection, for example), the local
216-- filename can be given explicitly as this second argument.
217-- @return boolean: true on success, false on failure.
218function download(url, filename)
219 assert(type(url) == "string")
220 assert(type(filename) == "string" or not filename)
221
222 if filename then
223 return fs_execute("wget --quiet --continue --output-document ", filename, url)
224 else
225 return fs_execute("wget --quiet --continue ", url)
226 end
227end
228
229--- Strip the path off a path+filename.
230-- @param pathname string: A path+name, such as "/a/b/c".
231-- @return string: The filename without its path, such as "c".
232function base_name(pathname)
233 assert(type(pathname) == "string")
234
235 local base = pathname:match(".*[/\\]([^/\\]*)")
236 return base or pathname
237end
238
239--- Strip the last extension of a filename.
240-- Example: "foo.tar.gz" becomes "foo.tar".
241-- If filename has no dots, returns it unchanged.
242-- @param filename string: The file name to strip.
243-- @return string: The stripped name.
244local function strip_extension(filename)
245 assert(type(filename) == "string")
246
247 return (filename:gsub("%.[^.]+$", "")) or filename
248end
249
250--- Uncompress gzip file.
251-- @param archive string: Filename of archive.
252-- @return boolean : success status
253local function gunzip(archive)
254 local cmd = fs_execute("gunzip -h 1>NUL 2>NUL") and 'gunzip' or
255 fs_execute("gzip -h 1>NUL 2>NUL") and 'gzip -d'
256 local ok = fs_execute(cmd, archive)
257 return ok
258end
259
260--- Unpack an archive.
261-- Extract the contents of an archive, detecting its format by
262-- filename extension.
263-- @param archive string: Filename of archive.
264-- @return boolean or (boolean, string): true on success, false and an error message on failure.
265function unpack_archive(archive)
266 assert(type(archive) == "string")
267
268 local ok
269 if archive:match("%.tar%.gz$") then
270 ok = gunzip(archive)
271 if ok then
272 ok = fs_execute("tar -xf ", strip_extension(archive))
273 end
274 elseif archive:match("%.tgz$") then
275 ok = gunzip(archive)
276 if ok then
277 ok = fs_execute("tar -xf ", strip_extension(archive)..".tar")
278 end
279 elseif archive:match("%.tar%.bz2$") then
280 ok = fs_execute("bunzip2 ", archive)
281 if ok then
282 ok = fs_execute("tar -xf ", strip_extension(archive))
283 end
284 elseif archive:match("%.zip$") then
285 ok = fs_execute("unzip ", archive)
286 elseif archive:match("%.lua$") or archive:match("%.c$") then
287 -- Ignore .lua and .c files; they don't need to be extracted.
288 return true
289 else
290 local ext = archive:match(".*(%..*)")
291 return false, "Unrecognized filename extension "..(ext or "")
292 end
293 if not ok then
294 return false, "Failed extracting "..archive
295 end
296 return true
297end
298
299--- Return an absolute pathname from a potentially relative one.
300-- @param pathname string: pathname to convert.
301-- @param relative_to string or nil: path to prepend when making
302-- pathname absolute, or the current dir in the dir stack if
303-- not given.
304-- @return string: The pathname converted to absolute.
305function absolute_name(pathname, relative_to)
306 assert(type(pathname) == "string")
307 assert(type(relative_to) == "string" or not relative_to)
308
309 relative_to = relative_to or fs_current_dir()
310 if pathname:match("^[\.a-zA-Z]?:?[\\/]") then
311 return pathname
312 else
313 return relative_to .. "/" .. pathname
314 end
315end
316
317--- Create a wrapper to make a script executable from the command-line.
318-- @param file string: Pathname of script to be made executable.
319-- @param dest string: Directory where to put the wrapper.
320-- @return boolean or (nil, string): True if succeeded, or nil and
321-- an error message.
322function wrap_script(file, dest)
323 assert(type(file) == "string")
324 assert(type(dest) == "string")
325
326 local base = fs_base_name(file)
327 local wrapname = dest.."/"..base..".bat"
328 local wrapper = io.open(wrapname, "w")
329 if not wrapper then
330 return nil, "Could not open "..wrapname.." for writing."
331 end
332 wrapper:write("@echo off\n")
333 wrapper:write("setlocal\n")
334 wrapper:write('set LUA_PATH='..package.path..";%LUA_PATH%\n")
335 wrapper:write('set LUA_CPATH='..package.cpath..";%LUA_CPATH%\n")
336 wrapper:write('"'..fs_make_path(cfg.variables["LUA_BINDIR"], cfg.lua_interpreter)..'" -lluarocks.require "'..file..'" %*\n')
337 wrapper:write("endlocal\n")
338 wrapper:close()
339 return true
340end
341
342function is_actual_binary(name)
343 name = name:lower()
344 if name:match("%.bat$") or name:match("%.exe$") then
345 return true
346 end
347 return false
348end
349
350function copy_binary(filename, dest)
351 local ok, err = fs_copy(filename, dest)
352 if not ok then
353 return nil, err
354 end
355 local exe_pattern = "%.[Ee][Xx][Ee]$"
356 local base = fs_base_name(filename)
357 if base:match(exe_pattern) then
358 base = base:gsub(exe_pattern, ".lua")
359 local helpname = dest.."/"..base
360 local helper = io.open(helpname, "w")
361 if not helper then
362 return nil, "Could not open "..helpname.." for writing."
363 end
364 helper:write('package.path=\"'..package.path:gsub("\\","\\\\")..';\"..package.path\n')
365 helper:write('package.cpath=\"'..package.path:gsub("\\","\\\\")..';\"..package.cpath\n')
366 helper:close()
367 end
368 return true
369end
diff --git a/src/luarocks/help.lua b/src/luarocks/help.lua
new file mode 100644
index 00000000..76e77a33
--- /dev/null
+++ b/src/luarocks/help.lua
@@ -0,0 +1,69 @@
1
2--- Module implementing the LuaRocks "help" command.
3-- This is a generic help display module, which
4-- uses a global table called "commands" to find commands
5-- to show help for; each command should be represented by a
6-- table containing "help" and "help_summary" fields.
7module("luarocks.help", package.seeall)
8
9local util = require("luarocks.util")
10
11help_summary = "Help on commands."
12
13help_arguments = "[<command>]"
14help = [[
15<command> is the command to show help for.
16]]
17
18--- Driver function for the "help" command.
19-- @param command string or nil: command to show help for; if not
20-- given, help summaries for all commands are shown.
21-- @return boolean or (nil, string): true if there were no errors
22-- or nil and an error message if an invalid command was requested.
23function run(...)
24 local flags, command = util.parse_flags(...)
25
26 if not command then
27 print([[
28LuaRocks ]]..program_version..[[, a module deployment system for Lua
29
30]]..program_name..[[ - ]]..program_description..[[
31
32usage: ]]..program_name..[[ [--from=<server> | --only-from=<server>] [--to=<tree>] [VAR=VALUE]... <command> [<argument>]
33
34Variables from the "variables" table of the configuration file
35can be overriden with VAR=VALUE assignments.
36
37--from=<server> Fetch rocks/rockspecs from this server
38 (takes priority over config file)
39--only-from=<server> Fetch rocks/rockspecs from this server only
40 (overrides any entries in the config file)
41--to=<tree> Which tree to operate on.
42
43Supported commands:
44]])
45 local names = {}
46 for name, command in pairs(commands) do
47 table.insert(names, name)
48 end
49 table.sort(names)
50 for _, name in ipairs(names) do
51 local command = commands[name]
52 print(name, command.help_summary)
53 end
54 else
55 command = command:gsub("-", "_")
56 if commands[command] then
57 local arguments = commands[command].help_arguments or "<argument>"
58 print()
59 print(program_name.." "..command.." "..arguments)
60 print()
61 print(command.." - "..commands[command].help_summary)
62 print()
63 print(commands[command].help)
64 else
65 return nil, "Unknown command '"..command.."'"
66 end
67 end
68 return true
69end
diff --git a/src/luarocks/install.lua b/src/luarocks/install.lua
new file mode 100644
index 00000000..b115a8a8
--- /dev/null
+++ b/src/luarocks/install.lua
@@ -0,0 +1,115 @@
1
2--- Module implementing the LuaRocks "install" command.
3-- Installs binary rocks.
4module("luarocks.install", package.seeall)
5
6local path = require("luarocks.path")
7local rep = require("luarocks.rep")
8local fetch = require("luarocks.fetch")
9local util = require("luarocks.util")
10local fs = require("luarocks.fs")
11local deps = require("luarocks.deps")
12local manif = require("luarocks.manif")
13local cfg = require("luarocks.cfg")
14
15help_summary = "Install a rock."
16
17help_arguments = "{<rock>|<name> [<version>]}"
18
19help = [[
20Argument may be the name of a rock to be fetched from a repository
21or a filename of a locally available rock.
22]]
23
24--- Install a binary rock.
25-- @param rock_file string: local or remote filename of a rock.
26-- @return boolean or (nil, string): True if succeeded or
27-- nil and an error message.
28function install_binary_rock(rock_file)
29 local name, version, arch = path.parse_rock_name(rock_file)
30 if not name then
31 return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
32 end
33 if arch ~= "all" and arch ~= cfg.arch then
34 return nil, "Incompatible architecture "..arch
35 end
36 if rep.is_installed(name, version) then
37 rep.delete_version(name, version)
38 end
39 local rollback = util.schedule_function(function()
40 fs.delete(path.install_dir(name, version))
41 fs.remove_dir_if_empty(path.versions_dir(name))
42 end)
43 local ok, err = fetch.fetch_and_unpack_rock(rock_file, path.install_dir(name, version))
44 if not ok then return nil, err end
45 ok, err = rep.install_bins(name, version)
46
47 local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
48 if err then
49 return nil, "Failed loading rockspec for installed package: "..err
50 end
51
52 ok, err = deps.check_external_deps(rockspec, "install")
53 if err then
54 return nil, err
55 end
56
57 ok, err = deps.fulfill_dependencies(rockspec)
58 if err then
59 return nil, err
60 end
61
62 ok, err = rep.run_hook(rockspec, "post_install")
63 if err then
64 return nil, err
65 end
66
67 ok, err = manif.update_manifest(name, version)
68 if err then
69 return nil, err
70 end
71 util.remove_scheduled_function(rollback)
72 return true
73end
74
75--- Driver function for the "install" command.
76-- @param name string: name of a binary rock. If an URL or pathname
77-- to a binary rock is given, fetches and installs it. If a rockspec or a
78-- source rock is given, forwards the request to the "build" command.
79-- If a package name is given, forwards the request to "search" and,
80-- if returned a result, installs the matching rock.
81-- @param version string: When passing a package name, a version number
82-- may also be given.
83-- @return boolean or (nil, string): True if installation was
84-- successful, nil and an error message otherwise.
85function run(...)
86 local flags, name, version = util.parse_flags(...)
87 if type(name) ~= "string" then
88 return nil, "Argument missing, see help."
89 end
90
91 if name:match("%.rockspec$") or name:match("%.src%.rock$") then
92 local build = require("luarocks.build")
93 return build.run(name)
94 elseif name:match("%.rock$") then
95 return install_binary_rock(name)
96 else
97 local search = require("luarocks.search")
98 local results, err = search.find_suitable_rock(search.make_query(name, version))
99 if err then
100 return nil, err
101 elseif type(results) == "string" then
102 local url = results
103 print("Installing "..url.."...")
104 return run(url)
105 else
106 print()
107 print("Could not determine which rock to install.")
108 print()
109 print("Search results:")
110 print("---------------")
111 search.print_results(results)
112 return nil, (next(results) and "Please narrow your query." or "No results found.")
113 end
114 end
115end
diff --git a/src/luarocks/list.lua b/src/luarocks/list.lua
new file mode 100644
index 00000000..1251653c
--- /dev/null
+++ b/src/luarocks/list.lua
@@ -0,0 +1,35 @@
1
2--- Module implementing the LuaRocks "list" command.
3-- Lists currently installed rocks.
4module("luarocks.list", package.seeall)
5
6local search = require("luarocks.search")
7local cfg = require("luarocks.cfg")
8local util = require("luarocks.util")
9local fs = require("luarocks.fs")
10
11help_summary = "Lists currently installed rocks."
12
13help = [[
14<argument> is a substring of a rock name to filter by.
15]]
16
17--- Driver function for "list" command.
18-- @param filter string or nil: A substring of a rock name to filter by.
19-- @param version string or nil: a version may also be passed.
20-- @return boolean: True if succeeded, nil on errors.
21function run(...)
22 local flags, filter, version = util.parse_flags(...)
23 local results = {}
24 local query = search.make_query(filter or "", version)
25 query.exact_name = false
26 for _, tree in ipairs(cfg.rocks_trees) do
27 search.manifest_search(results, fs.make_path(tree, "rocks"), query)
28 end
29 print()
30 print("Installed rocks:")
31 print("----------------")
32 print()
33 search.print_results(results, false)
34 return true
35end
diff --git a/src/luarocks/make.lua b/src/luarocks/make.lua
new file mode 100644
index 00000000..1c116cdb
--- /dev/null
+++ b/src/luarocks/make.lua
@@ -0,0 +1,54 @@
1
2--- Module implementing the LuaRocks "make" command.
3-- Builds sources in the current directory, but unlike "build",
4-- it does not fetch sources, etc., assuming everything is
5-- available in the current directory.
6module("luarocks.make", package.seeall)
7
8local build = require("luarocks.build")
9local fs = require("luarocks.fs")
10local util = require("luarocks.util")
11
12help_summary = "Compile package in current directory using a rockspec."
13help_arguments = "[<rockspec>]"
14help = [[
15Builds sources in the current directory, but unlike "build",
16it does not fetch sources, etc., assuming everything is
17available in the current directory. If no argument is given,
18look for a rockspec in the current directory. If more than one
19is found, you must specify which to use, through the command-line.
20
21This command is useful as a tool for debugging rockspecs.
22To install rocks, you'll normally want to use the "install" and
23"build" commands. See the help on those for details.
24]]
25
26--- Driver function for "make" command.
27-- @param name string: A local rockspec.
28-- @return boolean or (nil, string): True if build was successful; nil and an
29-- error message otherwise.
30function run(...)
31 local flags, rockspec = util.parse_flags(...)
32 assert(type(rockspec) == "string" or not rockspec)
33
34 if not rockspec then
35 local files = fs.dir(fs.current_dir())
36 for _, file in pairs(files) do
37 if file:match(".rockspec$") then
38 if rockspec then
39 return nil, "Please specify which rockspec file to use."
40 else
41 rockspec = file
42 end
43 end
44 end
45 if not rockspec then
46 return nil, "Argument missing: please specify a rockspec to use on current directory."
47 end
48 end
49 if not rockspec:match("%.rockspec$") then
50 return nil, "Invalid argument: 'make' takes a rockspec as a parameter. See help."
51 end
52
53 return build.build_rockspec(rockspec, false, true)
54end
diff --git a/src/luarocks/make_manifest.lua b/src/luarocks/make_manifest.lua
new file mode 100644
index 00000000..6edbd14b
--- /dev/null
+++ b/src/luarocks/make_manifest.lua
@@ -0,0 +1,32 @@
1
2--- Module implementing the luarocks-admin "make_manifest" command.
3-- Compile a manifest file for a repository.
4module("luarocks.make_manifest", package.seeall)
5
6local manif = require("luarocks.manif")
7local cfg = require("luarocks.cfg")
8
9help_summary = "Compile a manifest file for a repository."
10
11help = [[
12<argument>, if given, is a local repository pathname.
13]]
14
15--- Driver function for "make_manifest" command.
16-- @param repo string or nil: Pathname of a local repository. If not given,
17-- the default local repository configured as cfg.rocks_dir is used.
18-- @return boolean or (nil, string): True if manifest was generated,
19-- or nil and an error message.
20function run(repo)
21 assert(type(repo) == "string" or not repo)
22 repo = repo or cfg.rocks_dir
23
24 print("Making manifest for "..repo)
25
26 ok = manif.make_manifest(repo)
27 if ok then
28 print("Generating index.html for "..repo)
29 manif.make_index(repo)
30 end
31 return ok
32end
diff --git a/src/luarocks/manif.lua b/src/luarocks/manif.lua
new file mode 100644
index 00000000..98772319
--- /dev/null
+++ b/src/luarocks/manif.lua
@@ -0,0 +1,439 @@
1
2--- Functions for querying and manipulating manifest files.
3module("luarocks.manif", package.seeall)
4
5local util = require("luarocks.util")
6local fs = require("luarocks.fs")
7local search = require("luarocks.search")
8local rep = require("luarocks.rep")
9local deps = require("luarocks.deps")
10local cfg = require("luarocks.cfg")
11local persist = require("luarocks.persist")
12local fetch = require("luarocks.fetch")
13local type_check = require("luarocks.type_check")
14
15manifest_cache = {}
16
17--- Get all versions of a package listed in a manifest file.
18-- @param name string: a package name.
19-- @param manifest table or nil: a manifest table; if not given, the
20-- default local manifest table is used.
21-- @return table: An array of strings listing installed
22-- versions of a package.
23function get_versions(name, manifest)
24 assert(type(name) == "string")
25 assert(type(manifest) == "table" or not manifest)
26
27 if not manifest then
28 manifest = load_local_manifest(cfg.rocks_dir)
29 if not manifest then
30 return {}
31 end
32 end
33
34 local item = manifest.repository[name]
35 if item then
36 return util.keys(item)
37 end
38 return {}
39end
40
41--- Back-end function that actually loads the manifest
42-- and stores it in the manifest cache.
43-- @param file string: The local filename of the manifest file.
44-- @param repo_url string: The repository identifier.
45local function manifest_loader(file, repo_url, quick)
46 local manifest = persist.load_into_table(file)
47 if not manifest then
48 return nil, "Failed loading manifest for "..repo_url
49 end
50 if not quick then
51 local ok, err = type_check.type_check_manifest(manifest)
52 if not ok then
53 return nil, "Error checking manifest: "..err
54 end
55 end
56
57 manifest_cache[repo_url] = manifest
58 return manifest
59end
60
61--- Load a local or remote manifest describing a repository.
62-- All functions that use manifest tables assume they were obtained
63-- through either this function or load_local_manifest.
64-- @param repo_url string: URL or pathname for the repository.
65-- @return table or (nil, string): A table representing the manifest,
66-- or nil followed by an error message.
67function load_manifest(repo_url)
68 assert(type(repo_url) == "string")
69
70 if manifest_cache[repo_url] then
71 return manifest_cache[repo_url]
72 end
73
74 local protocol, pathname = fs.split_url(repo_url)
75 if protocol == "file" then
76 pathname = fs.make_path(pathname, "manifest")
77 else
78 local url = fs.make_path(repo_url, "manifest")
79 local name = repo_url:gsub("[/:]","_")
80 local file, dir = fetch.fetch_url_at_temp_dir(url, "luarocks-manifest-"..name)
81 if not file then
82 return nil, "Failed fetching manifest for "..repo_url
83 end
84 pathname = file
85 end
86 return manifest_loader(pathname, repo_url)
87end
88
89--- Load a local manifest describing a repository.
90-- All functions that use manifest tables assume they were obtained
91-- through either this function or load_manifest.
92-- @param repo_url string: URL or pathname for the repository.
93-- @return table or (nil, string): A table representing the manifest,
94-- or nil followed by an error message.
95function load_local_manifest(repo_url)
96 assert(type(repo_url) == "string")
97
98 if manifest_cache[repo_url] then
99 return manifest_cache[repo_url]
100 end
101
102 local pathname = fs.make_path(repo_url, "manifest")
103
104 return manifest_loader(pathname, repo_url, true)
105end
106
107--- Sort function for ordering rock identifiers in a manifest's
108-- modules table. Rocks are ordered alphabetically by name, and then
109-- by version which greater first.
110-- @param a string: Version to compare.
111-- @param b string: Version to compare.
112-- @return boolean: The comparison result, according to the
113-- rule outlined above.
114local function sort_pkgs(a, b)
115 assert(type(a) == "string")
116 assert(type(b) == "string")
117
118 local na, va = a:match("(.*)/(.*)$")
119 local nb, vb = b:match("(.*)/(.*)$")
120
121 return (na == nb) and deps.compare_versions(va, vb) or na < nb
122end
123
124--- Output a table listing items of a package.
125-- @param itemsfn function: a function for obtaining items of a package.
126-- pkg and version will be passed to it; it should return a table with
127-- items as keys.
128-- @param pkg string: package name
129-- @param version string: package version
130-- @param tbl table: the package matching table: keys should be item names
131-- and values arrays of strings with packages names in "name/version" format.
132local function store_package_items(itemsfn, pkg, version, tbl)
133 assert(type(itemsfn) == "function")
134 assert(type(pkg) == "string")
135 assert(type(version) == "string")
136 assert(type(tbl) == "table")
137
138 local path = pkg.."/"..version
139 local result = {}
140 for item, _ in pairs(itemsfn(pkg, version)) do
141 table.insert(result, item)
142 if not tbl[item] then
143 tbl[item] = {}
144 end
145 table.insert(tbl[item], path)
146 end
147 return result
148end
149
150--- Sort items of a package matching table by version number (higher versions first).
151-- @param tbl table: the package matching table: keys should be strings
152-- and values arrays of strings with packages names in "name/version" format.
153local function sort_package_matching_table(tbl)
154 assert(type(tbl) == "table")
155
156 if next(tbl) then
157 for item, pkgs in pairs(tbl) do
158 if #pkgs > 1 then
159 table.sort(pkgs, sort_pkgs)
160 -- Remove duplicates from the sorted array.
161 local prev = nil
162 local i = 1
163 while pkgs[i] do
164 local curr = pkgs[i]
165 if curr == prev then
166 table.remove(pkgs, i)
167 else
168 prev = curr
169 i = i + 1
170 end
171 end
172 end
173 end
174 end
175end
176
177--- Commit manifest to disk in given local repository.
178-- @param repo string: The directory of the local repository.
179-- @param manifest table: The manifest table
180-- @return boolean or (nil, string): true if successful, or nil and a
181-- message in case of errors.
182local function save_manifest(repo, manifest)
183 assert(type(repo) == "string")
184 assert(type(manifest) == "table")
185
186 local filename = fs.make_path(repo, "manifest")
187 return persist.save_from_table(filename, manifest)
188end
189
190--- Process the dependencies of a package to determine its dependency
191-- chain for loading modules.
192-- @param name string: Package name.
193-- @param version string: Package version.
194-- @return (table, table): A table listing dependencies as string-string pairs
195-- of names and versions, and a similar table of missing dependencies.
196local function update_dependencies(manifest)
197 for pkg, versions in pairs(manifest.repository) do
198 for version, repos in pairs(versions) do
199 local current = pkg.." "..version
200 for _, repo in ipairs(repos) do
201 if repo.arch == "installed" then
202 local missing
203 repo.dependencies, missing = deps.scan_deps({}, {}, manifest, pkg, version)
204 repo.dependencies[pkg] = nil
205 if missing then
206 for miss, _ in pairs(missing) do
207 if miss == current then
208 print("Tree inconsistency detected: "..current.." has no rockspec.")
209 else
210 print("Missing dependency for "..pkg.." "..version..": "..miss)
211 end
212 end
213 end
214 end
215 end
216 end
217 end
218end
219
220--- Store search results in a manifest table.
221-- @param results table: The search results as returned by search.disk_search.
222-- @param manifest table: A manifest table (must contain repository, modules, commands tables).
223local function store_results(results, manifest)
224 assert(type(results) == "table")
225 assert(type(manifest) == "table")
226
227 for pkg, versions in pairs(results) do
228 local pkgtable = manifest.repository[pkg] or {}
229 for version, repos in pairs(versions) do
230 local versiontable = {}
231 for _, repo in ipairs(repos) do
232 local repotable = {}
233 repotable.arch = repo.arch
234 if repo.arch == "installed" then
235 repotable.modules = store_package_items(rep.package_modules, pkg, version, manifest.modules)
236 repotable.commands = store_package_items(rep.package_commands, pkg, version, manifest.commands)
237 end
238 table.insert(versiontable, repotable)
239 end
240 pkgtable[version] = versiontable
241 end
242 manifest.repository[pkg] = pkgtable
243 end
244 update_dependencies(manifest)
245 sort_package_matching_table(manifest.modules)
246 sort_package_matching_table(manifest.commands)
247end
248
249--- Load a manifest file from a local repository and add to the repository
250-- information with regard to the given name and version.
251-- A file called 'manifest' will be written in the root of the given
252-- repository directory.
253-- @param name string: Name of a package from the repository.
254-- @param version string: Version of a package from the repository.
255-- @param repo string or nil: Pathname of a local repository. If not given,
256-- the default local repository configured as cfg.rocks_dir is used.
257-- @return boolean or (nil, string): True if manifest was generated,
258-- or nil and an error message.
259function update_manifest(name, version, repo)
260 assert(type(name) == "string")
261 assert(type(version) == "string")
262 assert(type(repo) == "string" or not repo)
263 repo = repo or cfg.rocks_dir
264
265 print("Updating manifest for "..repo)
266
267 local manifest, err = load_manifest(repo)
268 if not manifest then
269 print("No existing manifest. Attempting to rebuild...")
270 local ok, err = make_manifest(repo)
271 if not ok then
272 return nil, err
273 end
274 manifest, err = load_manifest(repo)
275 if not manifest then
276 return nil, err
277 end
278 end
279
280 local results = {[name] = {[version] = {{arch = "installed", repo = repo}}}}
281
282 store_results(results, manifest)
283 return save_manifest(repo, manifest)
284end
285
286--- Scan a LuaRocks repository and output a manifest file.
287-- A file called 'manifest' will be written in the root of the given
288-- repository directory.
289-- @param repo A local repository directory.
290-- @return boolean or (nil, string): True if manifest was generated,
291-- or nil and an error message.
292function make_manifest(repo)
293 assert(type(repo) == "string")
294
295 if not fs.is_dir(repo) then
296 return nil, "Cannot access repository at "..repo
297 end
298
299 local query = search.make_query("")
300 query.exact_name = false
301 query.arch = "any"
302 local results = search.disk_search(repo, query)
303
304 local manifest = { repository = {}, modules = {}, commands = {} }
305 manifest_cache[repo] = manifest
306 store_results(results, manifest)
307 return save_manifest(repo, manifest)
308end
309
310local index_header = [[
311<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
312<html>
313<head>
314<title>Available rocks</title>
315<meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
316<style>
317body {
318 background-color: white;
319 font-family: "bitstream vera sans", "verdana", "sans";
320 font-size: 14px;
321}
322a {
323 color: #0000c0;
324 text-decoration: none;
325}
326a:hover {
327 text-decoration: underline;
328}
329td.main {
330 border-style: none;
331}
332blockquote {
333 font-size: 12px;
334}
335td.package {
336 background-color: #f0f0f0;
337 vertical-align: top;
338}
339td.spacer {
340 height: 5px;
341}
342td.version {
343 background-color: #d0d0d0;
344 vertical-align: top;
345 text-align: left;
346 padding: 5px;
347 width: 100px;
348}
349p.manifest {
350 font-size: 8px;
351}
352</style>
353</head>
354<body>
355<h1>Available rocks</h1>
356<p>
357Lua modules avaliable from this location for use with <a href="http://www.luarocks.org">LuaRocks</a>:
358</p>
359<table class="main">
360]]
361
362local index_package_start = [[
363<td class="package">
364<p><a name="$anchor"></a><b>$package</b> - $summary<br/>
365</p><blockquote><p>$detailed<br/>
366<font size="-1"><a href="$original">latest sources</a> | <a href="$homepage">project homepage</a> | License: $license</font></p>
367</blockquote></a></td>
368<td class="version">
369]]
370
371local index_package_end = [[
372</td></tr>
373<tr><td colspan="2" class="spacer"></td></tr>
374]]
375
376local index_footer = [[
377</table>
378<p class="manifest">
379<a href="manifest">manifest file</a>
380</p>
381</body>
382</html>
383]]
384
385function make_index(repo)
386 if not fs.is_dir(repo) then
387 return nil, "Cannot access repository at "..repo
388 end
389 local manifest = load_manifest(repo)
390 files = fs.find(repo)
391 local out = io.open(fs.make_path(repo, "index.html"), "w")
392 out:write(index_header)
393 for package, version_list in util.sortedpairs(manifest.repository) do
394 local latest_rockspec = nil
395 local output = index_package_start
396 for version, data in util.sortedpairs(version_list, deps.compare_versions) do
397 local out_versions = {}
398 local arches = 0
399 output = output..version
400 local sep = ':&nbsp;'
401 for _, item in ipairs(data) do
402 output = output .. sep .. '<a href="$url">'..item.arch..'</a>'
403 sep = ',&nbsp;'
404 if item.arch == 'rockspec' then
405 local rs = ("%s-%s.rockspec"):format(package, version)
406 if not latest_rockspec then latest_rockspec = rs end
407 output = output:gsub("$url", rs)
408 else
409 output = output:gsub("$url", ("%s-%s.%s.rock"):format(package, version, item.arch))
410 end
411 end
412 output = output .. '<br/>'
413 output = output:gsub("$na", arches)
414 end
415 output = output .. index_package_end
416 if latest_rockspec then
417 local rockspec = persist.load_into_table(fs.make_path(repo, latest_rockspec))
418 local vars = {
419 anchor = package,
420 package = rockspec.package,
421 original = rockspec.source.url,
422 summary = rockspec.description.summary or "",
423 detailed = rockspec.description.detailed or "",
424 license = rockspec.description.license or "N/A",
425 homepage = rockspec.description.homepage or ""
426 }
427 vars.detailed = vars.detailed:gsub("\n\n", "</p><p>"):gsub("%s+", " ")
428 output = output:gsub("$(%w+)", vars)
429 else
430 output = output:gsub("$anchor", package)
431 output = output:gsub("$package", package)
432 output = output:gsub("$(%w+)", "")
433 end
434 out:write(output)
435 end
436 out:write(index_footer)
437 out:close()
438end
439
diff --git a/src/luarocks/pack.lua b/src/luarocks/pack.lua
new file mode 100644
index 00000000..3f64b1c0
--- /dev/null
+++ b/src/luarocks/pack.lua
@@ -0,0 +1,116 @@
1
2--- Module implementing the LuaRocks "pack" command.
3-- Creates a rock, packing sources or binaries.
4module("luarocks.pack", package.seeall)
5
6local path = require("luarocks.path")
7local rep = require("luarocks.rep")
8local fetch = require("luarocks.fetch")
9local fs = require("luarocks.fs")
10local cfg = require("luarocks.cfg")
11local util = require("luarocks.util")
12
13help_summary = "Create a rock, packing sources or binaries."
14help_arguments = "{<rockspec>|<name> [<version>]}"
15help = [[
16Argument may be a rockspec file, for creating a source rock,
17or the name of an installed package, for creating a binary rock.
18In the latter case, the app version may be given as a second
19argument.
20]]
21
22--- Create a source rock.
23-- Packages a rockspec and its required source files in a rock
24-- file with the .src.rock extension, which can later be built and
25-- installed with the "build" command.
26-- @param rockspec_file string: An URL or pathname for a rockspec file.
27-- @return string or (nil, string): The filename of the resulting
28-- .src.rock file; or nil and an error message.
29local function pack_source_rock(rockspec_file)
30 assert(type(rockspec_file) == "string")
31
32 rockspec_file = fs.absolute_name(rockspec_file)
33 local rockspec, err = fetch.load_rockspec(rockspec_file)
34 if err then
35 return nil, "Error loading rockspec: "..err
36 end
37
38 local name_version = rockspec.name .. "-" .. rockspec.version
39 local rock_file = fs.absolute_name(name_version .. ".src.rock")
40
41 local source_file, dir = fetch.fetch_sources(rockspec, false)
42 if not source_file then
43 return nil, dir
44 end
45 fs.change_dir(dir)
46
47 fs.delete(rock_file)
48 fs.copy(rockspec_file, dir)
49 if not fs.zip(rock_file, fs.base_name(rockspec_file), fs.base_name(source_file)) then
50 return nil, "Failed packing "..rock_file
51 end
52 fs.pop_dir()
53
54 return rock_file
55end
56
57-- @param name string: Name of package to pack.
58-- @param version string or nil: A version number may also be passed.
59-- @return string or (nil, string): The filename of the resulting
60-- .src.rock file; or nil and an error message.
61local function pack_binary_rock(name, version)
62 assert(type(name) == "string")
63 assert(type(version) == "string" or not version)
64
65 local versions = rep.get_versions(name)
66
67 if not versions then
68 return nil, "'"..name.."' does not seem to be an installed rock."
69 end
70 if not version then
71 if #versions > 1 then
72 return nil, "Please specify which version of '"..name.."' to pack."
73 end
74 version = versions[1]
75 end
76 if not version:match("[^-]+%-%d+") then
77 return nil, "Expected version "..version.." in version-revision format."
78 end
79 local prefix = path.install_dir(name, version)
80 if not fs.exists(prefix) then
81 return nil, "'"..name.." "..version.."' does not seem to be an installed rock."
82 end
83 local name_version = name .. "-" .. version
84 local rock_file = fs.absolute_name(name_version .. "."..cfg.arch..".rock")
85 fs.change_dir(prefix)
86 if not rep.is_binary_rock(name, version) then
87 rock_file = rock_file:gsub("%."..cfg.arch:gsub("%-","%%-").."%.", ".all.")
88 end
89 fs.delete(rock_file)
90 if not fs.zip(rock_file, unpack(fs.dir())) then
91 return nil, "Failed packing "..rock_file
92 end
93 fs.pop_dir()
94 return rock_file
95end
96
97--- Driver function for the "pack" command.
98-- @param arg string: may be a rockspec file, for creating a source rock,
99-- or the name of an installed package, for creating a binary rock.
100-- @param version string or nil: if the name of a package is given, a
101-- version may also be passed.
102-- @return boolean or (nil, string): true if successful or nil followed
103-- by an error message.
104function run(...)
105 local flags, arg, version = util.parse_flags(...)
106 assert(type(version) == "string" or not version)
107 if type(arg) ~= "string" then
108 return nil, "Argument missing, see help."
109 end
110
111 if arg:match(".*%.rockspec") then
112 return pack_source_rock(arg)
113 else
114 return pack_binary_rock(arg, version)
115 end
116end
diff --git a/src/luarocks/path.lua b/src/luarocks/path.lua
new file mode 100644
index 00000000..e236db01
--- /dev/null
+++ b/src/luarocks/path.lua
@@ -0,0 +1,224 @@
1
2--- Path and filename handling functions.
3-- All paths are configured in this module, making it a single
4-- point where the layout of the local installation is defined in LuaRocks.
5module("luarocks.path", package.seeall)
6
7local fs = require("luarocks.fs")
8local cfg = require("luarocks.cfg")
9
10--- Infer rockspec filename from a rock filename.
11-- @param rock_name string: Pathname of a rock file.
12-- @return string: Filename of the rockspec, without path.
13function rockspec_name_from_rock(rock_name)
14 assert(type(rock_name) == "string")
15 local base_name = fs.base_name(rock_name)
16 return base_name:match("(.*)%.[^.]*.rock") .. ".rockspec"
17end
18
19--- Get the repository directory for all versions of a package.
20-- @param name string: The package name.
21-- @return string: The resulting path -- does not guarantee that
22-- @param repo string or nil: If given, specifies the local repository to use.
23-- the package (and by extension, the path) exists.
24function versions_dir(name, repo)
25 assert(type(name) == "string")
26 assert(not repo or type(repo) == "string")
27
28 return fs.make_path(repo or cfg.rocks_dir, name)
29end
30
31--- Get the local installation directory (prefix) for a package.
32-- @param name string: The package name.
33-- @param version string: The package version.
34-- @param repo string or nil: If given, specifies the local repository to use.
35-- @return string: The resulting path -- does not guarantee that
36-- the package (and by extension, the path) exists.
37function install_dir(name, version, repo)
38 assert(type(name) == "string")
39 assert(type(version) == "string")
40 assert(not repo or type(repo) == "string")
41
42 return fs.make_path(repo or cfg.rocks_dir, name, version)
43end
44
45--- Get the local filename of the rockspec of an installed rock.
46-- @param name string: The package name.
47-- @param version string: The package version.
48-- @param repo string or nil: If given, specifies the local repository to use.
49-- @return string: The resulting path -- does not guarantee that
50-- the package (and by extension, the file) exists.
51function rockspec_file(name, version, repo)
52 assert(type(name) == "string")
53 assert(type(version) == "string")
54 assert(not repo or type(repo) == "string")
55
56 return fs.make_path(repo or cfg.rocks_dir, name, version, name.."-"..version..".rockspec")
57end
58
59--- Get the local installation directory for C libraries of a package.
60-- @param name string: The package name.
61-- @param version string: The package version.
62-- @param repo string or nil: If given, specifies the local repository to use.
63-- @return string: The resulting path -- does not guarantee that
64-- the package (and by extension, the path) exists.
65function lib_dir(name, version, repo)
66 assert(type(name) == "string")
67 assert(type(version) == "string")
68 assert(not repo or type(repo) == "string")
69
70 return fs.make_path(repo or cfg.rocks_dir, name, version, "lib")
71end
72
73--- Get the local installation directory for Lua modules of a package.
74-- @param name string: The package name.
75-- @param version string: The package version.
76-- @param repo string or nil: If given, specifies the local repository to use.
77-- @return string: The resulting path -- does not guarantee that
78-- the package (and by extension, the path) exists.
79function lua_dir(name, version, repo)
80 assert(type(name) == "string")
81 assert(type(version) == "string")
82 assert(not repo or type(repo) == "string")
83
84 return fs.make_path(repo or cfg.rocks_dir, name, version, "lua")
85end
86
87--- Get the local installation directory for documentation of a package.
88-- @param name string: The package name.
89-- @param version string: The package version.
90-- @param repo string or nil: If given, specifies the local repository to use.
91-- @return string: The resulting path -- does not guarantee that
92-- the package (and by extension, the path) exists.
93function doc_dir(name, version, repo)
94 assert(type(name) == "string")
95 assert(type(version) == "string")
96 assert(not repo or type(repo) == "string")
97
98 return fs.make_path(repo or cfg.rocks_dir, name, version, "doc")
99end
100
101--- Get the local installation directory for configuration files of a package.
102-- @param name string: The package name.
103-- @param version string: The package version.
104-- @param repo string or nil: If given, specifies the local repository to use.
105-- @return string: The resulting path -- does not guarantee that
106-- the package (and by extension, the path) exists.
107function conf_dir(name, version, repo)
108 assert(type(name) == "string")
109 assert(type(version) == "string")
110 assert(not repo or type(repo) == "string")
111
112 return fs.make_path(repo or cfg.rocks_dir, name, version, "conf")
113end
114
115--- Get the local installation directory for command-line scripts
116-- of a package.
117-- @param name string: The package name.
118-- @param version string: The package version.
119-- @param repo string or nil: If given, specifies the local repository to use.
120-- @return string: The resulting path -- does not guarantee that
121-- the package (and by extension, the path) exists.
122function bin_dir(name, version, repo)
123 assert(type(name) == "string")
124 assert(type(version) == "string")
125 assert(not repo or type(repo) == "string")
126
127 return fs.make_path(repo or cfg.rocks_dir, name, version, "bin")
128end
129
130--- Extract name, version and arch of a rock filename.
131-- @param rock_file string: pathname of a rock
132-- @return (string, string, string) or nil: name, version and arch
133-- of rock, or nil if name could not be parsed
134function parse_rock_name(rock_file)
135 assert(type(rock_file) == "string")
136 return fs.base_name(rock_file):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$")
137end
138
139--- Extract name and version of a rockspec filename.
140-- @param rockspec_file string: pathname of a rockspec
141-- @return (string, string) or nil: name and version
142-- of rockspec, or nil if name could not be parsed
143function parse_rockspec_name(rockspec_file)
144 assert(type(rockspec_file) == "string")
145 return fs.base_name(rockspec_file):match("(.*)-([^-]+-%d+)%.(rockspec)")
146end
147
148--- Make a rockspec or rock URL.
149-- @param pathname string: Base URL or pathname.
150-- @param name string: Package name.
151-- @param version string: Package version.
152-- @param arch string: Architecture identifier, or "rockspec" or "installed".
153-- @return string: A URL or pathname following LuaRocks naming conventions.
154function make_url(pathname, name, version, arch)
155 assert(type(pathname) == "string")
156 assert(type(name) == "string")
157 assert(type(version) == "string")
158 assert(type(arch) == "string")
159
160 local filename = name.."-"..version
161 if arch == "installed" then
162 filename = fs.make_path(name, version, filename..".rockspec")
163 elseif arch == "rockspec" then
164 filename = filename..".rockspec"
165 else
166 filename = filename.."."..arch..".rock"
167 end
168 return fs.make_path(pathname, filename)
169end
170
171--- Convert a pathname to a module identifier.
172-- In Unix, for example, a path "foo/bar/baz.lua" is converted to
173-- "foo.bar.baz"; "bla/init.lua" returns "bla"; "foo.so" returns "foo".
174-- @param file string: Pathname of module
175-- @return string: The module identifier, or nil if given path is
176-- not a conformant module path (the function does not check if the
177-- path actually exists).
178function path_to_module(file)
179 assert(type(file) == "string")
180
181 local name = file:match("(.*)%."..cfg.lua_extension.."$")
182 if name then
183 name = name:gsub(fs.dir_separator, ".")
184 local init = name:match("(.*)%.init$")
185 if init then
186 name = init
187 end
188 else
189 name = file:match("(.*)%."..cfg.lib_extension.."$")
190 if name then
191 name = name:gsub(fs.dir_separator, ".")
192 end
193 end
194 return name
195end
196
197--- Obtain the directory name where a module should be stored.
198-- For example, on Unix, "foo.bar.baz" will return "foo/bar".
199-- @param mod string: A module name in Lua dot-separated format.
200-- @return string: A directory name using the platform's separator.
201function module_to_path(mod)
202 assert(type(mod) == "string")
203 return (mod:gsub("[^.]*$", ""):gsub("%.", fs.dir_separator))
204end
205
206--- Set up path-related variables for a given rock.
207-- Create a "variables" table in the rockspec table, containing
208-- adjusted variables according to the configuration file.
209-- @param rockspec table: The rockspec table.
210function configure_paths(rockspec)
211 assert(type(rockspec) == "table")
212 local vars = {}
213 for k,v in pairs(cfg.variables) do
214 vars[k] = v
215 end
216 local name, version = rockspec.name, rockspec.version
217 vars.PREFIX = install_dir(name, version)
218 vars.LUADIR = lua_dir(name, version)
219 vars.LIBDIR = lib_dir(name, version)
220 vars.CONFDIR = conf_dir(name, version)
221 vars.BINDIR = bin_dir(name, version)
222 vars.DOCDIR = doc_dir(name, version)
223 rockspec.variables = vars
224end
diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua
new file mode 100644
index 00000000..4f69184c
--- /dev/null
+++ b/src/luarocks/persist.lua
@@ -0,0 +1,91 @@
1
2--- Utility module for loading files into tables and
3-- saving tables into files.
4-- Implemented separately to avoid interdependencies,
5-- as it is used in the bootstrapping stage of the cfg module.
6module("luarocks.persist", package.seeall)
7
8--- Load a Lua file containing assignments, storing them in a table.
9-- The global environment is not propagated to the loaded file.
10-- @param filename string: the name of the file.
11-- @param tbl table or nil: if given, this table is used to store
12-- loaded values.
13-- @return table or (nil, string): a table with the file's assignments
14-- as fields, or nil and a message in case of errors.
15function load_into_table(filename, tbl)
16 assert(type(filename) == "string")
17 assert(type(tbl) == "table" or not tbl)
18
19 local chunk, err = loadfile(filename)
20 if not chunk then
21 return nil, err
22 end
23 local result = {}
24 if tbl then result = tbl end
25 setfenv(chunk, result)
26 chunk()
27 return result
28end
29
30--- Write a table as Lua code representing a table to disk
31-- (that is, in curly brackets notation).
32-- This function handles only numbers, strings and tables
33-- are keys (tables are handled recursively).
34-- @param out userdata: a file object, open for writing.
35-- @param tbl table: the table to be written.
36local function write_table(out, tbl)
37 out:write("{")
38 local size = table.getn(tbl)
39 local sep = ""
40 local i = 1
41 for k, v in pairs(tbl) do
42 out:write(sep)
43 if type(k) == "number" then
44 if k ~= i then
45 out:write(tostring(k).."=")
46 else
47 i = i + 1
48 end
49 elseif type(k) == "table" then
50 out:write("[")
51 write_table(out, k)
52 out:write("]=")
53 else
54 if k:match("^[a-z_]+$") then
55 out:write(k.."=")
56 else
57 out:write("['"..k:gsub("'", "\\'").."']=")
58 end
59 end
60 local typ = type(v)
61 if typ == "table" then
62 write_table(out, v)
63 elseif typ == "string" then
64 out:write("'"..v:gsub("'", "\\'").."'")
65 else
66 out:write(tostring(v))
67 end
68 sep = ", "
69 end
70 out:write("}\n")
71end
72
73--- Save the contents of a table in a file.
74-- Each element of the table is saved as a global assignment.
75-- Only numbers, strings and tables (containing numbers, strings
76-- or other recursively processed tables) are supported.
77-- @return boolean or (nil, string): true if successful, or nil and a
78-- message in case of errors.
79function save_from_table(filename, tbl)
80 local out = io.open(filename, "w")
81 if not out then
82 return nil, "Cannot create file at "..filename
83 end
84 for k, v in pairs(tbl) do
85 out:write(k.." = ")
86 write_table(out, v)
87 out:write("\n")
88 end
89 out:close()
90 return true
91end
diff --git a/src/luarocks/remove.lua b/src/luarocks/remove.lua
new file mode 100644
index 00000000..d7162336
--- /dev/null
+++ b/src/luarocks/remove.lua
@@ -0,0 +1,163 @@
1
2--- Module implementing the LuaRocks "remove" command.
3-- Uninstalls rocks.
4module("luarocks.remove", package.seeall)
5
6local search = require("luarocks.search")
7local deps = require("luarocks.deps")
8local fetch = require("luarocks.fetch")
9local rep = require("luarocks.rep")
10local path = require("luarocks.path")
11local util = require("luarocks.util")
12local cfg = require("luarocks.cfg")
13local manif = require("luarocks.manif")
14
15help_summary = "Uninstall a rock."
16help_arguments = "[--force] <name> [<version>]"
17help = [[
18Argument is the name of a rock to be uninstalled.
19If a version is not given, try to remove all versions at once.
20Will only perform the removal if it does not break dependencies.
21To override this check and force the removal, use --force.
22]]
23
24--- Obtain a list of packages that depend on the given set of packages
25-- (where all packages of the set are versions of one program).
26-- @param name string: the name of a program
27-- @param versions array of string: the versions to be deleted.
28-- @return array of string: an empty table if no packages depend on any
29-- of the given list, or an array of strings in "name/version" format.
30local function check_dependents(name, versions)
31 local dependents = {}
32 local blacklist = {}
33 blacklist[name] = {}
34 for version, _ in pairs(versions) do
35 blacklist[name][version] = true
36 end
37 local local_rocks = {}
38 local query_all = search.make_query("")
39 query_all.exact_name = false
40 search.manifest_search(local_rocks, cfg.rocks_dir, query_all)
41 local_rocks[name] = nil
42 for rock_name, rock_versions in pairs(local_rocks) do
43 for rock_version, _ in pairs(rock_versions) do
44 local rockspec, err = fetch.load_rockspec(path.rockspec_file(rock_name, rock_version))
45 if rockspec then
46 local _, missing = deps.match_deps(rockspec, blacklist)
47 if missing[name] then
48 table.insert(dependents, { name = rock_name, version = rock_version })
49 end
50 end
51 end
52 end
53 return dependents
54end
55
56--- Delete given versions of a program.
57-- @param name string: the name of a program
58-- @param versions array of string: the versions to be deleted.
59-- @return boolean or (nil, string): true on success or nil and an error message.
60local function delete_versions(name, versions)
61
62 local manifest, err = manif.load_manifest(cfg.rocks_dir)
63 if not manifest then
64 return nil, err
65 end
66
67 local commands = {}
68 for version, data in pairs(versions) do
69 for _, command in pairs(manifest.repository[name][version][1].commands) do
70 if not commands[command] then commands[command] = {} end
71 table.insert(commands[command], name.."/"..version)
72 end
73 end
74
75 if next(commands) then
76 for command, users in pairs(commands) do
77 local providers_of_command = manifest.commands[command]
78 for _, user in ipairs(users) do
79 for i, value in ipairs(providers_of_command) do
80 if providers_of_command[i] == user then
81 table.remove(providers_of_command, i)
82 break
83 end
84 end
85 end
86 local remaining = next(providers_of_command)
87 if remaining then
88 local name, version = remaining:match("^([^/]*)/(.*)$")
89 rep.install_bins(name, version, command)
90 else
91 rep.delete_bin(command)
92 end
93 end
94 end
95
96 for version, _ in pairs(versions) do
97 print("Removing "..name.." "..version.."...")
98 rep.delete_version(name, version)
99 end
100
101 return true
102end
103
104--- Driver function for the "install" command.
105-- @param name string: name of a rock. If a version is given, refer to
106-- a specific version; otherwise, try to remove all versions.
107-- @param version string: When passing a package name, a version number
108-- may also be given.
109-- @return boolean or (nil, string): True if removal was
110-- successful, nil and an error message otherwise.
111function run(...)
112 local flags, name, version = util.parse_flags(...)
113
114 if type(name) ~= "string" then
115 return nil, "Argument missing, see help."
116 end
117 local results = {}
118 search.manifest_search(results, cfg.rocks_dir, search.make_query(name, version))
119
120 local versions = results[name]
121 if not versions then
122 return nil, "Could not find rock '"..name..(version and " "..version or "").."' in local tree."
123 else
124 local version = next(versions)
125 local second = next(versions, version)
126
127 print("Checking stability of dependencies on the absence of")
128 print(name.." "..table.concat(util.keys(versions), ", ").."...")
129 print()
130
131 local dependents = check_dependents(name, versions)
132
133 if #dependents == 0 or flags["force"] then
134 if #dependents > 0 then
135 print("The following packages may be broken by this forced removal:")
136 for _, dependent in ipairs(dependents) do
137 print(dependent.name.." "..dependent.version)
138 end
139 print()
140 end
141 local ok, err1 = delete_versions(name, versions)
142 local ok, err2 = manif.make_manifest(cfg.rocks_dir)
143 if err1 or err2 then
144 return nil, err1 or err2
145 end
146 else
147 if not second then
148 print("Will not remove "..name.." "..version..".")
149 print("Removing it would break dependencies for: ")
150 else
151 print("Will not remove all versions of "..name..".")
152 print("Removing them would break dependencies for: ")
153 end
154 for _, dependent in ipairs(dependents) do
155 print(dependent.name.." "..dependent.version)
156 end
157 print()
158 print("Use --force to force removal (warning: this may break modules).")
159 return nil, "Failed removing."
160 end
161 end
162 return true
163end
diff --git a/src/luarocks/rep.lua b/src/luarocks/rep.lua
new file mode 100644
index 00000000..b5797803
--- /dev/null
+++ b/src/luarocks/rep.lua
@@ -0,0 +1,187 @@
1
2--- Functions for managing the repository on disk.
3module("luarocks.rep", package.seeall)
4
5local fs = require("luarocks.fs")
6local path = require("luarocks.path")
7local cfg = require("luarocks.cfg")
8local util = require("luarocks.util")
9
10--- Get all installed versions of a package.
11-- @param name string: a package name.
12-- @return table or nil: An array of strings listing installed
13-- versions of a package, or nil if none is available.
14function get_versions(name)
15 assert(type(name) == "string")
16
17 local dirs = fs.dir(path.versions_dir(name))
18 return (dirs and #dirs > 0) and dirs or nil
19end
20
21--- Check if a package exists in a local repository.
22-- Version numbers are compared as exact string comparison.
23-- @param name string: name of package
24-- @param version string: package version in string format
25-- @return boolean: true if a package is installed,
26-- false otherwise.
27function is_installed(name, version)
28 assert(type(name) == "string")
29 assert(type(version) == "string")
30
31 return fs.is_dir(path.install_dir(name, version))
32end
33
34--- Delete a package from the local repository.
35-- Version numbers are compared as exact string comparison.
36-- @param name string: name of package
37-- @param version string: package version in string format
38function delete_version(name, version)
39 assert(type(name) == "string")
40 assert(type(version) == "string")
41
42 fs.delete(path.install_dir(name, version))
43 if not get_versions(name) then
44 fs.delete(fs.make_path(cfg.rocks_dir, name))
45 end
46end
47
48--- Delete a command-line item from the bin directory.
49-- @param command string: name of script
50function delete_bin(command)
51 assert(type(command) == "string")
52
53 fs.delete(fs.make_path(cfg.scripts_dir, command))
54end
55
56--- Install bin entries in the repository bin dir.
57-- @param name string: name of package
58-- @param version string: package version in string format
59-- @param single_file string or nil: optional parameter, indicating the name
60-- of a single file to install; if not given, all bin files from the package
61-- are installed.
62-- @return boolean or (nil, string): True if succeeded or nil and
63-- and error message.
64function install_bins(name, version, single_file)
65 assert(type(name) == "string")
66 assert(type(version) == "string")
67
68 local bindir = path.bin_dir(name, version)
69 if fs.exists(bindir) then
70 local ok, err = fs.make_dir(cfg.scripts_dir)
71 if not ok then
72 return nil, "Could not create "..cfg.scripts_dir
73 end
74 local files = single_file and {single_file} or fs.dir(bindir)
75 for _, file in pairs(files) do
76 local fullname = fs.make_path(bindir, file)
77 local match = file:match("%.lua$")
78 local file
79 if not match then
80 file = io.open(fullname)
81 end
82 if match or (file and file:read():match("#!.*lua.*")) then
83 ok, err = fs.wrap_script(fullname, cfg.scripts_dir)
84 else
85 ok, err = fs.copy_binary(fullname, cfg.scripts_dir)
86 end
87 if file then file:close() end
88 if not ok then
89 return nil, err
90 end
91 end
92 end
93 return true
94end
95
96--- Obtain a list of modules within an installed package.
97-- @param package string: The package name; for example "luasocket"
98-- @param version string: The exact version number including revision;
99-- for example "2.0.1-1".
100-- @return table: A table of modules where keys are module identifiers
101-- in "foo.bar" format and values are pathnames in architecture-dependent
102-- "foo/bar.so" format. If no modules are found or if package or version
103-- are invalid, an empty table is returned.
104function package_modules(package, version)
105 assert(type(package) == "string")
106 assert(type(version) == "string")
107
108 local result = {}
109 local luas = fs.find(path.lua_dir(package, version))
110 local libs = fs.find(path.lib_dir(package, version))
111 for _, file in ipairs(luas) do
112 local name = path.path_to_module(file)
113 if name then
114 result[name] = file
115 end
116 end
117 for _, file in ipairs(libs) do
118 local name = path.path_to_module(file)
119 if name then
120 result[name] = file
121 end
122 end
123 return result
124end
125
126--- Obtain a list of command-line scripts within an installed package.
127-- @param package string: The package name; for example "luasocket"
128-- @param version string: The exact version number including revision;
129-- for example "2.0.1-1".
130-- @return table: A table of items where keys are command names
131-- as strings and values are pathnames in architecture-dependent
132-- ".../bin/foo" format. If no modules are found or if package or version
133-- are invalid, an empty table is returned.
134function package_commands(package, version)
135 assert(type(package) == "string")
136 assert(type(version) == "string")
137
138 local result = {}
139 local bindir = path.bin_dir(package, version)
140 local bins = fs.find(bindir)
141 for _, file in ipairs(bins) do
142 if file then
143 result[file] = fs.make_path(bindir, file)
144 end
145 end
146 return result
147end
148
149--- Check if a rock contains binary parts or if it is pure Lua.
150-- @param name string: name of an installed rock
151-- @param version string: version of an installed rock
152-- @return boolean: returns true if rock contains platform-specific
153-- binary code, or false if it is a pure-Lua rock.
154function is_binary_rock(name, version)
155 local bin_dir = path.bin_dir(name, version)
156 local lib_dir = path.lib_dir(name, version)
157 if fs.exists(lib_dir) then
158 return true
159 end
160 if fs.exists(bin_dir) then
161 for _, name in pairs(fs.find(bin_dir)) do
162 if fs.is_actual_binary(fs.make_path(bin_dir, name)) then
163 return true
164 end
165 end
166 end
167 return false
168end
169
170function run_hook(rockspec, hook_name)
171 local hooks = rockspec.hooks
172 if not hooks then
173 return true
174 end
175 if not hooks.substituted_variables then
176 util.variable_substitutions(hooks, rockspec.variables)
177 hooks.substituted_variables = true
178 end
179 local hook = hooks[hook_name]
180 if hook then
181 print(hook)
182 if not fs.execute(hook) then
183 return nil, "Failed running "..hook_name.." hook."
184 end
185 end
186 return true
187end
diff --git a/src/luarocks/require.lua b/src/luarocks/require.lua
new file mode 100644
index 00000000..f5d26078
--- /dev/null
+++ b/src/luarocks/require.lua
@@ -0,0 +1,263 @@
1
2local global_env = _G
3local plain_require = require
4local plain_package_path = package.path
5local plain_package_cpath = package.cpath
6local package, assert, ipairs, pairs, os, print, table, type, next =
7 package, assert, ipairs, pairs, os, print, table, type, next
8
9--- Application interface with LuaRocks.
10-- Load this module to LuaRocks-enable an application:
11-- this overrides the require() function, making it able to
12-- load modules installed as rocks.
13module("luarocks.require")
14
15local path = plain_require("luarocks.path")
16local manif = plain_require("luarocks.manif")
17local deps = plain_require("luarocks.deps")
18local cfg = plain_require("luarocks.cfg")
19
20context = {}
21
22-- Contains a table when rocks trees are loaded,
23-- or 'false' to indicate rocks trees failed to load.
24-- 'nil' indicates rocks trees were not attempted to be loaded yet.
25rocks_trees = nil
26
27local function load_rocks_trees()
28 local any_ok = false
29 local trees = {}
30 for _, tree in pairs(cfg.rocks_trees) do
31 local rocks_dir = tree .. "/rocks/"
32 local manifest, err = manif.load_local_manifest(rocks_dir)
33 if manifest then
34 any_ok = true
35 table.insert(trees, {rocks_dir=rocks_dir, manifest=manifest})
36 end
37 end
38 if not any_ok then
39 rocks_trees = false
40 return false
41 end
42 rocks_trees = trees
43 return true
44end
45
46--- Process the dependencies of a package to determine its dependency
47-- chain for loading modules.
48-- @parse name string: The name of an installed rock.
49-- @parse version string: The version of the rock, in string format
50-- @parse manifest table: The local manifest table where this rock
51-- is installed.
52local function add_context(name, version, manifest)
53 -- assert(type(name) == "string")
54 -- assert(type(version) == "string")
55 -- assert(type(manifest) == "table")
56
57 if context[name] then
58 return
59 end
60 context[name] = version
61
62 local pkgdeps = manifest.dependencies and manifest.dependencies[name][version]
63 if pkgdeps then
64 for _, dep in ipairs(pkgdeps) do
65 local package, constraints = dep.name, dep.constraints
66
67 for _, tree in pairs(rocks_trees) do
68 local entries = tree.manifest.repository[package]
69 if entries then
70 for version, packages in pairs(entries) do
71 if (not constraints) or deps.match_constraints(deps.parse_version(version), constraints) then
72 add_context(package, version, tree.manifest)
73 end
74 end
75 end
76 end
77 end
78 end
79end
80
81--- Internal sorting function.
82-- @param a table: A provider table.
83-- @param b table: Another provider table.
84-- @return boolean: True if the version of a is greater than that of b.
85local function sort_versions(a,b)
86 return a.version > b.version
87end
88
89--- Specify a dependency chain for LuaRocks.
90-- In the presence of multiple versions of packages, it is necessary to,
91-- at some point, indicate which dependency chain we're following.
92-- set_context does this by allowing one to pick a package to be the
93-- root of this dependency chain. Once a dependency chain is picked it's
94-- easy to know which modules to load ("I want to use *this* version of
95-- A, which requires *that* version of B, which requires etc etc etc").
96-- @param name string: The package name of an installed rock.
97-- @param version string or nil: Optionally, a version number
98-- When a version is not given, it picks the highest version installed.
99-- @return boolean: true if succeeded, false otherwise.
100function set_context(name, version)
101 --assert(type(name) == "string")
102 --assert(type(version) == "string" or not version)
103
104 if rocks_trees == false or (not rocks_trees and not load_rocks_trees()) then
105 return false
106 end
107
108 local manifest
109 local vtables = {}
110 for _, tree in ipairs(rocks_trees) do
111 if version then
112 local manif_repo = tree.manifest.repository
113 if manif_repo[name] and manif_repo[name][version] then
114 manifest = tree.manifest
115 break
116 end
117 else
118 local versions = manif.get_versions(name, tree.manifest)
119 for _, version in ipairs(versions) do
120 table.insert(vtables, {version = deps.parse_version(version), manifest = tree.manifest})
121 end
122 end
123 end
124 if not version then
125 if not next(vtables) then
126 table.sort(vtables, sort_versions)
127 local highest = vtables[#vtables]
128 version = highest.version.string
129 manifest = highest.manifest
130 end
131 end
132 if not manifest then
133 return false
134 end
135
136 add_context(name, version, manifest)
137 -- TODO: platform independence
138 local lpath, cpath = "", ""
139 for name, version in pairs(context) do
140 lpath = lpath .. path.lua_dir(name, version) .. "/?.lua;"
141 lpath = lpath .. path.lua_dir(name, version) .. "/?/init.lua;"
142 cpath = cpath .. path.lib_dir(name, version) .."/?."..cfg.lib_extension..";"
143 end
144 global_env.package.path = lpath .. plain_package_path
145 global_env.package.cpath = cpath .. plain_package_cpath
146end
147
148--- Call the vanilla require() function using specially constructed
149-- package paths so that it finds exactly the version we want it to find.
150-- @param name string: The rock name.
151-- @param version string: The rock version.
152-- @param module string: The module name, in require() notation.
153-- @return The result returned by require().
154local function plain_require_on(module, name, version, rocks_dir, ...)
155 --assert(type(module) == "string")
156 --assert(type(name) == "string")
157 --assert(type(version) == "string")
158
159 local global_package = global_env.package
160 local save_path = global_package.path
161 local save_cpath = global_package.cpath
162 global_package.path = path.lua_dir(name, version, rocks_dir) .. "/?.lua;"
163 .. path.lua_dir(name, version, rocks_dir) .. "/?/init.lua;" .. save_path
164 global_package.cpath = path.lib_dir(name, version, rocks_dir) .. "/?."..cfg.lib_extension..";" .. save_cpath
165 local result = plain_require(module, ...)
166 global_package.path = save_path
167 global_package.cpath = save_cpath
168 return result
169end
170
171local function pick_module(module, constraints)
172 --assert(type(module) == "string")
173 --assert(not constraints or type(constraints) == "string")
174
175 if not rocks_trees and not load_rocks_trees() then
176 return nil
177 end
178
179 if constraints then
180 if type(constraints) == "string" then
181 constraints = deps.parse_constraints(constraints)
182 else
183 constraints = nil
184 end
185 end
186
187 local providers = {}
188 for _, tree in pairs(rocks_trees) do
189 local entries = tree.manifest.modules[module]
190 if entries then
191 for _, entry in pairs(entries) do
192 local name, version = entry:match("^([^/]*)/(.*)$")
193 if context[name] == version then
194 return name, version, tree
195 end
196 version = deps.parse_version(version)
197 if (not constraints) or deps.match_constraints(version, constraints) then
198 table.insert(providers, {name = name, version = version, repo = tree})
199 end
200 end
201 end
202 end
203
204 if next(providers) then
205 table.sort(providers, sort_versions)
206 local first = providers[1]
207 return first.name, first.version.string, first.repo
208 end
209end
210
211--- Inform which rock LuaRocks would use if require() is called
212-- with the given arguments.
213-- @param module string: The module name, like in plain require().
214-- @param constraints string or nil: An optional comma-separated
215-- list of version constraints.
216-- @return (string, string) or nil: Rock name and version if the
217-- requested module can be supplied by LuaRocks, or nil if it can't.
218function get_rock_from_module(module, constraints)
219 --assert(type(module) == "string")
220 --assert(not constraints or type(constraints) == "string")
221 local name, version = pick_module(module, constraints)
222 return name, version
223end
224
225--- Function that overloads require(), adding LuaRocks support.
226-- This function wraps around Lua's standard require() call,
227-- allowing it to find modules installed by LuaRocks.
228-- A module is searched in installed rocks that match the
229-- current LuaRocks context. If module is not part of the
230-- context, or if a context has not yet been set, the module
231-- in the package with the highest version is used.
232-- If a module is not available in any installed rock, plain
233-- require() is called, using the original package.path and
234-- package.cpath lookup locations.
235-- Additionally, version constraints for the matching rock may
236-- be given as a second parameter. If version constraints could
237-- not be fulfilled, this equates to the rock not being
238-- available: as such, plain require() with the default paths
239-- is called as a fallback.
240-- @param module string: The module name, like in plain require().
241-- @param constraints string or nil: An optional comma-separated
242-- list of version constraints.
243-- @return table: The module table (typically), like in plain
244-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a>
245-- in the Lua reference manual for details.
246function require(module, ...)
247 --assert(type(module) == "string")
248
249 if package.loaded[module] or rocks_trees == false or (not rocks_trees and not load_rocks_trees()) then
250 return plain_require(module, ...)
251 end
252
253 local name, version, repo = pick_module(module, ...)
254
255 if not name then
256 return plain_require(module, ...)
257 else
258 add_context(name, version, repo.manifest)
259 return plain_require_on(module, name, version, repo.rocks_dir, ...)
260 end
261end
262
263global_env.require = require
diff --git a/src/luarocks/search.lua b/src/luarocks/search.lua
new file mode 100644
index 00000000..a3ac68f3
--- /dev/null
+++ b/src/luarocks/search.lua
@@ -0,0 +1,394 @@
1
2--- Module implementing the LuaRocks "search" command.
3-- Queries LuaRocks servers.
4module("luarocks.search", package.seeall)
5
6local fs = require("luarocks.fs")
7local path = require("luarocks.path")
8local manif = require("luarocks.manif")
9local deps = require("luarocks.deps")
10local cfg = require("luarocks.cfg")
11local util = require("luarocks.util")
12
13help_summary = "Query the LuaRocks servers."
14help_arguments = "[--source] [--binary] { <name> [<version>] | --all }"
15help = [[
16--source Return only rockspecs and source rocks,
17 to be used with the "build" command.
18--binary Return only pure Lua and binary rocks (rocks that can be used
19 with the "install" command without requiring a C toolchain).
20--all List all contents of the server that are suitable to
21 this platform, do not filter by name.
22]]
23
24--- Convert the arch field of a query table to table format.
25-- @param query table: A query table.
26local function query_arch_as_table(query)
27 local format = type(query.arch)
28 if format == "table" then
29 return
30 elseif format == "nil" then
31 local accept = {}
32 accept["src"] = true
33 accept["all"] = true
34 accept["rockspec"] = true
35 accept["installed"] = true
36 accept[cfg.arch] = true
37 query.arch = accept
38 elseif format == "string" then
39 local accept = {}
40 for a in string.gmatch(query.arch, "[%w_]+") do
41 accept[a] = true
42 end
43 query.arch = accept
44 end
45end
46
47--- Store a search result (a rock or rockspec) in the results table.
48-- @param results table: The results table, where keys are package names and
49-- versions are tables matching version strings to an array of servers.
50-- @param name string: Package name.
51-- @param version string: Package version.
52-- @param arch string: Architecture of rock ("all", "src" or platform
53-- identifier), "rockspec" or "installed"
54-- @param repo string: Pathname of a local repository of URL of
55-- rocks server.
56local function store_result(results, name, version, arch, repo)
57 assert(type(results) == "table")
58 assert(type(name) == "string")
59 assert(type(version) == "string")
60 assert(type(arch) == "string")
61 assert(type(repo) == "string")
62
63 if not results[name] then results[name] = {} end
64 if not results[name][version] then results[name][version] = {} end
65 table.insert(results[name][version], {
66 arch = arch,
67 repo = repo
68 })
69end
70
71--- Test the name field of a query.
72-- If query has a boolean field exact_name set to false,
73-- then substring match is performed; otherwise, exact string
74-- comparison is done.
75-- @param query table: A query in dependency table format.
76-- @param name string: A package name.
77-- @return boolean: True if names match, false otherwise.
78local function match_name(query, name)
79 assert(type(query) == "table")
80 assert(type(name) == "string")
81 if query.exact_name == false then
82 return name:find(query.name, 0, true) and true or false
83 else
84 return name == query.name
85 end
86end
87
88--- Store a match in a results table if version matches query.
89-- Name, version, arch and repository path are stored in a given
90-- table, optionally checking if version and arch (if given) match
91-- a query.
92-- @param results table: The results table, where keys are package names and
93-- versions are tables matching version strings to an array of servers.
94-- @param repo string: URL or pathname of the repository.
95-- @param name string: The name of the package being tested.
96-- @param version string: The version of the package being tested.
97-- @param arch string: The arch of the package being tested.
98-- @param query table: A table describing the query in dependency
99-- format (for example, {name = "filesystem", exact_name = false,
100-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
101-- If the arch field is omitted, the local architecture (cfg.arch)
102-- is used. The special value "any" is also recognized, returning all
103-- matches regardless of architecture.
104local function store_if_match(results, repo, name, version, arch, query)
105 if match_name(query, name) then
106 if query.arch[arch] or query.arch["any"] then
107 if deps.match_constraints(deps.parse_version(version), query.constraints) then
108 store_result(results, name, version, arch, repo)
109 end
110 end
111 end
112end
113
114--- Perform search on a local repository.
115-- @param repo string: The pathname of the local repository.
116-- @param query table: A table describing the query in dependency
117-- format (for example, {name = "filesystem", exact_name = false,
118-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
119-- If the arch field is omitted, the local architecture (cfg.arch)
120-- is used. The special value "any" is also recognized, returning all
121-- matches regardless of architecture.
122-- @param results table or nil: If given, this table will store the
123-- results; if not given, a new table will be created.
124-- @param table: The results table, where keys are package names and
125-- versions are tables matching version strings to an array of servers.
126-- If a table was given in the "results" parameter, that is the result value.
127function disk_search(repo, query, results)
128 assert(type(repo) == "string")
129 assert(type(query) == "table")
130 assert(type(results) == "table" or not results)
131 if not results then
132 results = {}
133 end
134 query_arch_as_table(query)
135
136 for _, name in pairs(fs.dir(repo)) do
137 local pathname = fs.make_path(repo, name)
138 local rname, rversion, rarch = path.parse_rock_name(name)
139 if not rname then
140 rname, rversion, rarch = path.parse_rockspec_name(name)
141 end
142 if fs.is_dir(pathname) then
143 for _, version in pairs(fs.dir(pathname)) do
144 if version:match("-%d+$") then
145 store_if_match(results, repo, name, version, "installed", query)
146 end
147 end
148 elseif rname then
149 store_if_match(results, repo, rname, rversion, rarch, query)
150 end
151 end
152 return results
153end
154
155--- Perform search on a rocks server.
156-- @param results table: The results table, where keys are package names and
157-- versions are tables matching version strings to an array of servers.
158-- @param repo string: The URL of the rocks server.
159-- @param query table: A table describing the query in dependency
160-- format (for example, {name = "filesystem", exact_name = false,
161-- constraints = {op = "~>", version = {1,0}}}, arch = "rockspec").
162-- If the arch field is omitted, the local architecture (cfg.arch)
163-- is used. The special value "any" is also recognized, returning all
164-- matches regardless of architecture.
165function manifest_search(results, repo, query)
166 assert(type(results) == "table")
167 assert(type(repo) == "string")
168 assert(type(query) == "table")
169
170 query_arch_as_table(query)
171 local manifest, err = manif.load_manifest(repo)
172 if not manifest then
173 print(err)
174 return
175 end
176 for name, versions in pairs(manifest.repository) do
177 for version, items in pairs(versions) do
178 for _, item in ipairs(items) do
179 store_if_match(results, repo, name, version, item.arch, query)
180 end
181 end
182 end
183end
184
185--- Search on all configured rocks servers.
186-- @param query table: A dependency query.
187-- @return table or (nil, string): A table where keys are package names
188-- and values are tables matching version strings to an array of
189-- rocks servers; if no results are found, an empty table is returned.
190-- In case of errors, nil and and error message are returned.
191local function search_repos(query)
192 assert(type(query) == "table")
193
194 local results = {}
195 for _, repo in ipairs(cfg.rocks_servers) do
196 local protocol, pathname = fs.split_url(repo)
197 if protocol == "file" then
198 repo = pathname
199 end
200 manifest_search(results, repo, query)
201 end
202 return results
203end
204
205--- Prepare a query in dependency table format.
206-- @param name string: The query name.
207-- @param version string or nil:
208-- @return table: A query in table format
209function make_query(name, version)
210 assert(type(name) == "string")
211 assert(type(version) == "string" or not version)
212
213 local query = {
214 name = name,
215 constraints = {}
216 }
217 if version then
218 table.insert(query.constraints, { op = "~>", version = deps.parse_version(version)})
219 end
220 return query
221end
222
223--- Get the URL for the latest in a set of versions.
224-- @param name string: The package name to be used in the URL.
225-- @param versions table: An array of version informations, as stored
226-- in search results tables.
227-- @return string or nil: the URL for the latest version if one could
228-- be picked, or nil.
229local function pick_latest_version(name, versions)
230 assert(type(name) == "string")
231 assert(type(versions) == "table")
232
233 local vtables = {}
234 for v, _ in pairs(versions) do
235 table.insert(vtables, deps.parse_version(v))
236 end
237 table.sort(vtables)
238 local version = vtables[#vtables].string
239 local items = versions[version]
240 if items then
241 local pick = 1
242 for i, item in ipairs(items) do
243 if (item.arch == 'src' and items[pick].arch == 'rockspec')
244 or (item.arch ~= 'src' and item.arch ~= 'rockspec') then
245 pick = i
246 end
247 end
248 return path.make_url(items[pick].repo, name, version, items[pick].arch)
249 end
250 return nil
251end
252
253--- Attempt to get a single URL for a given search.
254-- @param query table: A dependency query.
255-- @return string or table or (nil, string): URL for matching rock if
256-- a single one was found, a table of candidates if it could not narrow to
257-- a single result, or nil followed by an error message.
258function find_suitable_rock(query)
259 assert(type(query) == "table")
260
261 local results, err = search_repos(query)
262 if not results then
263 return nil, err
264 end
265 local first = results and next(results)
266 if first and next(results, first) == nil then
267 return pick_latest_version(query.name, results[first])
268 elseif not first then
269 return nil, "No results matching query were found."
270 else
271 return results
272 end
273end
274
275--- Print a list of rocks/rockspecs on standard output.
276-- @param results table: A table where keys are package names and versions
277-- are tables matching version strings to an array of rocks servers.
278-- @param show_repo boolean or nil: Whether to show repository
279-- information or not. Default is true.
280function print_results(results, show_repo)
281 assert(type(results) == "table")
282 assert(type(show_repo) == "boolean" or not show_repo)
283 -- Force display of repo location for the time being
284 show_repo = true -- show_repo == nil and true or show_repo
285
286 for package, versions in util.sortedpairs(results) do
287 print(package)
288 for version, repos in util.sortedpairs(versions, deps.compare_versions) do
289 if show_repo then
290 for _, repo in ipairs(repos) do
291 print(" "..version.." ("..repo.arch..") - "..repo.repo)
292 end
293 else
294 print(" "..version)
295 end
296 end
297 print()
298 end
299end
300
301--- Splits a list of search results into two lists, one for "source" results
302-- to be used with the "build" command, and one for "binary" results to be
303-- used with the "install" command.
304-- @param results table: A search results table.
305-- @return (table, table): Two tables, one for source and one for binary
306-- results.
307local function split_source_and_binary_results(results)
308 local sources, binaries = {}, {}
309 for name, versions in pairs(results) do
310 for version, repos in pairs(versions) do
311 for _, repo in ipairs(repos) do
312 local where = sources
313 if repo.arch == "all" or repo.arch == cfg.arch then
314 where = binaries
315 end
316 store_result(where, name, version, repo.arch, repo.repo)
317 end
318 end
319 end
320 return sources, binaries
321end
322
323--- Given a name and optionally a version, try to find in the rocks
324-- servers a single .src.rock or .rockspec file that satisfies
325-- the request, and run the given function on it; or display to the
326-- user possibilities if it couldn't narrow down a single match.
327-- @param action function: A function that takes a .src.rock or
328-- .rockspec URL as a parameter.
329-- @string name string: A rock name
330-- @string version string or nil: A version number may also be given.
331-- @return The result of the action function, or nil and an error message.
332function act_on_src_or_rockspec(action, name, version)
333 assert(type(action) == "function")
334 assert(type(name) == "string")
335 assert(type(version) == "string" or not version)
336
337 local query = make_query(name, version)
338 query.arch = "src|rockspec"
339 local results, err = find_suitable_rock(query)
340 if type(results) == "string" then
341 return action(results)
342 elseif type(results) == "table" and next(results) then
343 print("Multiple search results were returned.")
344 print()
345 print("Search results:")
346 print("---------------")
347 print_results(results)
348 return nil, "Please narrow your query."
349 else
350 return nil, "Could not find a result named "..name.."."
351 end
352end
353
354--- Driver function for "search" command.
355-- @param name string: A substring of a rock name to search.
356-- @param version string or nil: a version may also be passed.
357-- @return boolean or (nil, string): True if build was successful; nil and an
358-- error message otherwise.
359function run(...)
360 local flags, name, version = util.parse_flags(...)
361
362 if flags["all"] then
363 name, version = "", ""
364 end
365
366 if type(name) ~= "string" and not flags["all"] then
367 return nil, "Enter name and version or use --all; see help."
368 end
369
370 local query = make_query(name, version)
371 query.exact_name = false
372 local results, err = search_repos(query)
373 if not results then
374 return nil, err
375 end
376 print()
377 print("Search results:")
378 print("===============")
379 print()
380 local sources, binaries = split_source_and_binary_results(results)
381 if next(sources) and not flags["binary"] then
382 print("Rockspecs and source rocks:")
383 print("---------------------------")
384 print()
385 print_results(sources, true)
386 end
387 if next(binaries) and not flags["source"] then
388 print("Binary and pure-Lua rocks:")
389 print("--------------------------")
390 print()
391 print_results(binaries, true)
392 end
393 return true
394end
diff --git a/src/luarocks/type_check.lua b/src/luarocks/type_check.lua
new file mode 100644
index 00000000..ec9d6c5c
--- /dev/null
+++ b/src/luarocks/type_check.lua
@@ -0,0 +1,233 @@
1
2--- Type-checking functions.
3-- Functions and definitions for doing a basic lint check on files
4-- loaded by LuaRocks.
5module("luarocks.type_check", package.seeall)
6
7rockspec_format = "1.0"
8
9rockspec_types = {
10 rockspec_format = "string",
11 MUST_package = "string",
12 MUST_version = "string",
13 description = {
14 summary = "string",
15 detailed = "string",
16 homepage = "string",
17 license = "string",
18 maintainer = "string"
19 },
20 dependencies = {
21 platforms = {},
22 ANY = "string"
23 },
24 supported_platforms = {
25 ANY = "string"
26 },
27 external_dependencies = {
28 platforms = {},
29 ANY = {
30 program = "string",
31 header = "string",
32 library = "string"
33 }
34 },
35 MUST_source = {
36 platforms = {},
37 MUST_url = "string",
38 md5 = "string",
39 file = "string",
40 dir = "string",
41 tag = "string",
42 branch = "string",
43 cvs_tag = "string",
44 cvs_module = "string"
45 },
46 build = {
47 platforms = {},
48 type = "string",
49 install = {
50 lua = {
51 MORE = true
52 },
53 lib = {
54 MORE = true
55 },
56 conf = {
57 MORE = true
58 },
59 bin = {
60 MORE = true
61 }
62 },
63 copy_directories = {
64 ANY = "string"
65 },
66 MORE = true
67 },
68 hooks = {
69 platforms = {},
70 post_install = "string"
71 }
72}
73
74rockspec_types.build.platforms.ANY = rockspec_types.build
75rockspec_types.dependencies.platforms.ANY = rockspec_types.dependencies
76rockspec_types.external_dependencies.platforms.ANY = rockspec_types.external_dependencies
77rockspec_types.MUST_source.platforms.ANY = rockspec_types.MUST_source
78rockspec_types.hooks.platforms.ANY = rockspec_types.hooks
79
80manifest_types = {
81 MUST_repository = {
82 -- packages
83 ANY = {
84 -- versions
85 ANY = {
86 -- items
87 ANY = {
88 MUST_arch = "string",
89 modules = { ANY = "string" },
90 commands = { ANY = "string" },
91 dependencies = { ANY = "string" },
92 -- TODO: to be extended with more metadata.
93 }
94 }
95 }
96 },
97 MUST_modules = {
98 -- modules
99 ANY = {
100 -- providers
101 ANY = "string"
102 }
103 },
104 MUST_commands = {
105 -- modules
106 ANY = {
107 -- commands
108 ANY = "string"
109 }
110 },
111 dependencies = {
112 -- each module
113 ANY = {
114 -- each version
115 ANY = {
116 -- each dependency
117 ANY = {
118 name = "string",
119 constraints = {
120 ANY = {
121 no_upgrade = "boolean",
122 op = "string",
123 version = {
124 string = "string",
125 ANY = 0,
126 }
127 }
128 }
129 }
130 }
131 }
132 }
133}
134
135local type_check_table
136
137--- Type check an object.
138-- The object is compared against an archetypical value
139-- matching the expected type -- the actual values don't matter,
140-- only their types. Tables are type checked recursively.
141-- @param name any: The object name (for error messages).
142-- @param item any: The object being checked.
143-- @param expected any: The reference object. In case of a table,
144-- its is structured as a type reference table.
145-- @return boolean or (nil, string): true if type checking
146-- succeeded, or nil and an error message if it failed.
147-- @see type_check_table
148local function type_check_item(name, item, expected, context)
149 name = tostring(name)
150
151 local item_type = type(item)
152 local expected_type = type(expected)
153 if expected_type == "number" then
154 if not tonumber(item) then
155 return nil, "Type mismatch on field "..context..name..": expected a number"
156 end
157 elseif expected_type == "table" then
158 if item_type ~= expected_type then
159 return nil, "Type mismatch on field "..context..name..": expected a table"
160 else
161 return type_check_table(item, expected, context..name..".")
162 end
163 elseif item_type ~= expected_type then
164 return nil, "Type mismatch on field "..context..name..": expected a "..expected_type
165 end
166 return true
167end
168
169--- Type check the contents of a table.
170-- The table's contents are compared against a reference table,
171-- which contains the recognized fields, with archetypical values
172-- matching the expected types -- the actual values of items in the
173-- reference table don't matter, only their types (ie, for field x
174-- in tbl that is correctly typed, type(tbl.x) == type(types.x)).
175-- If the reference table contains a field called MORE, then
176-- unknown fields in the checked table are accepted.
177-- If it contains a field called ANY, then its type will be
178-- used to check any unknown fields. If a field is prefixed
179-- with MUST_, it is mandatory; its absence from the table is
180-- a type error.
181-- Tables are type checked recursively.
182-- @param tbl table: The table to be type checked.
183-- @param types table: The reference table, containing
184-- values for recognized fields in the checked table.
185-- @return boolean or (nil, string): true if type checking
186-- succeeded, or nil and an error message if it failed.
187type_check_table = function(tbl, types, context)
188 assert(type(tbl) == "table")
189 assert(type(types) == "table")
190
191 for k, v in pairs(tbl) do
192 local t = types[k] or (type(k) == "string" and types["MUST_"..k]) or types.ANY
193 if t then
194 local ok, err = type_check_item(k, v, t, context)
195 if not ok then return nil, err end
196 elseif types.MORE then
197 -- Accept unknown field
198 else
199 return nil, "Unknown field "..k
200 end
201 end
202 for k, v in pairs(types) do
203 local mandatory_key = k:match("^MUST_(.+)")
204 if mandatory_key then
205 if not tbl[mandatory_key] then
206 return nil, "Mandatory field "..context..mandatory_key.." is missing."
207 end
208 end
209 end
210 return true
211end
212
213--- Type check a rockspec table.
214-- Verify the correctness of elements from a
215-- rockspec table, reporting on unknown fields and type
216-- mismatches.
217-- @return boolean or (nil, string): true if type checking
218-- succeeded, or nil and an error message if it failed.
219function type_check_rockspec(rockspec)
220 assert(type(rockspec) == "table")
221 return type_check_table(rockspec, rockspec_types, "")
222end
223
224--- Type check a manifest table.
225-- Verify the correctness of elements from a
226-- manifest table, reporting on unknown fields and type
227-- mismatches.
228-- @return boolean or (nil, string): true if type checking
229-- succeeded, or nil and an error message if it failed.
230function type_check_manifest(manifest)
231 assert(type(manifest) == "table")
232 return type_check_table(manifest, manifest_types, "")
233end
diff --git a/src/luarocks/unpack.lua b/src/luarocks/unpack.lua
new file mode 100644
index 00000000..60bc1295
--- /dev/null
+++ b/src/luarocks/unpack.lua
@@ -0,0 +1,148 @@
1
2--- Module implementing the LuaRocks "unpack" command.
3-- Unpack the contents of a rock.
4module("luarocks.unpack", package.seeall)
5
6local fetch = require("luarocks.fetch")
7local fs = require("luarocks.fs")
8local util = require("luarocks.util")
9local build = require("luarocks.build")
10
11help_summary = "Unpack the contents of a rock."
12help_arguments = "{<rock>|<name> [<version>]}"
13help = [[
14Unpacks the contents of a rock in a newly created directory.
15Argument may be a rock file, or the name of a rock in a rocks server.
16In the latter case, the app version may be given as a second argument.
17]]
18
19--- Load a rockspec file to the given directory, fetches the source
20-- files specified in the rockspec, and unpack them inside the directory.
21-- @param rockspec_file string: The URL for a rockspec file.
22-- @param dir_name string: The directory where to store and unpack files.
23-- @return table or (nil, string): the loaded rockspec table or
24-- nil and an error message.
25local function unpack_rockspec(rockspec_file, dir_name)
26 assert(type(rockspec_file) == "string")
27 assert(type(dir_name) == "string")
28
29 local rockspec = fetch.load_rockspec(rockspec_file)
30 if not rockspec then
31 return nil, "Failed loading rockspec "..rockspec_file
32 end
33 fs.change_dir(dir_name)
34 local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".")
35 if not ok then
36 return nil, sources_dir
37 end
38 fs.change_dir(dir_name)
39 build.apply_patches(rockspec)
40 fs.pop_dir()
41 return rockspec
42end
43
44--- Load a .rock file to the given directory and unpack it inside it.
45-- @param rock_file string: The URL for a .rock file.
46-- @param dir_name string: The directory where to unpack.
47-- @return table or (nil, string): the loaded rockspec table or
48-- nil and an error message.
49local function unpack_rock(rock_file, dir_name, kind)
50 assert(type(rock_file) == "string")
51 assert(type(dir_name) == "string")
52
53 local ok, err = fetch.fetch_and_unpack_rock(rock_file, dir_name)
54 if not ok then
55 return nil, "Failed unzipping rock "..rock_file
56 end
57 fs.change_dir(dir_name)
58 local rockspec_file = dir_name..".rockspec"
59 local rockspec, err = fetch.load_rockspec(rockspec_file)
60 if not rockspec then
61 return nil, "Failed loading rockspec "..rockspec_file..": "..err
62 end
63 if kind == "src" then
64 if rockspec.source.file then
65 local ok, err = fs.unpack_archive(rockspec.source.file)
66 if not ok then
67 return nil, err
68 end
69 fs.change_dir(rockspec.source.dir)
70 build.apply_patches(rockspec)
71 fs.pop_dir()
72 end
73 end
74 return rockspec
75end
76
77--- Create a directory and perform the necessary actions so that
78-- the sources for the rock and its rockspec are unpacked inside it,
79-- laid out properly so that the 'make' command is able to build the module.
80-- @param file string: A rockspec or .rock URL.
81-- @return boolean or (nil, string): true if successful or nil followed
82-- by an error message.
83local function run_unpacker(file)
84 assert(type(file) == "string")
85
86 local base_name = fs.base_name(file)
87 local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$")
88 if not extension then
89 dir_name, extension = base_name:match("(.*)%.(rockspec)$")
90 kind = "rockspec"
91 end
92 if not extension then
93 return nil, file.." does not seem to be a valid filename."
94 end
95
96 if (fs.exists(dir_name)) then
97 return nil, "Directory "..dir_name.." already exists."
98 end
99 fs.make_dir(dir_name)
100 local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name))
101
102 local rockspec, err
103 if extension == "rock" then
104 rockspec, err = unpack_rock(file, dir_name, kind)
105 elseif extension == "rockspec" then
106 rockspec, err = unpack_rockspec(file, dir_name)
107 end
108 if not rockspec then
109 return nil, err
110 end
111 if kind == "src" or kind == "rockspec" then
112 if rockspec.source.dir ~= "." then
113 local ok = fs.copy(rockspec.local_filename, rockspec.source.dir)
114 if not ok then
115 return nil, "Failed copying unpacked rockspec into unpacked source directory."
116 end
117 end
118 print()
119 print("Done. You may now enter directory ")
120 print(fs.make_path(dir_name, rockspec.source.dir))
121 print("and type 'luarocks make' to build.")
122 end
123 util.remove_scheduled_function(rollback)
124 return true
125end
126
127--- Driver function for the "unpack" command.
128-- @param name string: may be a rock filename, for unpacking a
129-- rock file or the name of a rock to be fetched and unpacked.
130-- @param version string or nil: if the name of a package is given, a
131-- version may also be passed.
132-- @return boolean or (nil, string): true if successful or nil followed
133-- by an error message.
134function run(...)
135 local flags, name, version = util.parse_flags(...)
136
137 assert(type(version) == "string" or not version)
138 if type(name) ~= "string" then
139 return nil, "Argument missing, see help."
140 end
141
142 if name:match(".*%.rock") or name:match(".*%.rockspec") then
143 return run_unpacker(name)
144 else
145 local search = require("luarocks.search")
146 return search.act_on_src_or_rockspec(run_unpacker, name, version)
147 end
148end
diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua
new file mode 100644
index 00000000..50efaa9d
--- /dev/null
+++ b/src/luarocks/util.lua
@@ -0,0 +1,180 @@
1
2local global_env = _G
3
4--- Utility functions shared by other modules.
5-- Does not requires modules directly (only as locals
6-- inside specific functions) to avoid interdependencies,
7-- as this is used in the bootstrapping stage of luarocks.cfg.
8module("luarocks.util", package.seeall)
9
10local scheduled_functions = {}
11
12--- Schedule a function to be executed upon program termination.
13-- This is useful for actions such as deleting temporary directories
14-- or failure rollbacks.
15-- @param f function: Function to be executed.
16-- @param ... arguments to be passed to function.
17-- @return table: A token representing the scheduled execution,
18-- which can be used to remove the item later from the list.
19function schedule_function(f, ...)
20 assert(type(f) == "function")
21
22 local item = { fn = f, args = {...} }
23 table.insert(scheduled_functions, item)
24 return item
25end
26
27--- Unschedule a function.
28-- This is useful for cancelling a rollback of a completed operation.
29-- @param table: The token representing the scheduled function that was
30-- returned from the schedule_function call.
31function remove_scheduled_function(item)
32 for k, v in pairs(scheduled_functions) do
33 if v == item then
34 table.remove(scheduled_functions, k)
35 return
36 end
37 end
38end
39
40--- Execute scheduled functions.
41-- Some calls create temporary files and/or directories and register
42-- corresponding cleanup functions. Calling this function will run
43-- these function, erasing temporaries.
44-- Functions are executed in the inverse order they were scheduled.
45function run_scheduled_functions()
46 local fs = require("luarocks.fs")
47 fs.change_dir_to_root()
48 for i = #scheduled_functions, 1, -1 do
49 local item = scheduled_functions[i]
50 item.fn(unpack(item.args))
51 end
52end
53
54--- Extract flags from an arguments list.
55-- Given string arguments, extract flag arguments into a flags set.
56-- For example, given "foo", "--tux=beep", "--bla", "bar", "--baz",
57-- it would return the following:
58-- {["bla"] = true, ["tux"] = "beep", ["baz"] = true}, "foo", "bar".
59function parse_flags(...)
60 local args = {...}
61 local flags = {}
62 for i = #args, 1, -1 do
63 local flag = args[i]:match("^%-%-(.*)")
64 if flag then
65 local var,val = flag:match("([a-z_%-]*)=(.*)")
66 if val then
67 flags[var] = val
68 else
69 flags[flag] = true
70 end
71 table.remove(args, i)
72 end
73 end
74 return flags, unpack(args)
75end
76
77--- Merges contents of src on top of dst's contents.
78-- @param dst Destination table, which will receive src's contents.
79-- @param src Table which provides new contents to dst.
80-- @see platform_overrides
81function deep_merge(dst, src)
82 for k, v in pairs(src) do
83 if type(v) == "table" then
84 if not dst[k] then
85 dst[k] = {}
86 end
87 deep_merge(dst[k], v)
88 else
89 dst[k] = v
90 end
91 end
92end
93
94--- Perform platform-specific overrides on a table.
95-- Overrides values of table with the contents of the appropriate
96-- subset of its "platforms" field. The "platforms" field should
97-- be a table containing subtables keyed with strings representing
98-- platform names. Names that match the contents of the global
99-- cfg.platforms setting are used. For example, if
100-- cfg.platforms= {"foo"}, then the fields of
101-- tbl.platforms.foo will overwrite those of tbl with the same
102-- names. For table values, the operation is performed recursively
103-- (tbl.platforms.foo.x.y.z overrides tbl.x.y.z; other contents of
104-- tbl.x are preserved).
105-- @param tbl table or nil: Table which may contain a "platforms" field;
106-- if it doesn't (or if nil is passed), this function does nothing.
107function platform_overrides(tbl)
108 assert(type(tbl) == "table" or not tbl)
109
110 local cfg = require("luarocks.cfg")
111
112 if not tbl then return end
113
114 if tbl.platforms then
115 for _, platform in ipairs(cfg.platforms) do
116 local platform_tbl = tbl.platforms[platform]
117 if platform_tbl then
118 deep_merge(tbl, platform_tbl)
119 end
120 end
121 end
122 tbl.platforms = nil
123end
124
125--- Perform make-style variable substitutions on string values of a table.
126-- For every string value tbl.x which contains a substring of the format
127-- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field
128-- exists in vars. Only string values are processed; this function
129-- does not scan subtables recursively.
130-- @param tbl table: Table to have its string values modified.
131-- @param vars table: Table containing string-string key-value pairs
132-- representing variables to replace in the strings values of tbl.
133function variable_substitutions(tbl, vars)
134 assert(type(tbl) == "table")
135 assert(type(vars) == "table")
136
137 local updated = {}
138 for k, v in pairs(tbl) do
139 if type(v) == "string" then
140 updated[k] = v:gsub("%$%((%a[%a%d_]+)%)", vars)
141 end
142 end
143 for k, v in pairs(updated) do
144 tbl[k] = v
145 end
146end
147
148--- Return an array of keys of a table.
149-- @param tbl table: The input table.
150-- @return table: The array of keys.
151function keys(tbl)
152 local ks = {}
153 for k,_ in pairs(tbl) do
154 table.insert(ks, k)
155 end
156 return ks
157end
158
159-- The iterator function used internally by util.sortedpairs.
160-- @param tbl table: The table to be iterated.
161-- @param sort_function function or nil: An optional comparison function
162-- to be used by table.sort when sorting keys.
163-- @see sortedpairs
164local function sortedpairs_iterator(tbl, sort_function)
165 local ks = keys(tbl)
166 table.sort(ks, sort_function)
167 for _, k in ipairs(ks) do
168 coroutine.yield(k, tbl[k])
169 end
170end
171
172--- A table iterator generator that returns elements sorted by key,
173-- to be used in "for" loops.
174-- @param tbl table: The table to be iterated.
175-- @param sort_function function or nil: An optional comparison function
176-- to be used by table.sort when sorting keys.
177-- @return function: the iterator function.
178function sortedpairs(tbl, sort_function)
179 return coroutine.wrap(function() sortedpairs_iterator(tbl, sort_function) end)
180end
diff --git a/test/run_tests.sh b/test/run_tests.sh
new file mode 100755
index 00000000..aa06854f
--- /dev/null
+++ b/test/run_tests.sh
@@ -0,0 +1,76 @@
1#!/bin/bash
2
3if [ -e ./run_tests.sh ]
4then
5 cd ../src
6elif [ -d src ]
7then
8 cd src
9elif ! [ -d luarocks ]
10then
11 echo "Go to the src directory and run this."
12 exit 1
13fi
14
15if [ ! -d ../rocks ]
16then
17 echo "Downloading entire rocks repository for tests"
18 cd ..
19 wget -r -nH -np -R"index.*" http://luarocks.luaforge.net/rocks/
20 cd src
21fi
22
23rocks=(
24 `ls ../rocks/*.rockspec | grep -v luacom`
25 `ls ../rocks/*.src.rock | grep -v luacom`
26)
27
28bin/luarocks-admin make-manifest ../rocks || exit 1
29
30[ "$1" ] && rocks=("$1")
31
32TRY() {
33 "$@" || {
34 echo "Failed running: $@"
35 exit 1
36 }
37}
38
39list_search() {
40 bin/luarocks list $name | grep $version
41}
42
43for rock in "${rocks[@]}"
44do
45 base=`basename $rock`
46 baserockspec=`basename $rock .rockspec`
47 basesrcrock=`basename $rock .src.rock`
48 if [ "$base" != "$baserockspec" ]
49 then
50 base=$baserockspec
51 name=`echo $base | sed 's/\(.*\)-[^-]*-[^-]*$/\1/'`
52 version=`echo $base | sed 's/.*-\([^-]*-[^-]*\)$/\1/'`
53 TRY bin/luarocks pack $rock
54 TRY bin/luarocks build $base.src.rock
55 TRY rm $base.src.rock
56 else
57 base=$basesrcrock
58 name=`echo $base | sed 's/\(.*\)-[^-]*-[^-]*$/\1/'`
59 version=`echo $base | sed 's/.*-\([^-]*-[^-]*\)$/\1/'`
60 TRY bin/luarocks build $rock
61 fi
62 TRY bin/luarocks pack $name $version
63 TRY bin/luarocks install $base.*.rock
64 TRY rm $base.*.rock
65 TRY list_search $name $version
66 bin/luarocks remove $name $version
67 # TODO: differentiate between error and dependency block.
68done
69
70if bin/luarocks install nonexistant | grep "No results"
71then echo "OK, got expected error."
72else exit 1
73fi
74
75TRY ../test/test_deps.lua
76TRY ../test/test_require.lua
diff --git a/test/test_deps.lua b/test/test_deps.lua
new file mode 100644
index 00000000..7236273c
--- /dev/null
+++ b/test/test_deps.lua
@@ -0,0 +1,67 @@
1#!/usr/bin/env lua
2
3deps = require "luarocks.deps"
4
5print(deps.show_dep(deps.parse_dep("lfs 2.1.9pre5"), true))
6print(deps.show_dep(deps.parse_dep("cgilua cvs-2"), true))
7print(deps.show_dep(deps.parse_dep("foobar 0.0.1beta"), true))
8print(deps.show_dep(deps.parse_dep("foobar 0.0.1a"), true))
9
10print(deps.show_dep(deps.parse_dep("foobar 1"), true))
11print(deps.show_dep(deps.parse_dep("foobar 2.0"), true))
12print(deps.show_dep(deps.parse_dep("foobar 3.5a4"), true))
13print(deps.show_dep(deps.parse_dep("foobar 1.1pre2"), true))
14print(deps.show_dep(deps.parse_dep("foobar 2.0-beta3"), true))
15print(deps.show_dep(deps.parse_dep("foobar 5.3"), true))
16print(deps.show_dep(deps.parse_dep("foobar 3.5rc2"), true))
17print(deps.show_dep(deps.parse_dep("foobar 4.19p"), true))
18
19print()
20comparisons = {
21-- first second eq le
22 {"Vista", "XP", false, true},
23 {"XP", "3.1", false, true},
24 {"1.0", "1.0", true, false},
25 {"2.2.10", "2.2-10", false, false},
26 {"2.2", "2.2-10", true, false},
27 {"1.0beta1", "1.0rc3", false, true},
28 {"2.0beta3", "2.0", false, true},
29 {"2.0beta", "2.0beta2", false, true},
30 {"2.0beta4", "2.0beta3", false, false},
31 {"2.1alpha1", "2.0beta1", false, false},
32 {"1.5p3", "1.5.1", false, true},
33 {"1.1.3", "1.1.3a", false, true},
34 {"1.5a100", "1.5b1", false, true},
35 {"2.0alpha100", "2.0beta1", false, true},
36 {"2.0.0beta3", "2.0beta2", false, false},
37 {"2.0-1", "2.0-2", false, true},
38 {"2.0-2", "2.0-1", false, false},
39 --[[
40 -- Corner cases I don't wish to handle by now.
41 {"2.0.0beta2", "2.0beta2", true, true},
42 {"2.0.0beta2", "2.0beta3", false, true},
43 ]]
44}
45
46local v1, v2
47
48err = false
49
50function result(test, expected)
51 if test == expected then
52 print(test, "OK")
53 else
54 print(test, "ERROR", deps.show_version(v1, true), deps.show_version(v2, true))
55 err = true
56 end
57end
58
59for _, c in ipairs(comparisons) do
60 v1, v2 = deps.parse_version(c[1]), deps.parse_version(c[2])
61 print(c[1].." == "..c[2].." ?")
62 result(v1 == v2, c[3])
63 print(c[1].." < "..c[2].." ?")
64 result(v1 < v2, c[4])
65end
66
67if err then os.exit(1) end
diff --git a/test/test_require.lua b/test/test_require.lua
new file mode 100755
index 00000000..2a652d29
--- /dev/null
+++ b/test/test_require.lua
@@ -0,0 +1,25 @@
1#!/usr/bin/env lua
2
3local luarocks = require("luarocks.require")
4
5luarocks.set_context("cgilua", "cvs-2")
6
7print(package.path)
8
9print(package.cpath)
10
11local socket = require("socket")
12if not socket then os.exit(1) end
13print(socket, socket._VERSION)
14
15local socket2 = require("socket")
16if not socket2 then os.exit(1) end
17print(socket2, socket2._VERSION)
18
19local mime = require("mime")
20if not mime then os.exit(1) end
21print(mime, mime._VERSION)
22
23local socket = require("lfs")
24if not lfs then os.exit(1) end
25print(lfs, lfs._VERSION)