diff options
author | hisham <hisham@9ca3f7c1-7366-0410-b1a3-b5c78f85698c> | 2009-04-01 17:11:57 +0000 |
---|---|---|
committer | hisham <hisham@9ca3f7c1-7366-0410-b1a3-b5c78f85698c> | 2009-04-01 17:11:57 +0000 |
commit | a88d6f2eeba2b3355c33fac6d736cf6086342f47 (patch) | |
tree | 03ebfa0ced0186e091609cf337e290580fab8c11 | |
download | luarocks-a88d6f2eeba2b3355c33fac6d736cf6086342f47.tar.gz luarocks-a88d6f2eeba2b3355c33fac6d736cf6086342f47.tar.bz2 luarocks-a88d6f2eeba2b3355c33fac6d736cf6086342f47.zip |
Import latest revision from CVS at luaforge.net
git-svn-id: http://luarocks.org/svn/luarocks/trunk@1 9ca3f7c1-7366-0410-b1a3-b5c78f85698c
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 @@ | |||
1 | LuaRocks is free software: it can be used for both academic and commercial | ||
2 | purposes at absolutely no cost. There are no royalties or GNU-like "copyleft" | ||
3 | restrictions. LuaRocks qualifies as Open Source software. Its licenses are | ||
4 | compatible with the GPL. LuaRocks is not in the public domain and the Kepler | ||
5 | Project keeps its copyright. The legal details are below. | ||
6 | |||
7 | The spirit of the license is that you are free to use LuaRocks for any purpose | ||
8 | at no cost without having to ask us. The only requirement is that if you do | ||
9 | use LuaRocks, then you should give us credit by including the appropriate | ||
10 | copyright notice somewhere in your product or its documentation. | ||
11 | |||
12 | ------------------------------------------------------------------------------ | ||
13 | |||
14 | Copyright © 2007 Kepler Project. | ||
15 | |||
16 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
17 | of this software and associated documentation files (the "Software"), to deal | ||
18 | in the Software without restriction, including without limitation the rights | ||
19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
20 | copies of the Software, and to permit persons to whom the Software is | ||
21 | furnished to do so, subject to the following conditions: | ||
22 | |||
23 | The above copyright notice and this permission notice shall be included in all | ||
24 | copies or substantial portions of the Software. | ||
25 | |||
26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
32 | SOFTWARE. | ||
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 | |||
3 | include config.unix | ||
4 | |||
5 | DESTDIR = | ||
6 | PREFIX ?= /usr/local | ||
7 | BINDIR ?= $(PREFIX)/bin | ||
8 | LUADIR ?= $(PREFIX)/share/lua/5.1/ | ||
9 | LUA_DIR ?= /usr/local | ||
10 | LUA_BINDIR ?= $(LUA_DIR)/bin | ||
11 | |||
12 | BIN_FILES = luarocks luarocks-admin | ||
13 | LUAROCKS_FILES = build/cmake.lua build/command.lua build.lua build/make.lua \ | ||
14 | command_line.lua cfg.lua deps.lua fetch.lua fs.lua fs/unix.lua \ | ||
15 | fs/win32.lua help.lua install.lua list.lua persist.lua \ | ||
16 | make_manifest.lua pack.lua path.lua rep.lua require.lua search.lua \ | ||
17 | type_check.lua util.lua remove.lua build/builtin.lua make.lua manif.lua unpack.lua \ | ||
18 | fetch/cvs.lua fetch/sscm.lua fetch/git.lua | ||
19 | |||
20 | CONFIG_FILE = $(SYSCONFDIR)/config.lua | ||
21 | |||
22 | all: | ||
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 | |||
75 | luadoc: | ||
76 | rm -rf doc/luadoc | ||
77 | mkdir -p doc/luadoc | ||
78 | cd src && luadoc -d ../doc/luadoc --nofiles luarocks/*.lua | ||
79 | |||
80 | check_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 | |||
91 | clean: | ||
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 | |||
100 | install: | ||
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 | |||
8 | PREFIX="/usr/local" | ||
9 | SYSCONFDIR="$PREFIX/etc/luarocks" | ||
10 | ROCKS_TREE="$PREFIX/lib/luarocks" | ||
11 | SCRIPTS_DIR="" | ||
12 | LUA_SUFFIX="" | ||
13 | LUA_DIR="/usr" | ||
14 | LUA_BINDIR="/usr/bin" | ||
15 | LUA_INCDIR="/usr/include" | ||
16 | LUA_LIBDIR="/usr/lib" | ||
17 | |||
18 | # ---------------------------------------------------------------------------- | ||
19 | # FUNCTION DEFINITIONS | ||
20 | # ---------------------------------------------------------------------------- | ||
21 | |||
22 | # Help | ||
23 | |||
24 | show_help() { | ||
25 | cat <<EOF | ||
26 | Configure 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. | ||
61 | EOF | ||
62 | } | ||
63 | |||
64 | # Helper functions | ||
65 | |||
66 | find_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 | |||
89 | find_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 | |||
109 | case `echo -n x` in | ||
110 | -n*) echo_n_flag='';; | ||
111 | *) echo_n_flag='-n';; | ||
112 | esac | ||
113 | |||
114 | echo_n() { | ||
115 | echo $echo_n_flag "$*" | ||
116 | } | ||
117 | |||
118 | die() { | ||
119 | echo "$*" | ||
120 | exit 1 | ||
121 | } | ||
122 | |||
123 | # ---------------------------------------------------------------------------- | ||
124 | # MAIN PROGRAM | ||
125 | # ---------------------------------------------------------------------------- | ||
126 | |||
127 | # Parse options | ||
128 | |||
129 | while [ -n "$1" ] | ||
130 | do | ||
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 | ||
210 | done | ||
211 | |||
212 | |||
213 | if [ "$PREFIX_SET" = "yes" -a ! "$SYSCONFDIR_SET" = "yes" ] | ||
214 | then | ||
215 | if [ "$PREFIX" = "/usr" ] | ||
216 | then SYSCONFDIR=/etc/luarocks | ||
217 | else SYSCONFDIR=$PREFIX/etc/luarocks | ||
218 | fi | ||
219 | fi | ||
220 | |||
221 | |||
222 | if [ "$PREFIX_SET" = "yes" -a ! "$ROCKS_TREE_SET" = "yes" ] | ||
223 | then | ||
224 | ROCKS_TREE=$PREFIX/lib/luarocks | ||
225 | fi | ||
226 | |||
227 | if [ "$LUA_SUFFIX_SET" != "yes" ] | ||
228 | then | ||
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 | ||
247 | fi | ||
248 | |||
249 | if [ "$LUA_DIR_SET" != "yes" ] | ||
250 | then | ||
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 | ||
267 | fi | ||
268 | |||
269 | if [ "$LUA_INCDIR_SET" != "yes" ] | ||
270 | then | ||
271 | LUA_INCDIR="$LUA_DIR/include" | ||
272 | fi | ||
273 | |||
274 | if [ "$LUA_LIBDIR_SET" != "yes" ] | ||
275 | then | ||
276 | LUA_LIBDIR="$LUA_DIR/lib" | ||
277 | fi | ||
278 | |||
279 | if [ "$LUA_DIR_SET" = "yes" ] | ||
280 | then | ||
281 | LUA_BINDIR="$LUA_DIR/bin" | ||
282 | fi | ||
283 | |||
284 | echo_n "Checking Lua includes... " | ||
285 | lua_h="$LUA_INCDIR/lua.h" | ||
286 | if [ -f "$lua_h" ] | ||
287 | then | ||
288 | echo "lua.h found in $lua_h" | ||
289 | else | ||
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 | ||
293 | fi | ||
294 | |||
295 | if [ "$LUAROCKS_DOWNLOADER_SET" != "yes" ] | ||
296 | then | ||
297 | find_helper "downloader helper program" wget curl fetch | ||
298 | LUAROCKS_DOWNLOADER=$HELPER | ||
299 | fi | ||
300 | |||
301 | if [ "$LUAROCKS_MD5CHECKER_SET" != "yes" ] | ||
302 | then | ||
303 | find_helper "MD5 checksum calculator" md5sum openssl md5 | ||
304 | LUAROCKS_MD5CHECKER=$HELPER | ||
305 | fi | ||
306 | |||
307 | echo_n "Configuring for system... " | ||
308 | if uname -s | ||
309 | then | ||
310 | LUAROCKS_UNAME_S=`uname -s` | ||
311 | else | ||
312 | echo "Could not determine operating system. 'uname -s' failed." | ||
313 | exit 1 | ||
314 | fi | ||
315 | echo_n "Configuring for architecture... " | ||
316 | if uname -m | ||
317 | then | ||
318 | LUAROCKS_UNAME_M=`uname -m` | ||
319 | else | ||
320 | echo "Could not determine processor architecture. 'uname -m' failed." | ||
321 | exit 1 | ||
322 | fi | ||
323 | |||
324 | if [ -f config.unix ]; then | ||
325 | rm -f config.unix | ||
326 | fi | ||
327 | |||
328 | # Write config | ||
329 | |||
330 | echo "Writing configuration..." | ||
331 | echo | ||
332 | |||
333 | cat <<EOF > config.unix | ||
334 | # This file was automatically generated by the configure script. | ||
335 | # Run "./configure --help" for details. | ||
336 | |||
337 | PREFIX=$PREFIX | ||
338 | SYSCONFDIR=$SYSCONFDIR | ||
339 | ROCKS_TREE=$ROCKS_TREE | ||
340 | SCRIPTS_DIR=$SCRIPTS_DIR | ||
341 | LUA_SUFFIX=$LUA_SUFFIX | ||
342 | LUA_DIR=$LUA_DIR | ||
343 | LUA_INCDIR=$LUA_INCDIR | ||
344 | LUA_LIBDIR=$LUA_LIBDIR | ||
345 | LUA_BINDIR=$LUA_BINDIR | ||
346 | FORCE_CONFIG=$FORCE_CONFIG | ||
347 | LUAROCKS_UNAME_M=$LUAROCKS_UNAME_M | ||
348 | LUAROCKS_UNAME_S=$LUAROCKS_UNAME_S | ||
349 | LUAROCKS_DOWNLOADER=$LUAROCKS_DOWNLOADER | ||
350 | LUAROCKS_MD5CHECKER=$LUAROCKS_MD5CHECKER | ||
351 | |||
352 | EOF | ||
353 | |||
354 | echo "Installation prefix: $PREFIX" | ||
355 | echo "LuaRocks configuration directory: $SYSCONFDIR" | ||
356 | echo "Using Lua from: $LUA_DIR" | ||
357 | |||
358 | make clean > /dev/null 2> /dev/null | ||
359 | |||
360 | echo | ||
361 | echo "Done. You can now run 'make' to build." | ||
362 | echo | ||
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 | |||
3 | REM Boy, it feels like 1994 all over again. | ||
4 | |||
5 | SETLOCAL | ||
6 | |||
7 | SET PREFIX=C:\LuaRocks | ||
8 | SET VERSION=1.0 | ||
9 | SET SYSCONFDIR=C:\LuaRocks | ||
10 | SET ROCKS_TREE=C:\LuaRocks | ||
11 | SET SCRIPTS_DIR= | ||
12 | SET FORCE=OFF | ||
13 | SET INSTALL_LUA=OFF | ||
14 | SET LUA_INTERPRETER= | ||
15 | SET LUA_PREFIX= | ||
16 | SET LUA_BINDIR= | ||
17 | SET LUA_INCDIR= | ||
18 | SET LUA_LIBDIR= | ||
19 | SET FORCE_CONFIG= | ||
20 | SET MKDIR=.\bin\mkdir -p | ||
21 | |||
22 | REM *********************************************************** | ||
23 | REM Option parser | ||
24 | REM *********************************************************** | ||
25 | |||
26 | :PARSE_LOOP | ||
27 | IF [%1]==[] GOTO DONE_PARSING | ||
28 | IF [%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 | ) | ||
55 | IF /I [%1]==[/P] ( | ||
56 | SET PREFIX=%2 | ||
57 | SHIFT /1 | ||
58 | SHIFT /1 | ||
59 | GOTO PARSE_LOOP | ||
60 | ) | ||
61 | IF /I [%1]==[/CONFIG] ( | ||
62 | SET SYSCONFDIR=%2 | ||
63 | SHIFT /1 | ||
64 | SHIFT /1 | ||
65 | GOTO PARSE_LOOP | ||
66 | ) | ||
67 | IF /I [%1]==[/TREE] ( | ||
68 | SET ROCKS_TREE=%2 | ||
69 | SHIFT /1 | ||
70 | SHIFT /1 | ||
71 | GOTO PARSE_LOOP | ||
72 | ) | ||
73 | IF /I [%1]==[/SCRIPTS] ( | ||
74 | SET SCRIPTS_DIR=%2 | ||
75 | SHIFT /1 | ||
76 | SHIFT /1 | ||
77 | GOTO PARSE_LOOP | ||
78 | ) | ||
79 | IF /I [%1]==[/L] ( | ||
80 | SET INSTALL_LUA=ON | ||
81 | SHIFT /1 | ||
82 | GOTO PARSE_LOOP | ||
83 | ) | ||
84 | IF /I [%1]==[/LUA] ( | ||
85 | SET LUA_PREFIX=%2 | ||
86 | SHIFT /1 | ||
87 | SHIFT /1 | ||
88 | GOTO PARSE_LOOP | ||
89 | ) | ||
90 | IF /I [%1]==[/LIB] ( | ||
91 | SET LUA_LIBDIR=%2 | ||
92 | SHIFT /1 | ||
93 | SHIFT /1 | ||
94 | GOTO PARSE_LOOP | ||
95 | ) | ||
96 | IF /I [%1]==[/INC] ( | ||
97 | SET LUA_INCDIR=%2 | ||
98 | SHIFT /1 | ||
99 | SHIFT /1 | ||
100 | GOTO PARSE_LOOP | ||
101 | ) | ||
102 | IF /I [%1]==[/BIN] ( | ||
103 | SET LUA_BINDIR=%2 | ||
104 | SHIFT /1 | ||
105 | SHIFT /1 | ||
106 | GOTO PARSE_LOOP | ||
107 | ) | ||
108 | IF /I [%1]==[/FORCECONFIG] ( | ||
109 | SET FORCE_CONFIG=ON | ||
110 | SHIFT /1 | ||
111 | GOTO PARSE_LOOP | ||
112 | ) | ||
113 | IF /I [%1]==[/F] ( | ||
114 | SET FORCE=ON | ||
115 | SHIFT /1 | ||
116 | GOTO PARSE_LOOP | ||
117 | ) | ||
118 | ECHO Unrecognized option: %1 | ||
119 | GOTO ERROR | ||
120 | :DONE_PARSING | ||
121 | |||
122 | SET FULL_PREFIX=%PREFIX%\%VERSION% | ||
123 | |||
124 | SET BINDIR=%FULL_PREFIX% | ||
125 | SET LIBDIR=%FULL_PREFIX% | ||
126 | SET LUADIR=%FULL_PREFIX%\lua | ||
127 | SET INCDIR=%FULL_PREFIX%\include | ||
128 | |||
129 | REM *********************************************************** | ||
130 | REM Detect Lua | ||
131 | REM *********************************************************** | ||
132 | |||
133 | IF [%INSTALL_LUA%]==[ON] GOTO USE_OWN_LUA | ||
134 | |||
135 | FOR %%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 | ) | ||
199 | ECHO Could not find Lua. Will install its own copy. | ||
200 | ECHO See /? for options for specifying the location of Lua. | ||
201 | :USE_OWN_LUA | ||
202 | SET INSTALL_LUA=ON | ||
203 | SET LUA_INTERPRETER=lua5.1 | ||
204 | SET LUA_BINDIR=%BINDIR% | ||
205 | SET LUA_LIBDIR=%LIBDIR% | ||
206 | SET LUA_INCDIR=%INCDIR% | ||
207 | :LUA_IS_SET | ||
208 | ECHO. | ||
209 | ECHO Will configure LuaRocks with the following paths: | ||
210 | ECHO Lua interpreter: %LUA_INTERPRETER% | ||
211 | ECHO Lua binaries: %LUA_BINDIR% | ||
212 | ECHO Lua libraries: %LUA_LIBDIR% | ||
213 | ECHO Lua includes: %LUA_INCDIR% | ||
214 | ECHO. | ||
215 | |||
216 | REM *********************************************************** | ||
217 | REM Install LuaRocks files | ||
218 | REM *********************************************************** | ||
219 | |||
220 | IF [%FORCE%]==[ON] ( | ||
221 | ECHO Removing %FULL_PREFIX%... | ||
222 | RD /S /Q "%FULL_PREFIX%" | ||
223 | ) | ||
224 | |||
225 | IF 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 | |||
230 | ECHO Installing LuaRocks in %FULL_PREFIX%... | ||
231 | IF NOT EXIST "%BINDIR%" %MKDIR% "%BINDIR%" | ||
232 | IF ERRORLEVEL 1 GOTO ERROR | ||
233 | IF [%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 | ) | ||
239 | COPY bin\*.* "%BINDIR%" >NUL | ||
240 | IF ERRORLEVEL 1 GOTO ERROR | ||
241 | COPY src\bin\*.* "%BINDIR%" >NUL | ||
242 | IF ERRORLEVEL 1 GOTO ERROR | ||
243 | FOR %%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 | ) | ||
254 | IF NOT EXIST "%LUADIR%\luarocks" %MKDIR% "%LUADIR%\luarocks" | ||
255 | IF ERRORLEVEL 1 GOTO ERROR | ||
256 | XCOPY /S src\luarocks\*.* "%LUADIR%\luarocks" >NUL | ||
257 | IF ERRORLEVEL 1 GOTO ERROR | ||
258 | |||
259 | RENAME "%LUADIR%\luarocks\cfg.lua" "cfg.lua.bak" | ||
260 | ECHO local LUA_INCDIR=[[%LUA_INCDIR%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
261 | ECHO local LUA_LIBDIR=[[%LUA_LIBDIR%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
262 | ECHO local LUA_BINDIR=[[%LUA_BINDIR%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
263 | ECHO local LUA_INTERPRETER=[[%LUA_INTERPRETER%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
264 | ECHO local LUAROCKS_UNAME_S=[[WindowsNT]]>> "%LUADIR%\luarocks\cfg.lua" | ||
265 | ECHO local LUAROCKS_UNAME_M=[[x86]]>> "%LUADIR%\luarocks\cfg.lua" | ||
266 | ECHO local LUAROCKS_SYSCONFIG=[[%SYSCONFDIR%/config.lua]]>> "%LUADIR%\luarocks\cfg.lua" | ||
267 | ECHO local LUAROCKS_ROCKS_TREE=[[%ROCKS_TREE%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
268 | ECHO local LUAROCKS_PREFIX=[[%PREFIX%]]>> "%LUADIR%\luarocks\cfg.lua" | ||
269 | IF NOT [%FORCE_CONFIG%]==[] ECHO local LUAROCKS_FORCE_CONFIG=true>> "%LUADIR%\luarocks\cfg.lua" | ||
270 | TYPE "%LUADIR%\luarocks\cfg.lua.bak">> "%LUADIR%\luarocks\cfg.lua" | ||
271 | |||
272 | DEL /F /Q "%LUADIR%\luarocks\cfg.lua.bak" | ||
273 | |||
274 | SET CONFIG_FILE=%SYSCONFDIR%\config.lua | ||
275 | |||
276 | IF NOT EXIST "%SYSCONFDIR%" %MKDIR% "%SYSCONFDIR%" | ||
277 | IF 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 | |||
288 | IF [%SCRIPTS_DIR%]==[] ( | ||
289 | %MKDIR% "%ROCKS_TREE%"\bin >NUL | ||
290 | COPY lua5.1\bin\*.dll "%ROCKS_TREE%"\bin >NUL | ||
291 | ) | ||
292 | IF NOT [%SCRIPTS_DIR%]==[] ( | ||
293 | %MKDIR% "%SCRIPTS_DIR%" >NUL | ||
294 | COPY lua5.1\bin\*.dll "%SCRIPTS_DIR%" >NUL | ||
295 | ) | ||
296 | |||
297 | IF NOT EXIST "%ROCKS_TREE%" %MKDIR% "%ROCKS_TREE%" | ||
298 | IF NOT EXIST "%APPDATA%/luarocks" %MKDIR% "%APPDATA%/luarocks" | ||
299 | |||
300 | REM *********************************************************** | ||
301 | REM Exit handlers | ||
302 | REM *********************************************************** | ||
303 | |||
304 | ECHO LuaRocks is installed! | ||
305 | :QUIT | ||
306 | ENDLOCAL | ||
307 | EXIT /B 0 | ||
308 | |||
309 | :ERROR | ||
310 | ECHO Failed installing LuaRocks. | ||
311 | ENDLOCAL | ||
312 | EXIT /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 | |||
3 | cvslist() { | ||
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 | |||
18 | if ! [ "$1" ] | ||
19 | then | ||
20 | echo "usage: $0 <version>" | ||
21 | exit 1 | ||
22 | fi | ||
23 | |||
24 | cvs list > /dev/null 2> /dev/null | ||
25 | if [ $? != 0 ] | ||
26 | then | ||
27 | echo "Your version of CVS may be too old. At least 1.12 is needed." | ||
28 | exit 1 | ||
29 | fi | ||
30 | |||
31 | make clean | ||
32 | |||
33 | out="luarocks-$1" | ||
34 | rm -rf "$out" | ||
35 | mkdir "$out" | ||
36 | list=`cvslist` | ||
37 | rm -f missing_ref | ||
38 | echo "$list" | while read i | ||
39 | do | ||
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 | ||
54 | done | ||
55 | if [ -e missing_ref ] | ||
56 | then | ||
57 | rm -f missing_ref | ||
58 | exit 1 | ||
59 | fi | ||
60 | rm -f "$out-win32.zip" "$out.tar.gz" | ||
61 | rm "$out/makedist" | ||
62 | rm "$out/install.bat" | ||
63 | tar czvpf "$out.tar.gz" "$out" | ||
64 | cp install.bat "$out" | ||
65 | cp -a win32/bin "$out" | ||
66 | cp -a win32/lua5.1 "$out" | ||
67 | zip -r "$out-win32.zip" "$out" | ||
68 | rm -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 | |||
3 | local command_line = require("luarocks.command_line") | ||
4 | |||
5 | program_name = "luarocks" | ||
6 | program_description = "LuaRocks main command-line interface" | ||
7 | |||
8 | commands = {} | ||
9 | commands.help = require("luarocks.help") | ||
10 | commands.pack = require("luarocks.pack") | ||
11 | commands.unpack = require("luarocks.unpack") | ||
12 | commands.build = require("luarocks.build") | ||
13 | commands.install = require("luarocks.install") | ||
14 | commands.search = require("luarocks.search") | ||
15 | commands.list = require("luarocks.list") | ||
16 | commands.remove = require("luarocks.remove") | ||
17 | commands.make = require("luarocks.make") | ||
18 | |||
19 | command_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 | |||
3 | local command_line = require("luarocks.command_line") | ||
4 | |||
5 | program_name = "luarocks-admin" | ||
6 | program_description = "LuaRocks repository administration interface" | ||
7 | |||
8 | commands = { | ||
9 | } | ||
10 | |||
11 | commands.help = require("luarocks.help") | ||
12 | commands.make_manifest = require("luarocks.make_manifest") | ||
13 | |||
14 | command_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. | ||
4 | module("luarocks.build", package.seeall) | ||
5 | |||
6 | local path = require("luarocks.path") | ||
7 | local util = require("luarocks.util") | ||
8 | local rep = require("luarocks.rep") | ||
9 | local fetch = require("luarocks.fetch") | ||
10 | local fs = require("luarocks.fs") | ||
11 | local deps = require("luarocks.deps") | ||
12 | local manif = require("luarocks.manif") | ||
13 | |||
14 | help_summary = "Build/compile a rock." | ||
15 | help_arguments = "{<rockspec>|<rock>|<name> [<version>]}" | ||
16 | help = [[ | ||
17 | Build a rock, compiling its C parts if any. | ||
18 | Argument may be a rockspec file, a source rock file | ||
19 | or 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. | ||
34 | local 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 | ||
51 | end | ||
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. | ||
56 | local 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 | ||
62 | end | ||
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. | ||
70 | function 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 | ||
88 | end | ||
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. | ||
96 | function 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 | ||
224 | end | ||
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. | ||
232 | local 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 | ||
245 | end | ||
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. | ||
255 | function 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 | ||
276 | end | ||
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. | ||
3 | module("luarocks.build.builtin", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local path = require("luarocks.path") | ||
7 | local util = require("luarocks.util") | ||
8 | local 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. | ||
13 | local 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 | ||
21 | end | ||
22 | |||
23 | --- Run a command displaying its execution on standard output. | ||
24 | -- @return boolean: true if command succeeds (status code 0), false | ||
25 | -- otherwise. | ||
26 | local function execute(...) | ||
27 | io.stdout:write(table.concat({...}, " ").."\n") | ||
28 | return fs.execute(...) | ||
29 | end | ||
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. | ||
35 | function 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 | ||
152 | end | ||
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. | ||
3 | module("luarocks.build.cmake", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local util = require("luarocks.util") | ||
7 | local 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. | ||
13 | function 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 | ||
54 | end | ||
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. | ||
3 | module("luarocks.build.command", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local 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. | ||
12 | function 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 | ||
30 | end | ||
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. | ||
3 | module("luarocks.build.make", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local util = require("luarocks.util") | ||
7 | local 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. | ||
16 | local 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 | ||
30 | end | ||
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. | ||
36 | function 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 | ||
84 | end | ||
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 | |||
2 | local 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. | ||
10 | module("luarocks.cfg") | ||
11 | |||
12 | local persist = require("luarocks.persist") | ||
13 | |||
14 | local detected = {} | ||
15 | local 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). | ||
22 | system = LUAROCKS_UNAME_S or io.popen("uname -s"):read("*l") | ||
23 | proc = LUAROCKS_UNAME_M or io.popen("uname -m"):read("*l") | ||
24 | if proc:match("i[%d]86") then | ||
25 | proc = "x86" | ||
26 | elseif proc:match("amd64") or proc:match("x86_64") then | ||
27 | proc = "x86_64" | ||
28 | elseif proc:match("Power Macintosh") then | ||
29 | proc = "powerpc" | ||
30 | end | ||
31 | |||
32 | if system == "FreeBSD" then | ||
33 | detected.unix = true | ||
34 | detected.freebsd = true | ||
35 | elseif system == "Darwin" then | ||
36 | detected.unix = true | ||
37 | detected.macosx = true | ||
38 | elseif system == "Linux" then | ||
39 | detected.unix = true | ||
40 | detected.linux = true | ||
41 | elseif system and system:match("^CYGWIN") then | ||
42 | detected.unix = true | ||
43 | detected.cygwin = true | ||
44 | elseif system and system:match("^Windows") then | ||
45 | detected.windows = true | ||
46 | else | ||
47 | detected.unix = true | ||
48 | -- Fall back to Unix in unknown systems. | ||
49 | end | ||
50 | |||
51 | local sys_config_file, home_config_file, home_tree | ||
52 | if 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/" | ||
57 | else | ||
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/" | ||
62 | end | ||
63 | |||
64 | variables = {} | ||
65 | rocks_trees = {} | ||
66 | |||
67 | persist.load_into_table(LUAROCKS_SYSCONFIG or sys_config_file, _M) | ||
68 | |||
69 | if 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 | ||
76 | end | ||
77 | |||
78 | local 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 | |||
92 | defaults.external_deps_subdirs = { | ||
93 | bin = "bin", | ||
94 | lib = "lib", | ||
95 | include = "include" | ||
96 | } | ||
97 | defaults.runtime_external_deps_subdirs = defaults.external_deps_subdirs | ||
98 | |||
99 | if 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 | } | ||
134 | end | ||
135 | |||
136 | if 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 | } | ||
164 | end | ||
165 | |||
166 | if 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" | ||
174 | end | ||
175 | |||
176 | defaults.external_lib_extension = defaults.lib_extension | ||
177 | |||
178 | if 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" | ||
185 | end | ||
186 | |||
187 | if 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" | ||
193 | end | ||
194 | |||
195 | if 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" | ||
202 | end | ||
203 | |||
204 | if proc == "x86_64" and not defaults.variables.CFLAGS:match("-fPIC") then | ||
205 | defaults.variables.CFLAGS = defaults.variables.CFLAGS.." -fPIC" | ||
206 | end | ||
207 | |||
208 | -- Expose some more values detected by LuaRocks for use by rockspec authors. | ||
209 | defaults.variables.LUA = defaults.lua_interpreter | ||
210 | defaults.variables.LIB_EXTENSION = defaults.lib_extension | ||
211 | defaults.variables.OBJ_EXTENSION = defaults.obj_extension | ||
212 | defaults.variables.LUAROCKS_PREFIX = LUAROCKS_PREFIX | ||
213 | |||
214 | local 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 | |||
224 | if not _M.variables then | ||
225 | _M.variables = {} | ||
226 | end | ||
227 | for k,v in pairs(defaults.variables) do | ||
228 | if not _M.variables[k] then | ||
229 | _M.variables[k] = v | ||
230 | end | ||
231 | end | ||
232 | |||
233 | setmetatable(_M, cfg_mt) | ||
234 | |||
235 | if 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 | ||
242 | end | ||
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 | |||
2 | program_version = "1.0" | ||
3 | |||
4 | --- Functions for command-line scripts. | ||
5 | module("luarocks.command_line", package.seeall) | ||
6 | |||
7 | local util = require("luarocks.util") | ||
8 | local cfg = require("luarocks.cfg") | ||
9 | local fs = require("luarocks.fs") | ||
10 | |||
11 | --- Display an error message and exit. | ||
12 | -- @param message string: The error message. | ||
13 | local 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) | ||
22 | end | ||
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. | ||
31 | function 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() | ||
133 | end | ||
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. | ||
14 | module("luarocks.deps", package.seeall) | ||
15 | |||
16 | local rep = require("luarocks.rep") | ||
17 | local search = require("luarocks.search") | ||
18 | local install = require("luarocks.install") | ||
19 | local cfg = require("luarocks.cfg") | ||
20 | local manif = require("luarocks.manif") | ||
21 | local fs = require("luarocks.fs") | ||
22 | local fetch = require("luarocks.fetch") | ||
23 | local path = require("luarocks.path") | ||
24 | |||
25 | local operators = { | ||
26 | ["=="] = "==", | ||
27 | ["~="] = "~=", | ||
28 | [">"] = ">", | ||
29 | ["<"] = "<", | ||
30 | [">="] = ">=", | ||
31 | ["<="] = "<=", | ||
32 | ["~>"] = "~>", | ||
33 | -- plus some convenience translations | ||
34 | [""] = "==", | ||
35 | ["="] = "==", | ||
36 | ["!="] = "~=" | ||
37 | } | ||
38 | |||
39 | local deltas = { | ||
40 | scm = 1000, | ||
41 | cvs = 1000, | ||
42 | rc = -1000, | ||
43 | pre = -10000, | ||
44 | beta = -100000, | ||
45 | alpha = -1000000 | ||
46 | } | ||
47 | |||
48 | local 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 | |||
91 | local version_cache = {} | ||
92 | setmetatable(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. | ||
107 | function 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 | ||
152 | end | ||
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. | ||
158 | function compare_versions(a, b) | ||
159 | return parse_version(a) > parse_version(b) | ||
160 | end | ||
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. | ||
170 | local 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 | ||
178 | end | ||
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. | ||
188 | function 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 | ||
201 | end | ||
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. | ||
213 | function 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 } | ||
221 | end | ||
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. | ||
228 | function 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) | ||
235 | end | ||
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. | ||
242 | function 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, ", ") | ||
251 | end | ||
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. | ||
265 | local 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 | ||
281 | end | ||
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. | ||
288 | function 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 | ||
307 | end | ||
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. | ||
316 | local 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 | ||
354 | end | ||
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. | ||
365 | function 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 | ||
385 | end | ||
386 | |||
387 | --- Return a set of values of a table. | ||
388 | -- @param tbl table: The input table. | ||
389 | -- @return table: The array of keys. | ||
390 | local function values_set(tbl) | ||
391 | local set = {} | ||
392 | for _, v in pairs(tbl) do | ||
393 | set[v] = true | ||
394 | end | ||
395 | return set | ||
396 | end | ||
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. | ||
404 | function 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 | ||
480 | end | ||
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. | ||
495 | function 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 | ||
573 | end | ||
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. | ||
581 | function 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 | ||
618 | end | ||
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. | ||
3 | module("luarocks.fetch", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local type_check = require("luarocks.type_check") | ||
7 | local path = require("luarocks.path") | ||
8 | local deps = require("luarocks.deps") | ||
9 | local persist = require("luarocks.persist") | ||
10 | local 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. | ||
23 | function 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 | ||
39 | end | ||
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. | ||
51 | function 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 | ||
74 | end | ||
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. | ||
84 | function 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 | ||
113 | end | ||
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. | ||
120 | function 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 | ||
193 | end | ||
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. | ||
202 | function 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) | ||
216 | end | ||
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. | ||
226 | function 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 | ||
257 | end | ||
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. | ||
267 | function 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) | ||
284 | end | ||
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. | ||
3 | module("luarocks.fetch.cvs", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local 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. | ||
15 | function 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 | ||
42 | end | ||
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. | ||
3 | module("luarocks.fetch.git", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local 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. | ||
15 | function 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 | ||
52 | end | ||
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 | ||
3 | module("luarocks.fetch.sscm", package.seeall) | ||
4 | |||
5 | local 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. | ||
14 | function 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 | ||
40 | end | ||
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 | |||
2 | local 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. | ||
9 | module("luarocks.fs", package.seeall) | ||
10 | |||
11 | local cfg = require("luarocks.cfg") | ||
12 | |||
13 | local fs_impl = nil | ||
14 | for _, 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 | ||
22 | end | ||
23 | |||
24 | local fs_unix = require("luarocks.fs.unix") | ||
25 | |||
26 | local 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 | |||
37 | setmetatable(luarocks.fs, fs_mt) | ||
38 | |||
39 | fs_unix.init_fs_functions(luarocks.fs) | ||
40 | if fs_impl then | ||
41 | fs_impl.init_fs_functions(luarocks.fs) | ||
42 | end | ||
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 | |||
2 | local 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. | ||
6 | module("luarocks.fs.unix", package.seeall) | ||
7 | |||
8 | local cfg = require("luarocks.cfg") | ||
9 | |||
10 | math.randomseed(os.time()) | ||
11 | |||
12 | dir_stack = {} | ||
13 | |||
14 | local 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 | |||
28 | function 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 | ||
42 | end | ||
43 | |||
44 | dir_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. | ||
50 | function Q(arg) | ||
51 | assert(type(arg) == "string") | ||
52 | |||
53 | return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'" | ||
54 | end | ||
55 | |||
56 | --- Obtain current directory. | ||
57 | -- Uses the module's internal dir stack. | ||
58 | -- @return string: the absolute pathname of the current directory. | ||
59 | function 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 | ||
70 | end | ||
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. | ||
77 | function 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 | ||
83 | end | ||
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. | ||
92 | function 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) | ||
100 | end | ||
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. | ||
107 | function change_dir(dir) | ||
108 | assert(type(dir) == "string") | ||
109 | table.insert(fs_dir_stack, dir) | ||
110 | end | ||
111 | |||
112 | --- Change directory to root. | ||
113 | -- Allows leaving a directory (e.g. for deleting it) in | ||
114 | -- a crossplatform way. | ||
115 | function change_dir_to_root() | ||
116 | table.insert(fs_dir_stack, "/") | ||
117 | end | ||
118 | |||
119 | --- Change working directory to the previous in the dir stack. | ||
120 | function pop_dir() | ||
121 | table.remove(fs_dir_stack) | ||
122 | end | ||
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. | ||
129 | function make_dir(dir) | ||
130 | assert(dir) | ||
131 | return fs_execute("mkdir -p", dir) | ||
132 | end | ||
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. | ||
138 | function remove_dir_if_empty(dir) | ||
139 | assert(dir) | ||
140 | fs_execute_string("rmdir "..fs_Q(dir).." 1> /dev/null 2> /dev/null") | ||
141 | end | ||
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. | ||
148 | function 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 | ||
155 | end | ||
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. | ||
162 | function 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 | ||
169 | end | ||
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. | ||
175 | function 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") | ||
179 | end | ||
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. | ||
186 | function 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 | ||
201 | end | ||
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. | ||
208 | function 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 | ||
223 | end | ||
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. | ||
230 | function zip(zipfile, ...) | ||
231 | return fs_execute("zip -r", zipfile, ...) | ||
232 | end | ||
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. | ||
237 | function unzip(zipfile) | ||
238 | assert(zipfile) | ||
239 | return fs_execute("unzip", zipfile) | ||
240 | end | ||
241 | |||
242 | --- Test for existance of a file. | ||
243 | -- @param file string: filename to test | ||
244 | -- @return boolean: true if file exists, false otherwise. | ||
245 | function exists(file) | ||
246 | assert(file) | ||
247 | return fs_execute("test -r", file) | ||
248 | end | ||
249 | |||
250 | --- Test is file/dir is writable. | ||
251 | -- @param file string: filename to test | ||
252 | -- @return boolean: true if file exists, false otherwise. | ||
253 | function is_writable(file) | ||
254 | assert(file) | ||
255 | return fs_execute("test -w", file) | ||
256 | end | ||
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. | ||
261 | function is_dir(file) | ||
262 | assert(file) | ||
263 | return fs_execute("test -d", file) | ||
264 | end | ||
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. | ||
273 | function 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 | ||
287 | end | ||
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". | ||
292 | function base_name(pathname) | ||
293 | assert(type(pathname) == "string") | ||
294 | |||
295 | local base = pathname:match(".*/([^/]*)") | ||
296 | return base or pathname | ||
297 | end | ||
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. | ||
304 | function dir_name(pathname) | ||
305 | assert(type(pathname) == "string") | ||
306 | |||
307 | local dir = pathname:gsub("/*$", ""):match("(.*/)[^/]*") | ||
308 | return dir or "" | ||
309 | end | ||
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. | ||
315 | function 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 | ||
324 | end | ||
325 | |||
326 | --- Apply a patch. | ||
327 | -- @param patchname string: The filename of the patch. | ||
328 | function patch(patchname) | ||
329 | return fs_execute("patch -p1 -f -i ", patchname) | ||
330 | end | ||
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. | ||
337 | function 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 | ||
360 | end | ||
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. | ||
367 | function 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 | ||
400 | end | ||
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. | ||
408 | function 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 | ||
418 | end | ||
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. | ||
428 | function 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, "/") | ||
439 | end | ||
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. | ||
446 | function 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 | ||
458 | end | ||
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. | ||
465 | function 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 | ||
486 | end | ||
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. | ||
493 | local 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 | ||
514 | end | ||
515 | |||
516 | function copy_binary(filename, dest) | ||
517 | return fs_copy(filename, dest) | ||
518 | end | ||
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. | ||
4 | module("luarocks.fs.win32", package.seeall) | ||
5 | |||
6 | local cfg = require("luarocks.cfg") | ||
7 | |||
8 | local 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 | |||
17 | function 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 | ||
26 | end | ||
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. | ||
32 | function 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('"', '\\"') .. '"' | ||
40 | end | ||
41 | |||
42 | local 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 | ||
49 | end | ||
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. | ||
56 | function 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 | ||
62 | end | ||
63 | |||
64 | --- Test for existance of a file. | ||
65 | -- @param file string: filename to test | ||
66 | -- @return boolean: true if file exists, false otherwise. | ||
67 | function exists(file) | ||
68 | assert(file) | ||
69 | return fs_execute("if not exist " .. fs_Q(file) .. | ||
70 | " invalidcommandname 2>NUL 1>NUL") | ||
71 | end | ||
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. | ||
76 | function is_dir(file) | ||
77 | assert(file) | ||
78 | return fs_execute("chdir /D " .. fs_Q(file) .. " 2>NUL 1>NUL") | ||
79 | end | ||
80 | |||
81 | --- Test is file/dir is writable. | ||
82 | -- @param file string: filename to test | ||
83 | -- @return boolean: true if file exists, false otherwise. | ||
84 | function 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 | ||
99 | end | ||
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. | ||
107 | function make_dir(dir) | ||
108 | assert(dir) | ||
109 | fs_execute("mkdir "..fs_Q(dir).." 1> NUL 2> NUL") | ||
110 | return 1 | ||
111 | end | ||
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. | ||
117 | function remove_dir_if_empty(dir) | ||
118 | assert(dir) | ||
119 | fs_execute_string("rmdir "..fs_Q(dir).." 1> NUL 2> NUL") | ||
120 | end | ||
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. | ||
127 | function 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 | ||
135 | end | ||
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. | ||
142 | function 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 | ||
149 | end | ||
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. | ||
155 | function 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") | ||
160 | end | ||
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. | ||
167 | function 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 | ||
183 | end | ||
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. | ||
190 | function 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 | ||
208 | end | ||
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. | ||
218 | function 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 | ||
227 | end | ||
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". | ||
232 | function base_name(pathname) | ||
233 | assert(type(pathname) == "string") | ||
234 | |||
235 | local base = pathname:match(".*[/\\]([^/\\]*)") | ||
236 | return base or pathname | ||
237 | end | ||
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. | ||
244 | local function strip_extension(filename) | ||
245 | assert(type(filename) == "string") | ||
246 | |||
247 | return (filename:gsub("%.[^.]+$", "")) or filename | ||
248 | end | ||
249 | |||
250 | --- Uncompress gzip file. | ||
251 | -- @param archive string: Filename of archive. | ||
252 | -- @return boolean : success status | ||
253 | local 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 | ||
258 | end | ||
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. | ||
265 | function 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 | ||
297 | end | ||
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. | ||
305 | function 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 | ||
315 | end | ||
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. | ||
322 | function 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 | ||
340 | end | ||
341 | |||
342 | function 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 | ||
348 | end | ||
349 | |||
350 | function 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 | ||
369 | end | ||
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. | ||
7 | module("luarocks.help", package.seeall) | ||
8 | |||
9 | local util = require("luarocks.util") | ||
10 | |||
11 | help_summary = "Help on commands." | ||
12 | |||
13 | help_arguments = "[<command>]" | ||
14 | help = [[ | ||
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. | ||
23 | function run(...) | ||
24 | local flags, command = util.parse_flags(...) | ||
25 | |||
26 | if not command then | ||
27 | print([[ | ||
28 | LuaRocks ]]..program_version..[[, a module deployment system for Lua | ||
29 | |||
30 | ]]..program_name..[[ - ]]..program_description..[[ | ||
31 | |||
32 | usage: ]]..program_name..[[ [--from=<server> | --only-from=<server>] [--to=<tree>] [VAR=VALUE]... <command> [<argument>] | ||
33 | |||
34 | Variables from the "variables" table of the configuration file | ||
35 | can 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 | |||
43 | Supported 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 | ||
69 | end | ||
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. | ||
4 | module("luarocks.install", package.seeall) | ||
5 | |||
6 | local path = require("luarocks.path") | ||
7 | local rep = require("luarocks.rep") | ||
8 | local fetch = require("luarocks.fetch") | ||
9 | local util = require("luarocks.util") | ||
10 | local fs = require("luarocks.fs") | ||
11 | local deps = require("luarocks.deps") | ||
12 | local manif = require("luarocks.manif") | ||
13 | local cfg = require("luarocks.cfg") | ||
14 | |||
15 | help_summary = "Install a rock." | ||
16 | |||
17 | help_arguments = "{<rock>|<name> [<version>]}" | ||
18 | |||
19 | help = [[ | ||
20 | Argument may be the name of a rock to be fetched from a repository | ||
21 | or 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. | ||
28 | function 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 | ||
73 | end | ||
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. | ||
85 | function 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 | ||
115 | end | ||
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. | ||
4 | module("luarocks.list", package.seeall) | ||
5 | |||
6 | local search = require("luarocks.search") | ||
7 | local cfg = require("luarocks.cfg") | ||
8 | local util = require("luarocks.util") | ||
9 | local fs = require("luarocks.fs") | ||
10 | |||
11 | help_summary = "Lists currently installed rocks." | ||
12 | |||
13 | help = [[ | ||
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. | ||
21 | function 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 | ||
35 | end | ||
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. | ||
6 | module("luarocks.make", package.seeall) | ||
7 | |||
8 | local build = require("luarocks.build") | ||
9 | local fs = require("luarocks.fs") | ||
10 | local util = require("luarocks.util") | ||
11 | |||
12 | help_summary = "Compile package in current directory using a rockspec." | ||
13 | help_arguments = "[<rockspec>]" | ||
14 | help = [[ | ||
15 | Builds sources in the current directory, but unlike "build", | ||
16 | it does not fetch sources, etc., assuming everything is | ||
17 | available in the current directory. If no argument is given, | ||
18 | look for a rockspec in the current directory. If more than one | ||
19 | is found, you must specify which to use, through the command-line. | ||
20 | |||
21 | This command is useful as a tool for debugging rockspecs. | ||
22 | To 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. | ||
30 | function 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) | ||
54 | end | ||
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. | ||
4 | module("luarocks.make_manifest", package.seeall) | ||
5 | |||
6 | local manif = require("luarocks.manif") | ||
7 | local cfg = require("luarocks.cfg") | ||
8 | |||
9 | help_summary = "Compile a manifest file for a repository." | ||
10 | |||
11 | help = [[ | ||
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. | ||
20 | function 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 | ||
32 | end | ||
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. | ||
3 | module("luarocks.manif", package.seeall) | ||
4 | |||
5 | local util = require("luarocks.util") | ||
6 | local fs = require("luarocks.fs") | ||
7 | local search = require("luarocks.search") | ||
8 | local rep = require("luarocks.rep") | ||
9 | local deps = require("luarocks.deps") | ||
10 | local cfg = require("luarocks.cfg") | ||
11 | local persist = require("luarocks.persist") | ||
12 | local fetch = require("luarocks.fetch") | ||
13 | local type_check = require("luarocks.type_check") | ||
14 | |||
15 | manifest_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. | ||
23 | function 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 {} | ||
39 | end | ||
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. | ||
45 | local 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 | ||
59 | end | ||
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. | ||
67 | function 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) | ||
87 | end | ||
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. | ||
95 | function 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) | ||
105 | end | ||
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. | ||
114 | local 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 | ||
122 | end | ||
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. | ||
132 | local 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 | ||
148 | end | ||
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. | ||
153 | local 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 | ||
175 | end | ||
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. | ||
182 | local 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) | ||
188 | end | ||
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. | ||
196 | local 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 | ||
218 | end | ||
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). | ||
223 | local 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) | ||
247 | end | ||
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. | ||
259 | function 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) | ||
284 | end | ||
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. | ||
292 | function 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) | ||
308 | end | ||
309 | |||
310 | local 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> | ||
317 | body { | ||
318 | background-color: white; | ||
319 | font-family: "bitstream vera sans", "verdana", "sans"; | ||
320 | font-size: 14px; | ||
321 | } | ||
322 | a { | ||
323 | color: #0000c0; | ||
324 | text-decoration: none; | ||
325 | } | ||
326 | a:hover { | ||
327 | text-decoration: underline; | ||
328 | } | ||
329 | td.main { | ||
330 | border-style: none; | ||
331 | } | ||
332 | blockquote { | ||
333 | font-size: 12px; | ||
334 | } | ||
335 | td.package { | ||
336 | background-color: #f0f0f0; | ||
337 | vertical-align: top; | ||
338 | } | ||
339 | td.spacer { | ||
340 | height: 5px; | ||
341 | } | ||
342 | td.version { | ||
343 | background-color: #d0d0d0; | ||
344 | vertical-align: top; | ||
345 | text-align: left; | ||
346 | padding: 5px; | ||
347 | width: 100px; | ||
348 | } | ||
349 | p.manifest { | ||
350 | font-size: 8px; | ||
351 | } | ||
352 | </style> | ||
353 | </head> | ||
354 | <body> | ||
355 | <h1>Available rocks</h1> | ||
356 | <p> | ||
357 | Lua modules avaliable from this location for use with <a href="http://www.luarocks.org">LuaRocks</a>: | ||
358 | </p> | ||
359 | <table class="main"> | ||
360 | ]] | ||
361 | |||
362 | local 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 | |||
371 | local index_package_end = [[ | ||
372 | </td></tr> | ||
373 | <tr><td colspan="2" class="spacer"></td></tr> | ||
374 | ]] | ||
375 | |||
376 | local index_footer = [[ | ||
377 | </table> | ||
378 | <p class="manifest"> | ||
379 | <a href="manifest">manifest file</a> | ||
380 | </p> | ||
381 | </body> | ||
382 | </html> | ||
383 | ]] | ||
384 | |||
385 | function 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 = ': ' | ||
401 | for _, item in ipairs(data) do | ||
402 | output = output .. sep .. '<a href="$url">'..item.arch..'</a>' | ||
403 | sep = ', ' | ||
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() | ||
438 | end | ||
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. | ||
4 | module("luarocks.pack", package.seeall) | ||
5 | |||
6 | local path = require("luarocks.path") | ||
7 | local rep = require("luarocks.rep") | ||
8 | local fetch = require("luarocks.fetch") | ||
9 | local fs = require("luarocks.fs") | ||
10 | local cfg = require("luarocks.cfg") | ||
11 | local util = require("luarocks.util") | ||
12 | |||
13 | help_summary = "Create a rock, packing sources or binaries." | ||
14 | help_arguments = "{<rockspec>|<name> [<version>]}" | ||
15 | help = [[ | ||
16 | Argument may be a rockspec file, for creating a source rock, | ||
17 | or the name of an installed package, for creating a binary rock. | ||
18 | In the latter case, the app version may be given as a second | ||
19 | argument. | ||
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. | ||
29 | local 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 | ||
55 | end | ||
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. | ||
61 | local 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 | ||
95 | end | ||
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. | ||
104 | function 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 | ||
116 | end | ||
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. | ||
5 | module("luarocks.path", package.seeall) | ||
6 | |||
7 | local fs = require("luarocks.fs") | ||
8 | local 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. | ||
13 | function 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" | ||
17 | end | ||
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. | ||
24 | function 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) | ||
29 | end | ||
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. | ||
37 | function 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) | ||
43 | end | ||
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. | ||
51 | function 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") | ||
57 | end | ||
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. | ||
65 | function 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") | ||
71 | end | ||
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. | ||
79 | function 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") | ||
85 | end | ||
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. | ||
93 | function 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") | ||
99 | end | ||
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. | ||
107 | function 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") | ||
113 | end | ||
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. | ||
122 | function 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") | ||
128 | end | ||
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 | ||
134 | function parse_rock_name(rock_file) | ||
135 | assert(type(rock_file) == "string") | ||
136 | return fs.base_name(rock_file):match("(.*)-([^-]+-%d+)%.([^.]+)%.rock$") | ||
137 | end | ||
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 | ||
143 | function parse_rockspec_name(rockspec_file) | ||
144 | assert(type(rockspec_file) == "string") | ||
145 | return fs.base_name(rockspec_file):match("(.*)-([^-]+-%d+)%.(rockspec)") | ||
146 | end | ||
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. | ||
154 | function 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) | ||
169 | end | ||
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). | ||
178 | function 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 | ||
195 | end | ||
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. | ||
201 | function module_to_path(mod) | ||
202 | assert(type(mod) == "string") | ||
203 | return (mod:gsub("[^.]*$", ""):gsub("%.", fs.dir_separator)) | ||
204 | end | ||
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. | ||
210 | function 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 | ||
224 | end | ||
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. | ||
6 | module("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. | ||
15 | function 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 | ||
28 | end | ||
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. | ||
36 | local 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") | ||
71 | end | ||
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. | ||
79 | function 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 | ||
91 | end | ||
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. | ||
4 | module("luarocks.remove", package.seeall) | ||
5 | |||
6 | local search = require("luarocks.search") | ||
7 | local deps = require("luarocks.deps") | ||
8 | local fetch = require("luarocks.fetch") | ||
9 | local rep = require("luarocks.rep") | ||
10 | local path = require("luarocks.path") | ||
11 | local util = require("luarocks.util") | ||
12 | local cfg = require("luarocks.cfg") | ||
13 | local manif = require("luarocks.manif") | ||
14 | |||
15 | help_summary = "Uninstall a rock." | ||
16 | help_arguments = "[--force] <name> [<version>]" | ||
17 | help = [[ | ||
18 | Argument is the name of a rock to be uninstalled. | ||
19 | If a version is not given, try to remove all versions at once. | ||
20 | Will only perform the removal if it does not break dependencies. | ||
21 | To 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. | ||
30 | local 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 | ||
54 | end | ||
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. | ||
60 | local 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 | ||
102 | end | ||
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. | ||
111 | function 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 | ||
163 | end | ||
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. | ||
3 | module("luarocks.rep", package.seeall) | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local path = require("luarocks.path") | ||
7 | local cfg = require("luarocks.cfg") | ||
8 | local 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. | ||
14 | function 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 | ||
19 | end | ||
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. | ||
27 | function 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)) | ||
32 | end | ||
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 | ||
38 | function 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 | ||
46 | end | ||
47 | |||
48 | --- Delete a command-line item from the bin directory. | ||
49 | -- @param command string: name of script | ||
50 | function delete_bin(command) | ||
51 | assert(type(command) == "string") | ||
52 | |||
53 | fs.delete(fs.make_path(cfg.scripts_dir, command)) | ||
54 | end | ||
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. | ||
64 | function 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 | ||
94 | end | ||
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. | ||
104 | function 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 | ||
124 | end | ||
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. | ||
134 | function 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 | ||
147 | end | ||
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. | ||
154 | function 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 | ||
168 | end | ||
169 | |||
170 | function 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 | ||
187 | end | ||
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 | |||
2 | local global_env = _G | ||
3 | local plain_require = require | ||
4 | local plain_package_path = package.path | ||
5 | local plain_package_cpath = package.cpath | ||
6 | local 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. | ||
13 | module("luarocks.require") | ||
14 | |||
15 | local path = plain_require("luarocks.path") | ||
16 | local manif = plain_require("luarocks.manif") | ||
17 | local deps = plain_require("luarocks.deps") | ||
18 | local cfg = plain_require("luarocks.cfg") | ||
19 | |||
20 | context = {} | ||
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. | ||
25 | rocks_trees = nil | ||
26 | |||
27 | local 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 | ||
44 | end | ||
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. | ||
52 | local 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 | ||
79 | end | ||
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. | ||
85 | local function sort_versions(a,b) | ||
86 | return a.version > b.version | ||
87 | end | ||
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. | ||
100 | function 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 | ||
146 | end | ||
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(). | ||
154 | local 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 | ||
169 | end | ||
170 | |||
171 | local 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 | ||
209 | end | ||
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. | ||
218 | function 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 | ||
223 | end | ||
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. | ||
246 | function 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 | ||
261 | end | ||
262 | |||
263 | global_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. | ||
4 | module("luarocks.search", package.seeall) | ||
5 | |||
6 | local fs = require("luarocks.fs") | ||
7 | local path = require("luarocks.path") | ||
8 | local manif = require("luarocks.manif") | ||
9 | local deps = require("luarocks.deps") | ||
10 | local cfg = require("luarocks.cfg") | ||
11 | local util = require("luarocks.util") | ||
12 | |||
13 | help_summary = "Query the LuaRocks servers." | ||
14 | help_arguments = "[--source] [--binary] { <name> [<version>] | --all }" | ||
15 | help = [[ | ||
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. | ||
26 | local 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 | ||
45 | end | ||
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. | ||
56 | local 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 | }) | ||
69 | end | ||
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. | ||
78 | local 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 | ||
86 | end | ||
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. | ||
104 | local 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 | ||
112 | end | ||
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. | ||
127 | function 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 | ||
153 | end | ||
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. | ||
165 | function 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 | ||
183 | end | ||
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. | ||
191 | local 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 | ||
203 | end | ||
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 | ||
209 | function 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 | ||
221 | end | ||
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. | ||
229 | local 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 | ||
251 | end | ||
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. | ||
258 | function 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 | ||
273 | end | ||
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. | ||
280 | function 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 | ||
299 | end | ||
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. | ||
307 | local 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 | ||
321 | end | ||
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. | ||
332 | function 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 | ||
352 | end | ||
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. | ||
359 | function 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 | ||
394 | end | ||
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. | ||
5 | module("luarocks.type_check", package.seeall) | ||
6 | |||
7 | rockspec_format = "1.0" | ||
8 | |||
9 | rockspec_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 | |||
74 | rockspec_types.build.platforms.ANY = rockspec_types.build | ||
75 | rockspec_types.dependencies.platforms.ANY = rockspec_types.dependencies | ||
76 | rockspec_types.external_dependencies.platforms.ANY = rockspec_types.external_dependencies | ||
77 | rockspec_types.MUST_source.platforms.ANY = rockspec_types.MUST_source | ||
78 | rockspec_types.hooks.platforms.ANY = rockspec_types.hooks | ||
79 | |||
80 | manifest_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 | |||
135 | local 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 | ||
148 | local 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 | ||
167 | end | ||
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. | ||
187 | type_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 | ||
211 | end | ||
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. | ||
219 | function type_check_rockspec(rockspec) | ||
220 | assert(type(rockspec) == "table") | ||
221 | return type_check_table(rockspec, rockspec_types, "") | ||
222 | end | ||
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. | ||
230 | function type_check_manifest(manifest) | ||
231 | assert(type(manifest) == "table") | ||
232 | return type_check_table(manifest, manifest_types, "") | ||
233 | end | ||
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. | ||
4 | module("luarocks.unpack", package.seeall) | ||
5 | |||
6 | local fetch = require("luarocks.fetch") | ||
7 | local fs = require("luarocks.fs") | ||
8 | local util = require("luarocks.util") | ||
9 | local build = require("luarocks.build") | ||
10 | |||
11 | help_summary = "Unpack the contents of a rock." | ||
12 | help_arguments = "{<rock>|<name> [<version>]}" | ||
13 | help = [[ | ||
14 | Unpacks the contents of a rock in a newly created directory. | ||
15 | Argument may be a rock file, or the name of a rock in a rocks server. | ||
16 | In 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. | ||
25 | local 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 | ||
42 | end | ||
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. | ||
49 | local 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 | ||
75 | end | ||
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. | ||
83 | local 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 | ||
125 | end | ||
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. | ||
134 | function 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 | ||
148 | end | ||
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 | |||
2 | local 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. | ||
8 | module("luarocks.util", package.seeall) | ||
9 | |||
10 | local 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. | ||
19 | function schedule_function(f, ...) | ||
20 | assert(type(f) == "function") | ||
21 | |||
22 | local item = { fn = f, args = {...} } | ||
23 | table.insert(scheduled_functions, item) | ||
24 | return item | ||
25 | end | ||
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. | ||
31 | function 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 | ||
38 | end | ||
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. | ||
45 | function 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 | ||
52 | end | ||
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". | ||
59 | function 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) | ||
75 | end | ||
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 | ||
81 | function 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 | ||
92 | end | ||
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. | ||
107 | function 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 | ||
123 | end | ||
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. | ||
133 | function 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 | ||
146 | end | ||
147 | |||
148 | --- Return an array of keys of a table. | ||
149 | -- @param tbl table: The input table. | ||
150 | -- @return table: The array of keys. | ||
151 | function keys(tbl) | ||
152 | local ks = {} | ||
153 | for k,_ in pairs(tbl) do | ||
154 | table.insert(ks, k) | ||
155 | end | ||
156 | return ks | ||
157 | end | ||
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 | ||
164 | local 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 | ||
170 | end | ||
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. | ||
178 | function sortedpairs(tbl, sort_function) | ||
179 | return coroutine.wrap(function() sortedpairs_iterator(tbl, sort_function) end) | ||
180 | end | ||
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 | |||
3 | if [ -e ./run_tests.sh ] | ||
4 | then | ||
5 | cd ../src | ||
6 | elif [ -d src ] | ||
7 | then | ||
8 | cd src | ||
9 | elif ! [ -d luarocks ] | ||
10 | then | ||
11 | echo "Go to the src directory and run this." | ||
12 | exit 1 | ||
13 | fi | ||
14 | |||
15 | if [ ! -d ../rocks ] | ||
16 | then | ||
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 | ||
21 | fi | ||
22 | |||
23 | rocks=( | ||
24 | `ls ../rocks/*.rockspec | grep -v luacom` | ||
25 | `ls ../rocks/*.src.rock | grep -v luacom` | ||
26 | ) | ||
27 | |||
28 | bin/luarocks-admin make-manifest ../rocks || exit 1 | ||
29 | |||
30 | [ "$1" ] && rocks=("$1") | ||
31 | |||
32 | TRY() { | ||
33 | "$@" || { | ||
34 | echo "Failed running: $@" | ||
35 | exit 1 | ||
36 | } | ||
37 | } | ||
38 | |||
39 | list_search() { | ||
40 | bin/luarocks list $name | grep $version | ||
41 | } | ||
42 | |||
43 | for rock in "${rocks[@]}" | ||
44 | do | ||
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. | ||
68 | done | ||
69 | |||
70 | if bin/luarocks install nonexistant | grep "No results" | ||
71 | then echo "OK, got expected error." | ||
72 | else exit 1 | ||
73 | fi | ||
74 | |||
75 | TRY ../test/test_deps.lua | ||
76 | TRY ../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 | |||
3 | deps = require "luarocks.deps" | ||
4 | |||
5 | print(deps.show_dep(deps.parse_dep("lfs 2.1.9pre5"), true)) | ||
6 | print(deps.show_dep(deps.parse_dep("cgilua cvs-2"), true)) | ||
7 | print(deps.show_dep(deps.parse_dep("foobar 0.0.1beta"), true)) | ||
8 | print(deps.show_dep(deps.parse_dep("foobar 0.0.1a"), true)) | ||
9 | |||
10 | print(deps.show_dep(deps.parse_dep("foobar 1"), true)) | ||
11 | print(deps.show_dep(deps.parse_dep("foobar 2.0"), true)) | ||
12 | print(deps.show_dep(deps.parse_dep("foobar 3.5a4"), true)) | ||
13 | print(deps.show_dep(deps.parse_dep("foobar 1.1pre2"), true)) | ||
14 | print(deps.show_dep(deps.parse_dep("foobar 2.0-beta3"), true)) | ||
15 | print(deps.show_dep(deps.parse_dep("foobar 5.3"), true)) | ||
16 | print(deps.show_dep(deps.parse_dep("foobar 3.5rc2"), true)) | ||
17 | print(deps.show_dep(deps.parse_dep("foobar 4.19p"), true)) | ||
18 | |||
19 | print() | ||
20 | comparisons = { | ||
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 | |||
46 | local v1, v2 | ||
47 | |||
48 | err = false | ||
49 | |||
50 | function 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 | ||
57 | end | ||
58 | |||
59 | for _, 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]) | ||
65 | end | ||
66 | |||
67 | if 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 | |||
3 | local luarocks = require("luarocks.require") | ||
4 | |||
5 | luarocks.set_context("cgilua", "cvs-2") | ||
6 | |||
7 | print(package.path) | ||
8 | |||
9 | print(package.cpath) | ||
10 | |||
11 | local socket = require("socket") | ||
12 | if not socket then os.exit(1) end | ||
13 | print(socket, socket._VERSION) | ||
14 | |||
15 | local socket2 = require("socket") | ||
16 | if not socket2 then os.exit(1) end | ||
17 | print(socket2, socket2._VERSION) | ||
18 | |||
19 | local mime = require("mime") | ||
20 | if not mime then os.exit(1) end | ||
21 | print(mime, mime._VERSION) | ||
22 | |||
23 | local socket = require("lfs") | ||
24 | if not lfs then os.exit(1) end | ||
25 | print(lfs, lfs._VERSION) | ||